# ******************************************************************************
#
# MOLUSCE
# ---------------------------------------------------------
# Modules for Land Use Change Simulations
#
# Copyright (C) 2012-2013 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at <http://www.gnu.org/licenses/>. You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
# ******************************************************************************

from typing import TYPE_CHECKING, Optional

from qgis.PyQt.QtCore import QSettings, Qt
from qgis.PyQt.QtWidgets import (
    QMessageBox,
    QTableWidgetItem,
    QWidget,
)

from molusce import molusceutils as utils
from molusce.algorithms.models.area_analysis.manager import (
    AreaAnalizerError,
    AreaAnalyst,
)
from molusce.algorithms.models.mce.mce import MCE, MCEError
from molusce.spinboxdelegate import SpinBoxDelegate
from molusce.ui.ui_multicriteriaevaluationwidgetbase import (
    Ui_MultiCriteriaEvaluationWidgetBase,
)

if TYPE_CHECKING:
    from molusce.moluscedialog import MolusceDialog


class MultiCriteriaEvaluationWidget(
    QWidget, Ui_MultiCriteriaEvaluationWidgetBase
):
    """
    Widget for configuring and training the Multi Criteria Evaluation (MCE) model.
    """

    def __init__(
        self, plugin: "MolusceDialog", parent: Optional[QWidget] = None
    ) -> None:
        """
        Initialize the Multi Criteria Evaluation Widget.

        :param plugin: The instance of the MolusceDialog class
                       that provides access to inputs and settings.
        :param parent: The parent widget, defaults to None.
        """
        super().__init__(parent)
        self.setupUi(self)

        self.plugin = plugin
        self.inputs = plugin.inputs

        self.settings = QSettings("NextGIS", "MOLUSCE")

        self.manageGui()

        self.btnTrainModel.clicked.connect(self.trainModel)
        self.tblMatrix.cellChanged.connect(self.__checkValue)

    def manageGui(self):
        if not utils.checkInputRasters(self.inputs):
            QMessageBox.warning(
                self.plugin,
                self.tr("Missed input data"),
                self.tr(
                    "Initial or final raster is not set. Please specify input data and try again"
                ),
            )
            return
        if not utils.checkFactors(self.inputs):
            QMessageBox.warning(
                self.plugin,
                self.tr("Missed input data"),
                self.tr(
                    "Factors rasters is not set. Please specify them and try again"
                ),
            )
            return

        self.spnInitialClass.setValue(
            int(self.settings.value("ui/MCE/initialClass", 0))
        )
        self.spnFinalClass.setValue(
            int(self.settings.value("ui/MCE/finalClass", 0))
        )

        gradations = self.inputs["initial"].getBandGradation(1)
        self.spnInitialClass.setRange(
            int(min(gradations)), int(max(gradations))
        )
        gradations = self.inputs["final"].getBandGradation(1)
        self.spnFinalClass.setRange(int(min(gradations)), int(max(gradations)))

        self.__prepareTable()

    def trainModel(self):
        if not utils.checkFactors(self.inputs):
            QMessageBox.warning(
                self.plugin,
                self.tr("Missed input data"),
                self.tr(
                    "Factors rasters is not set. Please specify them and try again"
                ),
            )
            return

        matrix = self.__checkMatrix()
        if len(matrix) == 0:
            QMessageBox.warning(
                self.plugin,
                self.tr("Incorrect matrix"),
                self.tr("Please fill the matrix with values"),
            )
            return

        self.settings.setValue(
            "ui/MCE/initialClass", self.spnInitialClass.value()
        )
        self.settings.setValue("ui/MCE/finalClass", self.spnFinalClass.value())

        try:
            areaAnalyst = AreaAnalyst(self.inputs["initial"], second=None)
        except AreaAnalizerError as error:
            QMessageBox.warning(
                self,
                self.tr("Invalid input rasters"),
                str(error),
            )
            return

        self.plugin.logMessage(self.tr("Init MCE model"))

        try:
            model = MCE(
                list(self.inputs["factors"].values()),
                matrix,
                self.spnInitialClass.value(),
                self.spnFinalClass.value(),
                areaAnalyst,
            )
        except MCEError as error:
            QMessageBox.warning(
                self,
                self.tr("Model training failed"),
                str(error),
            )
            return

        self.inputs["model"] = model

        self.plugin.logMessage(self.tr("MCE model is trained"))

        weights = model.getWeights()
        for i, w in enumerate(weights):
            item = QTableWidgetItem(str(w))
            self.tblWeights.setItem(0, i, item)
        self.tblWeights.resizeRowsToContents()
        self.tblWeights.resizeColumnsToContents()

        # Check consistency of the matrix
        c = model.getConsistency()
        if c < 0.1:
            QMessageBox.warning(
                self.plugin,
                self.tr("Consistent matrix"),
                self.tr(
                    "Matrix filled correctly. Consistency value is: %f. The model can be used."
                )
                % (c),
            )
        else:
            QMessageBox.warning(
                self.plugin,
                self.tr("Inconsistent matrix"),
                self.tr(
                    "Please adjust matrix before starting simulation. Consistency value is: %f"
                )
                % (c),
            )

    def __prepareTable(self) -> None:
        """
        Prepare and initialize the pairwise comparison and weights tables.

        This method sets up the matrix and weights tables for the multi-criteria
        evaluation, including row and column counts, labels, default values,
        and delegates for editing.

        :returns: None
        """
        band_count = self.inputs["bandCount"]
        self.tblMatrix.clear()
        self.tblMatrix.setRowCount(band_count)
        self.tblMatrix.setColumnCount(band_count)

        self.tblWeights.clear()
        self.tblWeights.setRowCount(1)
        self.tblWeights.setColumnCount(band_count)

        labels = [
            self.tr("{} (band {})").format(
                utils.getLayerById(key).name(), str(band + 1)
            )
            if value.getBandsCount() > 1
            else utils.getLayerById(key).name()
            for key, value in self.inputs["factors"].items()
            for band in range(value.getBandsCount())
        ]

        self.tblMatrix.setVerticalHeaderLabels(labels)
        self.tblMatrix.setHorizontalHeaderLabels(labels)
        self.tblWeights.setHorizontalHeaderLabels(labels)
        self.tblWeights.setVerticalHeaderLabels([self.tr("Weights")])

        self.delegate = SpinBoxDelegate(self.tblMatrix.model())
        for row in range(band_count):
            for column in range(band_count):
                item = QTableWidgetItem()
                if row == column:
                    item.setText("1")
                    item.setFlags(item.flags() ^ Qt.ItemFlag.ItemIsEditable)

                self.tblMatrix.setItem(row, column, item)
            self.tblMatrix.setItemDelegateForRow(row, self.delegate)

        self.tblMatrix.resizeRowsToContents()
        self.tblMatrix.resizeColumnsToContents()

        self.tblWeights.resizeRowsToContents()

    def __checkValue(self, row, col):
        item = self.tblMatrix.item(row, col)
        value = float(item.text())

        self.tblMatrix.blockSignals(True)
        self.tblMatrix.item(col, row).setText(str(1.0 / value))
        self.tblMatrix.blockSignals(False)

        self.tblMatrix.resizeRowsToContents()
        self.tblMatrix.resizeColumnsToContents()
        self.tblWeights.resizeRowsToContents()
        self.tblWeights.resizeColumnsToContents()

    def __checkMatrix(self):
        bandCount = self.inputs["bandCount"]
        matrix = []
        for row in range(bandCount):
            mrow = []
            for col in range(bandCount):
                if self.tblMatrix.item(row, col).text() == "":
                    return []

                mrow.append(float(self.tblMatrix.item(row, col).text()))

            matrix.append(mrow)

        return matrix
