from __future__ import annotations

from pathlib import Path

import numpy as np
from pyqtgraph import PlotDataItem

from nmreval.data.points import Points
from nmreval.io.dsc import Cyclohexane, DSCCalibrator, DSCSample

from ..Qt import QtWidgets, QtCore
from .._py.dscfile_dialog import Ui_Dialog


class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
    data_read = QtCore.pyqtSignal(list)
    file_ext = ['.txt', '.dsc']

    def __init__(self, sample=None, empty=None, reference=None, parent=None):
        super().__init__(parent=parent)
        self.setupUi(self)

        self.calibrator = DSCCalibrator()
        self.current_run = (-1, 'h')
        self.sample_idx = None
        self.fname = None

        self.raw_graph.setLabel('bottom', text='T', units='K')
        self.raw_graph.setTitle('Raw data')
        self.raw_graph.addLegend()
        self.raw_sample = PlotDataItem(x=[], y=[], name='Sample')
        self.empty_sample = PlotDataItem(x=[], y=[], pen={'dash': (5, 5)}, name='Empty')
        self.raw_graph.addItem(self.raw_sample)
        self.raw_graph.addItem(self.empty_sample)

        self.end_graph.setTitle('End result')
        self.end_graph.setLabel('bottom', text='T', units='K')
        self.baseline_sample = PlotDataItem(x=[], y=[])
        self.end_graph.addItem(self.baseline_sample)

        self.baseline_graph.setTitle('Time dependence')
        self.baseline_graph.setLabel('bottom', text='t', units='min')
        self.drift_sample = PlotDataItem(x=[], y=[])
        self.slope_graph = PlotDataItem(x=[], y=[], pen={'color': 'r', 'dash': (5, 5)})
        self.baseline_graph.addItem(self.drift_sample)
        self.baseline_graph.addItem(self.slope_graph)

        self.calib_graph.setTitle('Calibration')
        self.calib_graph.setLabel('bottom', text='T', units='K')
        self.ref_plotitems = []

        self.sample = None
        if sample is not None:
            self.add_sample(sample)

        self.empty = None
        if empty is not None:
            self.add_empty(empty=empty)

        self.references = []
        if reference is not None:
            if isinstance(reference, (str, Path, DSCSample)):
                reference = [reference]
                for r in reference:
                    self.add_reference(ref=r)

    def __call__(self, fname: Path | str):
        self.clear_plots()
        self.add_sample(fname)

        return self

    def add_sample(self, fname: Path | str):
        self.sample = self.calibrator.set_measurement(fname, mode='sample')
        self.fname = self.sample.fname

        self.step_listWidget.clear()

        for opts in self.sample.steps:
            item = QtWidgets.QListWidgetItem()
            item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable)
            item.setCheckState(QtCore.Qt.Unchecked)

            if opts[0] == 'i':
                item.setFlags(QtCore.Qt.NoItemFlags)
                item.setText(f'{opts[1]:.2f} K for {opts[1] / 60:.0f} min')
            else:
                item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable)
                item.setText(f'{opts[2]:.2f} K to {opts[3]:.2f} K with {opts[1]} K/min')

            self.step_listWidget.addItem(item)

    @QtCore.pyqtSlot(name='on_loadempty_button_clicked')
    def add_empty(self, empty=None):
        if empty is None:
            empty, _ = QtWidgets.QFileDialog.getOpenFileName(directory=str(self.fname.parent))

        if empty:
            self.empty = self.calibrator.set_measurement(empty, mode='empty')
            self.empty_label.setText(str(self.empty.fname.name))

            self.update_plots()

    @QtCore.pyqtSlot(name='on_delempty_button_clicked')
    def remove_empty(self):
        self.empty_label.setText('No empty measurement')
        self.calibrator.empty = None

        self.update_plots()

    @QtCore.pyqtSlot(name='on_ref_add_pushButton_clicked')
    def add_reference(self, ref: str | Path = None):
        if ref is None:
            ref, _ = QtWidgets.QFileDialog.getOpenFileName(directory=str(self.fname.parent))

        if ref:
            ref = self.calibrator.set_measurement(ref, mode='reference')

            self.references.append(ref)
            item = QtWidgets.QTableWidgetItem(str(ref.fname.name))
            item.setData(QtCore.Qt.UserRole, ref.fname)
            item.setFlags(QtCore.Qt.ItemIsEnabled)

            rowcnt = self.reference_tableWidget.rowCount()
            self.reference_tableWidget.setRowCount(rowcnt+1)
            self.reference_tableWidget.setItem(rowcnt, 0, item)
            self.reference_tableWidget.setCellWidget(rowcnt, 1, ReferenceComboBox())
            self.reference_tableWidget.resizeColumnsToContents()

        self.update_plots()

    @QtCore.pyqtSlot(name='on_ref_remove_pushButton_clicked')
    def remove_reference(self):
        idx = self.reference_tableWidget.currentRow()
        self.calibrator.remove_reference(self.reference_tableWidget.item(idx, 0).data(QtCore.Qt.UserRole))

        self.reference_tableWidget.removeRow(idx)
        self.update_plots()

    @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, name='on_step_listWidget_itemChanged')
    def select_run(self, item: QtWidgets.QListWidgetItem):

        idx = self.step_listWidget.indexFromItem(item).row()
        self.step_listWidget.blockSignals(True)
        for row in range(self.step_listWidget.count()):
            if idx == row:
                continue
            self.step_listWidget.item(row).setCheckState(QtCore.Qt.Unchecked)
        self.step_listWidget.blockSignals(False)

        if item.checkState() == QtCore.Qt.Checked:
            mode, rate, _, _ = self.sample.steps[idx]
            self.current_run = (rate, mode)
            self.sample_idx = idx
            self.update_plots()
        else:
            self.current_run = (-1, 'h')
            self.sample_idx = None
            self.clear_plots()

    def get_data(self, ):
        if self.sample_idx is None:
            return

        rate = self.current_run[0]
        slope_type = {self.none_radioButton: None,
                      self.isotherm_radioButton: 'iso',
                      self.slope_radioButton: 'curve'}[self.buttonGroup.checkedButton()]

        try:
            raw_sample, drift_value, sample_data, empty_data, slope = self.calibrator.get_data(self.sample_idx,
                                                                                               slope=slope_type)
        except ValueError as e:
            _msg = QtWidgets.QMessageBox.warning(self, 'No rate found', e.args[0])
            return

        self.calibrator.ref_list = []
        for row in range(self.reference_tableWidget.rowCount()):
            self.calibrator.ref_list.append(self.reference_tableWidget.cellWidget(row, 1).get_reference())

        calib_x, calib_y, regions = self.calibrator.get_calibration(rate)

        drift_value[0] /= 60.
        slope[0] /= 60.

        if calib_x is not None:
            sample_data[0] *= calib_x[0]
            sample_data[0] += calib_x[1]

            if self.cp_checkBox.isChecked():
                sample_data[1] *= calib_y

        return sample_data, raw_sample, empty_data, drift_value, slope, calib_x, calib_y, regions

    @QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonGroup_buttonClicked')
    @QtCore.pyqtSlot(int, name='on_cp_checkBox_stateChanged')
    def update_plots(self, _=None):
        sample_data, raw_sample, empty_data, drift_value, slope, calib_x, calib_y, regions = self.get_data()

        self.raw_sample.setData(x=raw_sample[0], y=raw_sample[1])
        self.drift_sample.setData(x=drift_value[0], y=drift_value[1])
        self.slope_graph.setData(x=slope[0], y=slope[1])

        if empty_data is not None:
            self.empty_sample.setData(x=empty_data[0], y=empty_data[1])

        self.calib_graph.clear()

        if calib_x is not None:
            for ref_zoom, onset, grad_points in regions:
                ref_plot = PlotDataItem(x=ref_zoom[0], y=ref_zoom[1])
                self.calib_graph.addItem(ref_plot)

                self.calib_graph.addItem(PlotDataItem(x=grad_points[0], y=grad_points[1],
                                                      symbol='x'))

                view_limits = -np.max(ref_zoom[1])/10, np.max(ref_zoom[1]) * 1.1
                cut_idx, = np.where((onset < view_limits[1]) & (onset > view_limits[0]))
                self.calib_graph.addItem(PlotDataItem(x=ref_zoom[0, cut_idx], y=onset[cut_idx],
                                                      pen={'color': 'r', 'dash': (2, 5)}))
                self.calib_graph.addItem(PlotDataItem(x=[ref_zoom[0, 0], ref_zoom[0, -1]], y=[0, 0],
                                                      pen={'color': 'r', 'dash': (2, 5)}))

        self.baseline_sample.setData(x=sample_data[0], y=sample_data[1])

    def clear_plots(self):
        for plot in [self.raw_sample, self.baseline_sample, self.empty_sample, self.drift_sample, self.slope_graph]:
            plot.setData(x=[], y=[])

        self.calib_graph.clear()

    def export_data(self, filesave=False, close_after=True):
        try:
            sample_data = self.get_data()[0]
        except TypeError:
            return

        rate, mode = self.current_run
        new_val = Points(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate} ({mode})')

        if filesave:
            new_val.savetxt(self.fname.with_name(f'{self.fname.stem} {rate}K-min {mode}.dat'.replace(' ', '_')))
        else:
            self.data_read.emit([new_val])

        if close_after:
            super().accept()

    @QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonBox_clicked')
    def button_clicked(self, bttn: QtWidgets.QAbstractButton):
        bttn_value = self.buttonBox.standardButton(bttn)
        if bttn_value in (self.buttonBox.Ok, self.buttonBox.Apply, self.buttonBox.Save):
            self.export_data(filesave=bttn_value==self.buttonBox.Save, close_after=bttn_value==self.buttonBox.Ok)
        else:
            super().close()


class ReferenceComboBox(QtWidgets.QComboBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.references = [Cyclohexane]
        for ref in self.references:
            self.addItem(ref.name)

    def get_reference(self):
        return self.references[self.currentIndex()]
