from typing import Any, Optional, cast, List, Tuple

from PySide2.QtCore import QAbstractTableModel, Qt, QModelIndex, Signal
from axipy import FloatCoord
from .constants import Col, FLOAT_FORMAT
from .utils import try_except_silent, Pnt

ROWS_ON_START = 3
COLS = 5


class Model(QAbstractTableModel):
    signal_row_count_changed = Signal()

    def __init__(self, parent=None) -> None:
        super().__init__(parent)

        self._rows = ROWS_ON_START

        # grid[row][col]
        self.__grid: List[List[Optional[float]]] = [
            [None for _x in range(COLS)] for _y in range(self._rows)
        ]

        self._table_source = None
        self._table_target = None

    def flags(self, index: QModelIndex) -> Qt.ItemFlags:
        if index.column() == Col.Error:
            return super().flags(index)

        return Qt.ItemIsEditable ^ super().flags(index)

    def rowCount(self, parent: QModelIndex = None) -> int:
        if parent is None:
            parent = QModelIndex()

        if parent.isValid():
            return 0

        return self._rows

    def insertRows(self, row: int, count: int, parent: QModelIndex = None) -> bool:
        if parent is None:
            parent = QModelIndex()

        last = row + count - 1
        self.beginInsertRows(parent, row, last)
        self._rows += count
        for i in range(count):
            self.__grid.append([None for _x in range(COLS)])
        self.endInsertRows()
        self.signal_row_count_changed.emit()
        return True

    def removeRows(self, row: int, count: int, parent: QModelIndex = None) -> bool:
        if parent is None:
            parent = QModelIndex()

        last = row + count - 1
        self.beginRemoveRows(parent, row, last)
        self._rows -= count
        del self.__grid[row:last + 1]
        self.endRemoveRows()
        self.signal_row_count_changed.emit()
        return True

    def columnCount(self, parent: QModelIndex = None) -> int:
        if parent is None:
            parent = QModelIndex()

        if parent.isValid():
            return 0

        return COLS

    def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole = Qt.DisplayRole) -> Any:
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            if section == Col.x1:
                return "x1"
            elif section == Col.y1:
                return "y1"
            elif section == Col.x2:
                return "x2"
            elif section == Col.y2:
                return "y2"
            elif section == Col.Error:
                return "Ошибка"

        elif role == Qt.DisplayRole and orientation == Qt.Vertical:
            return str(section + 1)

    def data(self, index: QModelIndex, role: Qt.ItemDataRole = Qt.DisplayRole) -> Any:
        row, col = index.row(), index.column()

        if role == Qt.DisplayRole or role == Qt.EditRole:
            elem = self.__grid[row][col]
            if elem is not None:
                elem = cast(float, elem)
                elem = FloatCoord(elem).as_string(**FLOAT_FORMAT)
            return elem

    def setData(self, index: QModelIndex, value: Any, role: Qt.ItemDataRole = Qt.EditRole) -> bool:
        if not self.checkIndex(index):
            return False

        if role == Qt.EditRole:
            value: float = self.__convert_string_to_float(value)
            self.grid_set(index.row(), index.column(), value)
            self.dataChanged.emit(index, index)
            return True

        return False

    @try_except_silent()
    def __convert_string_to_float(self, str_arg: str) -> Optional[float]:
        return float(str_arg)

    def grid_get(self, row: int, col: int) -> Optional[float]:
        return self.__grid[row][col]

    def grid_set(self, row: int, col: int, value: Optional[float]) -> None:
        self.__grid[row][col] = value
        if col != Col.Error:
            self.dataChanged.emit(row, col)

    def grid_find_available_row_for_point(self, range_: Tuple[int, int]) -> Optional[int]:
        for row in range(self.rowCount()):
            if all(self.grid_get(row, col) is None for col in range_):
                return row

    def grid_add_point(self, range_: Tuple[int, int], pnt: Pnt):
        pnt_range = (pnt.x, pnt.y)
        row = self.grid_find_available_row_for_point(range_)
        if row is None:
            row = self.rowCount()
            self.insertRow(row)

        for col, value in zip(range_, pnt_range):
            self.grid_set(row, col, value)
