
from CCP4PluginScript import CInternalPlugin,CPluginScript
from PyQt4 import QtCore
import os,re,time,sys

class coot_rebuild(CPluginScript):
#class coot_rebuild(CInternalPlugin):
    
    TASKMODULE = 'model_building'                               # Where this plugin will appear on the gui
    TASKTITLE = 'Rebuild model with coot'     # A short title for gui menu
    TASKNAME = 'coot_rebuild'                                  # Task name - should be same as class name
    TASKCOMMAND = 'coot'                                     # The command to run the executable
    TASKVERSION= 0.0                                     # Version of this plugin
    ASYNCHRONOUS = True
    TIMEOUT_PERIOD = 9999999.9
    MAINTAINER = 'martin.noble@newcastle.ac.uk'

    ERROR_CODES = {  200 : { 'description' : 'Coot exited with error status' }, 201 : { 'description' : 'Failed in harvest operation' },202 : { 'description' : 'Failed in processOutputFiles' }}

    def makeCommandAndScript(self):
        import CCP4Utils
        self.dropDir = os.path.join(self.workDirectory,'COOT_FILE_DROP')
        if not os.path.exists(self.dropDir):
          try:
            os.mkdir(self.dropDir)
          except:
            self.dropDir = self.workDirectory
            print 'Could not make dropDir reset to',self.dropDir

        '''
        # Look for a state file with name like the input coordinate file -
        # only works if the coord file is from coot
        if not self.container.inputData.COOTSTATEFILE.isSet() and self.container.inputData.XYZIN.fullPath.count('COOT_FILE_DROP')>0:
          statePath,fileName = os.path.split(self.container.inputData.XYZIN.fullPath)
          statePath = os.path.join(statePath,re.sub('output','state',fileName))
          print 'coot_rebuild derived state file',statePath, os.path.exists(statePath)
          if os.path.exists(statePath):
              self.container.inputData.COOTSTATEFILE.setFullPath(statePath)
        '''
        # Copy a startup state file to the drop directory
        if self.container.inputData.COOTSTATEFILE.isSet(): self.copyStateFile()

        '''
          import shutil
          try:
            shutil.copyfile(self.container.inputData.COOTSTATEFILE.__str__(),os.path.join(self.dropDir,'0-coot-history.py'))
          except:
            pass
        '''

        # Make a script file with additional menu options to save to i2
        self.cootScriptPath = os.path.normpath(os.path.join(self.workDirectory,'script.py'))
        if sys.platform == 'win32':
          self.cootScriptPath = re.sub(r'\\\\',r'\\',self.cootScriptPath)
        # Declare script text then re.sub in the variables
        if sys.platform == "win32":
          i2dir = CCP4Utils.getCCP4I2Dir().replace('\\','/')  
        else:
          i2dir = CCP4Utils.getCCP4I2Dir()
        script = """
import os, sys
pythonDefsFile = os.path.normpath((os.path.join('"""+i2dir+"""','wrappers','coot_rebuild','script')))
sys.path.append(pythonDefsFile)
import ccp4i2CootInterface
#Here provide that module with "global function calls it will need
for cootFunction in [coot_menubar_menu, add_simple_coot_menu_menuitem, read_pdb, set_molecule_name, make_and_draw_map, read_cif_dictionary, save_coordinates, save_state_file, molecule_chooser_gui, residue_centre, set_rotation_centre]:
    setattr(ccp4i2CootInterface, cootFunction.__name__, cootFunction)
print 'Managed to load ccp4i2CootInterface'
ccp4i2Interface = ccp4i2CootInterface.ccp4i2CootInterface(dropDir=r'"""+self.dropDir+"""')
print ccp4i2CootInterface, ccp4i2Interface
try:
    ccp4i2Interface.installMenus()
except Exception as e:
    print e
"""

        if self.container.inputData.USEKEYBINDINGS.isSet() and self.container.inputData.USEKEYBINDINGS:
            script+="""
add_key_binding("Refine Active Residue", "r", lambda: manual_refine_residues(0))
add_key_binding("Refine Active Residue AA", "x", lambda: refine_active_residue())
add_key_binding("Triple Refine", "t", lambda: manual_refine_residues(1))
add_key_binding("Autofit Rotamer", "j", lambda: auto_fit_rotamer_active_residue())
add_key_binding("Pepflip", "q", lambda: pepflip_active_residue())
add_key_binding("Go To Blob", "g", lambda: blob_under_pointer_to_screen_centre())
add_key_binding("Add Water", "w", lambda: place_typed_atom_at_pointer("Water"))
add_key_binding("Eigen-flip Ligand", "e", lambda: flip_active_ligand())

def key_binding_func_1():
    active_atom = active_residue()
    if (not active_atom):
        print "No active atom"
    else:
        imol      = active_atom[0]
        chain_id  = active_atom[1]
        res_no    = active_atom[2]
        ins_code  = active_atom[3]
        atom_name = active_atom[4]
        alt_conf  = active_atom[5]
        add_terminal_residue(imol, chain_id, res_no, "auto", 1)
add_key_binding("Add terminal residue", "y", lambda: key_binding_func_1())

def key_binding_func_2():
    active_atom = active_residue()
    if (not active_atom):
        print "No active atom"
    else:
        imol      = active_atom[0]
        chain_id  = active_atom[1]
        res_no    = active_atom[2]
        ins_code  = active_atom[3]
        atom_name = active_atom[4]
        alt_conf  = active_atom[5]
        fill_partial_residue(imol, chain_id, res_no, ins_code)
add_key_binding("Fill Partial", "k", lambda: key_binding_func_2())

add_key_binding("Kill Sidechain", "K", lambda:
                using_active_atom(delete_residue_sidechain,
                                  "aa_imol", "aa_chain_id", "aa_res_no", "aa_ins_code", 0))

refine_residue_sphere_radius = 3.5  # Angstroms
add_key_binding("Refine residue in a sphere", "R",
                lambda: sphere_refine(refine_residue_sphere_radius))

def key_binding_func_21():
    if not valid_map_molecule_qm(imol_refinement_map()):
        info_dialog("Must set the refinement map")
    else:
        # not using active atom
        active_atom = active_residue()
        if (not active_atom):
            add_status_bar_text("No active residue")
        else:
            imol      = active_atom[0]
            chain_id  = active_atom[1]
            res_no    = active_atom[2]
            ins_code  = active_atom[3]
            atom_name = active_atom[4]
            alt_conf  = active_atom[5]

            rc_spec = [chain_id, res_no, ins_code]
            ls = residues_near_residue(imol, rc_spec, 1.9)
            with_auto_accept([refine_residues, imol, [rc_spec] + ls])
add_key_binding("Neighbours Refine", "h", lambda: key_binding_func_21())

def key_binding_func_3():
    if (os.name == 'nt'):
        home = os.getenv('COOT_HOME')
    else:
        home = os.getenv('HOME')
    dir_1 = os.path.join(home, "data", "rnase")
    read_pdb(os.path.join(dir_1, "tutorial-modern.pdb"))
    make_and_draw_map(os.path.join(dir_1, "rnasa-1.8-all_refmac1.mtz"),
                      "/RNASE3GMP/COMPLEX/FWT",
                      "/RNASE3GMP/COMPLEX/PHWT",
                      "", 0, 0)
add_key_binding("Load RNAs files", "F9", lambda: key_binding_func_3())

def key_binding_func_4():
    keyboard_ghosts_mol = -1
    for mol in model_molecule_list():
        if (ncs_ghosts(mol)):
            keyboard_ghosts_mol = mol
            break
    if (draw_ncs_ghosts_state(keyboard_ghosts_mol) == 0):
        make_ncs_ghosts_maybe(keyboard_ghosts_mol)
        set_draw_ncs_ghosts(keyboard_ghosts_mol, 1)
    else:
        set_draw_ncs_ghosts(keyboard_ghosts_mol, 0)
add_key_binding("Toggle Ghosts", ":", lambda: key_binding_func_4())

add_key_binding("Hydrogens off", "[", lambda: set_draw_hydrogens(0, 0))
add_key_binding("Hydrogens on", "]", lambda: set_draw_hydrogens(0, 1))

def key_binding_func_5():
    active_atom = active_residue()
    if (not active_atom):
        add_status_bar_text("No active residue")
    else:
        imol      = active_atom[0]
        chain_id  = active_atom[1]
        res_no    = active_atom[2]
        ins_code  = active_atom[3]
        atom_name = active_atom[4]
        alt_conf  = active_atom[5]
        name = get_rotamer_name(imol, chain_id, res_no, ins_code)
        if (not name):
            add_status_bar_text("No Name found")
        else:
            if (name == ""):
                add_status_bar_text("No name for this")
            else:
                add_status_bar_text("Rotamer name: " + name)
add_key_binding("Rotamer name in Status Bar", "~", lambda: key_binding_func_5())

def key_binding_func_6():
    active_atom = active_residue()
    if (not active_atom):
        add_status_bar_text("No active residue")
    else:
        imol      = active_atom[0]
        chain_id  = active_atom[1]
        res_no    = active_atom[2]
        ins_code  = active_atom[3]
        atom_name = active_atom[4]
        alt_conf  = active_atom[5]
        regularize_zone(imol, chain_id,
                        res_no - 1, res_no + 1,
                        alt_conf)
add_key_binding("Regularize Residues", "B", lambda: key_binding_func_6())

def key_binding_func_7():
    using_active_atom([[fit_to_map_by_random_jiggle,
                        ["aa_imol", "aa_chain_id", "aa_res_no", "aa_ins_code"],
                        [100, 1.0]]])
add_key_binding("Jiggle Fit", "J", lambda: key_binding_func_7())
add_key_binding("Delete this water", "D", lambda: delete_atom(*active_residue()))

def key_binding_func_8():
    using_active_atom(add_terminal_residue,
                      "aa_imol", "aa_chain_id", "aa_res_no",
                      "auto", 1)
add_key_binding("Add Terminal Residue", "|", lambda: key_binding_func_8())

add_key_binding("Accept Baton Position", "`", lambda: accept_baton_position())
add_key_binding("Cootilus here", "N", lambda: find_nucleic_acids_local(6.0))
"""
        
        if self.container.inputData.XYZIN_LIST.isSet():
            try:
                iFile = 1
                for XYZIN in self.container.inputData.XYZIN_LIST:
                    if os.path.isfile(XYZIN.__str__()):
                        script += "try:\n"
                        script += ("  MolHandle_"+str(iFile)+"=read_pdb(r'"+XYZIN.__str__()+"')\n")
                        script += ("  ccp4i2Interface.patchMoleculeName(MolHandle_"+str(iFile)+",r'"+XYZIN.__str__()+"')\n")
                        script += "except:\n  pass\n"
                    else:
                        print 'coot_rebuild.makeCommandAndScript XYZIN does not exist:',XYZIN.__str__()
                    iFile += 1
            except:
                #an issue with the existence of files
                pass
        if self.container.inputData.FPHIIN_LIST.isSet():
            try:
                iFile = 1
                for FPHIIN in self.container.inputData.FPHIIN_LIST:
                    print ' reading file number ' + str ( iFile ) 
                    if os.path.isfile(FPHIIN.__str__()):
                        script += "try:\n"
                        script += ("  MapHandle_"+str(iFile)+"=make_and_draw_map(r'"+FPHIIN.__str__()+"', 'F', 'PHI', 'PHI', 0, 0)\n")
                        script += ("  ccp4i2Interface.patchMoleculeName(MapHandle_"+str(iFile)+",r'"+FPHIIN.__str__()+"')\n")
                        script += "except:\n  pass\n"
                    else:
                        print 'coot_rebuild.makeCommandAndScript FPHIIN does not exist:',FPHIIN.__str__()
                    iFile += 1
            except:
                print ' Exception '
                #an issue with the existence of files
                pass
        if self.container.inputData.DELFPHIIN_LIST.isSet():
            try:
                iFile = 1
                for DELFPHIIN in self.container.inputData.DELFPHIIN_LIST:
                    print ' reading diff file number ' + str ( iFile ) 
                    if os.path.isfile(DELFPHIIN.__str__()):
                        script += "try:\n"
                        script += ("  DifMapHandle_"+str(iFile)+"=make_and_draw_map(r'"+DELFPHIIN.__str__()+"', 'F', 'PHI', 'PHI', 0, 1)\n")
                        script += ("  ccp4i2Interface.patchMoleculeName(DifMapHandle_"+str(iFile)+",r'"+DELFPHIIN.__str__()+"')\n")
                        script += "except:\n  pass\n"
                    else:
                        print 'coot_rebuild.makeCommandAndScript FPHIIN does not exist:',DELFPHIIN.__str__()
                    iFile += 1
            except:
                print ' Exception '
                #an issue with the existence of files
                pass
        if self.container.inputData.COOTSCRIPTFILE.isSet():
            scriptLines = open(self.container.inputData.COOTSCRIPTFILE.fullPath.__str__()).readlines()
            if len(scriptLines)>0:
              script += 'try:\n'
              for line in scriptLines:
                  script += ('    '+line + '\n')
              script += 'except:\n    pass\n'

        CCP4Utils.saveFile(self.cootScriptPath,script)
        
        clArgs = ['--no-state-script','--python']
            
        #clArgs = ['--python','--pdb',self.container.inputData.XYZIN.fullPath.__str__()]
        
        

        dict_is_meaningful = True
        if self.container.inputData.DICT.isSet():
            
            try:
                from Bio.PDB.MMCIF2Dict import MMCIF2Dict
            
                mmcif_dict = MMCIF2Dict ( str ( self.container.inputData.DICT.fullPath.__str__() ) )
                lib_name    = mmcif_dict['_lib_name']
                lib_version = mmcif_dict['_lib_version']
                lib_update  = mmcif_dict['_lib_update']

                print lib_name[0]
                print lib_version[0]
                print lib_update[0]
                #MN This test does not work on dicts made by ACEDRG, which show up as ??? for these properties
                #if lib_name[0] == '?' and lib_version[0] == '?' and lib_update[0] == '?' :
                #    dict_is_meaningful = False
            except:
                print 'Bio python probably not available in this build of ccp4'
                    
        if self.container.inputData.DICT.isSet() and dict_is_meaningful :
            clArgs += ['--dictionary']
            clArgs += [self.container.inputData.DICT.fullPath.__str__()]
        
        
        #MN Please talk to me before changing the below.  COOTSTATEFILE has almost no place in how
        #i2 is used, but is incorporated into script.py above.
        clArgs += ['--script',self.cootScriptPath ]
        
        '''if  self.container.inputData.COOTSTATEFILE.exists():
          import CCP4Utils
          contents = CCP4Utils.readFile(self.container.inputData.COOTSTATEFILE.__str__())
          if len(contents)>5:
            clArgs += ['--script',self.container.inputData.COOTSTATEFILE.__str__()]
          else:
            clArgs += ['--script',self.cootScriptPath ]
        else:
          clArgs += ['--script',self.cootScriptPath ]
        '''
        
        self.appendCommandLine(clArgs)
        # Use Qt class to watch the drop directory
        self.fileSystemWatcher = QtCore.QFileSystemWatcher(parent=self)
        self.fileSystemWatcher.addPath(self.dropDir)
        self.connect(self.fileSystemWatcher,QtCore.SIGNAL('directoryChanged(const QString &)'),self.handleFileDrop)

        return CPluginScript.SUCCEEDED


    def numberOfOutputFiles(self):
        import glob
        outList = glob.glob(os.path.normpath(os.path.join(self.dropDir,'output*.pdb')))
        #print 'numberOfOutputFiles outList',os.path.join(self.dropDir,'output*.pdb'),outList
        #print 'numberOfOutputFiles xmlList',glob.glob(os.path.normpath(os.path.join(self.workDirectory,'*.xml')))
        maxIndx = 0
        for f in outList:
           fpath,fname = os.path.split(f)
           #print 'numberOfOutputFiles  fpath,fname', fpath,fname
           maxIndx =  max(maxIndx,int(fname[6:-4]))
        return maxIndx

    def handleFileDrop(self,directory):
        import time,glob
        print 'coot_rebuild',time.time()
        print 'coot_rebuild',glob.glob(os.path.join(self.workDirectory,'*.*'))
        #print 'handleFileDrop',directory
        #Note that I don't copy the file to the appropriate xyzout filename here, since the file may not yet
        #be closed and/or flushed

        
    def processOutputFiles(self):
        try:
            # First up import PDB files that have been output
            
            import os, glob, shutil
            globPath = os.path.normpath(os.path.join(self.dropDir,'output*.pdb'))
            outList = glob.glob(globPath)
            
            xyzoutList = self.container.outputData.XYZOUT
            for outputPDB in outList:
                fpath,fname = os.path.split(outputPDB)
                iFile = int(fname[6:-4])
                xyzoutList.append(xyzoutList.makeItem())
                outputFilePath = os.path.normpath(os.path.join(self.workDirectory,'XYZOUT_'+str(iFile)+'-coordinates.pdb'))
                shutil.copyfile(outputPDB, outputFilePath)
                xyzoutList[-1].setFullPath(outputFilePath)
                xyzoutList[-1].annotation = "Coot output file number"+str(iFile)
                xyzoutList[-1].subType = 1

            #Ligand builder places output cifs in the coot-cif directory as prorg-out.cif
            #'prodrgify this residue' places output cifs in the coot-cif directory as prodrg-???.cif
            #pyrogen create "TLC"_pyrogen.cif
            cifOutList = glob.glob(os.path.normpath(os.path.join(self.dropDir,'coot-ccp4', 'prodrg-*.cif')))
            cifOutList += glob.glob(os.path.normpath(os.path.join(self.workDirectory,'coot-ccp4', 'prodrg-*.cif')))
            cifOutList += glob.glob(os.path.normpath(os.path.join(self.workDirectory,'*pyrogen.cif')))
            cifOutList += glob.glob(os.path.normpath(os.path.join(self.workDirectory,'acedrg-*.cif')))

            dictoutList = self.container.outputData.DICTOUT
            for iFile, outputCIF in enumerate(cifOutList):
                fpath,fname = os.path.split(outputCIF)
                #Currently, there is only one cif generated
                #iFile = int(fname[10:-4])
                dictoutList.append(dictoutList.makeItem())
                #outputFilePath = os.path.join(self.workDirectory,'COOT_'+str(iFile)+'-prodrg.cif')
                outputFilePath = os.path.normpath(os.path.join(self.workDirectory,'COOT-'+str(iFile)+'.cif'))
                shutil.copyfile(outputCIF, outputFilePath)
                dictoutList[-1].setFullPath(outputFilePath)
                if 'acedrg' in fname: annotation='Coot/Acedrg created geometry for ligand'
                elif 'pyrogen' in fname: annotation='Coot/Pyrogen created geometry for ligand'
                elif 'prodrg' in fname: annotation='Coot/Prodrg created geometry for ligand'
                else: annotation='Coot/Prodrg created geometry for ligand'
                dictoutList[-1].annotation = annotation
                
            # Create a trivial xml output file
            import CCP4Utils
            from lxml import etree
            self.xmlroot = etree.Element('coot_rebuild')
            e = etree.Element('number_output_files')
            e.text = str(self.numberOfOutputFiles())
            e = etree.Element('number_output_dicts')
            e.text = str(len(dictoutList))
            self.xmlroot.append(e)
            
            #Separate out here activity to attempt merge into project dictionary....this seems flakey,
            #but is needed for ongoing work, so I ammaking it give an report a warning in case of failure, rather than
            #offer the sad face ofdoom
            try:
                for dictFile in dictoutList:
                    try:
                        self.mergeDictToProjectLib(fileName=dictFile.__str__())
                    except:
                        self.addReportWarning('mergeDictToProjectLib raised exception: Does not compromise output Dict')
                            
                    ligNodes = self.xmlroot.xpath('//LIGANDS')
                    if len(ligNodes) == 0: ligNode = etree.SubElement(self.xmlroot,'LIGANDS')
                    else: ligNode = ligNodes[0]
                    try:
                        annotation='Coot/Prodrg created geometry for'
                        for item in dictFile.fileContent.monomerList:
                            lig =  etree.SubElement(ligNode,'ligand')
                            lig.text = str(item.three_letter_code)
                            annotation += (' ' + str(item.three_letter_code))
                        dictFile.annotation = annotation
                    except:
                        self.addReportWarning('fileContent.monomerList raised exception: Does not compromise output Dict')
            except:
                self.addReportWarning('failed elsewhere in merging/analysing dicts: Does not compromise output Dict')
        except:
            self.appendErrorReport(202,'Data harvesting failed')
            
        CCP4Utils.saveEtreeToFile(self.xmlroot,self.makeFileName('PROGRAMXML'))
        if ( len(outList) + len(cifOutList) ) > 0:
          return CPluginScript.SUCCEEDED
        else:
          return CPluginScript.MARK_TO_DELETE

    def clearCootWorkingDir(self):
        # Remove the working directory state and history files 
        import glob
        zeroFileList = glob.glob(os.path.normpath(os.path.join(self.projectDirectory(),'CCP4_COOT','0-coot*')))
        for filn in zeroFileList:
            os.remove(filn)

    def copyStateFile(self):
        import CCP4Utils
        newText = ''
        text = CCP4Utils.readFile(self.container.inputData.COOTSTATEFILE.fullPath)
        for line in text.split('\n'):
          if line.count('handle-read-draw-molecule'):
            newText = newText + '(handle-read-draw-molecule "'+self.container.inputData.XYZIN_LIST[0].__str__()+'" 1)\n'
          else:
            newText = newText + line +'\n'
        CCP4Utils.saveFile(os.path.normpath(os.path.join(self.dropDir,'0-coot-history.scm')),text)
        
    def addReportWarning(self, text):
        from lxml import etree
        warningsNode = None
        warningsNodes = self.xmlroot.xpath('//Warnings')
        if len(warningsNodes) == 0: warningsNode = etree.SubElement(self.xmlroot, 'Warnings')
        else: warningsNode = warningsNodes[0]
        warningNode = etree.SubElement(warningsNode,'Warning')
        warningNode.text = text

