# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-2020 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__ = ["C. Nemoz", "H. Payno"]
__license__ = "MIT"
__date__ = "14/02/2020"


import logging
from typing import Optional

import tomwer.core.process.reconstruction.nabu.nabuslices
from tomwer.core.scan.hdf5scan import HDF5TomoScan
from tomwer.gui.reconstruction.nabu import check
from tomwer.core.settings import isOnLbsram

from contextlib import AbstractContextManager
from silx.gui import qt
from tomwer.synctools.stacks.reconstruction.nabu import NabuSliceProcessStack
from tomwer.core.process.reconstruction.nabu.nabuslices import NabuSlices
from tomwer.gui.reconstruction.nabu.slices import NabuWindow
from tomwer.core.scan.scanbase import TomwerScanBase
from ..utils import WidgetLongProcessing
from orangewidget.settings import Setting
from orangewidget import gui
from tomwer.core.scan.scanbase import _TomwerBaseDock
from orangewidget.widget import Input, Output
from ...orange.managedprocess import SuperviseOW
from tomwer.core.cluster import SlurmClusterConfiguration
from tomwer.core.scan.futurescan import FutureTomwerScan
import copy

_logger = logging.getLogger(__name__)


class NabuOW(WidgetLongProcessing, SuperviseOW):
    """
    A simple widget managing the copy of an incoming folder to an other one

    :param parent: the parent widget
    """

    # note of this widget should be the one registered on the documentation
    name = "nabu slice reconstruction"
    id = "orange.widgets.tomwer.reconstruction.NabuOW.NabuOW"
    description = "This widget will call nabu for running a reconstruction "
    icon = "icons/nabu_2d.svg"
    priority = 12
    keywords = ["tomography", "nabu", "reconstruction", "FBP", "filter"]

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

    # TODO: replace Nabu by default nabu recons by the new orange contrib
    static_input = Setting({"data": None, "nabu_params": None})

    _rpSetting = Setting(dict())
    # this one is keep for backward compatibility. Will be removed on 0.10 I guess

    ewokstaskclass = tomwer.core.process.reconstruction.nabu.nabuslices.NabuSlices

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

    class Inputs:
        reprocess = Input(
            name="change recons params",
            type=_TomwerBaseDock,
            doc="reconpute slice with different parameters",
        )
        data = Input(
            name="data",
            type=TomwerScanBase,
            doc="one scan to be process",
            default=True,
            multiple=False,
        )
        cluster_in = Input(
            name="cluster_config",
            type=SlurmClusterConfiguration,
            doc="slurm cluster to be used",
            multiple=False,
        )

    class Outputs:
        data = Output(name="data", type=TomwerScanBase, doc="one scan to be process")

        future_out = Output(
            name="future_data",
            type=FutureTomwerScan,
            doc="data with some remote processing",
        )

    class DialogCM(AbstractContextManager):
        """Simple context manager to hide / show button dialogs"""

        def __init__(self, dialogButtonsBox):
            self._dialogButtonsBox = dialogButtonsBox

        def __enter__(self):
            self._dialogButtonsBox.show()

        def __exit__(self, exc_type, exc_val, exc_tb):
            self._dialogButtonsBox.hide()

    def __init__(self, parent=None):
        """
        Widget which read the .hdf5 generated by octave and modify it.
        Then run a subprocess to call octave and run ftseries

        :param bool _connect_handler: True if we want to store the modifications
                                      on the setting. Need for unit test since
                                      keep alive qt widgets.
        :param recons_params: reconsparameter to be used by the FTWidget.
                              If None, some will be created.
        :type: :class:`QReconsParams`
        """
        SuperviseOW.__init__(self, parent)
        WidgetLongProcessing.__init__(self)
        self._slurmCluster = None
        self.__exec_for_ci = False
        # processing tool
        self._processingStack = NabuSliceProcessStack(self, process_id=self.process_id)

        _layout = gui.vBox(self.mainArea, self.name).layout()
        # main widget
        self._nabuWidget = NabuWindow(parent=self)
        _layout.addWidget(self._nabuWidget)
        # add button to validate when change reconstruction parameters is
        # called
        types = qt.QDialogButtonBox.Ok
        self._buttons = qt.QDialogButtonBox(self)
        self._buttons.setStandardButtons(types)
        _layout.addWidget(self._buttons)

        # set up
        self._buttons.hide()

        # load settings
        nabu_params = self.static_input.get("nabu_params", None)
        if nabu_params is None:
            nabu_params = self._rpSetting
        if nabu_params != dict():
            print("nabu params are", nabu_params)
            try:
                self._nabuWidget.setConfiguration(nabu_params)
            except Exception:
                _logger.warning("fail to load reconstruction settings")

        # expose API
        self.getMode = self._nabuWidget.getMode
        self.setMode = self._nabuWidget.setMode

        # connect signal / slot
        self._processingStack.sigComputationStarted.connect(self._startProcessing)
        self._processingStack.sigComputationEnded.connect(self._endProcessing)
        self._buttons.button(qt.QDialogButtonBox.Ok).clicked.connect(self.accept)
        self._nabuWidget.sigConfigChanged.connect(self._updateSettingsVals)

    @Inputs.data
    def process(self, scan: TomwerScanBase):
        assert isinstance(scan, (TomwerScanBase, type(None)))
        if scan is None:
            return
        scan_ = copy.copy(scan)
        scan_.clear_latest_reconstructions()

        if isinstance(scan, HDF5TomoScan):
            if not check.check_flat_series(
                scan_, logger=_logger, user_input=isOnLbsram(scan=scan_)
            ):
                self.Outputs.data.send(scan_)
                return
        if isinstance(scan, HDF5TomoScan):
            if not check.check_dark_series(
                scan_, logger=_logger, user_input=isOnLbsram(scan=scan_)
            ):
                self.Outputs.data.send(scan_)
                return

        _logger.info("add {} to the stack".format(str(scan)))
        # update the reconstruction mode if possible
        self._nabuWidget.setScan(scan_)
        self._processingStack.add(scan_, self.getConfiguration())

    @Inputs.reprocess
    def reprocess(self, scan):
        """Recompute nabu with different parameters"""
        # wait for user to tune the reconstruction
        if scan is None:
            return

        if scan.axis_params is None or scan.axis_params.relative_cor_value is None:
            # try to retrieve last computed cor value from nabu process
            r_cor = NabuSlices.retrieve_last_relative_cor(scan)
            if scan.axis_params is None:
                from tomwer.synctools.axis import QAxisRP

                scan.axis_params = QAxisRP()
            scan.axis_params.set_relative_value(float(r_cor) - scan.dim_1 / 2.0)
            try:
                scan.axis_params.set_relative_value(float(r_cor) - scan.dim_1 / 2.0)
            except Exception:
                pass

        self.show()
        with NabuOW.DialogCM(self._buttons):
            if self.__exec_for_ci is True:
                self._ciExec()
            else:
                if self.exec_():
                    # for now The behavior for reprocessing is the sama as for processing
                    if hasattr(scan, "instance"):
                        self.process(scan=scan.instance)
                    else:
                        self.process(scan=scan)

    @Inputs.cluster_in
    def setCluster(self, slurm_cluster: Optional[SlurmClusterConfiguration]):
        assert isinstance(
            slurm_cluster, (type(None), SlurmClusterConfiguration)
        ), f"Expect None of SlurmClusterConfiguration. Not {type(slurm_cluster)}"
        self._slurmCluster = slurm_cluster

    def _endProcessing(self, scan, future_scan):
        WidgetLongProcessing._endProcessing(self, scan)
        if scan is not None:
            self.Outputs.data.send(scan)
            self.sigScanReady.emit(scan)
        if future_scan is not None:
            self.Outputs.future_out.send(future_scan)

    def setDryRun(self, dry_run):
        self._processingStack.setDryRun(dry_run)

    def _ciExec(self):
        self.activateWindow()
        self.raise_()
        self.show()

    def _replaceExec_(self):
        """used for CI, replace the exec_ call ny"""
        self.__exec_for_ci = True

    def _updateSettingsVals(self):
        self.static_input = {
            "data": None,
            "nabu_params": self.getConfiguration(),
        }
        self._rpSetting = self.getConfiguration()

    def getConfiguration(self):
        config = self._nabuWidget.getConfiguration()
        config["cluster_config"] = self._slurmCluster
        return config

    def setConfiguration(self, config):
        # ignore slurm cluster. Defined by the upper widget
        config.pop("cluster_config", None)
        self._nabuWidget.setConfiguration(config=config)
