# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/

__authors__ = ["H. Payno"]
__license__ = "MIT"
__date__ = "01/02/2021"


from ..utils import WidgetLongProcessing
from Orange.widgets import gui
from orangecontrib.tomwer.orange.managedprocess import SuperviseOW
from Orange.widgets.settings import Setting
from orangecontrib.tomwer.orange.settings import CallbackSettingsHandler
from Orange.widgets.widget import Input, Output
from tomwer.synctools.stacks.reconstruction.saaxis import SAAxisProcessStack
from tomwer.synctools.saaxis import QSAAxisParams
from tomwer.synctools.axis import QAxisRP
from tomwer.core.process.reconstruction.axis import AxisProcess
from tomwer.core.scan.scanbase import TomwerScanBase
from silx.gui import qt
from tomwer.gui.reconstruction.saaxis.saaxis import SAAxisWindow as _SAAxisWindow
from processview.core.manager import ProcessManager, DatasetState
from tomwer.core import settings
from tomwer.core import utils
from tomwer.core.process.reconstruction.saaxis.saaxis import SAAxisProcess
from processview.core import helpers as pv_helpers
import functools
import logging
from typing import Union

_logger = logging.getLogger(__name__)


class SAAxisWindow(_SAAxisWindow):

    sigResultsToBeShow = qt.Signal()
    """signal emit when some results are ready to be display"""

    def __init__(self, Outputs, parent=None, process_id=None):
        _SAAxisWindow.__init__(self, parent=parent)
        self.Outputs = Outputs
        self._saaxis_params = QSAAxisParams()
        self._processing_stack = SAAxisProcessStack(
            saaxis_params=self._saaxis_params, process_id=process_id
        )
        self._cor_estimation_process = AxisProcess(self.getQAxisRP(), process_id=-1)

        # connect signal / slot
        self.sigValidated.connect(self.validateCurrentScan)

    def _computeEstimatedCor(self) -> Union[float, None]:
        """callback when calculation of a estimated cor is requested"""
        # TODO: check but should not be needed anymore
        scan = self.getScan()
        if scan is None:
            text = (
                "No scan is set on the widget currently. No automatic center "
                "of rotation to be computed"
            )
            _logger.warning(text)
            msg = qt.QMessageBox(self)
            msg.setIcon(qt.QMessageBox.Warning)
            msg.setText(text)
            msg.show()
            return
        else:
            axis_params_info = self.getQAxisRP().get_simple_str()
            try:
                text = "start automatic cor for {} with {}".format(
                    scan, axis_params_info
                )
                _logger.inform(text)
                self._cor_estimation_process.compute(scan=scan, wait=True)
            except Exception as e:
                text = (
                    "Unable to run automatic cor calculation. Reason is "
                    "{}.".format(e)
                )
                _logger.error(text)
                msg = qt.QMessageBox(self)
                msg.setIcon(qt.QMessageBox.Critical)
                msg.setText(text)
                msg.show()
                return None
            else:
                cor = scan.axis_params.relative_cor_value
                text = "automatic cor computed for {}: {} ({})".format(
                    scan, cor, axis_params_info
                )
                _logger.inform(text)
                self.setEstimatedCorPosition(value=cor)
                self.getAutomaticCorWindow().hide()
                return cor

    def _launchReconstructions(self):
        """callback when we want to launch the reconstruction of the
        slice for n cor value"""
        scan = self.getScan()
        if scan is None:
            return
        # step1: if isAutoFocus: validate automatically the scan
        # step2: update the interface if the current scan is the one displayed
        # else skip it
        callback = functools.partial(
            self._mightUpdateResult, scan, self.isAutoFocusLock()
        )
        self._processing_stack.add(
            scan=scan, configuration=self.getConfiguration(), callback=callback
        )

    def _validate(self):
        # TODO: check but should not be needed anymore
        pass

    def _mightUpdateResult(self, scan: TomwerScanBase, validate: bool):
        if not isinstance(scan, TomwerScanBase):
            raise TypeError("scan is expected to be an instance of TomwerScanBase")
        if not isinstance(validate, bool):
            raise TypeError("validate is expected to be a boolean")
        if scan == self.getScan():
            self.setCorScores(
                scan.saaxis_params.scores,
                score_method=scan.saaxis_params.score_method,
                img_width=scan.saaxis_params.image_width,
            )
            if scan.saaxis_params.autofocus is not None:
                self.setCurrentCorValue(scan.saaxis_params.autofocus)
            pm = ProcessManager()
            details = pm.get_dataset_details(
                dataset_id=scan.get_dataset_identifier(), process=self._processing_stack
            )
            ProcessManager().notify_dataset_state(
                dataset=scan,
                process=self._processing_stack,
                details=details,
                state=DatasetState.WAIT_USER_VALIDATION,
            )
            self.sigResultsToBeShow.emit()
        if validate:
            self.validateScan(scan)

    def wait_processing(self, wait_time):
        self._processing_stack._computationThread.wait(wait_time)

    def validateCurrentScan(self):
        return self.validateScan(self.getScan())

    def validateScan(self, scan):
        if scan is None:
            return
        assert isinstance(scan, TomwerScanBase)
        selected_cor_value = self.getCurrentCorValue()
        details = ProcessManager().get_dataset_details(
            dataset_id=scan.get_dataset_identifier(), process=self._processing_stack
        )
        if details is None:
            details = ""
        if selected_cor_value is None:
            infos = "no selected cor value. {} skip SAAXIS".format(scan)
            infos = "\n".join((infos, details))
            _logger.warning(infos)
            scan.axis_params.set_relative_value(None)
            pv_helpers.notify_skip(
                process=self._processing_stack, dataset=scan, details=infos
            )
        else:
            scan.axis_params.set_relative_value(selected_cor_value)
            infos = "cor selected for {}: relative: {}, absolute: {}".format(
                scan,
                scan.axis_params.relative_cor_value,
                scan.axis_params.absolute_cor_value,
            )
            infos = "\n".join((infos, details))
            pv_helpers.notify_succeed(
                process=self._processing_stack, dataset=scan, details=infos
            )
        SAAxisProcess.process_to_tomwer_processes(
            scan=scan,
        )
        self.Outputs.data_out.send(scan)


class SAAxisOW(SuperviseOW, WidgetLongProcessing):
    """
    Widget for semi-automatic center of rotation calculation

    behavior within a workflow:
    * when a scan arrived:
        * if he already has a center of rotation defined (if the axis widget
          has been defined for example) it will be used as
          'estimated center of rotation'
        * if no cor has been computed yet and if the .nx entry contains
          information regarding an "estimated_cor_from_motor" this value will
          be set.
    * if autofocus option is lock:
       * launch the series of reconstruction (with research width defined)
         and the estimated center of rotation if defined. Once the
         reconstruction is ended and if the autofocus button is still lock
         it will select the cor with the highest
         value and mode to workflow downstream.
    * hint: you can define a "multi-step" half-automatic center of rotation
       research by creating several "saaxis" widget and reducing the research
       width.

    Details about :ref:`saaxis score calculation`
    """

    name = "semi-automated center of rotation"
    id = "orange.widgets.tomwer.sa_axis"
    description = "use to compute the center of rotation semi automatic way"
    icon = "icons/saaxis.png"
    priority = 15
    category = "esrfWidgets"
    keywords = [
        "tomography",
        "semi automatic",
        "half automatic",
        "axis",
        "tomwer",
        "reconstruction",
        "rotation",
        "position",
        "center of rotation",
        "saaxis",
    ]

    want_main_area = True
    resizing_enabled = True
    allows_cycle = True
    compress_signal = False

    settingsHandler = CallbackSettingsHandler()

    sigScanReady = qt.Signal(TomwerScanBase)
    """Signal emitted when a scan is ready"""

    _rpSetting = Setting(dict())
    "SAAxisRP store as dict"

    class Inputs:
        data_in = Input(
            name=AxisProcess.inputs[0].name,
            type=AxisProcess.inputs[0].type,
            doc=AxisProcess.inputs[0].doc,
        )
        data_recompute_axis = Input(
            name=AxisProcess.inputs[1].name,
            type=AxisProcess.inputs[1].type,
            doc=AxisProcess.inputs[1].doc,
        )

    assert len(AxisProcess.outputs) == 1

    class Outputs:
        data_out = Output(
            name=AxisProcess.outputs[0].name,
            type=AxisProcess.outputs[0].type,
            doc=AxisProcess.outputs[0].doc,
        )

    def __init__(self, parent=None):
        """

        :param parent: QWidget parent or None
        """
        SuperviseOW.__init__(self, parent)
        WidgetLongProcessing.__init__(self)
        self._layout = gui.vBox(self.mainArea, self.name).layout()
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._widget = SAAxisWindow(
            Outputs=self.Outputs, parent=self, process_id=self.process_id
        )
        self._layout.addWidget(self._widget)
        self.setConfiguration(self._rpSetting)

        # connect signal / slot
        self._widget.sigConfigurationChanged.connect(self._updateSettings)
        self.destroyed.connect(self._widget.stop)
        self._widget._processing_stack.sigComputationStarted.connect(
            self._startProcessing
        )
        self._widget._processing_stack.sigComputationEnded.connect(self._endProcessing)
        self._widget.sigValidated.connect(self.accept)
        self._widget.sigResultsToBeShow.connect(self._raiseResults)

        # expose API
        self.wait_processing = self._widget.wait_processing

    def setConfiguration(self, configuration):
        if "workflow" in configuration:
            autofocus_lock = configuration["workflow"].get("autofocus_lock", None)
            if autofocus_lock is not None:
                self._widget.lockAutofocus(autofocus_lock)
            del configuration["workflow"]
        self._widget.setConfiguration(configuration)

    def getCurrentCorValue(self):
        return self._widget.getCurrentCorValue()

    def load_sinogram(self):
        self._widget.loadSinogram()

    def compute(self):
        self._widget.compute()

    def lockAutofocus(self, lock):
        self._widget.lockAutofocus(lock=lock)

    def isAutoFocusLock(self):
        return self._widget.isAutoFocusLock()

    @Inputs.data_in
    def process(self, scan):
        if scan is None:
            return
        if scan.axis_params is None:
            scan.axis_params = QAxisRP()
        if scan.saaxis_params is None:
            scan.saaxis_params = QSAAxisParams()
        self._skipCurrentScan(new_scan=scan)
        if settings.isOnLbsram(scan) and utils.isLowOnMemory(
            settings.get_lbsram_path()
        ):
            self.notify_skip(
                scan=scan,
                details="saaxis has been skiped for {} because "
                "of low space in lbsram".format(scan),
            )
            self.Outputs.data_out.send(scan)
        else:
            self._widget.setScan(scan=scan)
            self.notify_pending(scan)
            self.activateWindow()
            if self.isAutoFocusLock():
                self.compute()
            else:
                self.raise_()
                self.show()

    def _skipCurrentScan(self, new_scan):
        scan = self._widget.getScan()
        # if the same scan has been run several scan
        if scan is None or str(scan) == str(new_scan):
            return
        current_scan_state = ProcessManager().get_dataset_state(
            dataset_id=scan.get_dataset_identifier(), process=self
        )
        if current_scan_state in (
            DatasetState.PENDING,
            DatasetState.WAIT_USER_VALIDATION,
        ):
            details = "Was pending and has been replaced by another scan."
            self.notify_skip(scan=scan, details=details)
            self.Outputs.data_out.send(scan)

    @Inputs.data_recompute_axis
    def reprocess(self, dataset):
        self.lockAutofocus(False)
        self.process(scan=dataset)

    def validateCurrentScan(self):
        self._widget.validateCurrentScan()

    def _updateSettings(self):
        self._rpSetting = self._widget.getConfiguration()
        self._rpSetting["workflow"] = {
            "autofocus_lock": self._widget.isAutoFocusLock(),
        }

    def _raiseResults(self):
        if not self.isAutoFocusLock():
            self.raise_()
            self.show()
            self._widget.showResults()
