# /*##########################################################################
# Copyright (C) 2016-2017 European Synchrotron Radiation Facility
#
# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
# the ESRF by the Software group.
#
# 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.
#
#############################################################################*/

__author__ = ["H. Payno"]
__license__ = "MIT"
__date__ = "16/08/2021"

import os
from silx.gui import qt
from silx.utils.deprecation import deprecated
from tomwer.core.process.reconstruction.ftseries.ftseries import _Ftseries
from tomwer.core.scan.scanbase import TomwerScanBase
from tomwer.core.process.reconstruction.ftseries.params.fastsetupdefineglobals import (
    FastSetupAll,
)
from tomwer.core.process.reconstruction.ftseries import Ftseries
from tomwer.gui.reconstruction.ftserie.h5editor.h5filedialog import H5FileDialog
from tomwer.synctools.ftseries import QReconsParams
from tomwer.synctools.stacks.reconstruction.ftseries import ReconstructionStack
from tomwer.core.scan.scanfactory import ScanFactory
from processview.core.superviseprocess import SuperviseProcess
from tomwer.core.utils import ftseriesutils
import logging
import weakref

_logger = logging.getLogger(__name__)


class FtserieWidget(qt.QWidget, SuperviseProcess):
    _sizeHint = qt.QSize(700, 600)

    sigScanReady = qt.Signal(TomwerScanBase)

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

        self._explore_for_h5_file = False
        self._dry_run = False
        self._recons_params = recons_params or QReconsParams()
        self._reconstruction_stack = ReconstructionStack(process_id=process_id)
        self._reconstruction_stack.sigReconsFinished.connect(self._notifyScanReady)

        self.setLayout(qt.QVBoxLayout())

        self._recParamSetEditor = None
        self.__buildGUI()
        self._useThread = True

        self._validateButton.hide()
        self._excuteButton = qt.QPushButton("execute", parent=self)
        self._excuteButton.pressed.connect(self._callbackRun)
        self.layout().addWidget(self._excuteButton)

    @property
    def reconstruction_stack(self):
        return self._reconstruction_stack

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

    def setScan(self, scan):
        self._scan = weakref.ref(scan)
        self._recParamSetEditor.setScan(scan)

    @property
    def scan(self):
        if self._scan is None or self._scan() is None:
            return None
        else:
            return self._scan()

    def set_recons_params(self, recons_param):
        _Ftseries.set_recons_params(self, recons_param=recons_param)
        self.getReconsParamSetEditor().setReconsParams(recons_param)

    def _callbackRun(self):
        if isinstance(self._scan, weakref.ref):
            self.pathReceived(self._scan())
        else:
            self.pathReceived(self._scan)

    def isReconsParamsSaved(self):
        """

        :return: True if some previous reconstruction parameters have been
                 saved previously.
        """
        return not (self._reconsParams.params == {})

    def __buildGUI(self):
        """Main function to call the GUI"""
        self.layout().addWidget(self.getReconsParamSetEditor())
        self.layout().addWidget(self.__buildControl())

    def __buildControl(self):
        widget = qt.QWidget(self)
        widget.setLayout(qt.QVBoxLayout())
        widget.layout().setContentsMargins(0, 0, 0, 0)

        widgetLoadSave = qt.QWidget(self)
        widgetLoadSave.setLayout(qt.QHBoxLayout())
        widgetLoadSave.layout().setContentsMargins(0, 0, 0, 0)

        self._loadButton = qt.QPushButton("Load")
        self._loadButton.setToolTip(
            "Will load reconstruction parameters existing in the given file"
        )
        style = qt.QApplication.style()
        self._loadButton.setIcon(style.standardIcon(qt.QStyle.SP_FileIcon))
        self._loadButton.pressed.connect(self.askUserAndLoad)
        widgetLoadSave.layout().addWidget(self._loadButton)
        self._loadButton.setShortcut(qt.QKeySequence.Open)

        spacer = qt.QWidget(self)
        spacer.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum)
        widgetLoadSave.layout().addWidget(spacer)

        self._saveAs = qt.QPushButton("Save as")
        self._saveAs.setToolTip(
            "Store the current reconstruction parameters into a .h5 file"
        )
        style = qt.QApplication.style()
        self._saveAs.setIcon(style.standardIcon(qt.QStyle.SP_DialogSaveButton))
        self._saveAs.pressed.connect(self._saveAsCallBack)
        widgetLoadSave.layout().addWidget(self._saveAs)
        self._saveAs.setShortcut(qt.QKeySequence.Save)

        widgetValidateCancel = qt.QWidget(self)
        widgetValidateCancel.setLayout(qt.QHBoxLayout())
        widgetValidateCancel.layout().setContentsMargins(0, 0, 0, 0)

        spacer = qt.QWidget(self)
        spacer.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum)
        widgetValidateCancel.layout().addWidget(spacer)

        self._validateButton = qt.QPushButton("Validate")
        style = qt.QApplication.style()
        self._validateButton.setIcon(style.standardIcon(qt.QStyle.SP_DialogApplyButton))
        self._validateButton.pressed.connect(self._validated)
        widgetValidateCancel.layout().addWidget(self._validateButton)

        self._cancelButton = qt.QPushButton("Cancel")
        self._cancelButton.setIcon(style.standardIcon(qt.QStyle.SP_DialogCancelButton))
        self._cancelButton.pressed.connect(self.__canceled)
        widgetValidateCancel.layout().addWidget(self._cancelButton)

        widget.layout().addWidget(widgetLoadSave)
        widget.layout().addWidget(widgetValidateCancel)
        # the cancel action is undefined so we don't wan't to show this button
        self._cancelButton.setVisible(False)

        return widget

    def getReconsParamSetEditor(self):
        if self._recParamSetEditor is None:
            from tomwer.gui.reconstruction.ftserie.reconsparamseditor import (
                ReconsParamSetEditor,
            )  # avoid cyclic import

            self._recParamSetEditor = ReconsParamSetEditor(
                parent=None, reconsparams=self.recons_params
            )
            self._recParamSetEditor.indexOf(self._recParamSetEditor._dkRefWidget)
            self._reconsParamChanged()
            assert hasattr(self.recons_params, "sigChanged")
            self.recons_params.sigChanged.connect(self._reconsParamChanged)
        return self._recParamSetEditor

    def _reconsParamChanged(self):
        assert self._recParamSetEditor is not None
        self._recParamSetEditor.loadReconsParams(self.recons_params)

    def _updateh5Editor(self, b):
        if self._scan and self._scan.h5File:
            self.reload()

    def askUserH5File(self, save=False):
        """
        Ask user path to a h5File

        :param save: True if the file is used to saved the data. If False then
                     try to open one.
        """
        return H5FileDialog.askForH5File(save=save)

    def _saveAsCallBack(self):
        f = H5FileDialog.askForH5File(save=True)
        if f is not None:
            self.save(h5File=f, displayInfo=True)

    def showValidationButtons(self):
        """Set the buttons for reconstruction parameters visible"""
        self._validateButton.setVisible(True)
        self._cancelButton.setVisible(True)

    def hideValidationButtons(self):
        """Set the buttons for reconstruction parameters not visible"""
        self._validateButton.setVisible(False)
        self._cancelButton.setVisible(False)

    def hideExecuteButton(self):
        self._excuteButton.hide()

    def __canceled(self):
        self.reject()

    def _validated(self):
        self.accept()

    def sizeHint(self):
        """Return a reasonable default size"""
        return self._sizeHint

    def process(self, scan):
        """Call the core function 'run_reconstruction' of the ftseries script
        which will call octave to process reconstruction
        """
        if scan is None:
            return
        self._reconstruction_stack.add(
            scan=scan,
            inputs={
                "pyhst2_params": self.recons_params.to_dict(),
                "explore_for_h5_file": self._explore_for_h5_file,
                "dry_run": self._dry_run,
            },
        )

    def askUserAndLoad(self):
        """Process launch by activing the Load button"""
        f = self.askUserH5File()
        if f is not None:
            self.load_recons_params(f)

    @deprecated(replacement="setScan", since_version="0.8")
    def updatePath(self, path):
        # load if requested for some h5 with reconstruction parameters
        if self._explore_for_h5_file:
            h5file = ftseriesutils.tryToFindH5File(path, "newest")
            "h5 file is the file to load to update the reconstruction parameters"
            if h5file is not None:
                try:
                    self.load_recons_params(h5file)
                except Exception:
                    _logger.warning(
                        "Fail to load reconstruction parameters " "from %s" % h5file
                    )
                else:
                    _logger.info("Reconstruction parameters loaded from " "%s" % h5file)

        if isinstance(path, str):
            self.setScan(ScanFactory.create_scan_object(scan_path=path))
        elif isinstance(path, TomwerScanBase):
            self.setScan(path)
        else:
            raise TypeError(
                f"{path} is expected to be a string or an instance of {TomwerScanBase}"
            )

    def load_recons_params(self, params):
        if isinstance(params, dict):
            self._recons_params.copy(params)
        elif isinstance(params, str):
            if not os.path.isfile(params):
                raise ValueError(f"{params} is not a file")

            fsdg = FastSetupAll()
            fsdg.readAll(params, 3.8)
            self._recons_params.copy(fsdg.structures)
        else:
            raise TypeError(f"{params} is expected to be a file path or a dictionary")

    def save(self, h5File, displayInfo=True):
        Ftseries.save_recons_params(
            recons_params=self.recons_params.to_dict(),
            h5File=h5File,
            displayInfo=displayInfo,
        )

    def setH5Exploration(self, explore_for_h5_file):
        self._explore_for_h5_file = explore_for_h5_file

    def setDryRun(self, dry_run):
        self._dry_run = dry_run

    def _notifyScanReady(self, scan):
        if scan is None:
            return
        if not isinstance(scan, TomwerScanBase):
            raise TypeError(f"{scan} should be None or an instance of {TomwerScanBase}")
        else:
            self.sigScanReady.emit(scan)
