# -*- coding: iso-8859-1 -*-
# Copyright (C) 2011-2012  EDF R&D
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License.
#
# This library 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.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
#
# Author : Guillaume Boulant (EDF)
#

from PyQt4.QtGui import QDialog, QIcon
from PyQt4.QtCore import QObject, SIGNAL, SLOT, Qt

from plugindialog_ui import Ui_PluginDialog
from inputdialog import InputDialog
from inputdata import InputData
# __GBO__: uncomment this line and comment the previous one to use the
# demo input dialog instead of the real one.
#from demoinputdialog import InputDialog

import os
import salome
from salome.kernel import studyedit
from salome.kernel.uiexception import AdminException

from omniORB import CORBA
import SMESH
import smesh
import MESHJOB

gui_states = ["CAN_SELECT", "CAN_COMPUTE", "CAN_REFRESH", "CAN_PUBLISH"]

run_states = ["CREATED", "IN_PROCESS", "QUEUED", "RUNNING", "PAUSED"];
end_states = ["FINISHED", "ERROR"]
all_states = run_states+end_states;

# The SALOME launcher resource is specified by its name as defined in
# the file CatalogResources.xml (see root directory of the
# application). We could have a check box in the dialog to specify
# wether we want a local execution or a remote one.
resource_name = "localhost"
from salome.smesh.spadder.configreader import ConfigReader


class PluginDialog(QDialog):

    def __init__(self,parent = None,name = None,modal = 0,fl = 0):
        QDialog.__init__(self,parent)
        # Set up the user interface from Designer.
        self.__ui = Ui_PluginDialog()
        self.__ui.setupUi(self)

        # The default display strategy is to use a separate dialog box
        # to select the input data.
        self.viewInputFrame(False)

        # The icon are supposed to be located in the plugin folder,
        # i.e. in the same folder than this python module file
        iconfolder=os.path.dirname(os.path.abspath(__file__))
        icon = QIcon()
        icon.addFile(os.path.join(iconfolder,"input.png"))
        self.__ui.btnInput.setIcon(icon)
        icon = QIcon()
        icon.addFile(os.path.join(iconfolder,"compute.png"))
        self.__ui.btnCompute.setIcon(icon)
        icon = QIcon()
        icon.addFile(os.path.join(iconfolder,"refresh.png"))
        self.__ui.btnRefresh.setIcon(icon)
        icon = QIcon()
        icon.addFile(os.path.join(iconfolder,"publish.png"))
        self.__ui.btnPublish.setIcon(icon)
        icon = QIcon()
        icon.addFile(os.path.join(iconfolder,"clear.png"))
        self.__ui.btnClear.setIcon(icon)

        # Then, we can connect the slot to there associated button event
        self.connect(self.__ui.btnInput,       SIGNAL('clicked()'), self.onInput )
        self.connect(self.__ui.btnCompute,     SIGNAL('clicked()'), self.onCompute )
        self.connect(self.__ui.btnRefresh,     SIGNAL('clicked()'), self.onRefresh )
        self.connect(self.__ui.btnPublish,     SIGNAL('clicked()'), self.onPublish )
        self.connect(self.__ui.btnClear,       SIGNAL('clicked()'), self.onClear )

        self.clear()

        self.setupJobManager()
        

    def setupJobManager(self):
        '''
        This function configures the jobmanager by transmiting the
        parameters required for a local execution and a remote
        execution. The choice between "local" and "remote" is done at
        the initialize step, by specifing the name of the resource to
        be used.
        '''
        # We first 
        
        configReader = ConfigReader()
        config = configReader.getLocalConfig()
        configId = config.resname
        self.__getJobManager().configure(configId, config)
        # Note that the resname parameter is used as the key identifier of
        # the configuration in the job manager. As is, there can be then
        # only one configuration for each machine defined in the resources
        # catalog (no need to have further, I thing)
        config = configReader.getRemoteConfig()
        configId = config.resname
        self.__getJobManager().configure(configId, config)

        # We specify the default configuration identifier as the
        # resource name of the default configuration
        self.__configId = configReader.getDefaultConfig().resname


    def viewInputFrame(self, view=True):
        # By default, the top input frame is visible and the input
        # button is not visible.
        if view is False:
            self.__ui.frameInput.setVisible(False)
            self.__ui.btnInput.setVisible(True)
            # We create the input dialog that will be displayed when
            # button input is pressed:
            self.__inputDialog = InputDialog(self)
            # The window is kept on the top to ease the selection of
            # items in the object browser:
            self.__inputDialog.setWindowFlags(
                self.__inputDialog.windowFlags() | Qt.WindowStaysOnTopHint)
            # The signal inputValidated emited from inputDialog is
            # connected to the slot function onProcessInput:
            self.connect(self.__inputDialog, SIGNAL('inputValidated()'), self.onProcessInput)
            
        else:
            self.__ui.frameInput.setVisible(True)
            self.__ui.btnInput.setVisible(False)
            # This case is NOT IMPLEMENTED YET (not really). It could
            # be used to draw the input frame directly in the frame
            # frameInput of this dialog box.

    def getInputFrame(self):
        return self.__ui.frameInput
        
    def __setGuiState(self,states=["CAN_SELECT"]):
        if "CAN_SELECT" in states:
            self.__ui.btnInput.setEnabled(True)
        else:
            self.__ui.btnInput.setEnabled(False)
            
        if "CAN_COMPUTE" in states:
            self.__ui.btnCompute.setEnabled(True)
        else:
            self.__ui.btnCompute.setEnabled(False)

        if "CAN_REFRESH" in states:
            self.__ui.btnRefresh.setEnabled(True)
        else:
            self.__ui.btnRefresh.setEnabled(False)

        if "CAN_PUBLISH" in states:
            self.__ui.btnPublish.setEnabled(True)
        else:
            self.__ui.btnPublish.setEnabled(False)

    def __getJobManager(self):
        """
        This function requests a pointer to the MeshJobManager
        servant. Note that the component is loaded on first demand,
        and then the reference is recycled.
        """
        if self.__dict__.has_key("__jobManager") and self.__jobManager is not None:
            return self.__jobManager

        # WARN: we first have to update the SALOME components catalog
        # with the SPADDER components (because they are not defined in
        # the SMESH catalog, and then they are not in the default
        # catalog)
        from salome.smesh import spadder
        spadder.loadSpadderCatalog()
        # Then we can load the MeshJobManager component
        component=salome.lcc.FindOrLoadComponent("FactoryServer","MeshJobManager")
        if component is None:
            msg="ERR: the SALOME component MeshJobManager can't be reached"
            self.__log(msg)
            raise AdminException(msg)

        self.__jobManager = component
        return self.__jobManager

    def __log(self, message):
        """
        This function prints the specified message in the log area
        """ 
        self.__ui.txtLog.append(message)

    def __exportMesh(self, meshName, meshObject):
        '''
        This function exports the specified mesh object to a med
        file whose name (basepath) is built from the specified mesh
        name. This returns the filename.
        '''
        filename=str("/tmp/padder_inputfile_"+meshName+".med")
        meshObject.ExportToMEDX( filename, 0, SMESH.MED_V2_2, 1 )
        return filename

    def clear(self):
        """
        This function clears the log area and the states of the buttons
        """
        self.__listInputData = []
        self.__ui.txtLog.clear()
        self.__setGuiState(["CAN_SELECT"])
        self.__isRunning = False
        self.__ui.lblStatusBar.setText("Ready")

    def update(self):
        '''
        This function can be used to programmatically force the
        refresh of the dialog box, the job state in particular.
        '''
        if self.__isRunning:
            self.onRefresh()

    def onInput(self):
        '''
        This function is the slot connected to the Input button
        (signal clicked()). It opens the dialog window to input
        data. The dialog is opened in a window modal mode so that the
        SALOME study objects can be selected. In conterpart, this
        class must listen to signals emitted by the child dialog
        windows to process the validation event (see the slot
        onProcessInput which is connected to this event).
        '''
        self.__inputDialog.setData(self.__listInputData)
        self.__inputDialog.open()

    def onProcessInput(self):
        """
        This function is the slot connected to the signal
        inputValidated(), emit by the input dialog window when it's
        validated, i.e. OK is pressed and data are valid.
        """
        # The processing simply consists in requesting the input data
        # from the dialog window.
        self.__listInputData = self.__inputDialog.getData()
        self.__ui.lblStatusBar.setText("Input data OK")
        self.__log("INF: Press \"Compute\" to start the job")
        self.__setGuiState(["CAN_SELECT", "CAN_COMPUTE"])
        
    def onCompute(self):
        '''
        This function is the slot connected to the Compute button. It
        initializes a mesh computation job and start it using the
        SALOME launcher.  
        '''
        # We first have to create the list of parameters for the
        # initialize function. For that, we have to create the files
        # from the mesh objects:
        meshJobParameterList=[]
        concreteIndex=0
        for inputData in self.__listInputData:
            # For each input data, we have to create a
            # MeshJobParameter and add it to the list.
            filename  = self.__exportMesh(inputData.meshName, inputData.meshObject)
            if inputData.meshType == InputData.MESHTYPES.CONCRETE:
                filetype = MESHJOB.MED_CONCRETE
            else:
                filetype = MESHJOB.MED_STEELBAR

            parameter = MESHJOB.MeshJobParameter(
                file_name  = filename,
                file_type  = filetype,
                group_name = inputData.groupName)
            meshJobParameterList.append(parameter)

        jobManager = self.__getJobManager()
        self.__jobid = jobManager.initialize(meshJobParameterList, self.__configId)
        if self.__jobid < 0:
            self.__log("ERR: the job can't be initialized")
            return
        self.__log("INF: the job has been initialized with jobid = "+str(self.__jobid))
        
        startOk = jobManager.start(self.__jobid)
        if not startOk:
            self.__log("ERR: the job with jobid = "+str(self.__jobid)+" can't be started")
            return
        self.__log("INF: the job "+str(self.__jobid)+" has been started")
        self.__ui.lblStatusBar.setText("Submission OK")
        self.__setGuiState(["CAN_REFRESH"])
        self.__isRunning = True

    def onRefresh(self):
        """
        This function is the slot connected on the Refresh button. It
        requests the mesh job manager to get the state of the job and
        display it in the log area.
        """
        jobManager = self.__getJobManager()
        state = jobManager.getState(self.__jobid)
        self.__log("INF: job state = "+str(state))
        self.__ui.lblStatusBar.setText("")
        if state in run_states:
            return

        self.__isRunning = False
        if state == "FINISHED":
            self.__setGuiState(["CAN_PUBLISH"])
        else:
            self.__setGuiState(["CAN_SELECT"])


    def onPublish(self):
        """
        This function is the slot connected on the Publish button. It
        requests the mesh job manager to download the results data
        from the computation resource host and load the med file in
        the SALOME study. 
        """
        jobManager = self.__getJobManager()
        state = jobManager.getState(self.__jobid)
        if state in run_states:
            self.__log("WRN: jobid = "+str(self.__jobid)+" is not finished (state="+str(state)+")")
            return

        if state not in end_states:
            self.__log("ERR: jobid = "+str(self.__jobid)+" ended abnormally with state="+str(state))
            return

        meshJobResults = jobManager.finalize(self.__jobid)
        if state == "ERROR":
            self.__log("ERR: jobid = "+str(self.__jobid)+" ended with error: "+meshJobResults.status)
            return

        logsdirname = os.path.join(meshJobResults.results_dirname, "logs")
        self.__log("INF:  jobid="+str(self.__jobid)+" ended normally   : "+meshJobResults.status)
        self.__log("INF:  jobid="+str(self.__jobid)+" see log files in : "+logsdirname)

        medfilename = os.path.join(meshJobResults.results_dirname,
                                   meshJobResults.outputmesh_filename)

        smesh.SetCurrentStudy(studyedit.getActiveStudy())
        ([outputMesh], status) = smesh.CreateMeshesFromMED(medfilename)

        # By convention, the name of the output mesh in the study is
        # build from a constant string 'padder' and the session jobid
        meshname = 'padder_'+str(self.__jobid)
        smesh.SetName(outputMesh.GetMesh(), meshname)
        if salome.sg.hasDesktop():
            salome.sg.updateObjBrowser(0)

        self.__ui.lblStatusBar.setText("Publication OK")
        self.__setGuiState(["CAN_SELECT"])

    def onClear(self):
        """
        This function is the slot connected on the Clear button. It
        erases data in the dialog box and cancel the current job if
        one is running.
        """
        self.clear()
        


__dialog=None
def getDialog():
    """
    This function returns a singleton instance of the plugin dialog. 
    """
    global __dialog
    if __dialog is None:
        __dialog = PluginDialog()
    return __dialog

#
# ==============================================================================
# Basic use cases and unit test functions
# ==============================================================================
#
def TEST_PluginDialog():
    import sys
    from PyQt4.QtGui import QApplication
    from PyQt4.QtCore import QObject, SIGNAL, SLOT
    app = QApplication(sys.argv)
    QObject.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))

    dlg=PluginDialog()
    dlg.exec_()

if __name__ == "__main__":
    TEST_PluginDialog()

        

