from __future__ import annotations

import logging
import tempfile
from pathlib import Path

from ImageD11.unitcell import unitcell as UnitCell
from silx.gui import qt
from silx.gui.colors import Colormap
from silx.io.utils import DataUrl

from ewoks3dxrd.io import read_lattice_cell_data
from ewoks3dxrd.nexus.grains import save_indexed_grains_as_ascii
from ewoks3dxrd.nexus.peaks import read_peaks_attributes
from ewoks3dxrd.nexus.utils import group_exists
from ewoks3dxrd.tasks.index_grains import IndexGrains

from .common.ewoks3dxrd_widget import Ewoks3DXRDWidget
from .common.execution_control_group_box import ExecutionControlGroupBox
from .common.peak_filter_plot2d import PeakFilterPlot2D
from .indexer.deterministic_indexing_control_box import IndexerSettings

_logger = logging.getLogger(__name__)


class OWIndexing(Ewoks3DXRDWidget, ewokstaskclass=IndexGrains):
    name = "Indexing"
    description = "Indexes peaks to find grains. Alternative to Grid Indexing."
    icon = "icons/indexing.svg"
    _ewoks_inputs_to_hide_from_orange = (
        "overwrite",
        "rings",
        "scoring_rings",
        "max_grains",
        "reciprocal_dist_tol",
        "hkl_tols",
        "min_pks_frac",
        "cosine_tol",
    )

    def __init__(self):
        super().__init__()
        settingsWidget = qt.QWidget(self)
        settingLayout = qt.QVBoxLayout(settingsWidget)
        self._inputParametersBox = IndexerSettings(self)
        self._executeBox = ExecutionControlGroupBox(buttonText="Find Grains")
        self._executeBox.sigExecuteRequested.connect(self.execute_with_validation)
        self._executeBox.sigUrlChanged.connect(self._onDataURLEditFinished)
        settingLayout.addWidget(self._inputParametersBox, stretch=1)
        settingLayout.addWidget(self._executeBox)
        self.addControlWidget(settingsWidget)

        self._peaksPlot = PeakFilterPlot2D(self)
        self._indexedGrainInfo = qt.QPlainTextEdit(self)
        self._indexedGrainInfo.setReadOnly(True)
        self._plotTabWidget = qt.QTabWidget()
        self._plotTabWidget.addTab(self._peaksPlot, "Incoming Peaks")
        self._plotTabWidget.addTab(self._indexedGrainInfo, "Grains UBI's")

        self.addMainWidget(self._plotTabWidget)

        self.registerInput(
            "intensity_filtered_data_url",
            self._executeBox.getInputURL,
            self._executeBox.setInputURL,
        )
        self.registerInput(
            "overwrite",
            self._executeBox.isOverwriteChecked,
            self._executeBox.setOverwrite,
        )
        self.registerInput(
            "lattice_file",
            self._inputParametersBox.getLatticeFile,
            self._inputParametersBox.setLatticeFile,
        )
        self.registerInput(
            "wavelength",
            self._inputParametersBox.getWavelength,
            self._inputParametersBox.setWavelength,
        )
        self.registerInput(
            "rings",
            self._inputParametersBox.getRings,
            self._inputParametersBox.setRings,
        )
        self.registerInput(
            "scoring_rings",
            self._inputParametersBox.getScoringRings,
            self._inputParametersBox.setScoringRings,
        )
        self.registerInput(
            "max_grains",
            self._inputParametersBox.getMaxGrains,
            self._inputParametersBox.setMaxGrains,
        )
        self.registerInput(
            "reciprocal_dist_tol",
            self._inputParametersBox.getReciprocalDist,
            self._inputParametersBox.setReciprocalDist,
        )
        self.registerInput(
            "hkl_tols",
            self._inputParametersBox.getHKLSEQTOL,
            self._inputParametersBox.setHKLSEQTOL,
        )
        self._restoreDefaultInputs()

    def _onDataURLEditFinished(self) -> None:
        self._handlePeaksInputURL(
            urlWidget=self._executeBox.inputUrlWidget,
            defaultInputKey="intensity_filtered_data_url",
            errorTitle="Invalid Incoming Peaks Data group path",
        )

    def handleSuccessfulExecution(self):
        output_url = self.get_task_output_value("indexed_grain_data_url", None)
        if output_url is None:
            return
        self.setOutputDataUrl(output_url)
        self.displayOutputDataUrl()

    def validateInputs(self):
        self._executeBox.validate()

    def handleNewSignals(self):
        input_peaks_url = self.get_task_input_value("intensity_filtered_data_url", None)
        if input_peaks_url:
            self._executeBox.setInputURL(input_peaks_url)
        wavelength = self.get_task_input_value("wavelength", None)
        if wavelength:
            self.set_default_input("wavelength", wavelength)
            self._inputParametersBox.setWavelength(wavelength=wavelength)

        lattice_parameter_file = self.get_task_input_value("lattice_file", None)
        if lattice_parameter_file:
            self._inputParametersBox.setLatticeFile(str(lattice_parameter_file))

        self._plotIncomingPeaks()

    def _plotIncomingPeaks(self):
        input_peaks_url = self.get_task_input_value("intensity_filtered_data_url", None)
        if input_peaks_url is None:
            return

        data_url = DataUrl(input_peaks_url)
        nexus_file_path, data_group_path = (
            data_url.file_path(),
            data_url.data_path(),
        )
        incoming_3d_peaks = read_peaks_attributes(
            filename=nexus_file_path,
            process_name=data_group_path,
        )
        self._peaksPlot.addUnfilteredScatter(
            x=incoming_3d_peaks["ds"],
            y=incoming_3d_peaks["eta"],
            value=len(incoming_3d_peaks["eta"]) * [1],
            colormap=Colormap(name="viridis", autoscaleMode="percentile_1_99"),
        )
        self._ds_ring_limits = max(incoming_3d_peaks["ds"])

        self._peaksPlot.setGraphXLabel("Reciprocal distance (ds)")
        self._peaksPlot.setGraphYLabel("Sum Intensity")
        self._peaksPlot.resetZoom()
        if self._inputParametersBox.getLatticeFile():
            self._plotLatticeCurve()

    def _plotLatticeCurve(self):
        if self._executeBox.getInputURL() == "":
            return

        unit_cell = read_lattice_cell_data(
            lattice_data_file_path=self._inputParametersBox.getLatticeFile()
        )
        unit_cell = UnitCell(
            lattice_parameters=unit_cell.lattice_parameters,
            symmetry=int(unit_cell.space_group),
        )
        self._peaksPlot.addRings(unit_cell, self._ds_ring_limits)

    def displayOutputDataUrl(self):
        _logger.debug("GridIndexGrain: Display Output Data Url")
        url = self.getOutputDataUrl()
        data_url = DataUrl(url)
        group_exists(
            filename=data_url.file_path(), data_group_path=data_url.data_path()
        )
        tmp_grain_file = Path(tempfile.gettempdir()) / "indexed_grains.map"
        save_indexed_grains_as_ascii(
            grain_file_h5=data_url.file_path(),
            process_group_path=data_url.data_path(),
            grain_file_ascii=tmp_grain_file,
        )
        self._indexedGrainInfo.clear()
        if tmp_grain_file.exists():
            with open(tmp_grain_file, "r") as f:
                content = f.read()

            self._indexedGrainInfo.setPlainText(content)
            self._plotTabWidget.setCurrentWidget(self._indexedGrainInfo)
        else:
            self._indexedGrainInfo.setPlainText("Error: .map file was not generated.")
