# Table.py -- Pamhyr
# Copyright (C) 2023-2026  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 numpy as np
import logging
import traceback

from tools import timer, trace

from PyQt5.QtGui import (
    QFont, QColor
)
from PyQt5.QtWidgets import (
    QMessageBox, QStyledItemDelegate, QLineEdit
)
from PyQt5.QtCore import (
    QModelIndex, Qt, QVariant, QCoreApplication
)

from View.Tools.PamhyrTable import PamhyrTableModel

from Model.Geometry.PointXYZ import PointXYZ
from Model.Geometry.ProfileXYZ import ProfileXYZ

from View.Geometry.Profile.UndoCommand import *

logger = logging.getLogger()

_translate = QCoreApplication.translate


class GeometryProfileTableModel(PamhyrTableModel):
    def get_true_data_row(self, row):
        profile = self._data.point(row)

        return next(
            map(
                lambda e: e[0],
                filter(
                    lambda e: e[1] == profile,
                    enumerate(self._data._points)
                )
            ), 0
        )

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.DisplayRole:
                value = ""

                if index.column() == 0:
                    value = self._data.point(index.row()).x
                elif index.column() == 1:
                    value = self._data.point(index.row()).y
                elif index.column() == 2:
                    value = self._data.point(index.row()).z
                elif index.column() == 3:
                    value = self._data.point(index.row()).name
                elif index.column() == 4:
                    station = self._data.get_station()
                    if station is None:
                        return "-"
                    else:
                        value = station[index.row()]
                        return f"{value:.3f}"

                if 0 <= index.column() < 3:
                    return f"{value:.4f}"

                return f"{value}"

            if role == Qt.TextAlignmentRole:
                return Qt.AlignHCenter | Qt.AlignVCenter

            if index.column() == 2:
                value = self._data.point(index.row()).z
                if role == Qt.ForegroundRole:
                    if value == self._data.z_min():
                        return QColor("red")
                    elif value == self._data.z_max():
                        return QColor("blue")

            if index.column() == 3:
                value = self._data.point(index.row()).name

                if value.strip().upper() in ["RG", "RD"]:
                    if role == Qt.FontRole:
                        font = QFont()
                        font.setBold(True)
                        return font

                    if role == Qt.ForegroundRole:
                        return QColor("darkRed")

        return QVariant()

    def setData(self, index, value, role=Qt.EditRole):
        row = index.row()
        column = index.column()

        if role == Qt.EditRole:
            if self.is_same_data(index, value):
                return False

            try:
                if column == 0:
                    self._undo.push(
                        SetXCommand(
                            self._data, row,
                            self._data.point(row).x,
                            value.replace(",", ".")
                        )
                    )
                elif column == 1:
                    self._undo.push(
                        SetYCommand(
                            self._data, row,
                            self._data.point(row).y,
                            value.replace(",", ".")
                        )
                    )
                elif column == 2:
                    self._undo.push(
                        SetZCommand(
                            self._data, row,
                            self._data.point(row).z,
                            value.replace(",", ".")
                        )
                    )
                elif column == 3:
                    self._undo.push(
                        SetNameCommand(
                            self._data, row,
                            self._data.point(row).name,
                            value
                        )
                    )
            except Exception as e:
                logger.info(e)
                logger.debug(traceback.format_exc())

            self.dataChanged.emit(index, index)
            return True

        self.dataChanged.emit(index, index)
        self.layoutChanged.emit()
        return False

    def index(self, row, column, parent=QModelIndex()):
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        return self.createIndex(row, column, QModelIndex())

    def flags(self, index):
        flg = Qt.ItemIsSelectable

        if index.column() == 4:
            return flg

        return Qt.ItemIsEditable | Qt.ItemIsEnabled | flg

    def insert_row(self, row, parent=QModelIndex()):
        self.beginInsertRows(parent, row, row - 1)

        row = self.get_true_data_row(row)

        self._undo.push(
            AddCommand(
                self._data, row
            )
        )

        self.endInsertRows()
        self.layoutChanged.emit()

    def remove_rows(self, rows, parent=QModelIndex()):
        self.beginRemoveRows(parent, rows[0], rows[-1])

        self._undo.push(
            DelCommand(
                self._data, list(
                    map(
                        lambda r: self._data.point(r),
                        rows
                    )
                )
            )
        )

        self.endRemoveRows()
        self.layoutChanged.emit()

    def sort(self, column='x', order=Qt.AscendingOrder):
        self.layoutAboutToBeChanged.emit()

        reverse = (order != Qt.AscendingOrder)

        self._undo.push(
            SortCommand(
                self._data, column, reverse
            )
        )

        self.layoutChanged.emit()

    def move_up(self, row, parent=QModelIndex()):
        if row <= 0:
            return

        target = row + 1
        self.beginMoveRows(parent, row - 1, row - 1, parent, target)

        row = self.get_true_data_row(row)
        self._undo.push(
            MoveCommand(
                self._data, "up", row
            )
        )

        self.endMoveRows()
        self.layoutChanged.emit()

    def move_down(self, row, parent=QModelIndex()):
        if row > self._data.number_points:
            return

        target = row
        self.beginMoveRows(parent, row + 1, row + 1, parent, target)

        row = self.get_true_data_row(row)
        self._undo.push(
            MoveCommand(
                self._data, "down", row
            )
        )

        self.endMoveRows()
        self.layoutChanged.emit()

    def purge(self, np_purge):

        self._undo.push(
            PurgeCommand(
                self._data, np_purge
            )
        )

        self.layoutChanged.emit()

    def reverse(self):
        self._undo.push(
            ReverseCommand(
                self._data
            )
        )

        self.layoutChanged.emit()

    def paste(self, row, header, data):
        if row > self._data.number_points:
            return

        if len(data) == 0:
            return

        self.layoutAboutToBeChanged.emit()

        points = list(
            map(
                lambda d: self._data.point_from_data(header, d),
                data
            )
        )

        row = self.get_true_data_row(row)

        self._undo.push(
            PasteCommand(
                self._data, row, points
            )
        )

        self.layoutAboutToBeChanged.emit()
        self.layoutChanged.emit()

    def undo(self):
        self._undo.undo()
        self.layoutChanged.emit()

    def redo(self):
        self._undo.redo()
        self.layoutChanged.emit()
