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

"""
     Liz Potterton July 2013 - create and manage workflows
"""

import os
from PyQt4 import QtGui,QtCore
import CCP4Data,CCP4Container,CCP4CustomisationGui
from CCP4ErrorHandling import *
from CCP4Modules import WORKFLOWMANAGER,WEBBROWSER,PROJECTSMANAGER

def openWorkflowManagerGui():
  if CWorkflowManagerGui.insts is None:
    CWorkflowManagerGui.insts = CWorkflowManagerGui()
  CWorkflowManagerGui.insts.show()
  CWorkflowManagerGui.insts.raise_()


class CWorkflowManagerGui(CCP4CustomisationGui.CCustomisationGui):

  insts = None

  ERROR_CODES = { 201 : { 'description' : 'Unknown error creating workflow' },
                  202 : { 'description' : 'Unknown error attempting to edit workflow' } }


  def __init__(self,parent=None):
    CCP4CustomisationGui.CCustomisationGui.__init__(self,parent=parent,mode='workflow',title='Workflow Manager')

  def manager(self):
    return WORKFLOWMANAGER()

  def handleNew(self):
    if self.createWidget is None:
      self.createWidget = CCreateWorkflowDialog(self)
      self.connect(self.createWidget,QtCore.SIGNAL('workflowCreated'),self.handleEdit)
    else:
      self.createWidget.reset()
      
    self.createWidget.show()
    self.createWidget.raise_()


  def handleEdit(self,selected=None):
    if selected is None: selected = self.customListView.selectedItem()
    if selected is None: return
    try:
      editor = CWorkflowEditDialog(self,name=selected)
    except CException as e:
      e.warningMessage('Create workflow','Error opening workflow editor.\nPossibly a file is corrupted.\n'+self.manager().getDirectory(selected),parent=self)
      return
    except Exception as e:
      e = CException(self.__class__,202,exc_info=sys.exc_info())
      e.warningMessage('Create workflow','Error opening workflow editor.\nPossibly a file is corrupted.\n'+self.manager().getDirectory(selected),parent=self)
      return
    editor.show()


    
class CCreateWorkflowDialog(QtGui.QDialog):

  ERROR_CODES = { 201 : { 'description' : 'Unkown error saving workflow' } }

  def __init__(self,parent=None):
    QtGui.QDialog.__init__(self,parent)
    self.setWindowTitle('Create a workflow')
    self.setLayout(QtGui.QVBoxLayout())

    self.projectCombo = QtGui.QComboBox(self)
    self.projectCombo.setEditable(False)
    
    line = QtGui.QHBoxLayout()
    line.addWidget(QtGui.QLabel('Select from project'))
    line.addWidget(self.projectCombo)
    self.layout().addLayout(line)

    line = QtGui.QHBoxLayout()
    line.addWidget(QtGui.QLabel('Select jobs'))
    import CCP4Widgets
    self.jobsLineEdit = CCP4Widgets.CJobSelectionLineEdit(self)
    line.addWidget(self.jobsLineEdit)
    self.layout().addLayout(line)

    line = QtGui.QHBoxLayout()
    line.addWidget(QtGui.QLabel('Name'))
    self.nameLineEdit= QtGui.QLineEdit(self)
    self.nameLineEdit.setToolTip('Enter a one-word name for the workflow')
    line.addWidget(self.nameLineEdit)
    
    self.layout().addLayout(line)

    buttonBox = QtGui.QDialogButtonBox(self)
    but = buttonBox.addButton('Help',QtGui.QDialogButtonBox.HelpRole)
    but.setFocusPolicy(QtCore.Qt.NoFocus)
    self.connect(but,QtCore.SIGNAL('released()'),self.help)
    but = buttonBox.addButton('Cancel',QtGui.QDialogButtonBox.RejectRole)
    but.setFocusPolicy(QtCore.Qt.NoFocus)
    self.connect(but,QtCore.SIGNAL('released()'),self.cancel)
    but = buttonBox.addButton('Create workflow',QtGui.QDialogButtonBox.AcceptRole)
    but.setFocusPolicy(QtCore.Qt.NoFocus)
    self.connect(but,QtCore.SIGNAL('released()'),self.accept)
    self.layout().addWidget(buttonBox)

    self.reset()

  def reset(self):
    # get list of projectId,projectName,projectDir,parentId
    projectList =  PROJECTSMANAGER().db().listProjects(order='name')
    #print 'CCreateWorkflowDialog.reset projectList',projectList
    for project in projectList:
      item = self.projectCombo.addItem(project[1],QtCore.QVariant(project[0]))
    self.jobsLineEdit.clear()
    self.nameLineEdit.clear()

  def help(self):
    WEBBROWSER().loadWebPage(helpFileName='customisation')

  def cancel(self):
    self.hide()

  def accept(self):
    projectId = self.projectCombo.itemData(self.projectCombo.currentIndex()).toString().__str__()
    self.jobsLineEdit.setProjectId(projectId)
    jobList,errList = self.jobsLineEdit.getSelection()
    if len(errList)>0:
      text = 'Error interpreting job selection:'
      for err in errList: text = text + '\n' + err
      QtGui.QMessageBox.warning(self,'Create workflow',text)
      return

    name = self.nameLineEdit.text().__str__()
    if len(name)<1:
      QtGui.QMessageBox.warning(self,'Create workflow','Please provide a unique one-word name for workflow')
      return

    import re
    one_word_name = re.sub('[^a-zA-Z0-9_-]','_',name)
    if one_word_name != name:
      self.nameLineEdit.setText(one_word_name)
      QtGui.QMessageBox.warning(self,'Create workflow','The workflow name will be used as a directory name\n' +
                                'it has been changed to limited chacter set a-z,A-Z,0-9,_,-\n' +
                                "Please click 'Create workflow' again" )
      return

    diry = WORKFLOWMANAGER().getDirectory(name=name)
    if os.path.exists(diry):
      import functools
      msgBox = QtGui.QMessageBox()
      msgBox.setWindowTitle('Create workflow')
      msgBox.setText('There is already a workflow directory called\n'+os.path.split(diry)[1])
      b = msgBox.addButton(QtGui.QMessageBox.Cancel)
      b = msgBox.addButton('Overwrite',QtGui.QMessageBox.ApplyRole)
      self.connect(b,QtCore.SIGNAL('released()'),functools.partial(self.handleOverwrite,(projectId,jobList,name)))
      msgBox.exec_()
      return
    
    self.createWorkflow(projectId,jobList,name)
    self.hide()

  def handleOverwrite(self,args):
    projectId,jobList,name = args
    #print 'handleOverwrite',projectId,jobList,name
    self.createWorkflow(projectId,jobList,name,True)
    self.hide()

  def createWorkflow(self,projectId,jobList,name,overwrite=False,title=None):
    try:
      err = WORKFLOWMANAGER().createWorkflow(projectId=projectId,jobList=jobList,name=name,overwrite=overwrite,title=title)
    except CException as err:
      err.warningMessage('Create workflow','Error saving workflow',parent=self)
      return
    except Exception as e:
      import sys
      err = CException(self.__class__,201,exc_info=sys.exc_info())
      err.warningMessage('Create workflow','Error saving workflow',parent=self)
      return
    if err.maxSeverity()>SEVERITY_WARNING:
      err.warningMessage('Create workflow','Error saving workflow',parent=self)
      return
    self.emit(QtCore.SIGNAL('workflowCreated'),name)




class CWorkflowEditDialog(QtGui.QDialog):

  def __init__(self,parent,name):
    QtGui.QDialog.__init__(self,parent)
    self.setWindowTitle('Edit workflow interface:'+str(name))
    self.setLayout(QtGui.QVBoxLayout())
    MARGIN = 5
    self.layout().setSpacing(MARGIN)
    self.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN)

    label = QtGui.QLabel('Short title for workflow task',self)
    label.setObjectName('italic')
    self.layout().addWidget(label)
    self.setWindowTitle('Edit workflow: '+name)
    self.name = name
    directory = WORKFLOWMANAGER().getDirectory(self.name)

    import CCP4WorkflowManager
    self.workflowDef = CCP4WorkflowManager.CWorkflowDefinition(self,name=self.name)
    fileName = WORKFLOWMANAGER().getCustomFile(self.name)
    #print 'CWorkflowEditDialog workflowDef',self.name,self.workflowDef.header.pluginName,fileName
    self.workflowDef.loadDataFromXml(fileName,function='WORKFLOW')

    self.jobDefs = CCP4WorkflowManager.CWorkflowContainerList()
    self.jobDefs.loadContentsForWorkflow(directory)
    #print 'CWorkflowEditDialog.init jobDefs',self.jobDefs['0'].dataOrder()

    self.titleWidget = QtGui.QLineEdit(self)
    self.layout().addWidget(self.titleWidget)

    label = QtGui.QLabel('Label for input files in workflow gui',self)
    label.setObjectName('italic')
    self.layout().addWidget(label)
    self.inputWidgets = []
    for i in range(len(self.jobDefs['job_0'].inputData.dataOrder())):
      self.inputWidgets.append(CWorkflowEditInputWidget(self))
      self.layout().addWidget(self.inputWidgets[-1])

    label = QtGui.QLabel('Select overall workflow output files and file names to appear in gui',self)
    label.setObjectName('italic')
    self.layout().addWidget(label)
    self.outputFrame = CWorkflowOutputChooser(self)
    self.layout().addWidget(self.outputFrame)

    line = QtGui.QHBoxLayout()
    line.addStretch(2)
    butBox = QtGui.QDialogButtonBox(self)
    but = butBox.addButton('Save workflow',QtGui.QDialogButtonBox.ActionRole)
    but.setDefault(False)
    self.connect(but,QtCore.SIGNAL('released()'),self.apply)
    but = butBox.addButton(QtGui.QDialogButtonBox.Cancel)
    self.connect(but,QtCore.SIGNAL('released()'),self.close)
    line.addWidget(butBox)
    line.addStretch(2)

    self.layout().addLayout(line)

    self.outputFrame.load(self.workflowDef,self.jobDefs)
    self.updateViewFromModel()

  def close(self):
    self.hide()
    self.deleteLater()

  def apply(self):
    self.updateModelFromView()
    self.save()
    self.close()


  def updateViewFromModel(self):
    self.titleWidget.setText(self.jobDefs['job_0'].header.pluginTitle.__str__())
    inputFileList = self.jobDefs.inputFileList(workflowDef=self.workflowDef)
    ii = -1
    for inputFile in inputFileList:
      ii += 1
      try:
        self.inputWidgets[ii].desc.setText(inputFile['className']+ ' '+inputFile['name']+' '+inputFile.get('taskName',''))
        self.inputWidgets[ii].label.setText(inputFile['label'])
        self.inputWidgets[ii].setObjectName(inputFile['name'])
      except:
        print 'ERROR in CWorkflowEditDialog.updateViewFromModel for inputFile',inputFile

    for cb in self.outputFrame.findChildren(QtGui.QCheckBox): cb.setChecked(False)
    for outFile in self.workflowDef.jobDef.job_0.output:
      cbName = str(outFile.fromJob)+'_'+str(outFile.fromKey)
      cb = self.outputFrame.findChild(QtGui.QCheckBox,cbName)
      #print 'CWorkflowEditDialog.updateViewFromModel outFile',outFile,cbName,cb
      if cb is not None: cb.setChecked(True)
      le = self.outputFrame.findChild(QtGui.QLineEdit,cbName)
      if le is not None:
        #le.setText(self.jobDefs.__getattr__(str(outFile.fromJob)).outputData.__getattr__(str(outFile.fromKey)).annotation.__str__())
        le.setText(outFile.annotation.__str__())
                                         
  def updateModelFromView(self):
    import CCP4WorkflowManager,CCP4Container
    self.jobDefs['job_0'].header.pluginTitle.set(str(self.titleWidget.text()))
    for inputWidget in self.inputWidgets:
      label = str(inputWidget.label.text()).strip()
      if len(label)==0: label = None
      self.jobDefs['job_0'].inputData.__getattr__(str(inputWidget.objectName())).setQualifier('guiLabel',label)

    nOutputFiles = 0
    workflowOutput = CCP4WorkflowManager.CWorkflowDataFlowList(parent=self)
    workflowOutput.setObjectName('output')
    jobZeroOutputParams = CCP4Container.CContainer(parent=self,name='outputData')
    #print 'CWorkflowEditDialog.updateModelFromView jobDefs',self.jobDefs.dataOrder()
    for jobKey in self.workflowDef.jobDef.dataOrder():
      job = self.workflowDef.jobDef.__getattr__(jobKey)
      #print 'CWorkflowEditDialog.updateModelFromView jobDefs',self.jobDefs[str(jobKey)].dataOrder()
      for fileObj in job.allOutputFiles:
        obj = self.jobDefs[jobKey].outputData.__getattr__(fileObj.key.__str__())
        cb = self.outputFrame.findChild( QtGui.QCheckBox, jobKey+'_'+fileObj.key.__str__() )
        le = self.outputFrame.findChild( QtGui.QLineEdit, jobKey+'_'+fileObj.key.__str__() )
        if cb.isChecked():
          nOutputFiles+= 1
          outKey = 'OUTPUT'+str(nOutputFiles)
          annotation = le.text().__str__()
          if len(annotation)==0: annotation = None
          workflowOutput.append(
              { 'toKey': outKey,'fromJob' : jobKey, 'fromKey' : fileObj.key.__str__(), 'annotation' : annotation } )
          qualifiers = obj.qualifiers(default=False,custom=True)
          for item in ['contentFlag','subType','sameCrystalAs']:
              if qualifiers.has_key(item): del qualifiers[item]
          newObj = obj.__class__(parent=jobZeroOutputParams,name=outKey,qualifiers=qualifiers)
          jobZeroOutputParams.addObject(newObj)

                                         
    self.jobDefs['job_0'].__dict__['_value']['outputData'] =jobZeroOutputParams
    self.workflowDef.jobDef['job_0'].__dict__['_value']['output'] = workflowOutput
    
  def save(self):
    workflowDir = WORKFLOWMANAGER().getDirectory(self.name)
    self.workflowDef.header.pluginTitle = self.titleWidget.text().__str__()
    self.workflowDef.saveDataToXml(WORKFLOWMANAGER().getCustomFile(self.name,mustExist=False),function='WORKFLOW')
    self.jobDefs['job_0'].saveContentsToXml(fileName=os.path.join(workflowDir,'job_0.def.xml'))
    for jobKey in self.jobDefs.dataOrder()[1:]:
      self.jobDefs[jobKey].saveDataToXml(fileName=os.path.join(workflowDir,jobKey+'.params.xml'))
    WORKFLOWMANAGER().emitSignal('listChanged')
    
class CWorkflowEditInputWidget(QtGui.QFrame):

  def __init__(self,parent=None):
    QtGui.QFrame.__init__(self,parent=None)
    self.setLayout(QtGui.QHBoxLayout())
    MARGIN = 1
    self.layout().setSpacing(MARGIN)
    self.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN)
    self.desc = QtGui.QLabel(self)
    self.desc.setMinimumWidth(200)
    self.layout().addWidget(self.desc)
    self.label = QtGui.QLineEdit(self)
    self.label.setMinimumWidth(200)
    self.layout().addWidget(self.label)

class CWorkflowOutputChooser(QtGui.QScrollArea):

  def __init__(self,parent=None):
    QtGui.QScrollArea.__init__(self,parent)
    self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
    self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
    
    frame = QtGui.QFrame(self)
    frame.setLayout(QtGui.QGridLayout())
    MARGIN = 1
    frame.layout().setSpacing(MARGIN)
    frame.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN)    
    self.setWidget(frame)
    self.setWidgetResizable(1)
    
  def load(self,workflowDef,jobDefs):
    layout = self.widget().layout()
    #print 'CWorkflowOutputChooser.load',jobDefs.dataOrder()
    indx = -1
    for jobKey in workflowDef.jobDef.dataOrder()[1:]:
      indx += 1
      job = workflowDef.jobDef.__getattr__(jobKey)
      jobOutput = jobDefs.__getattr__(jobKey).outputData
      layout.addWidget(QtGui.QLabel(job.taskName.__str__(),self),indx,0,1,2)
      for fileObj in job.allOutputFiles:
        indx += 1
        key = fileObj.key.__str__()
        line = QtGui.QHBoxLayout()
        cb = QtGui.QCheckBox(key,self)
        cb.setObjectName(jobKey+'_'+key)
        #print 'CWorkflowOutputChooser.load',fileObj.key,fileObj.ifOverallOutput
        #if fileObj.ifOverallOutput: cb.setChecked(True)
        layout.addWidget(cb,indx,0)
        le = QtGui.QLineEdit(self)
        le.setObjectName(jobKey+'_'+key)
        layout.addWidget(le,indx,1)

    self.widget().show()

