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 save_par_file
from ewoks3dxrd.nexus.peaks import read_peaks_attributes
from ewoks3dxrd.tasks.geometry_transformation import GeometryTransformation

from .common.custom_peak_plot import CustomPeakPlot
from .common.ewoks3dxrd_plot2d import Ewoks3DXRDPlot2D
from .common.ewoks3dxrd_widget import Ewoks3DXRDWidget
from .common.execution_control_group_box import ExecutionControlGroupBox
from .common.utils import plotRings
from .geometry.geometry_control_box import GeometryControlBox


class OWGeometryTransformation(Ewoks3DXRDWidget, ewokstaskclass=GeometryTransformation):
    name = "Geometry Transformation"
    description = (
        "Generate ds, g-vectors, eta, etc on detector corrected segmented 3D Peaks."
    )
    icon = "icons/settings.svg"
    _ewoks_inputs_to_hide_from_orange = (
        "overwrite",
        "geometry_par_file",
    )

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

        self._ds_ring_limit = 1.5
        settingsWidget = qt.QWidget(self)
        settingLayout = qt.QVBoxLayout(settingsWidget)
        self._inputParametersBox = GeometryControlBox(self)
        self._inputParametersBox.latticeGroup.sigLatticeParamsChanged.connect(
            self._plotRings
        )
        self._inputParametersBox.latticeGroup.toggled.connect(self._toggleRings)
        self._inputParametersBox.latticeGroup.toggled.connect(
            self._inputParametersBox.latticeGroup.layout().setEnabled
        )
        self._executeBox = ExecutionControlGroupBox(
            buttonText="Compute Geometry Vectors for Peaks"
        )
        self._executeBox.sigExecuteRequested.connect(self.execute_with_validation)
        settingLayout.addWidget(self._inputParametersBox, stretch=1)
        settingLayout.addWidget(self._executeBox)
        self.addControlWidget(settingsWidget)

        self._plot = Ewoks3DXRDPlot2D(self)
        self._logPlotEvents(self._plot)
        self._plot.setKeepDataAspectRatio(False)
        self._plot.getColorBarWidget().setVisible(False)
        self._plot.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
        self._plot.setGraphXLabel("Reciprocal Distance (ds)")
        self._plot.setGraphYLabel("Azimuthal Angle (eta)")

        splitter = qt.QSplitter(self)
        self._customPlot = CustomPeakPlot(self)
        self._customPlot.sigAxisChanged.connect(self.plotCustomFilteredPeaksPlot)
        splitter.setOrientation(qt.Qt.Horizontal)
        splitter.addWidget(self._plot)
        splitter.addWidget(self._customPlot)

        self.addMainWidget(splitter)
        self.registerInput(
            "spatial_corrected_data_url",
            self._validateInputUrl,
            self._executeBox.setInputURL,
        )
        self.registerInput(
            "geometry_par_file",
            self._prepareGeometryFile,
            self._inputParametersBox.setGeometryFile,
        )
        self.registerInput(
            "overwrite",
            self._executeBox.isOverwriteChecked,
            self._executeBox.setOverwrite,
        )

    def __post_init__(self):
        super().__post_init__()
        self._plotRings()

    def _toggleRings(self, showRings: bool):
        if not showRings:
            self._plot.clearMarkers()
        else:
            self._plotRings()

    def _plotRings(self):
        if not self._inputParametersBox.latticeGroup.isChecked():
            return

        params = self._inputParametersBox.latticeGroup.getLatticeParameters()
        try:
            unit_cell = UnitCell(
                lattice_parameters=params["lattice_parameters"],
                symmetry=int(params["lattice_space_group"]),
            )
            plotRings(self._plot, unit_cell, limit=self._ds_ring_limit)
        except Exception as e:
            self.showError(e)

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

    def handleNewSignals(self):
        spatial_corrected_data_url = self.get_task_input_value(
            "spatial_corrected_data_url"
        )
        if spatial_corrected_data_url is not None:
            self._executeBox.setInputURL(spatial_corrected_data_url)

    def handleSuccessfulExecution(self):
        if self.get_task_output_value("geometry_updated_data_url") is None:
            return
        self.setOutputDataUrl(self.get_task_output_value("geometry_updated_data_url"))
        self._plotTaskGeneratedOutput()
        self.plotCustomFilteredPeaksPlot()

    def _plotTaskGeneratedOutput(self):
        data_url = DataUrl(self.getOutputDataUrl())
        nexus_file_path, geometry_data_group_path = (
            data_url.file_path(),
            data_url.data_path(),
        )
        geo_3d_peaks = read_peaks_attributes(
            filename=nexus_file_path,
            process_name=geometry_data_group_path,
        )
        self._customPlot.populateAxesToolbar(geo_3d_peaks.keys())

        scatter = self._plot.addScatter(
            x=geo_3d_peaks["ds"],
            y=geo_3d_peaks["eta"],
            value=geo_3d_peaks["Number_of_pixels"],
            colormap=Colormap(
                name="viridis", normalization="log", autoscaleMode="percentile_1_99"
            ),
            symbol="o",
            legend="aligned Peaks",
        )
        scatter.setSymbolSize(7)
        self._plot.setGraphXLabel("Reciprocal distance (ds)")
        self._plot.setGraphYLabel("Azimuthal angle (eta)")
        self._plot.resetZoomOnce()
        self._ds_ring_limit = max(geo_3d_peaks["ds"])
        self._plotRings()

    def _validateInputUrl(self):
        input_url = self._executeBox.getInputURL()
        if not input_url:
            raise ValueError("No detector corrected data URL to process.")

        return input_url

    def _prepareGeometryFile(self) -> str | None:
        file_path = Path(DataUrl(self._executeBox.getInputURL()).file_path())
        geometry_file = file_path.parent / "geometry_tdxrd.par"
        parameters = self._inputParametersBox.geometryPanel.getGeometryParameters()
        if parameters["distance"] == 0.0 or parameters["wavelength"] == 0.0:
            qt.QMessageBox.warning(
                self,
                "Check Geometry Settings",
                "Distance/Wavelength cannot be zero. Please provide a positive value.",
            )
            return None
        if not parameters:
            raise ValueError("No geometry parameters to write")
        save_par_file(geometry_file, parameters)
        return str(geometry_file)

    def plotCustomFilteredPeaksPlot(self):
        geometry_updated_data_url = self.getOutputDataUrl()
        if geometry_updated_data_url is None:
            return
        self._customPlot.plotFilteredPeaks(geometry_updated_data_url)

    def displayOutputDataUrl(self):
        self._plotTaskGeneratedOutput()
        self.plotCustomFilteredPeaksPlot()
