from itertools import cycle

import pyqtgraph as pg
from numpy import nanmax, nanmin, inf, argsort, where

from nmreval.lib.colors import Tab10
from ..lib.pg_objects import PlotItem, RegionItem

try:
    # numpy > 1.19 renamed some integration functions
    from scipy.integrate import cumulative_trapezoid
except ImportError:
    from scipy.integrate import cumtrapz as cumulative_trapezoid

from ..Qt import QtWidgets, QtCore, QtGui
from .._py.integral_widget import Ui_Form


class IntegralWidget(QtWidgets.QWidget, Ui_Form):
    colors = cycle(Tab10)

    requestData = QtCore.pyqtSignal(str)
    item_deleted = QtCore.pyqtSignal(pg.GraphicsObject)

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

        self.connected_figure = ''
        self.management = None

        self.graph_shown = None
        self.shown_set = None

        self.ranges = []
        self.lines = []
        self.areas = []

        self._x = None
        self._y = None
        self.max_area = 0
        self.max_y = inf
        self.min_y = -inf

    def __call__(self, graph_name, items):
        self.label_2.setText(f'Connected to {graph_name}\nChanging tab will remove all integration limits.')

        self.clear()

        self.set_combobox.blockSignals(True)
        self.set_combobox.clear()

        for sid, name in items:
            self.set_combobox.addItem(name, userData=sid)

        self.set_combobox.blockSignals(False)

        self.set_combobox.setCurrentIndex(0)
        self.change_set(0)

        return self

    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key_Delete:
            self.remove_integral()
        else:
            super().keyPressEvent(e)

    @QtCore.pyqtSlot(int, name='on_set_combobox_currentIndexChanged')
    def change_set(self, _: int):
        # key = self.set_combobox.itemData(idx)
        key = self.set_combobox.currentData()

        self._data = self.management[key]
        self.max_y = nanmax(self._data.y.real)
        self.min_y = nanmin(self._data.y.real)
        for idx, rnge in enumerate(self.ranges):
            self._update_values(idx, rnge)


    def add(self, pos):
        x = pos[0]
        self.ranges.append((x, x*1.1))

        c = next(IntegralWidget.colors)
        qc = QtGui.QColor(c.hex())
        qc.setAlpha(40)
        pen = pg.mkPen({f'color': c.rgb()})

        region = RegionItem(values=[x, x*1.1], mode='mid', brush=QtGui.QBrush(qc), pen=pen)
        integral_plot = PlotItem(x=[], y=[], pen=pen)

        region.sigRegionChanged.connect(self._update_integral)

        self.lines.append((region, integral_plot))
        self.areas.append(0)
        self._make_entry(c)

        return region, integral_plot

    def _make_entry(self, c):
        item = QtWidgets.QTreeWidgetItem()
        item.setText(0, f'Integral {len(self.ranges)}')

        item.setForeground(0, QtGui.QBrush(QtGui.QColor(c.hex())))

        pts_i = self.ranges[-1]
        item_list = []
        for text, val in [('Start', pts_i[0]), ('Stop', pts_i[1]), ('Areas', 0), ('Ratio', 1.)]:
            child = QtWidgets.QTreeWidgetItem()
            child.setFlags(QtCore.Qt.NoItemFlags)
            child.setText(0, f'{text}: {val:.5g}')
            child.setForeground(0, QtGui.QBrush(QtGui.QColor('black')))

            item_list.append(child)

        item.addChildren(item_list)

        self.treeWidget.addTopLevelItem(item)
        self.treeWidget.expandToDepth(1)

        self._update_values(len(self.ranges) - 1, pts_i)

    def _update_integral(self):
        idx = None
        sender = self.sender()
        for i, (reg, _) in enumerate(self.lines):
            if sender == reg:
                idx = i
                break

        if idx is None:
            return

        self._update_values(idx, sender.getRegion())

    def _update_values(self, idx, new_range):
        self.ranges[idx] = new_range

        area = self.make_integral(idx, *new_range)

        item = self.treeWidget.topLevelItem(idx)
        item.child(0).setText(0, f'Start: {new_range[0]:.5g}')
        item.child(1).setText(0, f'Stop: {new_range[1]:.5g}')

        if area is not None:
            self.areas[idx] = area
            item.child(2).setText(0, f'Area: {area:.5g}')
            if self.max_area > 0:
                self._set_ratios(idx, self.max_area)

        curr_max = max(self.areas)
        if curr_max != self.max_area:
            if curr_max > 0:
                root = self.treeWidget.invisibleRootItem()
                for i in range(root.childCount()):
                    self._set_ratios(i, curr_max)
            self.max_area = curr_max

    def _set_ratios(self, idx, max_value):
        item = self.treeWidget.invisibleRootItem().child(idx)
        area_i = self.areas[idx]
        item.child(3).setText(0, f'Ratio: {area_i / max_value:.3g}')

        integral_line = self.lines[idx][1]
        x_i, y_i = integral_line.getData()
        scale = (self.max_y - self.min_y) / y_i[-1] * (area_i / max_value)
        integral_line.setData(x=x_i, y=y_i * scale)

    def make_integral(self, idx, x_min, x_max):
        if self._data is None:
            self.change_set(0)

        integral = self._data.data.integrate(limits=(x_min, x_max), asarray=True)

        if integral.size != 0:
            area = integral[-1, 1]
            scale = (self.max_y-self.min_y) / area
            self.lines[idx][1].setData(x=integral[:, 0], y=integral[:, 1]*scale  + self.min_y)

            return area

        else:
            self.lines[idx][1].setData(x=[], y=[])
            return None

    def remove_integral(self):
        root = self.treeWidget.invisibleRootItem()
        for item in self.treeWidget.selectedItems():
            idx = root.indexOfChild(item)

            self.item_deleted.emit(self.lines[idx][0])
            self.item_deleted.emit(self.lines[idx][1])

            self.ranges.pop(idx)
            self.lines.pop(idx)
            self.areas.pop(idx)

            self.treeWidget.takeTopLevelItem(idx)

    @QtCore.pyqtSlot(name='on_pushButton_clicked')
    def convert_to_datasets(self):
        set_values = []
        areas = []

        new_sets = True

        for r in self.ranges:
            area_i = []
            for idx in range(self.set_combobox.count()):
                set_id = self.set_combobox.itemData(idx)
                d = self.management[set_id]
                if new_sets:
                    set_values.append(d.value)
                integration = d.data.integrate(limits=r, asarray=True)
                area_i.append(integration[-1, 1])

            areas.append(area_i)
            new_sets = False

        self.management.integral_datasets(self.ranges, set_values, areas)

    def clear(self):
        self.connected_figure = ''
        self.graph_shown = None
        self.shown_set = None

        self._data = None
        self.areas = []
        self.ranges = []
        self.lines = []

        self.max_area = 0
        self.max_y = inf
        self.min_y = -inf

        self.treeWidget.clear()


