# -*- coding: utf-8 -*-

from functools import partial
from pathlib import Path
import PreCourlis

import numpy as np
from qgis.core import Qgis, QgsApplication
from qgis.gui import QgsMessageBar, QgsDoubleSpinBox
from qgis.PyQt import uic, QtWidgets

from PreCourlis.lib.mascaret.mascaret_file import Section

# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(
    Path(PreCourlis.__file__).parent.resolve() / "ui" / "new_point_dialog.ui"
)


class NewPointDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, section: Section, index: int, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        self.message_bar = QgsMessageBar(self)
        self.layout().insertWidget(0, self.message_bar)

        self.section = section
        self.index = index

        self.layers_labels = []
        self.layers_spinboxes = []
        self.layers_refresh_buttons = []
        self.layers_info_labels = []

        self.idSpinBox.setEnabled(False)
        self.idSpinBox.setMinimum(0)
        self.idSpinBox.setMaximum(10000)

        self.absLatSpinBox.valueChanged.connect(self.abs_lat_changed)

        self.topoBathComboBox.addItem("B", "B")
        self.topoBathComboBox.addItem("T", "T")

        self.zSpinBox.setDecimals(3)

        self.idRefreshButton.setIcon(QgsApplication.getThemeIcon("/mActionRefresh.svg"))
        self.absLatRefreshButton.setIcon(
            QgsApplication.getThemeIcon("/mActionRefresh.svg")
        )
        self.absLatDispatchButton.setIcon(
            # QgsApplication.getThemeIcon("/mActionArrowDown.svg")
            QgsApplication.getThemeIcon("/mActionTiltDown.svg")
        )
        self.xRefreshButton.setIcon(QgsApplication.getThemeIcon("/mActionRefresh.svg"))
        self.yRefreshButton.setIcon(QgsApplication.getThemeIcon("/mActionRefresh.svg"))
        self.topoBathRefreshButton.setIcon(
            QgsApplication.getThemeIcon("/mActionRefresh.svg")
        )
        self.zRefreshButton.setIcon(QgsApplication.getThemeIcon("/mActionRefresh.svg"))

        self.absLatRefreshButton.clicked.connect(self.refresh_abs_lat)
        self.absLatDispatchButton.clicked.connect(self.dispatch_abs_lat)
        self.xRefreshButton.clicked.connect(self.refresh_x)
        self.yRefreshButton.clicked.connect(self.refresh_y)
        self.topoBathRefreshButton.clicked.connect(self.refresh_topo_bath)
        self.zRefreshButton.clicked.connect(self.refresh_z)

        self.create_layers_widgets()
        self.update_infos()
        self.refresh_all()

    def create_layer_widgets(self, i, layer_name):
        label = QtWidgets.QLabel(layer_name, self)
        spinbox = QgsDoubleSpinBox(self)
        spinbox.setDecimals(3)
        refresh_button = QtWidgets.QToolButton(self)
        refresh_button.setIcon(QgsApplication.getThemeIcon("/mActionRefresh.svg"))
        refresh_button.clicked.connect(partial(self.refresh_layer, i))
        info_label = QtWidgets.QLabel(self)

        current_row = self.gridLayout.rowCount()
        self.gridLayout.addWidget(label, current_row, 0)
        self.gridLayout.addWidget(spinbox, current_row, 1)
        self.gridLayout.addWidget(refresh_button, current_row, 2)
        self.gridLayout.addWidget(info_label, current_row, 4)

        self.layers_labels.append(label)
        self.layers_spinboxes.append(spinbox)
        self.layers_refresh_buttons.append(refresh_button)
        self.layers_info_labels.append(info_label)

    def create_layers_widgets(self):
        for i, layer_name in enumerate(self.section.layer_names):
            self.create_layer_widgets(i, layer_name)

    def update_infos(self):
        section = self.section
        index = self.index
        next_point = self.has_next_point()

        self.idInfoLabel.setText(
            f"{index + 1}" " - " f"{index + 2 if next_point else ''}"
        )
        self.idSpinBox.setValue(self.index + 2)

        self.absLatInfoLabel.setText(
            f"{round(section.distances[index], 2)}"
            " - "
            f"{round(section.distances[index + 1], 2) if next_point else ''}"
        )
        self.absLatSpinBox.setMinimum(section.distances[index])
        if self.has_next_point():
            self.absLatSpinBox.setMaximum(section.distances[index + 1])
        else:
            self.absLatSpinBox.setMaximum(10000000)

        self.xInfoLabel.setText(
            f"{round(section.x[index], 2)}"
            " - "
            f"{round(section.x[index + 1], 2) if next_point else ''}"
        )
        if self.has_next_point():
            self.xSpinBox.setMinimum(min(section.x[index], section.x[index + 1]))
            self.xSpinBox.setMaximum(max(section.x[index], section.x[index + 1]))
        else:
            self.xSpinBox.setMinimum(0)
            self.xSpinBox.setMaximum(10000000)

        self.yInfoLabel.setText(
            f"{round(section.y[index], 2)}"
            " - "
            f"{round(section.y[index + 1], 2) if next_point else ''}"
        )
        if self.has_next_point():
            self.ySpinBox.setMinimum(min(section.y[index], section.y[index + 1]))
            self.ySpinBox.setMaximum(max(section.y[index], section.y[index + 1]))
        else:
            self.ySpinBox.setMinimum(0)
            self.ySpinBox.setMaximum(10000000)

        self.topoBathInfoLabel.setText(
            f"{section.topo_bath[index]}"
            " - "
            f"{section.topo_bath[index + 1] if next_point else ''}"
        )

        self.zInfoLabel.setText(
            f"{round(section.z[index], 3)}"
            " - "
            f"{round(section.z[index + 1], 3) if next_point else ''}"
        )
        self.zSpinBox.setMinimum(0)
        self.zSpinBox.setMaximum(10000000)

        for i, layer_elev in enumerate(section.layers_elev):
            self.layers_info_labels[i].setText(
                f"{round(section.layers_elev[i][index], 3)}"
                " - "
                f"{round(section.layers_elev[i][index + 1], 3) if next_point else ''}"
            )
            self.layers_spinboxes[i].setMinimum(0)
            self.layers_spinboxes[i].setMaximum(10000000)

    def has_next_point(self):
        return len(self.section.distances) - 1 > self.index

    def abs_lat(self):
        return self.absLatSpinBox.value()

    def set_abs_lat(self, value):
        self.absLatSpinBox.setValue(value)

    def refresh_abs_lat(self):
        section = self.section
        index = self.index

        self.absLatSpinBox.setMinimum(section.distances[index])
        if self.has_next_point():
            self.set_abs_lat(
                (section.distances[index] + section.distances[index + 1]) / 2
            )
        else:
            self.set_abs_lat(section.distances[index])

    def dispatch_abs_lat(self):
        self.refresh_x()
        self.refresh_y()
        self.refresh_topo_bath()
        self.refresh_z()
        self.refresh_layers()

    def abs_lat_changed(self):
        self.set_x_dirty(True)
        self.set_y_dirty(True)
        self.set_topo_bath_dirty(True)
        self.set_z_dirty(True)
        for i in range(len(self.layers_refresh_buttons)):
            self.set_layer_dirty(i, True)

    def evaluate(self, distances, array, abs_lat):
        # if abs_lat > distances[-1]:
        if self.index == len(distances) - 1:
            # Extrapolation on the right
            fit = np.polyfit(
                distances[-2:],
                array[-2:],
                1,
            )
            f = np.poly1d(fit)
            return f(abs_lat)
        else:
            # interpolation
            return np.interp(
                abs_lat,
                distances,
                array,
                1,
            )

    def x(self):
        return self.xSpinBox.value()

    def set_x(self, value):
        self.xSpinBox.setValue(value)

    def refresh_x(self):
        x = self.evaluate(self.section.distances, self.section.x, self.abs_lat())
        if x is not None:
            self.set_x(x)
        self.set_x_dirty(False)

    def set_x_dirty(self, value):
        self.xRefreshButton.setStyleSheet("background-color: yellow;" if value else "")

    def y(self):
        return self.ySpinBox.value()

    def set_y(self, value):
        self.ySpinBox.setValue(value)

    def refresh_y(self):
        y = self.evaluate(self.section.distances, self.section.y, self.abs_lat())
        if y is not None:
            self.set_y(y)
        self.set_y_dirty(False)

    def set_y_dirty(self, value):
        self.yRefreshButton.setStyleSheet("background-color: yellow;" if value else "")

    def topo_bath(self):
        if self.topoBathComboBox.currentIndex() == -1:
            return None
        return self.topoBathComboBox.currentText()

    def set_topo_bath(self, value):
        if value is None:
            self.topoBathComboBox.setCurrentIndex(-1)
        self.topoBathComboBox.setCurrentText(value)

    def refresh_topo_bath(self):
        section = self.section
        index = self.index

        left = section.topo_bath[index]
        if self.has_next_point():
            right = section.topo_bath[index + 1]
            if left == right:
                self.set_topo_bath(left)
            else:
                self.set_topo_bath(None)
        elif left == "T":
            self.set_topo_bath("T")
        else:
            self.set_topo_bath(None)

        self.set_topo_bath_dirty(False)

    def set_topo_bath_dirty(self, value):
        self.topoBathRefreshButton.setStyleSheet(
            "background-color: yellow;" if value else ""
        )

    def z(self):
        return self.zSpinBox.value()

    def set_z(self, value):
        self.zSpinBox.setValue(value)

    def refresh_z(self):
        z = self.evaluate(self.section.distances, self.section.z, self.abs_lat())
        if z is not None:
            self.set_z(z)
        self.set_z_dirty(False)

    def set_z_dirty(self, value):
        self.zRefreshButton.setStyleSheet("background-color: yellow;" if value else "")

    def layer_value(self, i):
        return self.layers_spinboxes[i].value()

    def set_layer_value(self, i, value):
        self.layers_spinboxes[i].setValue(value)

    def refresh_layer(self, i):
        value = self.evaluate(
            self.section.distances, self.section.layers_elev[i], self.abs_lat()
        )
        if value is not None:
            self.set_layer_value(i, value)
        self.set_layer_dirty(i, False)

    def refresh_layers(self):
        for i in range(len(self.section.layers_elev)):
            self.refresh_layer(i)

    def set_layer_dirty(self, i, value):
        self.layers_refresh_buttons[i].setStyleSheet(
            "background-color: yellow;" if value else ""
        )

    def refresh_all(self):
        self.refresh_abs_lat()
        self.refresh_x()
        self.refresh_y()
        self.refresh_topo_bath()
        self.refresh_z()
        self.refresh_layers()

    def validate(self):
        if self.topo_bath() is None:
            return False, self.tr("Field topo_bat is required")
        return True, ""

    def accept(self, *args, **kwargs):
        ok, msg = self.validate()
        if not ok:
            self.message_bar.pushMessage(
                self.tr("Validation error"), msg, level=Qgis.Critical
            )
            return

        section = self.section
        index = self.index

        section.distances = np.insert(section.distances, index + 1, self.abs_lat())
        section.x = np.insert(section.x, index + 1, self.x())
        section.y = np.insert(section.y, index + 1, self.y())
        section.topo_bath.insert(index + 1, self.topo_bath())
        section.z = np.insert(section.z, index + 1, self.z())
        if section.layers_elev is not None and len(section.layers_elev) > 0:
            section.layers_elev = np.vstack(
                [
                    np.insert(layer_elev, index + 1, self.layer_value(i))
                    for i, layer_elev in enumerate(section.layers_elev)
                ]
            )

        super().accept(*args, **kwargs)
