# This wizard contributed by Ezequiel "Zac" Panepucci 011114
# modified by Warren L. DeLano

from pymol.wizard import Wizard
from pymol import cmd
import pymol
from chempy.models import Indexed
from chempy import Atom,Bond
from chempy.cpv import add, sub, cross_product, scale, dot_product
from chempy.cpv import normalize, project, remove_component, reverse
from pymol.cgo import *
import math

pseudo_atoms = [
   ['X', 'PLN1' , '1', 'PSDO', [0.0,0.0,0.0]],
   ['X', 'PLN2' , '1', 'PSDO', [10.0,0.0,0.0]],
   ['X', 'PLN3' , '1', 'PSDO', [10.0,10.0,0.0]],
   ['X', 'PLN4' , '1', 'PSDO', [10.0,10.0,4]]]

default_mode = 'box'

default_name = 'box'

class Box(Wizard):

    atom=None
    messages=1
    labeling=1
    obj_name=None


    def __init__(self,_self=cmd):
        Wizard.__init__(self,_self)
        
        self.editing_name = 0
        self.copying = 0
        
        self.points_name = ''
        self.set_name(default_name)
    
        self.mode = default_mode
        self.modes = [
            'box',
            'walls', 
            'plane',
	    'quad',
            ]

        self.mode_name = {
            'box':'Box',
            'walls':'Walls',
            'plane':'Plane',
	    'quad':'Quad',
            }

        smm = []
        smm.append([ 2, 'Box Mode', '' ])
        for a in self.modes:
            smm.append([ 1, self.mode_name[a], 'cmd.get_wizard().set_mode("'+a+'")'])
        self.menu['mode']=smm

        self.update_box()
        
    def set_mode(self,mode):
        if mode in self.modes:
            self.mode = mode
        self.status = 0
        self.update_box()
        self.cmd.refresh_wizard()
        
    def get_prompt(self):
        self.prompt = []
        return self.prompt

    def toggle_points(self):
        if self.points_name in self.cmd.get_names(enabled_only=1):
            self.cmd.disable(self.points_name)
        else:
            self.cmd.enable(self.points_name)

    def delete_box(self):
        self.cmd.delete(self.point_name)
        self.cmd.delete(self.cgo_name)

    def edit_name(self,copying=0):
        self.editing_name = 1
        self.copying = copying
        self.new_name = ''
        self.cmd.refresh_wizard()

    def auto_position(self,fract=0.75,size=1.0):

        if self.points_name in self.cmd.get_names():

	    if fract<0.5: fract = 1.0 - fract
	    if fract>0.995: fract = 0.995 # minimum 1% distance from clipping planes
	    
            fov = float(self.cmd.get_setting_legacy("field_of_view"))
	    
	    tan_half_fov = math.tan(math.pi*fov/360.0)
	    
            model = self.cmd.get_model(self.points_name)

	    view = self.cmd.get_view()

	    # locate box 1/4 and 3/4 distance from clipping plane
	    
	    one_minus_fract = 1.0 - fract

	    plane_z1 = - (view[11] + (one_minus_fract*view[15] + fract*view[16]))
	    plane_z2 = - (view[11] + (fract*view[15] + one_minus_fract*view[16]))
	    normal = [ 0.0, 0.0, 1.0 ]

	    # choose the size of the plane (larger than the fov, but not by much)

	    plane_size = size*(one_minus_fract*view[15] + fract*view[16])*tan_half_fov

	    obj = []
	    plane = [
		[ -plane_size, -plane_size, plane_z1 ],
		[ -plane_size,  plane_size, plane_z1 ],
		[  plane_size, -plane_size, plane_z1 ],
		[ -plane_size, -plane_size, plane_z2 ],		
		]

	    # then transform plane coordinates into model space 

	    plane = map( lambda p,v=view: [
	       v[0] * p[0] + v[1] * p[1] + v[2]* p[2],
	       v[3] * p[0] + v[4] * p[1] + v[5]* p[2],
	       v[6] * p[0] + v[7] * p[1] + v[8]* p[2]
	       ], plane )

	    normal = apply( lambda p,v=view:[
	       v[0] * p[0] + v[1] * p[1] + v[2]* p[2],
	       v[3] * p[0] + v[4] * p[1] + v[5]* p[2],
	       v[6] * p[0] + v[7] * p[1] + v[8]* p[2]
	       ], (normal,) )

	    plane = map( lambda p,v=view: [
	       v[12] + p[0], v[13] + p[1], v[14] + p[2]
	       ], plane )

	    model.atom[0].coord = plane[0]
	    model.atom[1].coord = plane[1]
	    model.atom[2].coord = plane[2]
	    model.atom[3].coord = plane[3]

	    self.cmd.load_model(model, '_tmp', zoom=0)
	    self.cmd.update(self.points_name,"_tmp")
	    self.cmd.delete("_tmp")

    def set_name(self,name):

        hidden_name = None
        
        if self.points_name != '':
            if self.points_name in self.cmd.get_names("all"):
                hidden_name = "_"+self.cgo_name
                self.cmd.disable(self.points_name)
                self.cmd.set_name(self.points_name, hidden_name) # hide 

        self.name = name
        self.points_name = self.name + "_points"
        self.cgo_name = self.name
        if self.copying and hidden_name != None:
            self.cmd.copy(self.points_name, hidden_name, zoom=0)
            print "copy"
        else:
            hidden_name = "_"+self.cgo_name
            if hidden_name in self.cmd.get_names("all"):
                self.cmd.set_name(hidden_name, self.points_name) 
        self.copying = 0
        
        if not self.points_name in self.cmd.get_names():
            model = Indexed()
            origin = self.cmd.get_view()[12:15]
            for a in pseudo_atoms:
                new_atom = Atom()
                (new_atom.symbol, new_atom.name, new_atom.resi, new_atom.resn, new_atom.coord) = a
                new_atom.coord[0] = new_atom.coord[0] + origin[0]
                new_atom.coord[1] = new_atom.coord[1] + origin[1]
                new_atom.coord[2] = new_atom.coord[2] + origin[2]
                new_atom.flags = 0x2200000 # set surface ignore flag
                model.atom.append(new_atom)
                
            self.cmd.load_model(model,self.points_name,zoom=0)
            self.cmd.set("surface_mode",0,self.points_name) # make sure no surface is shown
            self.coord = None
            self.cmd.color("green","%s`1"%self.points_name)
            self.cmd.color("green","%s`2"%self.points_name)
            self.cmd.color("red"  ,"%s`3"%self.points_name)
            self.cmd.color("blue" ,"%s`4"%self.points_name)
            self.cmd.show_as("nb_spheres",self.points_name)
	    
	    self.auto_position(0.75,0.5)

        self.cmd.enable(self.points_name)
        self.points_enabled = 1
        
    def update_box(self):

        if self.points_name in self.cmd.get_names():

            model = self.cmd.get_model(self.points_name)

            self.coord = (
                model.atom[0].coord,
                model.atom[1].coord,
                model.atom[2].coord,
                model.atom[3].coord,
                )

            p = self.coord[0]

            d10 = sub(self.coord[1], p)
            d20 = sub(self.coord[2], p)
            d30 = sub(self.coord[3], p)

            x10_20 = cross_product(d10,d20)
	    if self.mode != 'quad':
		if dot_product(d30,x10_20)<0.0:
		    p = model.atom[1].coord
		    d10 = sub(self.coord[0], p)
		    d20 = sub(self.coord[2], p)
		    d30 = sub(self.coord[3], p)
	    
            n10_20 = normalize(x10_20)
            n10 = normalize(d10)

            d100 = d10
	    d010 = remove_component(d20, n10)
	    if self.mode != 'quad':
		d001 = project(d30, n10_20)
	    else:
		d001 = n10_20

            n100 = normalize(d100)
            n010 = normalize(d010)
            n001 = normalize(d001)

            f100 = reverse(n100)
            f010 = reverse(n010)
            f001 = reverse(n001)

	    if self.mode == 'quad':
		p000 = p
		p100 = add(p, remove_component(d10,n001))
		p010 = add(p, remove_component(d20,n001))
		p001 = add(p, remove_component(d30,n001))
	    else:
		p000 = p
		p100 = add(p,d100)
		p010 = add(p,d010)
		p001 = add(p,d001)
		p110 = add(p100, d010)
		p011 = add(p010, d001)
		p101 = add(p100, d001)
		p111 = add(p110, d001)

            obj = []

            if self.mode == 'box': # standard box

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(f001)
                obj.append(VERTEX); obj.extend(p000)
                obj.append(VERTEX); obj.extend(p010)
                obj.append(VERTEX); obj.extend(p100)
                obj.append(VERTEX); obj.extend(p110)
                obj.append(END)

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(n001)
                obj.append(VERTEX); obj.extend(p001)
                obj.append(VERTEX); obj.extend(p101)
                obj.append(VERTEX); obj.extend(p011)
                obj.append(VERTEX); obj.extend(p111)
                obj.append(END)

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(f010)
                obj.append(VERTEX); obj.extend(p000)
                obj.append(VERTEX); obj.extend(p100)
                obj.append(VERTEX); obj.extend(p001)
                obj.append(VERTEX); obj.extend(p101)
                obj.append(END)

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(n010)
                obj.append(VERTEX); obj.extend(p010)
                obj.append(VERTEX); obj.extend(p011)
                obj.append(VERTEX); obj.extend(p110)
                obj.append(VERTEX); obj.extend(p111)
                obj.append(END)

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(f100)
                obj.append(VERTEX); obj.extend(p000)
                obj.append(VERTEX); obj.extend(p001)
                obj.append(VERTEX); obj.extend(p010)
                obj.append(VERTEX); obj.extend(p011)
                obj.append(END)

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(n100)
                obj.append(VERTEX); obj.extend(p100)
                obj.append(VERTEX); obj.extend(p110)
                obj.append(VERTEX); obj.extend(p101)
                obj.append(VERTEX); obj.extend(p111)
                obj.append(END)

                model.atom[0].coord = p000
                model.atom[1].coord = p100
                model.atom[2].coord = add(p010, scale(d100,0.5))
                model.atom[3].coord = add(add(p001, scale(d010,0.5)),d100)

            elif self.mode=='walls':

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(n001)
                obj.append(VERTEX); obj.extend(p000)
                obj.append(VERTEX); obj.extend(p100)
                obj.append(VERTEX); obj.extend(p010)
                obj.append(VERTEX); obj.extend(p110)
                obj.append(END)

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(n010)
                obj.append(VERTEX); obj.extend(p000)
                obj.append(VERTEX); obj.extend(p001)
                obj.append(VERTEX); obj.extend(p100)
                obj.append(VERTEX); obj.extend(p101)
                obj.append(END)

                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(n100)
                obj.append(VERTEX); obj.extend(p000)
                obj.append(VERTEX); obj.extend(p010)
                obj.append(VERTEX); obj.extend(p001)
                obj.append(VERTEX); obj.extend(p011)
                obj.append(END)

                model.atom[0].coord = p000
                model.atom[1].coord = p100
                model.atom[2].coord = p010
                model.atom[3].coord = p001
            elif self.mode=='plane':
                obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(n001)
                obj.append(VERTEX); obj.extend(p000)
                obj.append(VERTEX); obj.extend(p100)
                obj.append(VERTEX); obj.extend(p010)
                obj.append(VERTEX); obj.extend(p110)
                obj.append(END)
                model.atom[0].coord = p000
                model.atom[1].coord = p100
                model.atom[2].coord = p010
                model.atom[3].coord = add(add(p001, scale(d010,0.5)),scale(d100,0.5))
            elif self.mode=='quad':
		obj.extend([ BEGIN, TRIANGLE_STRIP ])
                obj.append(NORMAL); obj.extend(n001)
                obj.append(VERTEX); obj.extend(p000)
                obj.append(VERTEX); obj.extend(p100)
                obj.append(VERTEX); obj.extend(p010)
                obj.append(VERTEX); obj.extend(p001)
                obj.append(END)
                model.atom[0].coord = p000
                model.atom[1].coord = p100
                model.atom[2].coord = p010
                model.atom[3].coord = p001

            self.cmd.load_model(model, '_tmp', zoom=0)
            self.cmd.update(self.points_name,"_tmp")
            self.cmd.delete("_tmp")

            # then we load it into PyMOL

            self.cmd.delete(self.cgo_name)
            self.cmd.load_cgo(obj,self.cgo_name,zoom=0)
            self.cmd.order(self.cgo_name+" "+self.points_name,sort=1,location='bottom')
            self.cmd.set("nonbonded_size",math.sqrt(dot_product(d10,d10))/10,self.points_name)
        
    def get_panel(self):
        
        return [
            [ 1, 'Box Wizard',''],
            [ 3, self.mode_name[self.mode],'mode'],
            [ 2, 'Change Name','cmd.get_wizard().edit_name()'],
            [ 2, 'Copy Box','cmd.get_wizard().edit_name(copying=1)'],
            [ 2, 'Toggle Points','cmd.get_wizard().toggle_points()'],
#            [ 2, 'Update','cmd.get_wizard().update_box()'],
            [ 2, 'Auto-Position (50%)','cmd.get_wizard().auto_position(0.75)'],
            [ 2, 'Auto-Position (99%)','cmd.get_wizard().auto_position(0.99)'],
            [ 2, 'Done','cmd.set_wizard()'],
            ]

    def get_event_mask(self):
        if self.editing_name:
            return Wizard.event_mask_pick + Wizard.event_mask_select + \
                   Wizard.event_mask_scene + Wizard.event_mask_key
        else:
            return Wizard.event_mask_pick + Wizard.event_mask_select + Wizard.event_mask_scene 

    def do_scene(self):
        if self.points_name in self.cmd.get_names("objects"):
            if self.coord == None:
                self.update_box()
            else:
                model = self.cmd.get_model(self.points_name)
                coord = (
                    model.atom[0].coord,
                    model.atom[1].coord,
                    model.atom[2].coord,
                    model.atom[3].coord,
                    )
                if self.coord != coord:
                    self.update_box()
                
    def do_pick(self,bondFlag):
        pass

    def do_key(self,k,x,y,m):
        if k in [8,127]:
            self.new_name = self.new_name[:-1]
        elif k>32:
            self.new_name = self.new_name + chr(k)
        elif k==10 or k==13:
            self.editing_name = 0
            self.new_name = string.strip(self.new_name)
            if self.new_name == '':
                self.new_name = 'box'
            else:
                self.new_name = self.new_name
            self.set_name(self.new_name)
            self.update_box()
        self.cmd.refresh_wizard()
        return 1

    def get_prompt(self):
        if self.editing_name:
            self.prompt = [ "Enter box name: " + self.new_name ]
        else:
            self.prompt = None
        return self.prompt
