import re

from ..Qt import QtCore, QtWidgets, QtGui
from .._py.ptstab import Ui_Form
from ..lib.pg_objects import LogInfiniteLine, RegionItem

__all__ = ['PointSelectWidget']


REGION_RE = re.compile(r'(?P<first>[+-]*\d+(?:\.\d*)*(?:[eE][+-]*\d+)*)'
                       r'(?: ?- ?(?P<second>[+-]*\d+(?:\.\d*)*(?:[eE][+-]*\d+)*))*')


class PointSelectWidget(QtWidgets.QWidget, Ui_Form):
    widget_closed = QtCore.pyqtSignal()
    points_selected = QtCore.pyqtSignal(dict, str)
    point_removed = QtCore.pyqtSignal(LogInfiniteLine)

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

        self.pts = []
        self.pts_lines = []
        self.nop = 0
        self._prev_pos = ''
        self._last_item = None
        self.connected_figure = ''

        self._avg_modes = ['mean', 'sum', 'integral', 'std']
        self._special_values = ['max', 'absmax', 'min', 'absmin']
        self._group_modes = ['group', 'x']

        self.okButton.clicked.connect(self.apply)
        self.deleteButton.clicked.connect(self.remove_points)

        self.peaktable.itemChanged.connect(self.editing_finished)
        self.peaktable.itemDoubleClicked.connect(self.editing_started)

        self.left_limit.setValidator(QtGui.QDoubleValidator())
        self.right_limit.setValidator(QtGui.QDoubleValidator())

    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key.Key_Delete:
            self.remove_points()
        elif e.key() == QtCore.Qt.Key.Key_F2:
            self.editing_started()
        else:
            super().keyPressEvent(e)

    def clear(self):
        self.pts = []
        self.nop = 0
        self.peaktable.clear()
        self.pts_lines = []

    @QtCore.pyqtSlot(tuple, bool)
    def add(self, pos: tuple, double: bool):
        x = pos[0]
        if double:
            self.removepoint(-1)

            self.pts.append((x, x*1.1))
            item = RegionItem(values=[x, x*1.1], mode='mid')
            item.sigRegionChanged.connect(self._update_region)
        else:
            self.pts.append(x)
            item = LogInfiniteLine(pos=x, movable=True)
            item.sigPositionChanged.connect(self._update_line)

        self.pts_lines.append(item)
        self.nop += 1
        self._makerow()

        return item

    def remove_points(self):
        for i in sorted(self.peaktable.selectedIndexes(), key=lambda x: x.row(), reverse=True):
            self.removepoint(pos=i.row())

    def removepoint(self, pos=0):
        if pos == -1:
            pos = len(self.pts) - 1

        try:
            self.pts.pop(pos)
            self.nop -= 1
            item = self.peaktable.takeItem(pos)
            del item

            del_line = self.pts_lines.pop(pos)
            self.point_removed.emit(del_line)
            del del_line
        except IndexError:
            pass

    def _makerow(self):
        if isinstance(self.pts[-1], tuple):
            item = QtWidgets.QListWidgetItem(f'{self.pts[-1][0]:.5g} - {self.pts[-1][1]:.5g}')
        else:
            item = QtWidgets.QListWidgetItem(f'{self.pts[-1]:.5g}')
        item.setFlags(item.flags() ^ QtCore.Qt.ItemFlag.ItemIsEditable)
        self.peaktable.blockSignals(True)
        self.peaktable.addItem(item)
        self.peaktable.blockSignals(False)

    def closeEvent(self, evt):
        self.widget_closed.emit()
        super().closeEvent(evt)

    @QtCore.pyqtSlot()
    def apply(self) -> dict:
        ret_dic = {
            'avg_range': self.get_limits(),
            'avg_mode': self._avg_modes[self.average_combobox.currentIndex()],
            'special': None,
            'idx': None,
            'xy': (self.xbutton.isChecked(), self.ybutton.isChecked()),
            'groupby': self._group_modes[self.group_box.currentIndex()],
        }

        if self.special_checkbox.isChecked():
            ret_dic['special'] = self._special_values[self.special_comboBox.currentIndex()]

        if len(self.pts) != 0:
            ret_dic['idx'] = self.pts

        gid = self.graph_combobox.currentData() if not self.graph_checkbox.isChecked() else ''

        self.points_selected.emit(ret_dic, gid)

        return ret_dic

    def _update_region(self):
        try:
            idx = self.pts_lines.index(self.sender())
        except ValueError:
            return

        self.pts[idx] = self.sender().getRegion()
        self.peaktable.blockSignals(True)
        self.peaktable.item(idx).setText('{:.5g} - {:.5g}'.format(*self.pts[idx]))
        self.peaktable.blockSignals(False)

    def _update_line(self):
        try:
            idx = self.pts_lines.index(self.sender())
        except ValueError:
            return

        self.pts[idx] = self.sender().value()
        self.peaktable.blockSignals(True)
        self.peaktable.item(idx).setText(f'{self.pts[idx]:.5g}')
        self.peaktable.blockSignals(False)

    @QtCore.pyqtSlot(QtWidgets.QListWidgetItem)
    def editing_started(self, item=None):
        if item is None:
            item = self.peaktable.selectedItems()[0]
        self._prev_pos = item.text()
        self.peaktable.editItem(item)

    @QtCore.pyqtSlot(QtWidgets.QListWidgetItem)
    def editing_finished(self, it: QtWidgets.QListWidgetItem):
        m = re.match(REGION_RE, it.text()).groupdict()
        undo = True
        if m:
            start, stop = m['first'], m['second']
            row = self.peaktable.row(it)
            it_pts = self.pts_lines[row]
            if ((stop is None) and isinstance(it_pts, RegionItem)) or \
                    ((stop is not None) and isinstance(it_pts, LogInfiniteLine)):
                QtWidgets.QMessageBox().information(self, 'Invalid type',
                                                    'Conversion between point and region is not possible.')
            else:
                if stop is None:
                    it_pts.blockSignals(True)
                    it_pts.setValue(float(start))
                    it_pts.blockSignals(False)
                    self.pts[row] = float(start)
                else:
                    start, stop = float(start), float(stop)
                    pos = (min(start, stop), max(start, stop))
                    self.pts[row] = pos
                    self.peaktable.blockSignals(True)
                    it.setText(f'{pos[0]:.5g} - {pos[1]:.5g}')
                    self.peaktable.blockSignals(False)
                    it_pts.blockSignals(True)
                    it_pts.setRegion(pos, use_log=True)
                    it_pts.blockSignals(False)
                undo = False

        if undo:
            self.peaktable.blockSignals(True)
            it.setText(self._prev_pos)
            self.peaktable.blockSignals(False)

    def set_graphs(self, graphs: list):
        last_graph = self.graph_combobox.currentData()
        self.graph_combobox.clear()
        idx = 0
        for i, g in enumerate(graphs):
            self.graph_combobox.addItem(g[1], userData=g[0])
            if g[0] == last_graph:
                idx = i

        self.graph_combobox.setCurrentIndex(idx)

    @QtCore.pyqtSlot(int, name='on_graph_checkbox_stateChanged')
    def changed_state(self, checked):
        self.graph_combobox.setEnabled(checked != QtCore.Qt.CheckState.Checked)

    @QtCore.pyqtSlot(int, name='on_special_checkbox_stateChanged')
    def changed_special(self, checked: int):
        self.graph_combobox.setEnabled(checked != QtCore.Qt.CheckState.Checked)

    def get_limits(self) -> tuple[float, float, str]:
        try:
            left = float(self.left_limit.text())
        except ValueError:
            left = 0.

        try:
            right = float(self.right_limit.text())
        except ValueError:
            right = 0.

        return left, right, self.limit_combobox.currentText()

