from __future__ import annotations

import re

from nmreval.io.asciireader import AsciiReader
from nmreval.utils import NUMBER_RE, numbers_from_string

from ..Qt import QtGui, QtCore, QtWidgets
from .._py.asciidialog import Ui_ascii_reader


class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
    data_read = QtCore.pyqtSignal(list)
    file_ext = ['.dat', '.txt']

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

        self.reader = None
        self._matches = []
        self.regex_input.setText(NUMBER_RE.pattern)
        self.custom_input.setValidator(QtGui.QDoubleValidator())

        self.comment_textfield = QtWidgets.QPlainTextEdit(self.header_widget)
        self.comment_textfield.setReadOnly(True)
        pal = QtWidgets.QApplication.instance().palette()
        rgb = pal.color(pal.Base).getRgb()[:3]
        rgb2 = pal.color(pal.Text).getRgb()[:3]
        self.comment_textfield.setStyleSheet(f'background-color: rgb{rgb}; color: rgb{rgb2};')
        self.header_widget.setText('Header')
        self.header_widget.addWidget(self.comment_textfield)

        self.ascii_table.horizontalHeader().setStretchLastSection(True)
        self.buttonbox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.apply)
        self.buttonbox.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.accept)

        self.changestaggeredrange(0)

        self.ascii_table.contextMenuEvent = self.ctx_table
        self.ascii_table.horizontalHeader().setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
        self.ascii_table.horizontalHeader().customContextMenuRequested.connect(self.ctx_table)

        self.skip = False

        if fname is not None:
            self.__call__(fname)

    def __call__(self, fname, *args, **kwargs):
        self.reader = AsciiReader(fname)

        self.check_filename(self.regex_input.text())

        if self.skip:
            self.accept()
            return

        for i in [self.stag_lineEdit, self.start_lineedit, self.end_lineedit,
                  self.comment_textfield, self.ascii_table, self.delay_lineedit]:
            i.clear()
        self.staggered_checkBox.setChecked(False)
        self.log_checkBox.setChecked(False)

        self.set_gui()
        self.set_column_names(1)

        self.skippy_checkbox.blockSignals(True)
        self.skippy_checkbox.setCheckState(QtCore.Qt.CheckState.Unchecked)
        self.skippy_checkbox.blockSignals(False)

        return self

    def set_gui(self):
        for text in self.reader.header:
            self.comment_textfield.appendPlainText(text)

        tmp = self.line_spinBox.value()
        if self.reader.header:
            self.line_spinBox.setMaximum(len(self.reader.header))
        else:
            self.line_spinBox.setValue(0)
            self.line_spinBox.setEnabled(False)
        self.show_preview(10)
        self.line_spinBox.setValue(tmp)

        if self.reader.delays is not None:
            set_string = ''.join(str(d) + '\n' for d in self.reader.delays)
            self.delay_textfield.setPlainText(set_string)
            self.delay_lineedit.setText(str(len(self.reader.delays)))

    def ctx_table(self, _):
        menu = QtWidgets.QMenu(self)
        x_action = QtWidgets.QAction('Set as x', self)
        x_action.triggered.connect(lambda: self.set_columns('x'))
        y_action = QtWidgets.QAction('Set as y', self)
        y_action.triggered.connect(lambda: self.set_columns('y'))
        menu.addActions([x_action, y_action])
        if self.deltay_lineEdit.isEnabled():
            yerr_action = QtWidgets.QAction('Set as \u0394y', self)
            yerr_action.triggered.connect(lambda: self.set_columns('yerr'))
            menu.addAction(yerr_action)
        menu.popup(QtGui.QCursor.pos())

    def set_columns(self, mode: str):
        cols = ' '.join([str(s.column()+1) for s in self.ascii_table.selectionModel().selectedColumns()])
        try:
            lineedit = {'x': self.x_lineedit, 'y': self.y_lineedit, 'yerr': self.deltay_lineEdit}[mode]
            lineedit.setText(cols)
        except KeyError:
            pass

    @QtCore.pyqtSlot(int, name='on_preview_spinBox_valueChanged')
    def show_preview(self, line_no: int):
        preview, width, comments = self.reader.make_preview(line_no)
        self.ascii_table.setRowCount(min(line_no, len(preview)))
        self.ascii_table.setColumnCount(width + 1)

        for i, line in enumerate(preview):
            comment_line = comments[i]
            for j, field in enumerate(line):
                it = QtWidgets.QTableWidgetItem(field)
                self.ascii_table.setItem(i, j, it)

            it = QtWidgets.QTableWidgetItem(comment_line)
            self.ascii_table.setItem(i, len(line), it)

        self.ascii_table.resizeColumnsToContents()

    @QtCore.pyqtSlot(int, name='on_column_checkBox_stateChanged')
    @QtCore.pyqtSlot(int, name='on_line_spinBox_valueChanged')
    def set_column_names(self, _):
        self.ascii_table.setHorizontalHeaderLabels(map(str, range(1, self.ascii_table.columnCount() + 1)))
        if self.column_checkBox.isChecked() and self.line_spinBox.isEnabled():
            header_line = self.reader.header[self.line_spinBox.value()-1]
            header_line = header_line.strip('\n\t\r, ')
            header_line = re.sub(r'[\t, ;]+(?!\w*})', ';', header_line)

            self.ascii_table.setHorizontalHeaderLabels(header_line.split(';'))

    @QtCore.pyqtSlot(int, name='on_staggered_checkBox_stateChanged')
    def changestaggeredrange(self, state: int):
        self.stag_lineEdit.setEnabled(state)

    @QtCore.pyqtSlot(name='on_pushButton_clicked')
    def calc_delays(self):
        try:
            start = float(self.start_lineedit.text())
            stop = float(self.end_lineedit.text())
            num_delays = int(self.delay_lineedit.text())
            is_staggered = self.staggered_checkBox.isChecked()
            if is_staggered:
                staggered_range = int(self.stag_lineEdit.text())
            else:
                staggered_range = 0
        except ValueError:
            _ = QtWidgets.QMessageBox.information(self, 'No delays',
                                                  'Delays cannot be calculated: Not enough or wrong arguments.')
            return

        self.reader.calc_delays(start, stop, num_delays, log=self.log_checkBox.isChecked(),
                                stagg=staggered_range, stag_size=staggered_range)

        if self.reader.delays is not None:
            set_string = ''.join(str(d) + '\n' for d in self.reader.delays)
            self.delay_textfield.setPlainText(set_string)

    @QtCore.pyqtSlot(name='on_delay_textfield_textChanged')
    def delays_changed(self):
        new_delays = str(self.delay_textfield.toPlainText()).rstrip('\n').split('\n')
        self.delay_lineedit.setText(str(len(new_delays)))
        if new_delays[0] == '':
            new_delays = None
        else:
            for k, v in enumerate(new_delays):
                try:
                    new_delays[k] = float(v)
                except ValueError:
                    new_delays[k] = -1
        self.reader.delays = new_delays

    @QtCore.pyqtSlot()
    def accept(self):
        if self.apply():
            super().accept()

    def apply(self):
        # default row for x is the first row, it will be superseded if an integer number is given.
        x = self.x_lineedit.text()
        is_valid = True
        if x:
            try:
                x = int(x)-1
            except ValueError:
                pass
        else:
            x = None

        if not self.check_column_numbers(x, max(self.reader.width)):
            _ = QtWidgets.QMessageBox.information(self, 'Improper input',
                                                  f'Input for x axis is invalid')
            return False

        try:
            y = [int(t)-1 for t in self.y_lineedit.text().split(' ')]
        except ValueError:
            y = None

        if not self.check_column_numbers(y, max(self.reader.width)):
            _ = QtWidgets.QMessageBox.information(self, 'Improper input',
                                                  f'Input for y axis is invalid')
            return False

        try:
            y_err = [int(t)-1 for t in self.deltay_lineEdit.text().split(' ')]
        except ValueError:
            y_err = None

        mode = self.buttonGroup.checkedButton().text()
        if mode != 'Points':
            y_err = None

        if not self.check_column_numbers(y, max(self.reader.width)):
            _ = QtWidgets.QMessageBox.information(self, 'Improper input',
                                                  f'Input for y_err axis is invalid')
            return False

        col_header = None
        if self.column_checkBox.isChecked():
            col_header = []
            for i in range(self.ascii_table.columnCount()):
                if self.ascii_table.horizontalHeaderItem(i) is not None:
                    col_header.append(self.ascii_table.horizontalHeaderItem(i).text())
                else:
                    col_header.append(i)

        if y is not None and col_header is not None:
            col_header = [col_header[i] for i in range(len(col_header))]

        try:
            ret_dic = self.reader.export(
                x=x,
                y=y,
                yerr=y_err,
                mode=mode,
                col_names=col_header,
                num_value=self.get_numerical_value(),
            )
            self.data_read.emit(ret_dic)

        except Exception as e:
            _ = QtWidgets.QMessageBox.information(self, 'Reading failed',
                                                  f'Import data failed with\n {e.args[0]}')
            return False

        return True

    @QtCore.pyqtSlot(int, name='on_buttonGroup_buttonClicked')
    def show_error(self, val: int):
        self.deltay_lineEdit.setEnabled(val == -2)

    @QtCore.pyqtSlot(int, name='on_skippy_checkbox_stateChanged')
    def skip_next_dial(self, _: int):
        self.skip = self.skippy_checkbox.isChecked()

    @QtCore.pyqtSlot(str, name='on_regex_input_textChanged')
    def check_filename(self, pattern: str = NUMBER_RE.pattern):
        if self.reader is None:
            return

        success = True
        self._matches = []

        if pattern:
            try:
                re_pattern = re.compile(pattern)
            except re.error:
                success = False
            else:
                self._matches = [m for m in re_pattern.finditer(str(self.reader.fname.stem))]
        else:
            success = False

        # matches exist and have numbers in them
        if self._matches and all([len(numbers_from_string(m.group())) for m in self._matches]):
            self.re_match_index.blockSignals(True)
            self.re_match_index.setMaximum(len(self._matches))
            self.re_match_index.blockSignals(False)
        else:
            success = False

        if success:
            self.regex_input.setStyleSheet('color: rgb(0, 0, 0)')
        else:
            self.regex_input.setStyleSheet('background-color: rgba(255, 0, 0, 50)')

        self.show_match(self.re_match_index.value())

    @QtCore.pyqtSlot(int, name='on_re_match_index_valueChanged')
    def show_match(self, idx: int = 0):
        fname = str(self.reader.fname.stem)

        if self._matches:
            m = self._matches[idx-1]
            self.label_8.setText(f'{fname[:m.start()]}<b>{fname[m.start():m.end()]}</b>{fname[m.end():]}')
        else:
            self.label_8.setText(fname)

    def get_numerical_value(self) -> float:
        val = 0
        if self.re_button.isChecked() and self._matches:
            m = self._matches[self.re_match_index.value()-1]
            val = numbers_from_string(m.group())
            # numbers_from returns list of floats we use first match if available
            val = val[0] if val else 0.0
        elif self.custom_button.isChecked():
            val = float(self.custom_input.text())

        return val

    def check_column_numbers(self, values: int | list[int] | str | None, num_column: int) -> bool:
        is_valid = False
        if values is None:
            is_valid = True
        elif values == 'index':
            is_valid = True

        elif isinstance(values, int):
            is_valid = values < num_column
        elif isinstance(values, list):
            try:
                is_valid = all(v < num_column for v in values)
            except TypeError:
                is_valid = False

        return is_valid

