# $Id$
#
# Copyright (C) 2002-2006 Greg Landrum and Rational Discovery LLC
#
#   @@ All Rights Reserved @@
#  This file is part of the RDKit.
#  The contents are covered by the terms of the BSD license
#  which is included in the file license.txt, found at the root
#  of the RDKit source tree.
#
""" tools for interacting with chemdraw

"""
import string,tempfile,os,time
try:
  import pythoncom
  from win32com.client import gencache,Dispatch,constants
  import win32com.client.gencache
  cdxModule = win32com.client.gencache.EnsureModule("{5F646AAB-3B56-48D2-904C-A68D7989C251}", 0, 7, 0)
except:
  cdxModule = None
  _cdxVersion=0
  raise ImportError,"ChemDraw version (at least version 7) not found."
else:
  _cdxVersion=7

if cdxModule:
  from win32com.client import Dispatch
  import win32gui
import re

cdApp = None
theDoc = None
theObjs = None
selectItem = None
cleanItem = None
centerItem = None

def StartChemDraw(visible=True,openDoc=False,showDoc=False):
  """ launches chemdraw """
  global cdApp,theDoc,theObjs,selectItem,cleanItem,centerItem
  if cdApp is not None:
    # if called more than once, do a restart
    holder = None
    selectItem = None
    cleanItem = None
    centerItem = None
    theObjs = None
    theDoc = None
    cdApp = None

  cdApp = Dispatch('ChemDraw.Application')
  if openDoc:
    theDoc = cdApp.Documents.Add()
    theObjs = theDoc.Objects
  else:
    theDoc = None
  selectItem = cdApp.MenuBars(1).Menus(2).MenuItems(8)
  cleanItem = cdApp.MenuBars(1).Menus(5).MenuItems(6)
  if _cdxVersion == 6:
    centerItem = cdApp.MenuBars(1).Menus(4).MenuItems(1)
  else:
    centerItem = cdApp.MenuBars(1).Menus(4).MenuItems(7)
  if visible:
    cdApp.Visible=1
    if theDoc and showDoc:
      theDoc.Activate()

def ReactivateChemDraw(openDoc=True,showDoc=True):
  global cdApp,theDoc,theObjs
  cdApp.Visible=1
  if openDoc:
    theDoc = cdApp.Documents.Add()
    if theDoc and showDoc:
      theDoc.Activate()
    theObjs = theDoc.Objects

# ------------------------------------------------------------------
#  interactions with Chemdraw
# ------------------------------------------------------------------
def CDXConvert(inData,inFormat,outFormat):
  """converts the data passed in from one format to another

    inFormat should be one of the following:
       chemical/x-cdx                   chemical/cdx 
       chemical/x-daylight-smiles       chemical/daylight-smiles 
       chemical/x-mdl-isis              chemical/mdl-isis 
       chemical/x-mdl-molfile           chemical/mdl-molfile 
       chemical/x-mdl-rxn               chemical/mdl-rxn 
       chemical/x-mdl-tgf               chemical/mdl-tgf 
       chemical/x-questel-F1  
       chemical/x-questel-F1-query 

    outFormat should be one of the preceding or:
       image/x-png                      image/png 
       image/x-wmf                      image/wmf 
       image/tiff  
       application/postscript  
       image/gif  
  """
  global theObjs,theDoc
  if cdApp is None:
    StartChemDraw()
  if theObjs is None:
    if theDoc is None:
      theDoc = cdApp.Documents.Add()
    theObjs = theDoc.Objects
  theObjs.SetData(inFormat,inData,pythoncom.Missing)
  outD = theObjs.GetData(outFormat)
  theObjs.Clear()
  return outD



def CDXClean(inData,inFormat,outFormat): 
  """calls the CDXLib Clean function on the data passed in.

    CDXLib_Clean attempts to clean (prettify) the data before
    doing an output conversion.  It can be thought of as CDXConvert++.

    CDXClean supports the same input and output specifiers as CDXConvert
    (see above)

  """
  global cdApp,theDoc,theObjs,selectItem,cleanItem
  if cdApp is None:
    StartChemDraw()
  if theObjs is None:
    if theDoc is None:
      theDoc = cdApp.Documents.Add()
    theObjs = theDoc.Objects
  theObjs.SetData(inFormat,inData,pythoncom.Missing)
  theObjs.Select()
  cleanItem.Execute()
  outD = theObjs.GetData(outFormat)
  theObjs.Clear()
  return outD

def CDXDisplay(inData,inFormat='chemical/cdx',clear=1):
  """ displays the data in Chemdraw """
  global cdApp,theDoc,theObjs,selectItem,cleanItem,centerItem
  if cdApp is None:
    StartChemDraw()
  try:
    theDoc.Activate()
  except:
    ReactivateChemDraw()
    theObjs = theDoc.Objects
  if clear:
    theObjs.Clear()
  theObjs.SetData(inFormat,inData,pythoncom.Missing)
  return

def CDXGrab(outFormat='chemical/x-mdl-molfile'):
  """ returns the contents of the active chemdraw document

  """
  global cdApp,theDoc
  if cdApp is None:
    res = ""
  else:
    cdApp.Visible=1
    if not cdApp.ActiveDocument:
      ReactivateChemDraw()
    try:
      res = cdApp.ActiveDocument.Objects.GetData(outFormat)
    except:
      res = ""
  return res

def CloseChemdraw():
  """ shuts down chemdraw

  """
  global cdApp
  try:
    cdApp.Quit()
  except:
    pass
  Exit()

def Exit():
  """ destroys our link to Chemdraw

  """
  global cdApp
  cdApp = None

def SaveChemDrawDoc(fileName='save.cdx'):
  """force chemdraw to save the active document

  NOTE: the extension of the filename will determine the format
   used to save the file.
  """
  d = cdApp.ActiveDocument
  d.SaveAs(fileName)

def CloseChemDrawDoc():
  """force chemdraw to save the active document

  NOTE: the extension of the filename will determine the format
   used to save the file.
  """
  d = cdApp.ActiveDocument
  d.Close()

def RaiseWindowNamed(nameRe):
  # start by getting a list of all the windows:
  cb = lambda x,y: y.append(x)
  wins = []
  win32gui.EnumWindows(cb,wins)

  # now check to see if any match our regexp:
  tgtWin = -1
  for win in wins:
    txt = win32gui.GetWindowText(win)
    if nameRe.match(txt):
      tgtWin=win
      break

  if tgtWin>=0:
    win32gui.ShowWindow(tgtWin,1)
    win32gui.BringWindowToTop(tgtWin)

def RaiseChemDraw():
  e = re.compile('^ChemDraw')
  RaiseWindowNamed(e)

  
try:
  from PIL import Image
  import cStringIO
  def SmilesToPilImage(smilesStr):
    """takes a SMILES string and returns a PIL image using chemdraw

    """
    return MolToPilImage(smilesStr,inFormat='chemical/daylight-smiles',outFormat='image/gif')
  def MolToPilImage(dataStr,inFormat='chemical/daylight-smiles',outFormat='image/gif'):
    """takes a molecule string and returns a PIL image using chemdraw

    """
    # do the conversion...
    res = CDXConvert(dataStr,inFormat,outFormat)
    dataFile = cStringIO.StringIO(str(res))
    img = Image.open(dataFile).convert('RGB')
    return img
except ImportError:
  def SmilesToPilImage(smilesStr):
    print 'You need to have PIL installed to use this functionality'
    return None
  def MolToPilImage(dataStr,inFormat='chemical/daylight-smiles',outFormat='image/gif'):
    print 'You need to have PIL installed to use this functionality'
    return None


# ------------------------------------------------------------------
#  interactions with Chem3D
# ------------------------------------------------------------------
c3dApp = None
def StartChem3D(visible=0):
  """ launches Chem3D """
  global c3dApp
  c3dApp = Dispatch('Chem3D.Application')
  if not c3dApp.Visible:
    c3dApp.Visible = visible

def CloseChem3D():
  """ shuts down Chem3D """
  global c3dApp
  c3dApp.Quit()
  c3dApp = None

availChem3DProps = ('DipoleMoment','BendEnergy','Non14VDWEnergy','StericEnergy',
                    'StretchBendEnergy','StretchEnergy','TorsionEnergy','VDW14Energy')
def Add3DCoordsToMol(data,format,props={}):
  """ adds 3D coordinates to the data passed in using Chem3D

    **Arguments**

      - data: the molecular data

      - format: the format of _data_.  Should be something accepted by
        _CDXConvert_

      - props: (optional) a dictionary used to return calculated properties
  
  """
  global c3dApp
  if c3dApp is None:
    StartChem3D()
  if format != 'chemical/mdl-molfile':
    molData = CDXClean(data,format,'chemical/mdl-molfile')
  else:
    molData = data
  molFName = tempfile.mktemp('.mol')
  open(molFName,'wb+').write(molData)
  doc = c3dApp.Documents.Open(molFName)

  if not doc:
    print 'cannot open molecule'
    raise ValueError,'No Molecule'

  # set up the MM2 job
  job = Dispatch('Chem3D.MM2Job')
  job.Type=1
  job.DisplayEveryIteration=0
  job.RecordEveryIteration=0

  # start the calculation...
  doc.MM2Compute(job)
  # and wait for it to finish
  while doc.ComputeStatus in [0x434f4d50,0x50454e44]:
    pass
  #outFName = tempfile.mktemp('.mol')
  # this is horrible, but apparently Chem3D gets pissy with tempfiles:
  outFName = os.getcwd()+'/to3d.mol'
  doc.SaveAs(outFName)

  # generate the properties
  for prop in availChem3DProps:
    props[prop] = eval('doc.%s'%prop)

  doc.Close(0)

  os.unlink(molFName)
  c3dData = open(outFName,'r').read()
  gone = 0
  while not gone:
    try:
      os.unlink(outFName)
    except:
      time.sleep(.5)
    else:
      gone = 1
  return c3dData

def OptimizeSDFile(inFileName,outFileName,problemFileName='problems.sdf',
                   restartEvery=20):
  """  optimizes the structure of every molecule in the input SD file

    **Arguments**

      - inFileName: name of the input SD file

      - outFileName: name of the output SD file

      - problemFileName: (optional) name of the SD file used to store molecules which
        fail during the optimization process

      - restartEvery: (optional)  Chem3D will be shut down and restarted
        every _restartEvery_ molecules to try and keep core leaks under control

  """
  inFile = open(inFileName,'r')
  outFile = open(outFileName,'w+')
  problemFile = None
  props = {}
  lines = []
  nextLine = inFile.readline()
  skip = 0
  nDone = 0
  t1 = time.time()
  while nextLine != '':
    if nextLine.find('M  END') != -1:
      lines.append(nextLine)
      molBlock = string.join(lines,'')

      try:
        newMolBlock = Add3DCoordsToMol(molBlock,'chemical/mdl-molfile',props=props)
      except:
        badBlock = molBlock
        skip = 1
        lines = []
      else:
        skip = 0
        lines = [newMolBlock]
    elif nextLine.find('$$$$') != -1:
      t2 = time.time()
      nDone += 1
      print 'finished molecule %d in %f seconds'%(nDone,time.time()-t1)
      t1 = time.time()
      if nDone%restartEvery == 0:
        CloseChem3D()
        StartChem3D()
        outFile.close()
        outFile = open(outFileName,'a')
      if not skip:
        for prop in props.keys():
          lines.append('> <%s>\n%f\n\n'%(prop,props[prop]))
        lines.append(nextLine) 
        outFile.write(string.join(lines,''))
        lines = []
      else:
        skip = 0
        lines.append(nextLine)
        if problemFile is None:
          problemFile = open(problemFileName,'w+')
        problemFile.write(badBlock)
        problemFile.write(string.join(lines,''))
        lines = []
    else:
      lines.append(nextLine)
    nextLine = inFile.readline()  
  outFile.close()
  if problemFile is not None:
    problemFile.close()
        
if __name__=='__main__':
  inStr = 'CCC(C=O)CCC'
  img = SmilesToPilImage(inStr)
  img.save('foo.jpg')
  convStr = CDXClean(inStr,'chemical/x-daylight-smiles','chemical/x-daylight-smiles')
  print 'in:',inStr
  print 'out:',convStr
  convStr = CDXConvert(inStr,'chemical/x-daylight-smiles','chemical/x-mdl-molfile')
  print 'in:',inStr
  print 'out:',convStr
  convStr2 = CDXClean(convStr,'chemical/x-mdl-molfile','chemical/x-mdl-molfile')  
  print 'out2:',convStr2

  inStr = 'COc1ccc(c2onc(c2C(=O)NCCc3ccc(F)cc3)c4ccc(F)cc4)c(OC)c1'
  convStr = CDXConvert(inStr,'chemical/x-daylight-smiles','chemical/x-mdl-molfile')
  out = open('test.mol','w+')
  out.write(convStr)
  out.close()
  
