"""
     python/ui/picture_wizard.py: CCP4MG Molecular Graphics Program
     Copyright (C) 2001-2008 University of York, CCLRC
     Copyright (C) 2009 University of York
     Copyright (C) 2012 STFC

     This library is free software: you can redistribute it and/or
     modify it under the terms of the GNU Lesser General Public License
     version 3, modified in accordance with the provisions of the 
     license to address the requirements of UK law.
 
     You should have received a copy of the modified GNU Lesser General 
     Public License along with this library.  If not, copies may be 
     downloaded from http://www.ccp4.ac.uk/ccp4license.php
 
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU Lesser General Public License for more details.
"""

import os, sys, utils
from Application import Application
from global_definitions import *

#---------------------------------------------------------------------
#---------------------------------------------------------------------
#---------------------------------------------------------------------
class picture_wizard(Application):
#---------------------------------------------------------------------
#---------------------------------------------------------------------
#---------------------------------------------------------------------

  insts = None
  initialise = {
    #'default_drawing_style' :  'Bonds:all_atoms',
    'default_drawing_style' :  'Ribbons:Blend_colour_N_to_C_terminus',
    'show_wizard_choices' : 0,
    'keep_display_objects' : 0,
    'hide_unselected_dataobj' : 0,
    'recentre' : 0 }

    # Dont save/restore user_master_dirs as can lead to mess if moving status
    # files between machines. Need to implement GUI to this parameter to enable
    # user control of search path
    
    #'user_master_dirs' : [],
  
#----------------------------------------------------------------------
  def __init__(self):
#----------------------------------------------------------------------
    picture_wizard.insts = self
    for key,value in picture_wizard.initialise.items():
      setattr(self,key,value)
    self.user_master_dirs = []
      
    self.master_dir = os.path.join(os.environ["CCP4_MGDATA"] , "picture_wizard_files")
    for dir in [os.path.join(utils.get_CCP4MG(),'data','picture_wizard_files')]:
      if os.path.exists(dir) and not self.user_master_dirs.count(dir):
        self.user_master_dirs.append(dir)
    Application.__init__(self,'picture_wizard')
    #print "user_master_dirs",self.user_master_dirs
    
    self.selected_snapshot = ''
    self.previous_chosen = {}
    self.previous_chosen_dataobj = {}
    self.last_picked_atom = []

  

#-------------------------------------------------------------------------------
  def open(self):
#-------------------------------------------------------------------------------
    Application.Application.open(self)
        
#----------------------------------------------------------------------------
  def delete(self):
#----------------------------------------------------------------------------
    if picture_wizard.insts: picture_wizard.insts = None
    Application.Application.delete(self)
    del self

#------------------------------------------------------------------------
  def getparams(self):
#------------------------------------------------------------------------
    pars = {}
    for item in picture_wizard.initialise.keys():
      pars[item] = getattr(self,item)
    return pars
  
#------------------------------------------------------------------------
  def setparams(self,params):
#------------------------------------------------------------------------
    for key,value in params.items():
      # Check that default drawing style exists.
      if key == 'default_drawing_style':
         filename = self.snapshot_file(drawing_style=value,mode='mgpic')
         if not os.path.exists(filename):
           print "Default wizard drawing style could not be restored."
           print "Possibly restoring a status file from before 2.8.0."
           continue
      setattr(self,key,value)
      
#-------------------------------------------------------------------------
  def read_mgpic_file_section(self,filename='',section='',drawing_style=''):
#-------------------------------------------------------------------------
    if drawing_style: filename = self.snapshot_file(drawing_style=drawing_style,mode='mgpic')
    if not os.path.exists(filename): return [1,'No file '+filename]

    f = utils.openFile(filename,'r')
    if not f: return [1,'Error opening file '+filename]

    try:
      text = f.read()
    except:
      try:
        f.close()
      except:
        pass
      return [1,"Error reading file "+filename]
    try:
      f.close()
    except:
      pass
    
    import re,string

    sec_list = re.split('#SECTION ',text)

    if len(sec_list) == 1:
      #No sections in file - assume all SCRIPT
      if section == 'SCRIPT':
        return [0,text]
      else:
        return [0,'']
    else:
      for sec_text in sec_list[1:]:
        isec = string.find(sec_text,section)
        jsec = string.find(sec_text,'\n')
        if isec>=0 and isec<jsec:
          if ['TITLE','NOTES'].count(section):
            return [0,string.strip(sec_text[jsec+1:]).strip('"""').rstrip('\n').lstrip('\n')]
          else:
            return [0,sec_text[jsec+1:]]
        
    return [0,'']


#------------------------------------------------------------------------
  def read_mgpic_file_choices(self,filename='',drawing_style=''):
#------------------------------------------------------------------------

    ret = self.read_mgpic_file_section(filename=filename,drawing_style=drawing_style,section='CHOICES')
    query = 0
   
    if ret[0]:
      return ret
    else:
      if not ret[1]:
        choices = [ { 'name' : 'MolData1',
                    'type' : 'MolData',
                    'label' : 'Model' ,
                    'all' : 1 } ]
      else:
        choices = []
        globs = {}
        locs = {}
        try:
          exec(ret[1],globs,locs)
        except:
          pass   
        for key in ['CHOICES','choices']:
          if locs.has_key(key): choices = locs[key]
        if locs.has_key('QUERY'): query = locs['QUERY']
      if choices:
        if query:
          return [-1,choices]
        else:
          return [0,choices]
      else:
        return [1,'Error reading choices from '+filename]


#------------------------------------------------------------------------
  def set_default_drawing_style(self,name=''):
#------------------------------------------------------------------------
    self.default_drawing_style = name
    

#-------------------------------------------------------------------------
  def define_model_style_menu(self,style=[],tree=1):
#-------------------------------------------------------------------------
    # define a menu to appear when loading PDB file etc
    import glob,re
    if tree:
      style_menu = []
    else:
      style_menu = ['All atoms']
      style_alias = ['all_atoms']

    for m in [ [self.master_dir], self.user_master_dirs]:
      for master in m:
        if os.path.exists(master):
          #print "define_model_style_menu master",master
          # Sort out list of directories with 'general' first
          base_list = []
          for dr in glob.glob(os.path.join (master,'*')):
            base = os.path.basename(dr)
            if os.path.isdir(dr) and ['cvs','CVS','utils'].count(base)==0 and len(glob.glob(os.path.join (dr,"*")))>0:
              base_list.append(base)

          # Put the more general groups first
          for item in ['Ligand_binding_site','Ribbons','Bonds']:
            if base_list.count(item):
              base_list.remove(item)
              base_list.insert(0,item)

          # Add item to menu for each file in the directory
          #print "dir_list",dir_list
          for group in base_list:
            filelist = []
            for file in  glob.glob(os.path.join (master,group,'*.mgpic.py')):
              filelist.append( os.path.split(file)[1][0:-9])
            if os.path.exists(os.path.join (master,group,'ORDER')):
              try:
                order_f = open(os.path.join (master,group,'ORDER'))
                order_lines = order_f.readlines()
                order_f.close()
                filelist_sort = []
                for order_l in order_lines[:]:
                  if order_l.strip() in filelist:
                    filelist_sort.append(order_l.strip())
                    filelist.remove(order_l.strip())
                for fl in filelist:
                    filelist_sort.append(fl)
                filelist = filelist_sort
              except:
                filelist.sort() # Just order alphabetically.
            else:
              filelist.sort() # Just order alphabetically.
            filequerylist = []
            for file in filelist:
              foo = self.read_mgpic_file_section(filename=os.path.join (master,group,file+'.mgpic.py'),section='GUIIGNORE')
              if len(foo)==2 and foo[0]==0 and foo[1].strip()=='1':
                continue
              query,choices=self.read_mgpic_file_choices(filename=os.path.join (master,group,file+'.mgpic.py'))
              filequerylist.append([file,query])
              #print "define_model_style_menu filelist",filequerylist
            if len(filequerylist)>0:
              if tree:
                style_menu.append([group,filequerylist])
              else:
                for file in filelist:
                  style_menu.append(re.sub("_"," ",group+':'+file))
                  style_alias.append(group+':'+file)

    if tree:
      return style_menu
    else:
      return style_menu,style_alias
    
    
#-----------------------------------------------------------------------
  def edit_picture(self,picture='',new_name='',dir='',delete='',copy_to='',
                   **keywords):
#-----------------------------------------------------------------------
    if picture and self.snapshot_list.count(picture):
      self.edited_picture = picture
    if not hasattr(self,'edited_picture'):return
    old_snapshot=self.snapshot_file(index=self.edited_picture,mode='snapshot',dir=dir)
    old_mgpic=self.snapshot_file(index=self.edited_picture,mode='mgpic',dir=dir)
    
    if new_name:
      import re
      new_name=re.sub(' ','_',new_name)
      new_snapshot=self.snapshot_file(index=new_name,mode='snapshot',dir=dir)
      new_mgpic=self.snapshot_file(index=new_name,mode='mgpic',dir=dir)
      os.rename(old_snapshot,new_snapshot)
      os.rename(old_mgpic,new_mgpic)
      
    elif delete:
      if os.path.exists(old_snapshot): os.remove(old_snapshot)
      if os.path.exists(old_mgpic): os.remove(old_mgpic)
      
    elif copy_to:
      if not self.style_dirs.count(copy_to): return
      new_dir = ''
      if os.path.exists(os.path.join(self.master_dir,copy_to)):
        new_dir = os.path.join(self.master_dir,copy_to)
      else:
        for dd in self.user_master_dirs:
          if os.path.exists(os.path.join(dd,copy_to)):
            new_dir = os.path.join(dd,copy_to)
      #print "new_dir",new_dir
      if new_dir:
        import shutil
      
        new_snapshot=self.snapshot_file(index=self.edited_picture,mode='snapshot',dir=new_dir)
        new_mgpic=self.snapshot_file(index=self.edited_picture,mode='mgpic',dir=new_dir)
      shutil.copyfile(old_snapshot,new_snapshot)
      shutil.copyfile(old_mgpic,new_mgpic)

      
#------------------------------------------------------------------------
  def create_folder(self,new_style_folder='',**keywords):
#------------------------------------------------------------------------
    import re
    if self.style_menu.count(new_style_folder):
      MGGUI().WarningMessage('Style name: '+new_style_folder+'already exists',help='picture_wizard',target='style_folders')
      return
      
    dir = os.path.join(utils.get_CCP4MG(),'data','picture_wizard_files',re.sub(' ','_',new_style_folder))
    if os.path.exists(dir):
       MGGUI().WarningMessage('Style directory: '+new_style_folder+'already exists in '+ os.path.join(utils.get_CCP4MG(),'data'))
    try:
      os.mkdir(dir)
    except:
       MGGUI().WarningMessage('Error creating style directory: '+dir)
       return
    self.style_dirs.append(re.sub(' ','_',new_style_folder))
    self.style_menu.append([ new_style_folder, re.sub(' ','_',new_style_folder)])          
      
#---------------------------------------------------------------------------
  def save_style ( self,new_style='',dir='',**keywords):
#---------------------------------------------------------------------------
    import re
    new_name = re.sub(' ','_',new_style)
    if self.snapshot_list.count(new_name):
      MGGUI().WarningMessage('Style name '+new_name+' is already used')
      return

    filn = self.snapshot_file(index=new_name,dir=dir,mode='mgpic')
    stat = PD().save_picture_definition(filename=filn,limited_save=1,
          save_list=['display','params'],style='mgpic')
    if stat:
       MGGUI().WarningMessage('Error saving '+new_name)
       return

    filn = self.snapshot_file(index=new_name,dir=dir,mode='snapshot')
    SS().make_screenshot(filename=filn,mode='snapshot',screenshot_width=200,screenshot_height=200,clear_labels=1)

    #Need to wait for the picture saved before recompiling snapshot_list?
    ntry = 0
    import time
    while ntry < 10 and not os.path.exists(filn):
      ntry = ntry + 1
      time.sleep(1)

    
      
#-----------------------------------------------------------------------
  def snapshot_file(self,index='',mode='snapshot',dir='',version='',drawing_style=''):
#-----------------------------------------------------------------------
    '''
    Return path for file containing status or snapshot image for given index
    '''
    import string
    if drawing_style and string.find(drawing_style,':')>=0 :
      fdir,index = string.split(drawing_style,':')
      filn = os.path.join(self.master_dir,fdir,str(index))
    elif dir:
      filn = os.path.join(dir,str(index))
    else:
      filn = os.path.join(self.master_dir,str(index))
      
    if  mode == 'snapshot':
      ext = '.snapshot.ppm'
    elif  mode == 'pngsnapshot':
      ext = '.snapshot.png'
    else:
      ext = '.mgpic.py'


    # Add the version number extension
    if ['new','replaced','edited'].count(version):
      import time,string,re
      t0 = string.split(time.ctime(time.time()))
      t1 = t0[0]+'_'+t0[2]+'_'+t0[1]+'_'+re.sub(':','_',t0[3])
      filn = filn + '.' + t1 
      if version != 'new': filn = filn + '_' + version
      
    elif version:
      filn = filn + '.' + str(version)

    filn = filn+ext
    #print "snapshot_file",index,'*',mode,'*',version,'*',filn
    return filn


#---------------------------------------------------------------------------
  def create_picture(self,trap_errors=-1,chosen={}, chosen_dataobj={},
                     keep_display_objects=1,drawing_style='',mgpic_file='',force_recolour_bymol=False,**keywords):
#---------------------------------------------------------------------------
    import re,string,types
    import dataobj

    createdDispobj = []
    if trap_errors<0: trap_errors= 1-DEVELOPER()
    if drawing_style: mgpic_file = self.snapshot_file(drawing_style=drawing_style,mode='mgpic')

    # These will be set as global variables for the contect that runs the mgpic scripts 
    chosen_def = self.initialise_chosen(mgpic_file)
    chosen_def.update(chosen)
    chosen = chosen_def
    #print "create_picture chosen",chosen,chosen_dataobj
    ret = self.read_mgpic_file_section(filename=mgpic_file,section='SCRIPT')
    #print "create_picture ret",ret
    if ret[0]:  return ret
    script_text = ret[1]

    import os,exceptions,pprint,picture_definition_classes
    message = ''
    
    for repeat in [1]:
      globs = {}
      globs['DataList'] = []
      globs.update(chosen)
      locs = {}
      #print 'pic_wiz.create_picture globs',globs
      try:
        execfile(os.path.join(os.environ["CCP4MG"],'python','ui','picture_definition_classes.py'),globs)
      except  StandardError, e:
        message = message + 'Error loading picture_definition_classes.py\n'
        print e.args
        MGGUI().WarningMessage(message,help='picture_definition',target='errors')
        return [1,message]

      
      #Make the loaded DataObj object known to the script
      for key,value in chosen_dataobj.items():
        if not value:
          value = []
        elif not isinstance(value,types.ListType):
          value = [value]
        for obj_name in value:
          obj_list = get_dataobj(name=obj_name)
          if obj_list:
            obj = obj_list[0]
            obj.set_visibility(visibility=1)
            if obj.object_type == 'MolData':
              globs['DataList'].append(picture_definition_classes.MolData(name=obj.name))
            elif obj.object_type == 'MapData':
              globs['DataList'].append(picture_definition_classes.MapData(name=obj.name))
            elif obj.object_type == 'FieldData':
              globs['DataList'].append(picture_definition_classes.FieldData(name=obj.name))
            elif obj.object_type == 'VectorsData':
              globs['DataList'].append(picture_definition_classes.VectorsData(name=obj.name))
            globs['DataList'][-1].save(dataobj=obj)
            globs['DataList'][-1].dataobj = obj
            globs['DataList'][-1].objinsts = []
            globs['DataList'][-1].objinsts.extend(obj.objinsts)
            globs['DataList'][-1].objnames = []
            globs['DataList'][-1].objnames.extend(obj.objnames)
            globs['force_recolour_bymol'] = force_recolour_bymol
     
            globs[key] = obj                        


      if trap_errors:
        try:
          exec(script_text,globs,locs)
        except StandardError, e:
          message = message + 'Error reading '+ mgpic_file +'\n'
          
          if isinstance(e,exceptions.SyntaxError):
            spaces=''
            import types
            if hasattr(e,'offset') and isinstance(e.offset,types.IntType):
             for i in range(0,e.offset): spaces = spaces+' '
            if hasattr(e,'lineno') and isinstance(e.lineno,types.IntType):
              message = message + 'Invalid syntax in line '+ str(e.lineno)+ \
                       ':\n\n'
            if hasattr(e,'text') and isinstance(e.text,types.StringType):
              message = message + re.sub('\n','',e.text) +'\n' + \
                       spaces + '*\n\n'
            message = message + '(Hint: also check preceeding line)\n'
          else:
            pp = pprint.PrettyPrinter()
            message = message + pp.pformat(e.args)

          # Keep this diagnostic to go to log file
          print "Error reading ",mgpic_file,e.args
      else:
        # Let it crash
        exec(script_text,globs,locs)
        #print 'locs',locs

      #print "globs",globs.keys()
      #print "locs",locs.keys()

      #Everything OK?
      if message:
        MGGUI().WarningMessage(message,help='picture_definition',target='errors')
        return [1,message]

      elif locs.has_key('ERROR_MESSAGE') and locs['ERROR_MESSAGE']:
        message = 'Error interpreting '+ mgpic_file +'\n\n'
        for line in locs['ERROR_MESSAGE']:
          message = message + line + '\n'
        MGGUI().WarningMessage(message,help='picture_definition',target='errors')
        return [1,message]

      elif locs.has_key('WARNING_MESSAGE') and locs['WARNING_MESSAGE']:
        message = 'Warning interpreting '+ mgpic_file +'\n\n'
        for line in locs['WARNING_MESSAGE']:
          message = message + line + '\n'
        MGGUI().WarningMessage(message,help='picture_definition',target='errors')

      # Load any colour scheme managers
      for obj in globs['ColourSchemeManager'].insts:
        pm = PM(obj.name)
        if pm:
          pm.restore_picture_definition(params=obj.params)
      globs['ColourSchemeManager'].insts = []

      # Restore the shadow objects to real objects
      for obj in globs['DataList']:
        for disp in obj.objinsts:
          if isinstance(disp,dataobj.DispObjShadow):
            disp.restore(obj.name,deleteEmpty=True)
            createdDispobj.append(disp.name)

      if globs['twoDList']:
        twoD=dataobj.DataObjShadow(name='GL2dScreen',object_type='GL2dScreen',objinsts=globs['twoDList'])
        rv = twoD.restore()
        if rv[1]: message = message+rv[1]+'\n'
        rv =  twoD.restore_display_objects()
        if rv[1]: message = message+rv[1]+'\n'

      # Try restoring ParamsManagers 
      import picture_definition_classes
      for obj in globs['ParamsManager'].insts:
        pm = PM(obj.name)
        if pm:
          #pm.restore_picture_definition(params=obj.params)
          # Better to use update_data_status which forces
          # update of dependents
          pm.update_data_status(params=obj.params)
      globs['ParamsManager'].insts = []

      if globs['View'].insts:
        MAINWINDOW().setViewParams(params= globs['View'].insts[0].params)

      if globs['Colours'].insts and len(globs['Colours'].insts)>0 and hasattr(globs['Colours'].insts[0],"params"):
        for key,value in globs['Colours'].insts[0].params:
          indx = MGCOLOUR().index(key,safe=0)
          if indx>=0:
            MGCOLOUR().reset(name=key,newrgb=value)
          else:
            MGCOLOUR().new(newname=key,newrgb=value)

      if globs['CrystalList']:
        xtl=dataobj.DataObjShadow(name='CrystalData',object_type='CrystalData',objinsts=globs['CrystalList'])
        if not get_dataobj('CrystalData'):
          rv = xtl.restore()
          if rv[1]: message = message+rv[1]+'\n'
        else:
          xtl.set_dataobj(get_dataobj('CrystalData')[0])
        rv =  xtl.restore_display_objects(override_visible=0)
        if rv[1]: message = message+rv[1]+'\n'
      del locs,globs
      return [0,createdDispobj]

#--------------------------------------------------------------------------------------------
  def initialise_chosen(self,filename=''):
#--------------------------------------------------------------------------------------------
    # Create dictionary with  default values for the selection parameters that appear in the
    # picture wizard selection gui.  This is used when the gui has not been opened.
    chosen = {}
    query,choices=self.read_mgpic_file_choices(filename=filename)
    if query>0: return {}
    #print 'initialise_chosen',query,choices
    
    for item in choices:
      if item.has_key('name') and item.has_key('type') and not ['MolData','MapData','VectorData','FieldData'].count(item['type']):
        if item.has_key('initial'):
          chosen[item['name']] = item['initial']
        else:
          chosen[item['name']] = ''
    return chosen
