# Copyright (c) 2011-2014, B.I.Stepanov Institute of Physics, National Academy
# of Sciences of Belarus.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

from PyQt4.QtCore import *

__all__ = ['MatlabMultiProcess']

# *****************************************************************************
class MatlabMultiProcess(QObject):
    """A class with the same interface as that of 'MatlabProcess', but
    capable of invoking several Matlab optimization procedures in a row."""

    # ---- Signals ------------------------------------------------------------
    retrievalFinished = pyqtSignal()
    outputAvailable = pyqtSignal(QString)
    iterationCompleted = pyqtSignal(int)

    # ---- Public overridable methods -----------------------------------------
    def __init__(self):
        QObject.__init__(self)

        self.matlabProcessGenerator = None
        self.matlabProcess = None
        self.retrievalOutputs = []

    # ---- Public methods -----------------------------------------------------
    def startRetrieval(self, errorMessage = None):

        # Cancel the results of previous retrievals, if any.
        self.retrievalOutputs = []

        # Initialize the multi-step retrieval process.
        self.matlabProcessGenerator = self.getMatlabProcessGenerator()
        # Launch the first optimization procedure yielded by the generator.
        self.launchSubRetrieval()

    def cancelRetrieval(self):

        # Silently cancel any of the pending optimization procedures.
        if self.matlabProcessGenerator is not None:
            self.matlabProcessGenerator.close()

        # Terminate the running optimization procedure, if any.
        if self.matlabProcess is not None:
            self.matlabProcess.cancelRetrieval()

    def getErrorMessage(self):

        if self.matlabProcess is None:
            return None

        return self.matlabProcess.getErrorMessage()

    def getRetrievalOutput(self, iteration = -1):

        if len(self.retrievalOutputs) == 0:
            return None

        return self.retrievalOutputs[iteration]

    def getAllRetrievalOutputs(self):

        return self.retrievalOutputs

    # ---- Protected overridable methods --------------------------------------
    def getMatlabProcessGenerator(self):
        """Construct and return a generator object that will yield
        'MatlabProcess' instances to be invoked.

        Whenever the generator function runs into a 'yield' statement, its
        execution will be paused until the yielded 'MatlabProcess' is finished.
        If that process terminates with an error, the generator function will
        be closed with 'GeneratorExit' exception. Otherwise, it will resume its
        execution from the point where it had been interrupted.

        Warning: the generator function should not take 'self' (or any
        structure containing a reference to 'self') as either of its
        arguments, as otherwise a circular reference would occur between 'self'
        and the generator object."""

        return None

    # ---- Private methods ----------------------------------------------------
    def launchSubRetrieval(self):

        try:
            # Query information about the next retrieval from the generator.
            self.matlabProcess = self.matlabProcessGenerator.next()

        # If no retrievals are pending, this means that all the retrieval
        # procedures have completed with success.
        except StopIteration:
            self.retrievalFinished.emit()
            return

        # Start the retrieval process.
        self.matlabProcess.retrievalFinished.connect(
            self.onSubRetrievalFinished)
        self.matlabProcess.outputAvailable.connect(self.onOutputAvailable)
        self.matlabProcess.iterationCompleted.connect(
            self.iterationCompleted)

        self.matlabProcess.startRetrieval()

    # ---- Private slots ------------------------------------------------------
    def onSubRetrievalFinished(self):

        if self.matlabProcess.getErrorMessage() is not None:

            # Cancel the complete multi-step retrieval if an error is
            # encountered during any of its steps.
            self.matlabProcessGenerator.close()

            # Finish the failed retrieval. The error message to be returned
            # by 'getErrorMessage' has already been set in
            # 'self.matlabProcess'.
            self.retrievalFinished.emit()

        else:
            # Save all the intermediate results from the completed optimization
            # procedure.
            self.retrievalOutputs += (
                self.matlabProcess.getAllRetrievalOutputs())

            # Check if there still are optimization procedures pending, and
            # launch the next one if they are.
            self.launchSubRetrieval()

    def onOutputAvailable(self, outputLine):

        self.outputAvailable.emit(outputLine)

    def onIterationCompleted(self, iterationCount):

        self.iterationCompleted.emit(iterationCount)
