import numpy as np
from itertools import cycle

from pyqtgraph import mkColor, mkPen, mkBrush

from nmreval.lib.colors import Tab10

from ..Qt import QtGui, QtCore, QtWidgets
from .._py.shift_scale_dialog import Ui_shift_dialog
from ..lib.pg_objects import PlotItem
from ..lib.spinboxes import SciSpinBox


class QShift(QtWidgets.QDialog, Ui_shift_dialog):
    valuesChanged = QtCore.pyqtSignal(dict, tuple)

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

        self.graphicsView.setMenuEnabled(False)
        self.splitter.setSizes([int(self.width()/4), int(self.width()*2/3)])

        self.movements = {}
        self.data = {}
        self._colors = cycle(Tab10)

        self.graphicsView.plotItem.ctrl.logXCheck.blockSignals(True)
        self.graphicsView.plotItem.ctrl.logYCheck.blockSignals(True)

        delegate = SpinBoxDelegate()
        delegate.valueChanged.connect(self.shift)
        self.shift_table.setItemDelegate(delegate)
        self.x_shift_spinbox.valueChanged.connect(lambda: self.glob_shift('h'))
        self.y_shift_spinbox.valueChanged.connect(lambda: self.glob_shift('v'))

        delegate = SpinBoxDelegate()
        delegate.valueChanged.connect(self.scale)
        self.scale_table.setItemDelegate(delegate)
        self.x_scale_spinbox.valueChanged.connect(lambda: self.glob_scale('h'))
        self.y_scale_spinbox.valueChanged.connect(lambda: self.glob_scale('v'))

    def add_item(self, idx, name, x, y):
        color = mkColor(next(self._colors).rgb())

        if len(y) == 1:
            sym_kwds = {'symbol': 'o', 'symbolBrush': mkBrush(color=color), 'symbolPen': mkPen(color=color)}
        else:
            sym_kwds = {'symbol': None, 'symbolBrush': mkBrush(color=color), 'symbolPen': mkPen(color=color)}

        if np.iscomplexobj(y):
            pl = [PlotItem(x=x, y=y.real, name=name, pen=mkPen(color=color), **sym_kwds),
                  PlotItem(x=x, y=y.imag, name=name, pen=mkPen(color=color), **sym_kwds)]
        else:
            pl = [PlotItem(x=x, y=y, name=name, pen=mkPen(color=color), **sym_kwds)]

        self.data[idx] = (pl, x, y)

        # [[horizontal shift, vertical shift], [horizontal scale, vertical scale]]
        self.movements[idx] = [[0, 0], [1, 1]]

        for i, tw in enumerate([self.shift_table, self.scale_table]):
            tw.blockSignals(True)
            row = tw.rowCount()
            tw.insertRow(row)

            item = QtWidgets.QTableWidgetItem(name)
            item.setForeground(QtGui.QBrush(color))
            item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
            item.setCheckState(QtCore.Qt.Checked)
            item.setData(QtCore.Qt.UserRole, idx)
            tw.setItem(row, 0, item)

            tw.setItem(row, 1, QtWidgets.QTableWidgetItem(str(i)))
            tw.setItem(row, 2, QtWidgets.QTableWidgetItem(str(i)))

            tw.blockSignals(False)
        for i in pl:
            self.graphicsView.addItem(i)

    def set_graphs(self, graphs: list):
        for key, name in graphs:
            self.data_combobox.addItem(name, userData=key)
            self.values_combobox.addItem(name, userData=key)

    def glob_shift_scale(self, widget: QtWidgets.QTableWidget, mode: int, col: int, value: float):
        for row in range(widget.rowCount()):
            if widget.item(row, 0).checkState() == QtCore.Qt.Checked:
                item = widget.item(row, col)
                item.setText(str(value))
                self.shift_scale(widget, mode, row, col-1, value)

    def glob_shift(self, direction: str):
        if direction == 'h':
            val = self.x_shift_spinbox.value()
            self.glob_shift_scale(self.shift_table, 0, 1, val)
        else:
            val = self.y_shift_spinbox.value()
            self.glob_shift_scale(self.shift_table, 0, 2, val)

    def glob_scale(self, direction: str):
        if direction == 'h':
            val = self.x_scale_spinbox.value()
            self.glob_shift_scale(self.scale_table, 1, 1, val)
        else:
            val = self.y_scale_spinbox.value()
            self.glob_shift_scale(self.scale_table, 1, 2, val)

    def shift_scale(self, widget: QtWidgets.QTableWidget, mode: int,
                    row: int, col: int, value: float):
        item = widget.item(row, 0)
        key = item.data(QtCore.Qt.UserRole)
        self.movements[key][mode][col] = value

        (x_off, y_off), (x_scale, y_scale) = self.movements[key]

        pl, x, y = self.data[key]
        y_part = [np.real, np.imag]
        for i, item in enumerate(pl):
            item.setData(x=x*x_scale+x_off, y=y_part[i](y) * y_scale + y_off)

    @QtCore.pyqtSlot(int, int, float)
    def shift(self, row: int, column: int, value: float):
        self.shift_scale(self.shift_table, 0, row, column-1, value)

    @QtCore.pyqtSlot(int, int, float)
    def scale(self, row: int, column: int, value: float):
        self.shift_scale(self.scale_table, 1, row, column-1, value)

    @QtCore.pyqtSlot(int, name='on_xlog_checkbox_stateChanged')
    @QtCore.pyqtSlot(int, name='on_ylog_checkbox_stateChanged')
    def set_log(self, state: int):
        if self.sender() == self.xlog_checkbox:
            log_state = self.graphicsView.plotItem.ctrl.logXCheck
            func = self.graphicsView.setXRange
        else:
            log_state = self.graphicsView.plotItem.ctrl.logYCheck
            func = self.graphicsView.setYRange

        log_state.setCheckState(state)
        self.graphicsView.plotItem.updateLogMode()
        # For some combinations of Python, pyqt, and pyqtgraph, updateLogMode does not update
        # view range to log values and logTickValues generates LOTS of ticks, which freezes everything.
        # -> Call setRange to interrupt the loop.
        func(0, 1)
        self.graphicsView.enableAutoRange()

    def on_overwrite_checkbox_stateChanged(self, state: int):
        self.data_newgraph.setVisible(state != QtCore.Qt.Checked)
        self.data_combobox.setVisible(state != QtCore.Qt.Checked)

    def on_value_checkbox_stateChanged(self, state: int):
        self.values_newgraph.setVisible(state == QtCore.Qt.Checked)
        self.values_combobox.setVisible(state == QtCore.Qt.Checked)

    def on_data_newgraph_stateChanged(self, state: int):
        self.data_combobox.setEnabled(state != QtCore.Qt.Checked)

    def on_values_newgraph_stateChanged(self, state: int):
        self.values_combobox.setEnabled(state != QtCore.Qt.Checked)

    def accept(self):
        data_saving = None
        if not self.overwrite_checkbox.isChecked():
            if self.data_newgraph.isChecked():
                data_saving = ''
            else:
                data_saving = self.data_combobox.currentData()

        value_saving = None
        if self.value_checkbox.isChecked():
            if self.values_newgraph.isChecked():
                value_saving = ''
            else:
                value_saving = self.values_combobox.currentData()

        self.valuesChanged.emit(self.movements, (data_saving, value_saving))
        self.close()


class SpinBoxDelegate(QtWidgets.QStyledItemDelegate):
    valueChanged = QtCore.pyqtSignal(int, int, float)

    def createEditor(self, parent: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem,
                     idx: QtCore.QModelIndex) -> QtWidgets.QWidget:
        editor = SciSpinBox(parent)
        editor.valueChanged.connect(self.new_value)

        return editor

    def new_value(self, val):
        # geht bestimmt besser...
        table = self.sender().parent().parent()
        self.valueChanged.emit(table.currentRow(), table.currentColumn(), val)
