from __future__ import annotations

import os

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.peaks import read_peaks_attributes
from ewoks3dxrd.tasks.filter_by_index import FilterByIndexer
from ewoks3dxrd.tasks.filter_by_index import Inputs

from .common.ewoks3dxrd_widget import Ewoks3DXRDWidget
from .common.execution_control_group_box import ExecutionControlGroupBoxWithProcessName
from .common.indexer_filter_control_box import IndexerControlBox
from .common.peak_filter_plot2d import PeakFilterPlot2D


class OWIndexerFilter(Ewoks3DXRDWidget, ewokstaskclass=FilterByIndexer):
    name = "Index Filter"
    description = "Choose rings aligned peaks."
    icon = "icons/rings-smallest.svg"

    _ewoks_inputs_to_hide_from_orange = (
        "overwrite",
        "rings",
        "process_group_name",
    )

    def __init__(self):
        super().__init__()

        settingsWidget = qt.QWidget(self)
        settingLayout = qt.QVBoxLayout(settingsWidget)
        self._inputParametersBox = IndexerControlBox(self)
        self._executeBox = ExecutionControlGroupBoxWithProcessName(
            buttonText="Filter Peaks"
        )
        self._executeBox.sigExecuteRequested.connect(self.execute_with_validation)
        self._executeBox.sigUrlChanged.connect(self._onDataURLEditFinished)
        self._executeBox.setProcessName(
            Inputs.model_fields["process_group_name"].default
        )
        settingLayout.addWidget(self._inputParametersBox)
        settingLayout.addWidget(self._executeBox)
        self.addControlWidget(settingsWidget)
        self._inputParametersBox.latticeFile.sigMasterFileChanged.connect(
            self._setLatticeFileInput
        )
        self._inputParametersBox.dsTol.editingFinished.connect(self._setDSTOL)
        self._inputParametersBox.wavelength.editingFinished.connect(self._setWavelength)

        self._plot = PeakFilterPlot2D(self)
        self._indexerFilterInfo = qt.QPlainTextEdit(self)
        self._indexerFilterInfo.setReadOnly(True)
        self._plotTabWidget = qt.QTabWidget()
        self._plotTabWidget.addTab(self._plot, "Visualization")
        self._plotTabWidget.addTab(self._indexerFilterInfo, "Indexer Log")

        self.addMainWidget(self._plotTabWidget)

        self.registerInput(
            "intensity_filtered_data_url",
            self._executeBox.getInputURL,
            self._executeBox.setInputURL,
        )
        self.registerInput(
            "rings",
            self._inputParametersBox.ringIndexTuple.getValue,
            self._inputParametersBox.ringIndexTuple.setValue,
        )
        self.registerInput(
            "ds_tol",
            self._inputParametersBox.dsTol.value,
            self._inputParametersBox.dsTol.setValue,
        )
        self.registerInput(
            "wavelength",
            self._inputParametersBox.wavelength.value,
            self._inputParametersBox.wavelength.setValue,
        )
        self.registerInput(
            "lattice_file",
            self._inputParametersBox.latticeFile.getText,
            self._inputParametersBox.latticeFile.setText,
        )
        self.registerInput(
            "process_group_name",
            self._executeBox.getProcessName,
            self._executeBox.setProcessName,
        )

        self.registerInput(
            "overwrite",
            self._executeBox.isOverwriteChecked,
            self._executeBox.setOverwrite,
        )

    def _setDSTOL(
        self,
    ):
        self.set_default_input("ds_tol", self._inputParametersBox.dsTol.value())

    def _setWavelength(
        self,
    ):
        self.set_default_input(
            "wavelength", self._inputParametersBox.wavelength.value()
        )

    def validateInputs(self):
        self._inputParametersBox.validate()
        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)
            self._plotIncomingPeaks()
        wavelength = self.get_task_input_value("wavelength", None)
        if wavelength:
            self.set_default_input("wavelength", wavelength)
            self._inputParametersBox.wavelength.setValue(wavelength)
        ds_tol = self.get_task_input_value("ds_tol", None)
        if ds_tol:
            self._inputParametersBox.dsTol.setValue(ds_tol)
        lattice_parameter_file = self.get_task_input_value("lattice_file", None)
        if lattice_parameter_file:
            self._inputParametersBox.latticeFile.setText(str(lattice_parameter_file))

        if (
            wavelength is None
            or ds_tol is None
            or lattice_parameter_file is None
            or input_peaks_url is None
        ):
            return
        self.validateInputs()
        self._plotIncomingPeaks()

    def _setLatticeFileInput(self, latticeFile: str):
        if not os.path.exists(latticeFile):
            return
        unit_cell = read_lattice_cell_data(lattice_data_file_path=latticeFile)
        try:
            UnitCell(
                lattice_parameters=unit_cell.lattice_parameters,
                symmetry=int(unit_cell.space_group),
            )
        except Exception as e:
            self.showError(e, title="Lattice Parameters in Lattice File.")
            return
        self.set_default_input("lattice_file", latticeFile)

    def _plotIncomingPeaks(self):
        input_url = self.get_task_input_value("intensity_filtered_data_url", None)
        if input_url is None:
            return
        data_url = DataUrl(input_url)
        nexus_file_path, lattice_data_group_path = (
            data_url.file_path(),
            data_url.data_path(),
        )
        intensity_3d_peaks = read_peaks_attributes(
            filename=nexus_file_path,
            process_name=lattice_data_group_path,
        )
        self._plot.addUnfilteredScatter(
            x=intensity_3d_peaks["ds"],
            y=intensity_3d_peaks["sum_intensity"],
            value=len(intensity_3d_peaks["sum_intensity"]) * [1],
            colormap=Colormap(name="viridis", autoscaleMode="percentile_1_99"),
        )
        self._plot.setGraphXLabel("Reciprocal distance (ds)")
        self._plot.setGraphYLabel("Sum Intensity")
        self._plot.setYAxisLogarithmic(True)
        self._plot.resetZoomOnce()
        self._plotRings(ds_limit=max(intensity_3d_peaks["ds"]))

    def _plotRings(self, ds_limit: float):
        lattice_file_name = self.get_task_input_value("lattice_file")
        if not lattice_file_name:
            return
        unit_cell = read_lattice_cell_data(lattice_data_file_path=lattice_file_name)
        try:
            unit_cell = UnitCell(
                lattice_parameters=unit_cell.lattice_parameters,
                symmetry=int(unit_cell.space_group),
            )
            self._plot.addRings(unit_cell, ds_limit)
        except Exception as e:
            self.showError(e, title="Lattice Ring Plot")

    def handleSuccessfulExecution(self):
        if self.get_task_output_value("indexer_filtered_data_url", None) is None:
            return
        self.setOutputDataUrl(self.get_task_output_value("indexer_filtered_data_url"))
        self.displayOutputDataUrl()
        indexer_log = self.get_task_output_value("indexer_log", "")
        if indexer_log:
            self._indexerFilterInfo.setPlainText(indexer_log)

    def displayOutputDataUrl(self):
        data_url = DataUrl(self.getOutputDataUrl())
        nexus_file_path, data_group_path = (
            data_url.file_path(),
            data_url.data_path(),
        )
        index_3d_peaks = read_peaks_attributes(
            filename=nexus_file_path,
            process_name=data_group_path,
        )
        self._plot.addFilteredScatter(
            x=index_3d_peaks["ds"],
            y=index_3d_peaks["sum_intensity"],
            value=index_3d_peaks["Number_of_pixels"],
            colormap=Colormap(
                name="viridis", normalization="log", autoscaleMode="percentile_1_99"
            ),
        )
        self._plot.resetZoomOnce()

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

    def _getRingIndices(self):
        ringIndices = self._inputParametersBox.ringIndexTuple.getValue()
        if not ringIndices:
            raise ValueError("No ring selected")

        return ringIndices
