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

     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.
"""


#===================================================================
#===================================================================
#===================================================================
class ModelInfo:
#===================================================================
  insts = ''
  monomer_library_elements = []  # list of *_EL.cif files in monomer library
  
#-----------------------------------------------------------------
  def __init__(self):
#-----------------------------------------------------------------
    import utils
    import os
    ModelInfo.insts = self
    self.ModInfo = {}
   # list of loaded files
    self.user_file = {}
    filename = ['dotCCP4MG','generic.ccp4mg',os.path.join(utils.get_CCP4MG(),'generic.ccp4mg')]
    self.user_file['generic']=filename
    
    # ? this should be in generic
    self.generic_selection = {}

    for set,filename in self.user_file.items():
      self.restore(set,filename)

#------------------------------------------------------------------
  def setparams(self,params=''):
#------------------------------------------------------------------
    '''
    Restore data - called from DisplayTableShadow on program
    initialisation
    '''
    #print "setparams",params
    for key,value in params.items(): setattr(self,key,value)

    import types
    import os
    for set,filename in self.user_file.items():
      if not isinstance(filename,types.ListType):
        filename = ['FULLPATH',os.path.split(filename)[1],filename]
        #print "ModelInfo restoring",filename
        self.user_file[set] = filename
      self.restore(set=set,filename=filename)

      
#------------------------------------------------------------------
  def getparams(self):
#------------------------------------------------------------------
    '''
    Save status - called from DisplayTableShadow
    Presently saves list of user_defined_files i.e. any
    loaded .ccp4mg which are not associated with a PDB file
    '''
    params = {}
    params['user_file'] = self.user_file
    return params

#--------------------------------------------------------------------
  def save_user_files(self):
#--------------------------------------------------------------------
    #print "save_user_files",self.user_file
    for set,filename in  self.user_file.items():
      self.save(set=set,filename=filename)
      
       
#--------------------------------------------------------------------
  def save(self,set='',filename=[]):
#--------------------------------------------------------------------
    '''
    Save ModInfo class to .ccp4mg file
    "set" is the GUI label for the set of data - usually
    the PDB filename root
    '''
    import os
    import pickle
    import sys

    #print "ModelInfo save",set,filename

    if not set or not self.ModInfo.has_key(set): return 1
    if not filename:
      if self.user_file.has_key(set):
        filename = self.user_file[set]
      else:
        import global_definitions
        dobj = global_definitions.data(set)
        #print 'ModelInfo.save dobj',dobj
        if dobj:
          filename = dobj.filename
        else:
          return 2
    #print "ModelInfo save",filename
      
    pickle_file =os.path.splitext(filename[2])[0] + '.ccp4mg'
    #print "save ModInfo"+pickle_file
    try:
      if sys.platform == 'win32':
        f = open(pickle_file,'w')
      else:
        f = open(pickle_file,'wb')
    except:
      #print "ERROR opening file",pickle_file
      return 3
    try:
      pickle.dump(self.ModInfo[set],f)
      f.close()
    except:
      pass

    return 0



  
#--------------------------------------------------------------------
  def restore(self,set='',filename=[],user_file=0,override=1,create=1):
#--------------------------------------------------------------------
    '''
    Restore ModInfo class from a .ccp4mg file
    For GUI purposes the data has a name "set" which is uusually
    the filename root of the PDB file 
    '''
    import os
    import pickle

    #print "restore self.ModInfo",self.ModInfo,'set',set

    # override=0 when called as initialising PDB - saves
    # repeating load if cloned PDBs
    if set and self.ModInfo.has_key(set) and not override:
      return [0]
    
    if set == 'generic':
      import utils
      pickle_file = ['dotCCP4MG','generic.ccp4mg',os.path.join(utils.get_CCP4MG(), \
                                                     'generic.ccp4mg')]
    elif filename:
      name = os.path.splitext(filename[1])[0] + '.ccp4mg'
      pickle_file = [filename[0],name,os.path.splitext(filename[2])[0] + '.ccp4mg']
    else:
      return [1]
      
    #print "restore",pickle_file
    if not os.path.exists(pickle_file[2]):
      if create:
        if not set:set=utils.fileRoot(filename[1])
        self.ModInfo[set]=ModInfo()
        self.ModInfo[set].initialise(pdbfile=filename[2])
        if user_file: self.user_file[set]=pickle_file
        #print "creating",set
        if create>=2:
          self.save(set=set,filename=filename)
        return [2]


    obj = None
    try:
      f = open(pickle_file[2],'r')
      obj = pickle.load(f)
      #print "restore obj",obj
    except:
      # If this file is associated with a PDB file then give
      # user a waring message
      return [2,'Error reading file '+pickle_file[2]]
      
    if not set: 
      import utils
      set = utils.fileRoot(filename[1])
    #print "ModelInfo restore",set
    self.ModInfo[set] = obj
    if user_file:self.user_file[set]=pickle_file
    return [0]

#-----------------------------------------------------------------------
  def delete(self,pickle_file='',exit='',filename=[],check=0,**keywords):
#-----------------------------------------------------------------------
    #print "delete",exit,pickle_file
    if exit != 'delete': return
    import os
    import GUI
    if not pickle_file and filename :
      pickle_file=os.path.splitext(filename[2])[0] + '.ccp4mg'
    if not pickle_file or not os.path.exists(pickle_file): return 1
    try:
      os.remove(pickle_file)
    except:
      GUI.GUI.insts.WarningMessage("Error attempting to delete file "+ \
       pickle_file+ \
       "\\nPlease remove it or there will be problems handling\\n"+ \
       filename)
      return 2

    return 0

#--------------------------------------------------------------------
  def close(self,set='',check=1):
#--------------------------------------------------------------------
    '''
    Close the data set
    '''
    if self.ModInfo.has_key(set):
      del self.ModInfo[set]
      if self.user_file.has_key(set): del self.user_file[set]
      
#---------------------------------------------------------------------
  def list_loaded(self):
#---------------------------------------------------------------------
    '''
    Return a list of the loaded sets of ModInfo
    '''
    menu = self.user_file.keys()
    if menu.count('generic')>0: menu.remove('generic')
    return menu

#---------------------------------------------------------------------
  def interpret_set(self,set=''):
#---------------------------------------------------------------------
    # Ensure that set is a list - if wildcard then set default to all sets
    import types

    # Make sure its a list
    if not isinstance(set,types.ListType):
      set = [set]

    # Replace a '*' or '' with list of currently loaded user_file
    for i in range(len(set)):
      if set[i] == '*' or set[i] == '':
        set.pop(i)
        for item in self.user_file.keys(): set.insert(i,item)

    #print " interpret_set",set   
    return set

#-------------------------------------------------------------------
  def get_set(self,set=''):
#-------------------------------------------------------------------
    # get all info relating to one coordinate file
    if self.ModInfo.has_key(set):
      #print "get_set",set,self.ModInfo[set]
      return  self.ModInfo[set]
    else:
      return {}

#---------------------------------------------------------------------
  def set_set(self,set='',ModInfo = {}):
#---------------------------------------------------------------------
    #print "set_set",set,ModInfo
    self.ModInfo[set] = ModInfo
      
  
#----------------------------------------------------------------------
  def get_info(self,info='',set='',key='',first=0):
#----------------------------------------------------------------------
    '''
    Return a status,dictionary where dictionary is the data of type
    'info' (eg user_selection)
    from the given set(s) - or if set == '' or set=='*' then all sets 
    either return a whole dictionary (key='') or dictionary with the item 'key'
    if first is true then only return first key that satisfies criteria
    '''
    if not info: return [1,{}]

    # Ensure that set is a list - if wildcard then set default to all sets
    set = self.interpret_set(set)

    #if key == 'edit_secstr': print "get_info info,key,set",info,key,set,
    output = {}
    for s in set:
      #print "has_key", s,self.ModInfo.has_key(s)
      #if self.ModInfo.has_key(s): print "hasattr", hasattr(self.ModInfo[s],info)
      if self.ModInfo.has_key(s) and hasattr(self.ModInfo[s],info):
        data = getattr(self.ModInfo[s],info)
        #if key == 'edit_secstr': print "get_info data",self.ModInfo[s],data
        if key:
          if data.has_key(key):
            output[key]=data[key]
            if first:
              return [0,output]
        else:
          for k,v in data.items(): output[k]=v
    return [0,output]
      
#----------------------------------------------------------------------
  def add_info(self,info='',set='',key='',value='',pdbfile='',save=0):
#----------------------------------------------------------------------
    #print "ModelInfo.add_info",set,info,key,value
    if not set or not info or not key: return 1
    changed = []

    if not self.ModInfo.has_key(set):
      self.ModInfo[set]=ModInfo()
      self.ModInfo[set].initialise(pdbfile=pdbfile)
      
    if hasattr(self.ModInfo[set],info):
      dic = getattr(self.ModInfo[set],info)
    else:
      dic = {}
    dic[key]=value 
    setattr(self.ModInfo[set],info,dic)
    changed.append(set)
    #print "add_info set",hasattr(self.ModInfo[set],info),getattr(self.ModInfo[set],info)

    if save:
      #save the edited set(s)
      for set in changed:
        self.save(set=set)
    return 0

#----------------------------------------------------------------------
  def delete_info(self,info='',set='',key='',first=0,save=0):
#----------------------------------------------------------------------
    '''
    Delete the data of type 'info' (eg user_selection) with given key
    optionally from the given set(s) 
    '''

    #print "delete_info",set,info,key
    set=self.interpret_set(set)
    
    if len(set)==1 and not self.ModInfo.has_key(set[0]): return 1
    if not info or not key: return 2

    changed = []
    for s in set:
      if self.ModInfo.has_key(s) and hasattr(self.ModInfo[s],info):
        if key == '*':
          try:
            delattr(self.ModInfo[s],info)
            changed.append(s)
          except:
            pass
        else:
          try:
            dic = getattr(self.ModInfo[s],info)
            if dic.has_key(key):
              del dic[key]
              setattr(self.ModInfo[s],dic)
            changed.append(s)
            if first: return 0
          except:
            pass
    if not changed: return 2

    if save:
      #save the edited set(s)
      for set in changed:
        self.save(set=set)
      
    return 0
    
#-----------------------------------------------------------------------
  def get_protocol_menu(self,type='',set=''):
#-----------------------------------------------------------------------
    '''
    Get definitions for menus to select by amino acid/element/etc
    Precedence of definitions: from the model (from .ccp4mg), from
    generic or from selection_protocols.py file
    '''
    import selection_protocols
    groups = getattr(selection_protocols,type+"_groups",{})
    names = getattr(selection_protocols,type,{})
    order = []
    if names.has_key('ORDER'):
      for item in names['ORDER']:order.append(item)

    #print "get_protocol_menu first order",order,names
    status,dic=self.get_info(set=set,info='selection_protocols', \
                             key=type+'_groups')
    if not status and dic:
      for key,value in dic[type+'_groups'].items():groups[key]=value

    status,dic=self.get_info(set=set,info='selection_protocols', \
                             key=type)
    if (not status) and dic:
      for key,value in dic[type].items():
        if key == 'ORDER':
          for item in value:
            if not order.count(item):order.append(item)
        else:
          names[key]=value

    #print "names",order,names
     
    menu = []
    alias = []
    if order:
      for item in order:
        if names.has_key(item):
          menu.append(names[item])
          alias.append(item)
        else:
          menu.append(item)
          alias.append(item)
    else:
      menu = names.values()
      alias = names.keys()

    return [menu,alias,groups]

#--------------------------------------------------------------------
  def add_name(self,key='',set='',new_name=''):
#--------------------------------------------------------------------
    '''
    Get a list of the residue/element types
    '''
    import selection_protocols
    import types
    order = []
    #print "add_name",key,new_name
    # BEware shallow copy... changes to order end up in selection_protocols
    std_sele = getattr(selection_protocols,key,{})
    if std_sele.has_key('ORDER'):
      for item in  std_sele['ORDER']: order.append(item)
    status,sel_prot= self.get_info(set=[set,'generic'], \
                  info='selection_protocols',key=key,first=1)
    if not sel_prot.has_key(key): sel_prot[key] = {}
    if sel_prot[key].has_key('ORDER'):
      for item in sel_prot[key]['ORDER']:
        if not order.count(item): order.append(item)
    
    if not order.count(new_name): order.append(new_name)
    #print "order",order
    sel_prot[key]['ORDER'] = order
    self.add_info(set=set, \
                info='selection_protocols',key=key,value=sel_prot[key])
    

#--------------------------------------------------------------------
  def get_names(self,key='',set=''):
#--------------------------------------------------------------------
    '''
    Get a list of the residue/element types
    '''
    import selection_protocols
    import types
    # BEware shallow copy... changes to order end up in selection_protocols
    order=[]
    
    if key == 'conflicts':
      return self.get_monomer_lib_conflicts()
    
    std_sele = getattr(selection_protocols,key,{})
    if std_sele.has_key('ORDER'):
      for item in  std_sele['ORDER']: order.append(item)
    status,sel_prot= self.get_info(set=[set,'generic'], \
                  info='selection_protocols',key=key,first=1)
    if not status and sel_prot.has_key(key):
      #sel_prot[key] can be either a list or a dictionary with an item 'ORDER'
      if isinstance(sel_prot[key],types.ListType):
        for item in sel_prot[key]:
          if not order.count(item):order.append(item)
      elif sel_prot[key].has_key('ORDER'):
        for item in sel_prot[key]['ORDER']: order.append(item)
    #print "get_names order",order
    return order

#-------------------------------------------------------------------------
  def get_names_string(self,key='',set=''):
#------------------------------------------------------------------------
    name_list = self.get_names(key=key,set=set)
    if not name_list: return ""
    text = name_list[0]
    if len(name_list) <= 1: return text
    for item in name_list[1:]: text = text + ',' + item
    return text

#------------------------------------------------------------------------
  def get_monomer_lib_conflicts(self):
#------------------------------------------------------------------------
    import os,utils,glob
    if not ModelInfo.monomer_library_elements:
      monlib = os.path.join(utils.get_CCP4MG(),'data','monomer_library')
      search_path = os.path.join(os.environ["CCP4_MGDATA"],'monomer_library','*','*_EL.cif')
      element_files = glob.glob(search_path)
      #print 'ModelInfo.get_elements_from_monomer_lib',  search_path, element_files
      ele_list = []
      for item in element_files:
        ele_list.append(os.path.split(item)[-1][0:-7])
      #print 'ModelInfo.get_elements_from_monomer_lib', ele_list
      ModelInfo.monomer_library_elements = ele_list
    return ModelInfo.monomer_library_elements

#----------------------------------------------------------------------
  def expand_selection_alias (self,alias='',set=''):
#----------------------------------------------------------------------
    '''
    Get the expansion of a selection alias
    Precedence is given to user defined selections
    '''
    expanded = []
    # Is it a user defined selection - either associated with
    # the model itself or from any loaded .ccp4mg
    
    if set:
      set_list=[set,'generic']
    else:
      set_list=['generic']
      
    status,user_selection=self.get_info(set=[set,'generic'], \
             info='user_selection',key=alias,first=1)
    if not status and user_selection:
      expanded = user_selection[alias] 
    #print "MolData expanded",expanded

    # Is the alias a generic selection from selection_protocols file
    if not expanded:
      import selection_protocols
      expanded = selection_protocols.generic_selection.get(alias,None)    
      #if expanded[1] == alias: expanded = None
    #print "initial expanded",expanded
    if expanded: expanded = self.apply_patches(input=expanded,set=set)
    #print "from apply_patches",expanded
    return expanded
  
#------------------------------------------------------------------------
  def apply_patches( self,input=[],set='' ):
#------------------------------------------------------------------------
    '''
    Substitute for selection aliases such as 'amino_acid' - can be
     model specific definition of list of residue types / element types
    '''
    #print "apply_patches",input,"set",set
    import re
    if not input:
      return input
    if input[0]==None:
      # Expand any residue/atom type/etc lists
      defn = input[1]
      #print "apply_patches defn",defn
      match = re.search('%([^%]+)%',defn)
      while match:
        type = match.groups()[0]
        names = self.get_names(key=type,set=set)
        #print "apply_patches type",type,names
        if not names:
          defn = re.sub('%'+type+'%','',defn)
        elif len(names) == 1:          
          defn = re.sub('%'+type+'%',names[0],defn)
        else:
          sub = names[0]
          for item in names[1:]: sub = sub + ',' + item
          #print "type",type,"sub",sub
          defn = re.sub('%'+type+'%',sub,defn)
        match = re.search('%([^%]+)%',defn)
      #print "final expanded",[None,defn]
      return [None,defn]
    elif len(input)==2:
      return [input[0],self.apply_patches(input[1],set=set) ]
    elif len(input)>2:
      return [input[0],self.apply_patches(input[1],set=set), \
                       self.apply_patches(input[2],set=set)]
    else:
      return input

  

#---------------------------------------------------------------
  def save_picture_definition(self,pretty_print=None,style='method',
                              set='',info='',keys=[] ):
#---------------------------------------------------------------
    alias = { 'user_selection' : 'SelectionScheme',
              'user_colour_schemes' : 'ColourScheme',}
    if not alias.has_key(info): return
    pic_def_class =  alias[info]
    import utils
    indent = 5
    if style == 'method':
      b_end = ' )\n\n'
      b_start = alias[info] + ' (' 
    else:
      b_end = '}'
      b_start = alias[info] + ' = {'
    stream = ''
    status,info_dict = self.get_info(set=set,info=info)
    #print "info_dict",info_dict
    if status: return ''
    if info == 'user_selection':
      status,info_selparams = self.get_info(set=set,info='user_selparams')
    
    import model_selection
    SP = model_selection.SelectionParser()
    if info == 'user_selection':
      for name,defn in info_dict.items():
        if not keys or keys.count(name):
          com = SP.list_to_command(defn)
          if not com[0]:
            stream = stream+ b_start + \
               utils.picture_defn_item('name',name,pretty_print,style=style) + \
               utils.picture_defn_item('selection',com[1],pretty_print,style=style)
            if set == 'generic':  stream = stream+utils.picture_defn_item('context','generic',pretty_print,style=style)
            if info_selparams.has_key(name):
               stream = stream+utils.picture_defn_item('selparams',info_selparams[name],pretty_print,style=style)
            stream = stream[0:-1] + b_end
    elif info == 'user_colour_schemes':
      for name,defn in info_dict.items():
        if not keys or keys.count(name):
          output = []
          for coldef in defn:
            com = SP.list_to_command(coldef[1])
            if not com[0]: output.append([coldef[0],com[1]])
          stream = stream+ b_start + \
             utils.picture_defn_item('name',name,pretty_print,style=style) + \
             utils.picture_defn_item('colours',output,pretty_print,style=style,indent=10)
          
          if set == 'generic':  stream = stream+utils.picture_defn_item('context','generic',pretty_print,style=style)
        
          stream = stream[0:-1] + b_end
        
    return stream

#---------------------------------------------------------------
  def getColourSchemeNames(self,MolData=''):
#---------------------------------------------------------------
    scheme_list = []

    #Create list of the sets to search
    sets = []
    # NB the set should be the name label
    if MolData: sets.append(MolData)
    sets.append('generic')
    sets.extend(self.list_loaded())

    for set in sets:
      status,user_colour_schemes = self.get_info( \
      set=[set],info='user_colour_schemes')
      if not status:
        col_list = []
        for col in user_colour_schemes.keys(): col_list.append(col)
        if col_list:          
          scheme_list.append([set,col_list])

    #print "getColourSchemes",scheme_list
    return scheme_list

#----------------------------------------------------------------
#----------------------------------------------------------------
#----------------------------------------------------------------
class ModInfo:
#----------------------------------------------------------------
#----------------------------------------------------------------
#----------------------------------------------------------------
# class to hold the data associted with coordinate file
# saved as .ccp4mg file

#---------------------------------------------------------------
  def init(self):
#---------------------------------------------------------------
    pass

#---------------------------------------------------------------
  def initialise(self,pdbfile=''):
#---------------------------------------------------------------
    import version
    import utils
    self.ccp4mg_version = version.ccp4mg_version
    if pdbfile:
      self.pdbfile = pdbfile

