from qgis.PyQt import QtCore
from qgis.PyQt.QtCore import QVariant
from qgis.PyQt.QtGui import QBrush, QColor, QFont, QStandardItemModel

from openlog.datamodel.assay.uncertainty import AssayColumnUncertainty, UncertaintyType


class AssayColumnUncertaintyTableModel(QStandardItemModel):
    COLUMN_COL = 0
    UNCERTAINTY_TYPE = 1
    WIDE_INTERVAL = 2
    LOWER_OUTTER_BOUNDARY = 3
    LOWER_INNER_BOUNDARY = 4
    UPPER_INNER_BOUNDARY = 5
    UPPER_OUTTER_BOUNDARY = 6

    def __init__(self, parent=None) -> None:
        """
        QStandardItemModel for column definition display

        Args:
            parent: QWidget
        """
        super().__init__(parent)
        self.setHorizontalHeaderLabels(
            [
                "Column",
                "Uncertainty type",
                "Wide interval (Δ)",
                "Lower outer boundary (Δmin)",
                "Lower inner boundary (δmin)",
                "Upper inner boundary (δmax)",
                "Upper outer boundary (Δmax)",
            ]
        )

    def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
        """
        Override QStandardItemModel flags to remove edition for column col

        Args:
            index: QModelIndex

        Returns: index flags

        """
        flags = super().flags(index)
        if index.column() == self.COLUMN_COL:
            flags = flags & QtCore.Qt.ItemIsEditable

        return flags

    def data(
        self, index: QtCore.QModelIndex, role: int = QtCore.Qt.DisplayRole
    ) -> QVariant:
        """
        Override QStandardItemModel data() for font of expected column

        Args:
            index: QModelIndex
            role: Qt role

        Returns: QVariant

        """
        result = super().data(index, role)
        if role == QtCore.Qt.FontRole and index.column() == self.COLUMN_COL:
            result = QFont()
            result.setBold(True)

        if role == QtCore.Qt.BackgroundRole:
            result = self._get_background_brush(index)

        return result

    def _get_background_brush(self, index: QtCore.QModelIndex) -> QBrush:
        """
        Return cell background QBrush depending uncertainty type.
        """
        type_ = self.data(self.index(index.row(), self.UNCERTAINTY_TYPE))
        if type_ == "None" and index.column() > self.UNCERTAINTY_TYPE:
            return QBrush(QColor("#808080"))
        if type_ == "Interval" and index.column() > self.WIDE_INTERVAL:
            return QBrush(QColor("#808080"))
        if type_ == "Boundary pair" and index.column() in [
            self.WIDE_INTERVAL,
            self.LOWER_INNER_BOUNDARY,
            self.UPPER_INNER_BOUNDARY,
        ]:
            return QBrush(QColor("#808080"))
        if type_ == "Boundary quad" and index.column() in [self.WIDE_INTERVAL]:
            return QBrush(QColor("#808080"))

        return QBrush(QColor("white"))

    def add_assay_column_uncertainty(
        self, column: str, uncertainty: AssayColumnUncertainty
    ) -> None:
        """
        Add a row in QStandardItemModel

        Args:
            column : (str) column name
            uncertainty: AssayColumnUncertainty
        """
        self.insertRow(self.rowCount())
        row = self.rowCount() - 1
        self._initialize_assay_column_uncertainty(row, column, uncertainty)

    def remove_column(self, column: str) -> None:
        """
        Remove a column uncertainty definition

        Args:
            column: (str) column name

        """
        for i in range(self.rowCount()):
            column_row = str(self.data(self.index(i, self.COLUMN_COL)))
            if column_row == column:
                self.removeRow(i)
                return

    def set_assay_column_uncertainty(
        self, column_uncertainty: {str: AssayColumnUncertainty}
    ) -> None:
        """
        Define expected column and remove not used column

        Args:
            column_uncertainty:
        """
        columns_name = column_uncertainty.keys()
        for i in reversed(range(self.rowCount())):
            expected_col = str(self.data(self.index(i, self.COLUMN_COL)))
            if expected_col not in columns_name:
                self.removeRow(i)

        for col, uncertainty in column_uncertainty.items():
            row_index = self._get_column_row_index(col)
            if row_index != -1:
                self._set_assay_column_uncertainty(row_index, col, uncertainty)
            else:
                self.add_assay_column_uncertainty(col, uncertainty)

    def get_assay_column_uncertainty_types(self) -> {str: UncertaintyType}:
        """
        Return dict of UncertaintyType.

        Returns: {str: UncertaintyType}

        """
        result = {}
        for i in range(self.rowCount()):
            expected_col = str(self.data(self.index(i, self.COLUMN_COL)))
            if expected_col:
                str_type_ = self.data(self.index(i, self.UNCERTAINTY_TYPE))
                if str_type_ == "None":
                    uncertainty_type = UncertaintyType.UNDEFINED
                elif str_type_ == "Interval":
                    uncertainty_type = UncertaintyType.ONE_COLUMN
                elif str_type_ == "Boundary pair":
                    uncertainty_type = UncertaintyType.TWO_COLUMN
                else:
                    uncertainty_type = UncertaintyType.FOUR_COLUMN

                result[expected_col] = uncertainty_type
        return result

    def get_assay_column_uncertainty(self) -> {str: AssayColumnUncertainty}:
        """
        Return dict of AssayColumnUncertainty with mapped columns.

        Returns: {str: AssayColumnUncertainty}

        """
        result = {}
        for i in range(self.rowCount()):
            expected_col = str(self.data(self.index(i, self.COLUMN_COL)))
            if expected_col:
                type_ = self.data(self.index(i, self.UNCERTAINTY_TYPE))
                column = AssayColumnUncertainty()
                if type_ == "Interval":
                    column.upper_whisker_column = self.data(
                        self.index(i, self.WIDE_INTERVAL)
                    )
                elif type_ == "Boundary pair":
                    column.upper_whisker_column = self.data(
                        self.index(i, self.UPPER_OUTTER_BOUNDARY)
                    )
                    column.lower_whisker_column = self.data(
                        self.index(i, self.LOWER_OUTTER_BOUNDARY)
                    )
                elif type_ == "Boundary quad":
                    column.upper_whisker_column = self.data(
                        self.index(i, self.UPPER_OUTTER_BOUNDARY)
                    )
                    column.lower_whisker_column = self.data(
                        self.index(i, self.LOWER_OUTTER_BOUNDARY)
                    )
                    column.upper_box_column = self.data(
                        self.index(i, self.UPPER_INNER_BOUNDARY)
                    )
                    column.lower_box_column = self.data(
                        self.index(i, self.LOWER_INNER_BOUNDARY)
                    )

                result[expected_col] = column
        return result

    def _initialize_assay_column_uncertainty(
        self, row: int, column: str, uncertainty: AssayColumnUncertainty
    ) -> None:

        type_ = uncertainty.get_uncertainty_type()
        self.setData(self.index(row, self.COLUMN_COL), column)

        if type_ == UncertaintyType.UNDEFINED:
            self.setData(self.index(row, self.UNCERTAINTY_TYPE), "None")

        elif type_ == UncertaintyType.ONE_COLUMN:
            self.setData(self.index(row, self.UNCERTAINTY_TYPE), "Interval")
            self.setData(
                self.index(row, self.WIDE_INTERVAL), uncertainty.upper_whisker_column
            )

        elif type_ == UncertaintyType.TWO_COLUMN:
            self.setData(self.index(row, self.UNCERTAINTY_TYPE), "Boundary pair")
            self.setData(
                self.index(row, self.UPPER_OUTTER_BOUNDARY),
                uncertainty.upper_whisker_column,
            )
            self.setData(
                self.index(row, self.LOWER_OUTTER_BOUNDARY),
                uncertainty.lower_whisker_column,
            )
        else:
            self.setData(self.index(row, self.UNCERTAINTY_TYPE), "Boundary quad")
            self.setData(
                self.index(row, self.UPPER_OUTTER_BOUNDARY),
                uncertainty.upper_whisker_column,
            )
            self.setData(
                self.index(row, self.LOWER_OUTTER_BOUNDARY),
                uncertainty.lower_whisker_column,
            )
            self.setData(
                self.index(row, self.UPPER_INNER_BOUNDARY), uncertainty.upper_box_column
            )
            self.setData(
                self.index(row, self.LOWER_INNER_BOUNDARY), uncertainty.lower_box_column
            )

    def _reinitialize_mapping_columns(self, row: int, columns: list = None):
        """
        Set blank to given columns for a row.
        """
        if columns is None:
            columns = [
                self.WIDE_INTERVAL,
                self.UPPER_INNER_BOUNDARY,
                self.LOWER_INNER_BOUNDARY,
                self.UPPER_OUTTER_BOUNDARY,
                self.LOWER_OUTTER_BOUNDARY,
            ]
        for col in columns:
            self.setData(self.index(row, col), "")

    def reinitialize_all_mapping_columns(self):
        """
        Set blank for all columns of the table.
        """
        for row in range(self.rowCount()):
            self._reinitialize_mapping_columns(row)

    def _set_assay_column_uncertainty(
        self, row: int, column: str, uncertainty: AssayColumnUncertainty
    ) -> None:

        defined_type = self.data(self.index(row, self.UNCERTAINTY_TYPE))
        self.setData(self.index(row, self.COLUMN_COL), column)
        self._reinitialize_mapping_columns(row)

        if defined_type == "Interval":
            self.setData(
                self.index(row, self.WIDE_INTERVAL), uncertainty.upper_whisker_column
            )

        elif defined_type == "Boundary pair":

            self.setData(
                self.index(row, self.UPPER_OUTTER_BOUNDARY),
                uncertainty.upper_whisker_column,
            )
            self.setData(
                self.index(row, self.LOWER_OUTTER_BOUNDARY),
                uncertainty.lower_whisker_column,
            )

        elif defined_type == "Boundary quad":
            self.setData(
                self.index(row, self.UPPER_OUTTER_BOUNDARY),
                uncertainty.upper_whisker_column,
            )
            self.setData(
                self.index(row, self.LOWER_OUTTER_BOUNDARY),
                uncertainty.lower_whisker_column,
            )
            self.setData(
                self.index(row, self.UPPER_INNER_BOUNDARY), uncertainty.upper_box_column
            )
            self.setData(
                self.index(row, self.LOWER_INNER_BOUNDARY), uncertainty.lower_box_column
            )

    def _get_column_row_index(self, col: str) -> int:
        """
        Get expected column column index (-1 if expected column not available)

        Args:
            col: (str) expected column

        Returns: expected column column index (-1 if expected column not available)

        """
        row = -1
        for i in range(self.rowCount()):
            val = str(self.data(self.index(i, self.COLUMN_COL)))
            if val == col:
                row = i
        return row

    def _get_or_create_column(self, col: str) -> int:
        """
        Get or create column

        Args:
            col: expected column name

        Returns: column index

        """
        row = self._get_column_row_index(col)
        if row == -1:
            self.add_assay_column_uncertainty(
                column=col, uncertainty=AssayColumnUncertainty()
            )
            row = self.rowCount() - 1
        return row
