from numpy import inf

from nmreval.utils.text import convert

from ..Qt import QtGui, QtCore, QtWidgets
from .._py.mean_form import Ui_mean_form


class QDelayWidget(QtWidgets.QWidget):
    get_data = QtCore.pyqtSignal(str)
    newDelay = QtCore.pyqtSignal()

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

        self.vals = ''
        self.dim = 0
        self.plainTextEdit.setVisible(False)

    def __call__(self):
        self.vals = ''
        self.dim = 0
        self.comboBox.clear()
        self.comboBox.blockSignals(True)
        self.comboBox.addItem('New list...')
        self.comboBox.blockSignals(True)
        self.plainTextEdit.setVisible(False)

    def add_items(self, item):
        self.comboBox.blockSignals(True)
        if isinstance(item, list):
            self.comboBox.insertItems(0, item)
        else:
            self.comboBox.insertItem(0, item)
        self.comboBox.setCurrentIndex(0)
        self.comboBox.blockSignals(False)

    @QtCore.pyqtSlot(name='on_toolButton_clicked')
    def show_values(self):
        if self.plainTextEdit.isVisible():
            self.plainTextEdit.clear()
            self.plainTextEdit.setVisible(False)
        elif self.comboBox.currentText() == 'New list...':
            self.newDelay.emit()
        else:
            self.get_data.emit(self.comboBox.currentText())
            self.plainTextEdit.setVisible(True)
            self.plainTextEdit.setPlainText(self.vals)


class LineEdit(QtWidgets.QLineEdit):
    values_requested = QtCore.pyqtSignal()

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

    def contextMenuEvent(self, evt):
        menu = self.createStandardContextMenu()
        request_action = menu.addAction('Use value of sets')

        action = menu.exec(evt.globalPos())

        if action == request_action:
            self.values_requested.emit()


class LineEditPost(QtWidgets.QLineEdit):
    values_requested = QtCore.pyqtSignal()

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

        self.suffix = ''
        self.prefix = ''

        self.editingFinished.connect(self.add_fixes)

    def add_fixes(self):
        self.setText(self.prefix+super().text()+self.suffix)

    def text(self):
        text = super().text()
        if text.startswith(self.prefix):
            text = text[len(self.prefix):]
        if text.endswith(self.suffix):
            text = text[:-len(self.suffix)]

        return text


class FormWidget(QtWidgets.QWidget):
    types = {
        'float': (float, QtGui.QDoubleValidator),
        'int': (int, QtGui.QIntValidator),
        'str': (str, lambda: 0),
    }

    valueChanged = QtCore.pyqtSignal(object)
    stateChanged = QtCore.pyqtSignal(bool)

    def __init__(self, name: str, validator: str = 'float', fixable: bool = False, parent=None):
        super().__init__(parent=parent)

        self._init_ui()

        self._name = name

        self._type = FormWidget.types[validator][0]
        self.vals.setValidator(FormWidget.types[validator][1]())
        self.vals.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0)

        self.label.setText(convert(name))

        self._checkable = fixable
        self.checkBox.setVisible(fixable)
        self.checkBox.stateChanged.connect(lambda x: self.stateChanged.emit(True if x else False))

    def _init_ui(self):
        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(1, 1, 1, 1)
        layout.setSpacing(3)

        self.label = QtWidgets.QLabel(self)
        layout.addWidget(self.label)

        layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum))

        self.vals = QtWidgets.QLineEdit(self)
        layout.addWidget(self.vals)

        self.checkBox = QtWidgets.QCheckBox('Fix?', self)
        layout.addWidget(self.checkBox)

    @property
    def value(self):
        try:
            return self._type(self.vals.text().replace(',', '.'))
        except ValueError:
            return {float: 0.0, int: 0, str: ''}[self._type]

    @value.setter
    def value(self, val):
        if self._type == 'str':
            self.vals.setText(val)
        elif self._type == 'int':
            self.vals.setText(f'{float(val):.0f}')
        else:
            self.vals.setText(f'{float(val):.5g}')

    def setChecked(self, enable):
        if self._checkable:
            self.checkBox.setCheckState(QtCore.Qt.Checked if enable else QtCore.Qt.Unchecked)
        else:
            print(f'Parameter {self._name} is not variable')

    def isChecked(self):
        return self.checkBox.isChecked()


class SelectionWidget(QtWidgets.QWidget):
    selectionChanged = QtCore.pyqtSignal(str, object)

    def __init__(self, label: str, argname: str, opts: dict, parent=None):
        super().__init__(parent=parent)

        self._init_ui()
        self.label.setText(convert(label))
        for k in opts.keys():
            self.comboBox.addItem(k)

        self.argname = argname
        self.options = opts

        self.comboBox.currentIndexChanged.connect(lambda idx: self.selectionChanged.emit(self.argname, self.value))

    def _init_ui(self):
        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(1, 1, 1, 1)
        layout.setSpacing(2)

        self.label = QtWidgets.QLabel(self)
        layout.addWidget(self.label)

        layout.addSpacerItem(QtWidgets.QSpacerItem(65, 20,
                                                   QtWidgets.QSizePolicy.Expanding,
                                                   QtWidgets.QSizePolicy.Minimum))

        self.comboBox = QtWidgets.QComboBox(self)
        layout.addWidget(self.comboBox)
        self.setLayout(layout)

    @property
    def value(self):
        return {self.argname: self.options[self.comboBox.currentText()]}

    def get_parameter(self):
        return str(self.comboBox.currentText())

    @value.setter
    def value(self, val):
        if isinstance(val, dict):
            val = list(val.values())[0]
        key = [k for k, v in self.options.items() if v == val][0]
        self.comboBox.setCurrentIndex(self.comboBox.findText(key))


class Widget(QtWidgets.QWidget, Ui_mean_form):
    valueChanged = QtCore.pyqtSignal()

    def __init__(self, name: str, tree: dict, collapsing: bool = False, parent=None):
        super().__init__(parent=parent)
        self.setupUi(self)

        self._tree = {}

        self._collapse = collapsing

        self.label.setText(convert(name))
        self.lineEdit.setValidator(QtGui.QDoubleValidator())

        self.set_graphs(tree)
        self.change_graph(0)

        if self._collapse:
            self.digit_checkbox.hide()
            self.frame.hide()
            self.data_checkbox.stateChanged.connect(self.collapse_widgets)
        else:
            self.data_checkbox.stateChanged.connect(self.change_mode)
            self.digit_checkbox.stateChanged.connect(self.change_mode)

    def set_graphs(self, graph: dict):
        self.graph_combobox.blockSignals(True)
        self._tree.clear()
        for key, (name, _) in graph.items():
            self.graph_combobox.addItem(name, userData=key)
        self.graph_combobox.blockSignals(False)
        self._tree.update(graph)
        self.change_graph(0)

    @QtCore.pyqtSlot(int, name='on_graph_combobox_currentIndexChanged')
    def change_graph(self, idx: int):
        self.set_combobox.clear()

        key = self.graph_combobox.itemData(idx, QtCore.Qt.UserRole)
        if key is not None:
            for set_key, set_name in self._tree[key][1]:
                self.set_combobox.addItem(set_name, userData=set_key)

    def on_lineEdit_textChanged(self, text=''):
        if text:
            self.valueChanged.emit()

    @property
    def value(self):
        if self.data_checkbox.isChecked():
            return self.set_combobox.currentData()
        else:
            try:
                return float(self.lineEdit.text())
            except ValueError:
                return

    @QtCore.pyqtSlot(int)
    def change_mode(self, state: int):
        box = self.sender()
        other_box = self.data_checkbox if box == self.digit_checkbox else self.digit_checkbox

        if (state == QtCore.Qt.Unchecked) and (other_box.checkState() == QtCore.Qt.Unchecked):
            box.blockSignals(True)
            box.setCheckState(QtCore.Qt.Checked)
            box.blockSignals(False)
            return

        other_box.blockSignals(True)
        other_box.setChecked(False)
        other_box.blockSignals(False)

        self.valueChanged.emit()

    @QtCore.pyqtSlot(int)
    def collapse_widgets(self, state: int):
        data_is_checked = state == QtCore.Qt.Checked
        self.frame.setVisible(data_is_checked)
        self.lineEdit.setVisible(not data_is_checked)


class CheckBoxHeader(QtWidgets.QHeaderView):
    clicked = QtCore.pyqtSignal(int, bool)

    _x_offset = 3
    _y_offset = 0  # This value is calculated later, based on the height of the paint rect
    _width = 20
    _height = 20

    def __init__(self, column_indices, orientation=QtCore.Qt.Horizontal, parent=None):
        super().__init__(orientation, parent)
        self.setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
        # self.setClickable(True)

        if isinstance(column_indices, list) or isinstance(column_indices, tuple):
            self.column_indices = column_indices
        elif isinstance(column_indices, int):
            self.column_indices = [column_indices]
        else:
            raise RuntimeError('column_indices must be a list, tuple or integer')

        self.isChecked = {}
        for column in self.column_indices:
            self.isChecked[column] = 0

    def paintSection(self, painter, rect, logicalIndex):
        painter.save()
        super().paintSection(painter, rect, logicalIndex)
        painter.restore()

        self._y_offset = int((rect.height()-self._width)/2.)

        if logicalIndex in self.column_indices:
            option = QtWidgets.QStyleOptionButton()
            option.rect = QtCore.QRect(rect.x() + self._x_offset, rect.y() + self._y_offset, self._width, self._height)
            option.state = QtWidgets.QStyle.State_Enabled | QtWidgets.QStyle.State_Active
            if self.isChecked[logicalIndex] == 2:
                option.state |= QtWidgets.QStyle.State_NoChange
            elif self.isChecked[logicalIndex]:
                option.state |= QtWidgets.QStyle.State_On
            else:
                option.state |= QtWidgets.QStyle.State_Off

            self.style().drawControl(QtWidgets.QStyle.CE_CheckBox,option,painter)

    def updateCheckState(self, index, state):
        self.isChecked[index] = state
        self.viewport().update()

    def mousePressEvent(self, event):
        index = self.logicalIndexAt(event.pos())
        if 0 <= index < self.count():
            x = self.sectionPosition(index)
            if x + self._x_offset < event.pos().x() < x + self._x_offset + self._width and self._y_offset < event.pos().y() < self._y_offset + self._height:
                if self.isChecked[index] == 1:
                    self.isChecked[index] = 0
                else:
                    self.isChecked[index] = 1

                self.clicked.emit(index, self.isChecked[index])
                self.viewport().update()
            else:
                super().mousePressEvent(event)
        else:
            super().mousePressEvent(event)


class CustomRangeDialog(QtWidgets.QDialog):
    """ Simple dialog to enter x and y limits to fit regions"""
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self.gl = QtWidgets.QGridLayout()
        self.gl.addWidget(QtWidgets.QLabel('Leave empty for complete range'), 0, 0, 1, 4)

        self._limits = [[], []]
        for i, orient in enumerate(['x', 'y'], start=1):
            self.gl.addWidget(QtWidgets.QLabel(orient + ' from'), i, 0)
            self.gl.addWidget(QtWidgets.QLabel('to'), i, 2)
            for j in [0, 1]:
                lim = QtWidgets.QLineEdit()
                lim.setValidator(QtGui.QDoubleValidator())
                self._limits[i-1].append(lim)
                self.gl.addWidget(lim, i, 2*j+1)

        self.buttonbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok)
        self.buttonbox.accepted.connect(self.accept)

        self.gl.addWidget(self.buttonbox, 3, 0, 1, 4)
        self.setLayout(self.gl)

    @property
    def limits(self):
        ret_val = []
        for orient in self._limits:
            for i, w in enumerate(orient):
                val = w.text()
                if val == '':
                    ret_val.append(-inf if i == 0 else inf)
                else:
                    ret_val.append(float(val))
        return ret_val


class ElideComboBox(QtWidgets.QComboBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.view().setTextElideMode(QtCore.Qt.ElideRight)

    def paintEvent(self, evt: QtGui.QPaintEvent):
        opt = QtWidgets.QStyleOptionComboBox()
        self.initStyleOption(opt)

        painter = QtWidgets.QStylePainter(self)
        painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt)

        rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, opt, QtWidgets.QStyle.SC_ComboBoxEditField, self)

        opt.currentText = painter.fontMetrics().elidedText(opt.currentText, QtCore.Qt.ElideRight, rect.width())
        painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt)
