# Window.py -- Pamhyr
# Copyright (C) 2023-2025  INRAE
#
# This program 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 3 of the License, or
# (at your option) any later version.
#
# This program 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.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

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

import os
import logging

from tools import trace, timer, logger_exception

from View.Tools.PamhyrWindow import PamhyrWindow

from PyQt5.QtGui import (
    QKeySequence, QIcon,
)

from PyQt5.QtCore import (
    Qt, QVariant, QAbstractTableModel,
    QCoreApplication, QModelIndex, pyqtSlot,
    QRect, QItemSelectionModel,
)

from PyQt5.QtWidgets import (
    QDialogButtonBox, QPushButton, QLineEdit,
    QFileDialog, QTableView, QAbstractItemView,
    QUndoStack, QShortcut, QAction, QItemDelegate,
    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
    QVBoxLayout, QToolBar, QAction, QToolButton, QLabel,
)

from Modules import Modules

from View.WeatherParameters.UndoCommand import (
    SetCommand,
)

from View.WeatherParameters.TableDefault import (
    WeatherParametersTableDefaultModel,
)

from View.WeatherParameters.Table import (
    WeatherParametersTableModel, ComboBoxDelegate,
)

from View.WeatherParameters.translate import WeatherParametersTranslate

from View.WeatherParameters.Edit.Window import EditWeatherParametersWindow

from Solver.Mage import Mage8

_translate = QCoreApplication.translate

logger = logging.getLogger()


class WeatherParametersWindow(PamhyrWindow):
    _pamhyr_ui = "WeatherParameters"
    _pamhyr_name = "Weather parameters"

    def __init__(self, data=None, study=None,
                 config=None, parent=None):
        self._data = data
        trad = WeatherParametersTranslate()

        self.weather_params = [trad.get_dict("weather_parameters")[k] for
                               k in trad.get_dict("weather_parameters").keys()]
        name = (
            trad[self._pamhyr_name] +
            " - " + study.name
        )

        super(WeatherParametersWindow, self).__init__(
            title=name,
            study=study,
            config=config,
            trad=trad,
            parent=parent
        )

        self._hash_data.append(data)

        self._wp_acronyms = [
            "AT", "SH", "GR", "RWS", "GT", "GFR", "ALB", "SC", "CCF"
        ]
        self.types_with_data = [
            "AT", "SH", "GR", "RWS", "GT", "GFR"
        ]
        self.setup_table()
        self.setup_connections()

    def setup_connections(self):
        path_icons = os.path.join(self._get_ui_directory(), f"ressources")

        layout = self.find(QVBoxLayout, f"verticalLayout_1")
        toolBar = QToolBar()
        layout.addWidget(toolBar)

        # Add buttons to the toolbar
        self.action_add = QAction(self)
        self.action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))

        if self._study.is_editable():
            self.action_add.triggered.connect(self.add)
        self.action_delete = QAction(self)
        self.action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))

        if self._study.is_editable():
            self.action_delete.triggered.connect(self.delete)

        self.action_edit = QAction(self)
        self.action_edit.setIcon(QIcon(os.path.join(path_icons, f"edit.png")))
        self.action_edit.triggered.connect(self.edit)

        toolBar.addAction(self.action_add)
        toolBar.addAction(self.action_delete)
        toolBar.addAction(self.action_edit)

        layout.addWidget(self.table_spec_title)
        layout.addWidget(self.table_spec)

    def setup_table(self):
        self.table_default = self.find(QTableView, f"tableView")

        if self._study.is_editable():
            editable_headers = ["value"]
        else:
            editable_headers = []

        self._table = WeatherParametersTableDefaultModel(
            table_view=self.table_default,
            table_headers=self._trad.get_dict("table_headers"),
            editable_headers=editable_headers,
            delegates={},
            data=self._study.river.weather_parameters.default,
            opt_data=self.weather_params,
            undo=self._undo_stack,
            trad=self._trad
        )

        self.table_default.setModel(self._table)
        self.table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table_default.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_default.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch
        )
        self.table_default.setAlternatingRowColors(True)
        self.table_default.selectionModel().selectionChanged.connect(
            self.on_table_default_selection_changed
        )

        self.table_spec_title = QLabel()
        self.table_spec = QTableView()

        self._delegate_reach = ComboBoxDelegate(
            trad=self._trad,
            data=self._study.river,
            weather_param_lst=None,
            parent=self,
            mode="reaches"
        )
        self._delegate_rk = ComboBoxDelegate(
            trad=self._trad,
            data=self._study.river,
            weather_param_lst=self._study.river.weather_parameters,
            parent=self,
            mode="rk"
        )

        if self._study.is_editable():
            editable_headers_spec = self._trad.get_dict("table_headers_spec")
        else:
            editable_headers_spec = []

        self._table_spec = WeatherParametersTableModel(
            table_view=self.table_spec,
            table_headers=self._trad.get_dict("table_headers_spec"),
            editable_headers=editable_headers_spec,
            delegates={
                "reach": self._delegate_reach,
                "start_rk": self._delegate_rk,
                "end_rk": self._delegate_rk
            },
            data=self._study.river.weather_parameters,
            undo=self._undo_stack,
            trad=self._trad,
            river=self._study.river
        )

        self.table_spec.setModel(self._table_spec)
        self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table_spec.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch
        )
        self.table_spec.setAlternatingRowColors(True)

        selectionModel = self.table_spec.selectionModel()
        index = self.table_spec.model().index(0, 0)

        selectionModel.select(
            index,
            QItemSelectionModel.Rows |
            QItemSelectionModel.ClearAndSelect |
            QItemSelectionModel.Select
        )
        self.table_spec.scrollTo(index)

        self._selected_default_row_index = 0
        self.select_table_default_row(0)

    def select_table_default_row(self, row):
        model = self.table_default.model()
        if model is None or model.rowCount() == 0:
            return

        row = max(0, min(row, model.rowCount() - 1))
        index = model.index(row, 0)

        self.table_default.setCurrentIndex(index)
        self.table_default.selectionModel().select(
            index,
            QItemSelectionModel.Rows |
            QItemSelectionModel.ClearAndSelect |
            QItemSelectionModel.Select
        )
        self.table_default.scrollTo(index)

    def on_table_default_selection_changed(self, selected, deselected):
        rows = self.table_default.selectionModel().selectedRows()
        if len(rows) == 0:
            self.select_table_default_row(self._selected_default_row_index)
            return self._selected_default_row_index

        self._selected_default_row_index = rows[0].row()
        type_selected = self._wp_acronyms[self._selected_default_row_index]
        tab_enabled = type_selected in self.types_with_data
        self.enable_actions(tab_enabled)

        self.update_table_spec_title(enabled=tab_enabled)

        self._table_spec.update_tab_spec(
            type=type_selected, enabled=tab_enabled
        )

        return self._selected_default_row_index

    def enable_actions(self, enabled):
        if hasattr(self, "action_add"):
            self.action_add.setEnabled(enabled)
        if hasattr(self, "action_delete"):
            self.action_delete.setEnabled(enabled)
        if hasattr(self, "action_edit"):
            self.action_edit.setEnabled(enabled)

    def update_table_spec_title(self, enabled=True):
        if enabled:
            row = self._selected_default_row_index
            if 0 <= row < len(self.weather_params):
                self.table_spec_title.setText(self.weather_params[row])
        else:
            self.table_spec_title.setText("")

    def index_selected_row(self):
        table = self.table_spec
        rows = table.selectionModel()\
                    .selectedRows()

        if len(rows) == 0:
            return 0

        return rows[0].row()

    def index_selected_rows(self):
        table = self.table_default
        return list(
            # Delete duplicate
            set(
                map(
                    lambda i: i.row(),
                    table.selectedIndexes()
                )
            )
        )

    def index_selected_rows_spec(self):
        table = self.table_spec
        return list(
            # Delete duplicate
            set(
                map(
                    lambda i: i.row(),
                    table.selectedIndexes()
                )
            )
        )

    def move_up(self):
        row = self.index_selected_row()
        self._table.move_up(row)
        self._update()

    def move_down(self):
        row = self.index_selected_row()
        self._table.move_down(row)
        self._update()

    def _copy(self):
        rows = list(
            map(
                lambda row: row.row(),
                self.tableView.selectionModel().selectedRows()
            )
        )

        table = list(
            map(
                lambda eic: list(
                    map(
                        lambda k: eic[1][k],
                        ["rk", "discharge", "elevation"]
                    )
                ),
                filter(
                    lambda eic: eic[0] in rows,
                    enumerate(self._ics.lst())
                )
            )
        )

        self.copyTableIntoClipboard(table)

    def _paste(self):
        header, data = self.parseClipboardTable()

        if len(data) + len(header) == 0:
            return

        logger.debug(
            "IC: Paste: " +
            f"header = {header}, " +
            f"data = {data}"
        )

        try:
            row = self.index_selected_row()
            self._table.paste(row, [], data)
        except Exception as e:
            logger_exception(e)

        self._update()

    def _undo(self):
        undo_stack = self._undo_stack
        if undo_stack is None or not undo_stack.canUndo():
            return

        if isinstance(undo_stack.command(undo_stack.index() - 1), SetCommand):
            self._table.undo()
        else:
            self._table_spec.undo()

        self._update()

    def _redo(self):
        undo_stack = self._undo_stack
        if undo_stack is None or not undo_stack.canRedo():
            return

        if isinstance(undo_stack.command(undo_stack.index()), SetCommand):
            self._table.redo()
        else:
            self._table_spec.redo()

        self._update()

    def add(self):
        rows = self.index_selected_rows()
        if rows is not None and len(rows) > 0:
            if len(self._data.lst) == 0 or len(rows) == 0:
                self._table_spec.add(0)
            else:
                self._table_spec.add(rows[0])

    def delete(self):
        rows = self.index_selected_rows_spec()
        if len(rows) == 0:
            return
        self._table_spec.delete(rows)

    def edit(self):
        rows = self.index_selected_rows_spec()
        data_rows = {
            id(wp): i for i, wp in enumerate(self._data.lst)
        }
        rows = [data_rows[id(self._table_spec._lst[r])]
                for r in rows
                if 0 <= r < len(self._table_spec._lst)]
        for row in rows:
            data = self._data.get(row)

            if self.sub_window_exists(
                EditWeatherParametersWindow,
                data=[self._study, None, data]
            ):
                continue

            win = EditWeatherParametersWindow(
                data=data,
                study=self._study,
                parent=self
            )
            win.show()
