from typing import List
from pathlib import Path
import PreCourlis

from qgis.PyQt import (
    QtCore,
    QtWidgets,
    uic,
)

from qgis.PyQt.QtCore import Qt

from qgis.PyQt.QtWidgets import (
    QAbstractItemView,
    QDialogButtonBox,
    QMessageBox,
    QItemDelegate,
    QComboBox,
)
from qgis.utils import iface
from qgis.core import (
    QgsVectorLayer,
    QgsMapLayer,
    QgsProject,
    QgsWkbTypes,
    Qgis,
)
from qgis.gui import QgsMessageBar

from PreCourlis.core.interpolation_area import (
    InterpolationAreaManager,
)

from PreCourlis.widgets.interpolation_areas_table_model import (
    InterpolationAreasTableModel,
)

# 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"
    / "interpolation_areas_config_dialog.ui"
)


class InterpolationAreasConfigDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super().__init__(parent, QtCore.Qt.Window)

        # Set up the user interface from Designer through FORM_CLASS.
        # After self.setupUi() you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

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

        # Settup interpolation areas table model
        self._tableModelIsDirty = False
        self._currentLayer = None

        self._interpolationAreasTableModel = InterpolationAreasTableModel(self)
        self._interpolationAreasTableModel.setInterpolationAreas([])
        self._interpolationAreasTableModel.dataChanged.connect(self._modelChanged)

        self._fromProfilIdComboBoxDelegate = ProfilIdComboBoxDelegate(self)
        self._toProfilIdComboBoxDelegate = ProfilIdComboBoxDelegate(self)

        self.interpolationAreasTableView.setModel(self._interpolationAreasTableModel)
        self.interpolationAreasTableView.setSelectionBehavior(
            QAbstractItemView.SelectRows
        )
        self.interpolationAreasTableView.setItemDelegateForColumn(
            0,
            self._fromProfilIdComboBoxDelegate,
        )
        self.interpolationAreasTableView.setItemDelegateForColumn(
            1,
            self._toProfilIdComboBoxDelegate,
        )
        self.interpolationAreasTableView.horizontalHeader().setSectionResizeMode(
            0, QtWidgets.QHeaderView.ResizeToContents
        )
        self.interpolationAreasTableView.horizontalHeader().setSectionResizeMode(
            1, QtWidgets.QHeaderView.ResizeToContents
        )
        self.interpolationAreasTableView.horizontalHeader().setSectionResizeMode(
            2, QtWidgets.QHeaderView.Stretch
        )

        self.initProfilesMapLayerCb()

        # Add/remove row buttons
        self.addRowButton.clicked.connect(self.addRowButtonClicked)
        self.removeRowButton.clicked.connect(self.removeRowButtonClicked)

        self.updateRemoveButtonState()
        self.interpolationAreasTableView.selectionModel().selectionChanged.connect(
            self.updateRemoveButtonState
        )

        # Buttonbox signals connections
        self.buttonsBox.button(QDialogButtonBox.Save).clicked.connect(
            self.saveButtonClicked
        )
        self.buttonsBox.button(QDialogButtonBox.Close).clicked.connect(
            self.closeButtonClicked
        )
        self.buttonsBox.button(QDialogButtonBox.Reset).clicked.connect(
            self.resetButtonClicked
        )

        self._initFromActiveLayer()

    def _initFromActiveLayer(self) -> None:
        # Select active layer if one and not excluded
        excluded_layers = self.profilesMapLayerCb.exceptedLayerList()
        layer = iface.activeLayer()
        if layer is not None and layer not in excluded_layers:
            self.profilesMapLayerCb.setLayer(layer)
            self.profilesLayerChanged(layer)

    def set_layer(self, layer):
        self.profilesMapLayerCb.setLayer(layer)
        self.profilesLayerChanged(layer)

    def _setTableModelIsDirty(self, dirty):
        self._tableModelIsDirty = dirty
        self.buttonsBox.button(QDialogButtonBox.Save).setEnabled(dirty)

    def _modelChanged(self):
        self._setTableModelIsDirty(True)

        self.interpolationAreasTableView.horizontalHeader().setSectionResizeMode(
            0, QtWidgets.QHeaderView.ResizeToContents
        )
        self.interpolationAreasTableView.horizontalHeader().setSectionResizeMode(
            1, QtWidgets.QHeaderView.ResizeToContents
        )

    def reset(self):
        # Reload value from custom property
        self._interpolationAreasTableModel.setInterpolationAreas(
            InterpolationAreaManager.readInterpolationAreasFromLayer(self._currentLayer)
        )

        self._setTableModelIsDirty(False)

    def save(self):
        interpolation_areas = self._interpolationAreasTableModel.interpolationAreas()

        ok, error_message = InterpolationAreaManager.validateInterpolationAreasOfLayer(
            interpolation_areas, self._currentLayer
        )
        if ok is False:
            self.messageBar.pushMessage(
                self.tr("Error"), error_message, level=Qgis.Critical
            )
        else:
            # All good
            InterpolationAreaManager.writeInterpolationAreasOfLayer(
                self._currentLayer,
                interpolation_areas,
            )

            self._setTableModelIsDirty(False)

    def resetButtonClicked(self):
        self.reset()

    def saveButtonClicked(self):
        self.save()

    def closeButtonClicked(self):
        if self._tableModelIsDirty:
            self.showMessageBoxToAskIfUserWantToSaveChanges()

        self.close()

    def addRowButtonClicked(self):
        selection = self.interpolationAreasTableView.selectionModel()
        if selection.hasSelection():
            # Add row after selection index
            self._interpolationAreasTableModel.insertRows(
                selection.currentIndex().row() + 1, 1
            )
            self.interpolationAreasTableView.selectRow(
                selection.currentIndex().row() + 1
            )
        else:
            # Add row at the beginning
            self._interpolationAreasTableModel.insertRows(0, 1)
            self.interpolationAreasTableView.selectRow(0)

        self._setTableModelIsDirty(True)

    def removeRowButtonClicked(self):
        selection = self.interpolationAreasTableView.selectionModel()
        if not selection.hasSelection():
            return

        for index in reversed(sorted(selection.selectedRows())):
            self.interpolationAreasTableView.model().removeRows(index.row(), 1)

        self._setTableModelIsDirty(True)

    def updateRemoveButtonState(self):
        self.removeRowButton.setEnabled(
            self.interpolationAreasTableView.selectionModel().hasSelection()
        )

    def showMessageBoxToAskIfUserWantToSaveChanges(self):
        saveMessageBox = QMessageBox(self)
        saveMessageBox.setWindowTitle("Table data are modified")
        saveMessageBox.setText("Do you want to save the changes ?")
        saveMessageBox.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
        result = saveMessageBox.exec()

        if result == QMessageBox.Yes:
            self.save()

    def initProfilesMapLayerCb(self):
        def accept_layer(layer):
            if layer.type() != QgsMapLayer.VectorLayer:
                return False
            if layer.geometryType() != QgsWkbTypes.LineGeometry:
                return False
            if layer.fields().indexFromName("sec_id") == -1:
                return False
            return True

        excluded_layers = []
        for layer in QgsProject.instance().mapLayers().values():
            if not accept_layer(layer):
                excluded_layers.append(layer)
        self.profilesMapLayerCb.setExceptedLayerList(excluded_layers)

        # profiles layer ComboBox selection
        self.profilesMapLayerCb.layerChanged.connect(self.profilesLayerChanged)

    def profilesLayerChanged(self, layer: QgsVectorLayer):
        if self._currentLayer is not None and self._tableModelIsDirty:
            self.showMessageBoxToAskIfUserWantToSaveChanges()

        self._currentLayer = layer
        self._interpolationAreasTableModel.setLayer(layer)

        if layer is None:
            self._interpolationAreasTableModel.setInterpolationAreas([])
            self._fromProfilIdComboBoxDelegate.setAvailableProfiles([])
            self._toProfilIdComboBoxDelegate.setAvailableProfiles([])

            # disable buttons
            self.addRowButton.setDisabled(True)
            self.buttonsBox.button(QDialogButtonBox.Reset).setDisabled(True)

        else:
            self._interpolationAreasTableModel.setInterpolationAreas(
                InterpolationAreaManager.readInterpolationAreasFromLayer(layer)
            )

            availableProfiles = []
            for feature in layer.getFeatures():
                availableProfiles.append(
                    [
                        feature.attribute("sec_id"),
                        feature.attribute("sec_name"),
                        feature.attribute("abs_long"),
                    ]
                )

            self._fromProfilIdComboBoxDelegate.setAvailableProfiles(availableProfiles)
            self._toProfilIdComboBoxDelegate.setAvailableProfiles(availableProfiles)

            # enable buttons
            self.addRowButton.setEnabled(True)
            self.buttonsBox.button(QDialogButtonBox.Reset).setEnabled(True)

        self._setTableModelIsDirty(False)
        self.updateRemoveButtonState()


class ProfilIdComboBoxDelegate(QItemDelegate):
    """
    A delegate to add QComboBox listing available profiles id,
    in every cell of the given column.
    """

    def __init__(self, parent):
        super(ProfilIdComboBoxDelegate, self).__init__(parent)
        self.parent = parent
        self._availableProfiles = []

    def setAvailableProfiles(self, availableProfiles: List):
        self._availableProfiles = availableProfiles

    def createEditor(self, parent, option, index):
        combobox = QComboBox(parent)

        for profile in self._availableProfiles:
            combobox.addItem(
                f"{profile[0]}: {profile[1]} ({round(profile[2], 3)})",  # displayed value
                profile[0],  # model value
            )

        return combobox

    def setEditorData(self, editor, index):
        data = index.data(Qt.EditRole)
        cbIndex = editor.findData(data)
        if cbIndex >= 0:
            editor.setCurrentIndex(cbIndex)

    def setModelData(self, editor, model, index):
        model.setData(index, editor.currentData(), Qt.EditRole)
