"""
     CCP4DataManager.py: CCP4 GUI Project
     Copyright (C) 2010 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.
"""

"""
   Liz Potterton Aug 2010 - Class to keep track of all CCP4Data and CCP4Widget classes
"""


from CCP4Config import GRAPHICAL,DEVELOPER
from CCP4ErrorHandling import *
import CCP4Utils


def DATAMANAGER():
    if CDataManager.insts is None:
        CDataManager.insts = CDataManager()
    return CDataManager.insts

class CDataManager:

    EXCLUDE_CLASSES = ['CData','CBaseData','CCollection','CList','CDict','CContainer']
    EXCLUDE_FILES = ['CCP4ComFilePatchManager.py','CCP4CustomTaskManager.py','CCP4ImportedJobManager.py','CCP4WorkflowManager.py','CCP4Preferences.py']


    ERROR_CODES = { 101: { 'description' : 'No model given for widget()' },
                    102: { 'description' : 'The model is not a CCP4 CData' },
                    103: { 'description' : 'No parent widget given for widget()' },
                    104: { 'description' : 'Parent widget is not a Qt QWidget' },
                    105: { 'description' : 'No suitable widget class found for model' },
                    106: { 'description' : 'Error handling qualifiers for widget' },
                    107: { 'description' : 'Loading DATAMANAGER, failed attempting to import module' },
                    108: { 'description' : 'Undetermined error creating widget' }
                    }

    insts = None
    def __init__(self):
      import os
      self.clsLookup= {}
      self.widgetLookup= {}
      self.viewLookup= {}
      self.toUpper = {}
      self._searchPath =  [ os.path.join(CCP4Utils.getCCP4I2Dir(),'core') ]
      if  GRAPHICAL(): self._searchPath.extend (
                           [ os.path.join(CCP4Utils.getCCP4I2Dir(),'qtgui') ] )
      rv = self.buildClassLookup()
      if len(rv)>0: print rv.report()

    def searchPath(self):
      return self._searchPath

    def reportClassSearchPath(self):
      import os
      import CCP4Utils
      return  [ os.path.join(CCP4Utils.getCCP4I2Dir(),'report') ]

    def appendSearchPath(self,path):
      # Append a path which could include wildcard * - must work
      # with glob
      import os
      path = os.path.abspath(path)
      if not self._searchPath.count(path):
        self._searchPath.append(path)

    def customClasses(self):
      if GRAPHICAL():
        import CCP4Data,CCP4Widgets,CCP4ReportWidgets
        return [ ]
      else:
        import CCP4Data
        return [  ]
                   

    def buildClassLookup(self):
      import inspect,types,string
      import CCP4Data
      if GRAPHICAL():
        import CCP4Widgets
      myErrorReport = CErrorReport()
      pyFileList = CCP4Utils.globSearchPath(self.searchPath(),'*.py')
      idx,fn = CCP4Utils.listReMatch(pyFileList,'.*CCP4Update\.py')
      if len(idx)>0: pyFileList.pop(idx[0])
      for pyFile in pyFileList:
        module,err = CCP4Utils.importFileModule(pyFile)
        if module is None:
          myErrorReport.append(self.__class__,107,stack=False,details=str(pyFile)+'\n'+str(err))
        clsList = inspect.getmembers(module,inspect.isclass)
        for name,cls in clsList:
          if issubclass(cls,CCP4Data.CData):
            self.clsLookup[name] = cls
            self.toUpper[string.lower(name)] = name

        if GRAPHICAL():
          for name,cls in clsList:
            if issubclass(cls,CCP4Widgets.CViewWidget):
              self.widgetLookup[name] = cls
              model = getattr(cls,'MODEL_CLASS',None)
              if model is not None:
                if isinstance(model,types.TupleType):
                  for item in model:
                    self.viewLookup[item] = cls
                else:
                  self.viewLookup[model] = cls

      for name,cls,widgetCls in self.customClasses():
        self.clsLookup[name] = cls
        self.toUpper[string.lower(name)] = name
        if widgetCls is not None: self.viewLookup[cls] = widgetCls
      return myErrorReport

    def printLookup(self):
      classNameList = self.clsLookup.keys()
      classNameList.sort()
      for item in classNameList:
         print "{0:20} {1:30} {2:30}".format(item,self.clsLookup[item],self.viewLookup.get(self.clsLookup[item],'No widget'))
       
    def getClass(self,className=''):
      if self.clsLookup.has_key(className):
        return self.clsLookup[className]
      elif self.toUpper.has_key(className):
        return self.clsLookup[self.toUpper[className]]
      elif self.widgetLookup.has_key(className):
        return self.widgetLookup[className]
      else:
        return None

    def getWidgetClass(self,model=None,modelClass=None,name=''):
      import CCP4Data
      import types
      if modelClass is None:
        try:
          modelClass = model.__class__
        except:
          pass

      #raise CException(self.__class__,102)
      widgetClass =  self.viewLookup.get(modelClass,None)
      #print 'getWidgetClass',name,modelClass,widgetClass
      if widgetClass is not None: return widgetClass
      # Try looking for a CView for a parent class
      cls = modelClass
      while cls != CCP4Data.CData and widgetClass is None:
        try:
          cls = cls.__bases__[0]
          #print 'getWidgetClass trying',name,cls
          widgetClass =  self.viewLookup.get(cls,None)
        except:
          cls = CCP4Data.CData
        #print 'getWidgetClass cls,widgetClass',cls,widgetClass
      if widgetClass is None:  raise CException(self.__class__,105,'For data: '+name)
      return widgetClass
        

    def widget(self,model=None,parentWidget=None,qualifiers={},name='',modelClass=None):
      if not GRAPHICAL(): return None
      from PyQt4 import QtGui
      widgetClass =  self.getWidgetClass(model=model,modelClass=modelClass,name=name)
      
      if parentWidget is None: raise CException(self.__class__,103)
      if not isinstance(parentWidget,QtGui.QWidget): raise CException(self.__class__,104)


      try:
        if model is not None:
          qualis = { 'dragType' :  model.__class__.__name__[1:] }
          qualis.update(model.qualifiers())
        elif modelClass is not None:
          qualis = { 'dragType' :  modelClass.__name__[1:] }
        else:
          qualis = {}
        qualis.update(qualifiers)
      except:
        raise CException(self.__class__,106)
    
    
      if DEVELOPER():
        widget = widgetClass(parent=parentWidget,model=model,qualifiers=qualis)
      else:
        try:
          widget = widgetClass(parent=parentWidget,model=model,qualifiers=qualis)
        except CException as e:
          raise e
        except:
          raise CException(self.__class__,108,stack=False)
          
      return widget

    def buildQStandardItemModel(self,parent=None,mode=None):
      from PyQt4 import QtGui
      import inspect,os
      import CCP4Data
      myErrorReport = CErrorReport()

      model = QtGui.QStandardItemModel(parent)
      root = model.invisibleRootItem()

      if mode == 'repgen':
        pyFileList = CCP4Utils.globSearchPath(self.reportClassSearchPath(),'*.py')
      else:
        pyFileList = CCP4Utils.globSearchPath(self.searchPath(),'*.py')
      for pyFile in pyFileList:
        if os.path.split(pyFile)[1] not in self.EXCLUDE_FILES:
          module,err = CCP4Utils.importFileModule(pyFile)
          moduleItem = None
          if module is None:
            myErrorReport.append(self.__class__,107,pyFile,stack=False,details=str(err))
          clsList = inspect.getmembers(module,inspect.isclass)
          for name,cls in clsList:
            if issubclass(cls,CCP4Data.CData) and not ['CData','CBaseData','CCollection','CList','CDict','CContainer'].count(name):
              if moduleItem is None:
                  moduleItem = QtGui.QStandardItem(module.__name__)
                  root.appendRow(moduleItem)
              clsItem = QtGui.QStandardItem(cls.__name__)
              if cls.__doc__ is not None: clsItem.setToolTip(cls.__doc__)
              moduleItem.appendRow(clsItem)
      return model

    def makeHtmlClassListing(self):
      import CCP4DefEd,os,inspect,re
      import CCP4Utils
      import CCP4Data
      import CCP4Modules
      import CCP4Annotation

      
      text = {}
      indexText = ''
      pyFileList = CCP4Utils.globSearchPath(self.searchPath(),'*.py')
      for pyFile in pyFileList:
        if os.path.split(pyFile)[1] not in self.EXCLUDE_FILES:
          module,err = CCP4Utils.importFileModule(pyFile)
          if module is None:
            myErrorReport.append(self.__class__,107,pyFile,stack=False,details=str(err))
          moduleName = os.path.splitext(os.path.basename(pyFile))[0]
          text = '<html>\n'
          clsList = inspect.getmembers(module,inspect.isclass)
          nClass = 0
          for name,cls in clsList:
            if issubclass(cls,CCP4Data.CData) and not name in self.EXCLUDE_CLASSES:
              nClass = nClass + 1
              info = CCP4DefEd.CClassInfo(name,CCP4Modules.QTAPPLICATION())
              text = text + info.getInfo(html=False)
        
          text = text +'</html>\n'
          if nClass>0:
            fileName = os.path.join(CCP4Utils.getCCP4I2Dir(),'docs','developers','modules',moduleName+'.html')
            CCP4Utils.saveFile(fileName=fileName,text=text)
            indexText = indexText + '<h4>'+moduleName+'</h4>\n'
            for name,cls in clsList:
              if issubclass(cls,CCP4Data.CData) and not name in self.EXCLUDE_CLASSES:
                doc = cls.__doc__
                if doc is None: doc = ''
                indexText = indexText + '<a href="./'+moduleName+'.html#'+name+'">'+name+'</a> '+doc+'<br/>\n'

      index0Text = CCP4Utils.readFile(fileName= os.path.join(CCP4Utils.getCCP4I2Dir(),'docs','developers','modules','index0.html'))
      allText = re.sub('INSERT_CLASS_LINKS_HERE',indexText,index0Text)
      t = CCP4Annotation.CTime()
      t.setCurrentTime()
      allText = re.sub('INSERT_DATE',str(t),allText)
      CCP4Utils.saveFile(fileName=os.path.join(CCP4Utils.getCCP4I2Dir(),'docs','developers','modules','index.html'),text=allText)

      
      return

#=======================================================================================================
import unittest

# Weird - this is failing with Qt error message - can not create widget when no GUI
# But works OK when do same thing manually 

'''
def TESTSUITE():
  suite = unittest.defaultTestLoader.loadTestsFromTestCase(testDataManager)
  return suite

def testModule():
  suite = TESTSUITE()
  unittest.TextTestRunner(verbosity=2).run(suite)

'''


class testDataManager(unittest.TestCase):

  def setUp(self):
    '''
    if GRAPHICAL():
      from CCP4Modules import QTAPPLICATION
      self.app = QTAPPLICATION()
      print 'testDataManager.setUp',GRAPHICAL(),type(self.app)
      from PyQt4 import QtGui
      self.dialog = QtGui.QDialog()
    '''
    from CCP4Data import CFloat
    self.testData = CFloat()
    self.manager = DATAMANAGER()

  def test1(self):
    import CCP4Widgets
    if GRAPHICAL():
      from CCP4Modules import QTAPPLICATION
      app = QTAPPLICATION()
      widgetClass = self.manager.getWidgetClass(self.testData)
      self.assertEqual(widgetClass,CCP4Widgets.CFloatView,'Failed to return correct widget class')
    else:
      self.fail('Can not test CCP4DataMananger in non-graphical mode')

  def test2(self):
    if GRAPHICAL():
      from CCP4Modules import QTAPPLICATION
      app = QTAPPLICATION()
      import CCP4Widgets
      from PyQt4 import QtGui
      dialog = QtGui.QDialog()
      widget = self.manager.widget(model=self.testData,parentWidget=dialog)
      self.assertTrue(isinstance(widget,CCP4Widgets.CFloatView),'Failed to create CFloatView widget')
    else:
      self.fail('Can not test CCP4DataMananger in non-graphical mode')

