# 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__ = ["C. Nemoz", "H. Payno"]
__license__ = "MIT"
__date__ = "10/01/2018"


import copy
import logging

import tomoscan.esrf.scan.utils
from orangewidget import gui
from orangewidget.settings import Setting
from orangewidget.widget import Input, Output
from processview.core.manager import DatasetState, ProcessManager
from silx.gui import qt

import tomwer.core.process.reconstruction.darkref.darkrefscopy
from orangecontrib.tomwer.orange.managedprocess import SuperviseOW
from tomwer.core.scan.scanbase import TomwerScanBase
from tomwer.gui.reconstruction.darkref.darkrefcopywidget import DarkRefAndCopyWidget
from tomwer.synctools.darkref import QDKRFRP
from tomwer.synctools.stacks.reconstruction.dkrefcopy import DarkRefCopyProcessStack
from tomwer.utils import docstring

from ..utils import WidgetLongProcessing

_logger = logging.getLogger(__name__)


class DarkRefAndCopyOW(SuperviseOW, WidgetLongProcessing):
    """
    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 = "reduced darks and flats"
    id = "orange.widgets.tomwer.darkrefs"
    description = (
        "This widget will generate reduced darks and flats for a received scan "
    )
    icon = "icons/darkref.svg"
    priority = 15
    keywords = ["tomography", "dark", "darks", "ref", "refs", "flat", "flats"]

    want_main_area = True
    resizing_enabled = True

    _ewoks_default_inputs = Setting({"data": None, "dark_ref_params": None})

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

    ewokstaskclass = (
        tomwer.core.process.reconstruction.darkref.darkrefscopy.DarkRefsCopy
    )

    class Inputs:
        data = Input(
            name="data",
            type=TomwerScanBase,
            doc="one scan to be process",
            multiple=True,
        )
        reduced_darks = Input(
            name="reduced dark(s)",
            type=dict,
            doc="dict containing reduced dark(s)",
            multiple=False,
        )
        reduced_flats = Input(
            name="reduced flat(s)",
            type=dict,
            doc="dict of containing reduced flat(s)",
            multiple=False,
        )

    class Outputs:
        data = Output(name="data", type=TomwerScanBase, doc="one scan to be process")
        reduced_darks = Output(
            name="reduced dark(s)",
            type=dict,
            doc="DataUrl of the reduced dark. Key is the relative position, value is the reduced dark",
        )
        reduced_flats = Output(
            name="reduced flat(s)",
            type=dict,
            doc="DataUrl of the reduced flat. Key is the relative position, value is the reduced flat",
        )

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

        :param QKRFRP reconsparams: reconstruction parameters
        """
        SuperviseOW.__init__(self, parent)
        WidgetLongProcessing.__init__(self)
        recons_params = reconsparams or QDKRFRP()
        self._processing_stack = DarkRefCopyProcessStack(process_id=self.process_id)

        dark_ref_params = self._ewoks_default_inputs.get("dark_ref_params", None)
        if dark_ref_params not in ({}, None):
            try:
                recons_params.dkrf.load_from_dict(dark_ref_params)
            except Exception:
                _logger.warning("fail to load reconstruction settings")

        self.widget = DarkRefAndCopyWidget(
            save_dir=self._processing_stack._save_dir,
            parent=self,
            reconsparams=recons_params,
            process_id=self.process_id,
        )
        self._layout = gui.vBox(self.mainArea, self.name).layout()
        self._layout.addWidget(self.widget)
        self.setForceSync = self.widget.setForceSync

        # expose API
        self.setModeAuto = self.widget.set_mode_auto
        self.setRefsFromScan = self.widget.setRefsFromScan
        self.setCopyActive = self.widget.setCopyActive

        # connect signal / slot
        self.widget.sigProcessingStart.connect(self._startProcessing)
        self.widget.sigProcessingEnd.connect(self._endProcessing)
        self.widget.sigScanReady.connect(self.signalReady)
        self.widget.recons_params.sigChanged.connect(self._updateSettingsVals)
        self.widget.sigModeAutoChanged.connect(self._updateSettingsVals)
        self.widget.sigCopyActivationChanged.connect(self._updateSettingsVals)
        self.widget.sigClearCache.connect(self._processing_stack.clear_cache)
        self._processing_stack.sigComputationStarted.connect(self._startProcessing)
        self._processing_stack.sigComputationEnded.connect(self._endProcessing)
        self._processing_stack.sigRefSetted.connect(self.widget.setRefSetBy)

        # load some other copy parameters
        if dark_ref_params not in ({}, None):
            try:
                if "activate" in dark_ref_params:
                    self.widget.setCopyActive(dark_ref_params.pop("activate"))
                if "auto" in dark_ref_params:
                    auto_mode = dark_ref_params.pop("auto")
                    # insure backward compatibility. Has beem saved as a tuple (this was a typo)
                    if isinstance(auto_mode, tuple) and isinstance(auto_mode[0], bool):
                        auto_mode = auto_mode[0]
                    self.widget.setModeAuto(auto_mode)
            except Exception:
                _logger.warning("fail to load reconstruction settings")

        self.setCaption(self.windowTitle())

    def __new__(cls, *args, **kwargs):
        # ensure backward compatibility with 'static_input'
        static_input = kwargs.get("stored_settings", {}).get("static_input", None)
        if static_input not in (None, {}):
            _logger.warning(
                "static_input has been deprecated. Will be replaced by _ewoks_default_inputs in the workflow file. Please save the workflow to apply modifications"
            )
            kwargs["stored_settings"]["_ewoks_default_inputs"] = static_input
        return super().__new__(cls, *args, **kwargs)

    @Inputs.data
    def process(self, scanID, *args, **kwargs):
        if scanID is None:
            return
        assert isinstance(scanID, TomwerScanBase)
        ProcessManager().notify_dataset_state(
            dataset=scanID, process=self, state=DatasetState.PENDING
        )
        configuration = self.widget.recons_params.to_dict()
        configuration["process_only_copy"] = False  # we never only do copy from the gui
        configuration["process_only_dkrf"] = not self.widget.isCopyActive()
        configuration["mode_auto"] = self.widget.isOnModeAuto()

        self._processing_stack.add(copy.copy(scanID), configuration=configuration)

    @Inputs.reduced_darks
    def received_reduced_darks(self, reduced_darks: dict):
        # we consider if the darks are provided then the user want to use those and don't copy them manually
        self.setModeAuto(False)
        reduced_darks.pop("reduce_frames_name", None)
        self.setReducedDarks(reduced_darks)

    @Inputs.reduced_flats
    def received_reduced_flats(self, reduced_flats: dict):
        # we consider if the darks are provided then the user want to use those and don't copy them manually
        self.setModeAuto(False)
        reduced_flats.pop("reduce_frames_name", None)
        self.setReducedFlats(reduced_flats)

    @docstring(SuperviseOW)
    def reprocess(self, dataset):
        self.process(dataset)

    def signalReady(self, scanID):
        assert isinstance(scanID, TomwerScanBase)
        self.Outputs.data.send(scanID)
        self.sigScanReady.emit(scanID)

    def _updateSettingsVals(self):
        self._ewoks_default_inputs = {
            "data": None,
            "dark_ref_params": self.widget.recons_params.to_dict(),
        }
        self._ewoks_default_inputs["dark_ref_params"][
            "auto"
        ] = self.widget.isOnModeAuto()
        self._ewoks_default_inputs["dark_ref_params"][
            "activate"
        ] = self.widget.isCopyActive()

    @property
    def recons_params(self):
        return self.widget.recons_params

    def close(self):
        self.widget.close()
        super(DarkRefAndCopyOW, self).close()

    def _endProcessing(self, scan):
        WidgetLongProcessing._endProcessing(self, scan)
        self.Outputs.data.send(scan)
        self.sigScanReady.emit(scan)
        if scan.reduced_darks not in (None, {}):
            # we want to send those in relative position to have something generic. This is a convention for now
            reduced_darks = scan.reduced_darks
            reduced_darks.pop("reduce_frames_name", None)
            self.Outputs.reduced_darks.send(
                tomoscan.esrf.scan.utils.from_absolute_reduced_frames_to_relative(
                    reduced_frames=reduced_darks, scan=scan
                )
            )
        if scan.reduced_flats not in (None, {}):
            # we want to send those in relative position to have something generic. This is a convention for now
            reduced_flats = scan.reduced_flats
            reduced_flats.pop("reduce_frames_name", None)
            self.Outputs.reduced_flats.send(
                tomoscan.esrf.scan.utils.from_absolute_reduced_frames_to_relative(
                    reduced_frames=reduced_flats, scan=scan
                )
            )
        _logger.info(f"{scan} ended")

    def setReducedDarks(self, darks: dict):
        self.widget._refCopyWidget.save_darks_to_be_copied(darks)

    def setReducedFlats(self, flats: dict):
        self.widget._refCopyWidget.save_flats_to_be_copied(flats)
