from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Optional, cast, Tuple, Final

import PySide2.QtWidgets
import axipy
from PySide2.QtCore import Qt, Slot
from PySide2.QtGui import QColor
from axipy import DlgButtons, DlgIcon
from .observer import get_tables_from_active_map_view
from .ui.ui_dialog import Ui_Dialog
from .worker import Worker, WorkType

if TYPE_CHECKING:
    from .__init__ import PointsCatalog


class SettingsKeys(str, Enum):
    SOURCE_TABLE: str = "SOURCE_TABLE"
    NODES_PATH: str = "NODES_PATH"
    NODES_STYLE: str = "NODES_STYLE"
    NUMBER_EACH_HOLE: str = "NUMBER_EACH_HOLE"
    NUMBER_EACH_COLLECTION_ITEM: str = "NUMBER_EACH_COLLECTION_ITEM"
    NODES_STARTING_NUMBER: str = "NODES_STARTING_NUMBER"
    IS_CONTOURS_CHECKED: str = "IS_CONTOURS_CHECKED"
    CONTOURS_PATH: str = "CONTOURS_PATH"
    NUMBER_EACH_HOLE_CONTOURS: str = "NUMBER_EACH_HOLE_CONTOURS"
    NUMBER_EACH_COLLECTION_ITEM_CONTOURS: str = "NUMBER_EACH_COLLECTION_ITEM_CONTOURS"
    CONTOURS_STARTING_NUMBER: str = "CONTOURS_STARTING_NUMBER"


# noinspection PyMethodMayBeStatic
class Dialog(PySide2.QtWidgets.QDialog, Ui_Dialog):

    def __init__(self, plugin: 'PointsCatalog', *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.setupUi(self)
        self.plugin: 'PointsCatalog' = plugin

        self.MAX_RENAME_COUNT: Final[int] = 1000
        self.DEFAULT_NODES_NAME: Final[str] = "nodes.tab"
        self.DEFAULT_CONTOURS_NAME: Final[str] = "contours.tab"

        self._ask_overwrite_nodes: bool = False
        self._ask_overwrite_contours: bool = False

        self._save_path_nodes: Optional[Path] = None
        self._save_path_contours: Optional[Path] = None
        self.init_default_save_paths()

        names = dict.fromkeys(elem.name for elem in get_tables_from_active_map_view())
        for name in names:
            self.cb_choose_source.addItem(name)
        selection = axipy.data_manager.selection
        if selection is not None:
            if selection.base_table.name in names:
                self.cb_choose_source.insertItem(0, selection.name)
        self.cb_choose_source.setCurrentIndex(0)

        self.tb_table_vertices.clicked.connect(self.slot_save_file_dialog_vertices)
        self.tb_table_contours.clicked.connect(self.slot_save_file_dialog_contours)

        style = axipy.PointStyle.create_mi_font(35, QColor(Qt.black), 6)
        self.style_button.style = style

        try:
            self.restore_settings()
        except Exception as e:
            print("Ошибка восстановления настроек:", str(e))
        else:
            self.init_default_save_paths(self.save_path_nodes.parent, self.save_path_contours.parent)

        self.buttonBox.button(self.buttonBox.Ok).setText(self.plugin.tr("Создать"))

    @property
    def save_path_nodes(self) -> Path:
        if self._save_path_nodes is None:  # Guard
            self.init_default_save_paths()
        return self._save_path_nodes

    # Not thread safe
    @save_path_nodes.setter
    def save_path_nodes(self, value: Path) -> None:
        self._save_path_nodes = value
        self.le_table_vertices.setText(str(value))

    @property
    def save_path_contours(self) -> Path:
        if self._save_path_contours is None:  # Guard
            self.init_default_save_paths()
        return self._save_path_contours

    # Not thread safe
    @save_path_contours.setter
    def save_path_contours(self, value: Path) -> None:
        self._save_path_contours = value
        self.le_table_contours.setText(str(value))

    def init_default_save_paths(
            self,
            parent_path_nodes: Optional[Path] = None,
            parent_path_contours: Optional[Path] = None,
    ) -> None:
        default_save_path_nodes: Path = axipy.CurrentSettings.LastSavePath
        default_save_path_contours: Path = axipy.CurrentSettings.LastSavePath
        if parent_path_nodes is not None:
            default_save_path_nodes = parent_path_nodes
        if parent_path_contours is not None:
            default_save_path_contours = parent_path_contours
        # nodes
        nodes = default_save_path_nodes / self.DEFAULT_NODES_NAME
        nodes, count_nodes = self.ensure_non_existent_path(nodes)
        # contours
        contours = default_save_path_contours / self.DEFAULT_CONTOURS_NAME
        contours, count_contours = self.ensure_non_existent_path(contours)

        if count_nodes > count_contours:
            contours, count_contours = self.ensure_non_existent_path(contours, count_nodes)
        elif count_nodes > count_contours:
            nodes, count_nodes = self.ensure_non_existent_path(nodes, count_contours)

        self._ask_overwrite_nodes = False
        self._ask_overwrite_contours = False
        if count_nodes > self.MAX_RENAME_COUNT:
            self._ask_overwrite_nodes = True
        if count_contours > self.MAX_RENAME_COUNT:
            self._ask_overwrite_contours = True

        self.save_path_nodes = nodes
        self.save_path_contours = contours

    def ensure_non_existent_path(self, path: Path, start: Optional[int] = None) -> Tuple[Path, int]:
        name_len = len(path.stem)
        n: int = 0

        if start is not None and start <= self.MAX_RENAME_COUNT:
            n = start
            path = path.with_name(f"{path.stem[:name_len]}_{n}{path.suffix}")

        while path.exists():
            n += 1
            path = path.with_name(f"{path.stem[:name_len]}_{n}{path.suffix}")
            if n > self.MAX_RENAME_COUNT:  # guard
                break

        return path, n

    def done(self, return_code: int) -> None:
        if return_code == self.Accepted:
            if self.check_if_out_file_path_opened(self.save_path_nodes):
                axipy.show_message(
                    title=self.plugin.title,
                    text=f"Не удалось построить каталог точек. Файл '{self.save_path_nodes}' уже открыт.",
                    icon=axipy.DlgIcon.ERROR,
                )
                return None
            if self.gb_create_table_contours.isChecked() and self.check_if_out_file_path_opened(
                    self.save_path_contours):
                axipy.show_message(
                    title=self.plugin.title,
                    text=f"Не удалось построить каталог точек. Файл '{self.save_path_contours}' уже открыт.",
                    icon=axipy.DlgIcon.ERROR,
                )
                return None

            if self._ask_overwrite_nodes and self.save_path_nodes.exists():
                result = axipy.show_dialog(
                    title=self.plugin.title,
                    text=f"Файл '{self.save_path_nodes}' уже существует. Перезаписать?",
                    buttons=axipy.DlgButtons.YES_NO,
                    default_button=DlgButtons.YES,
                    icon=DlgIcon.WARNING,
                )
                if result != DlgButtons.YES:
                    return None

            if (
                    self.gb_create_table_contours.isChecked() and
                    self._ask_overwrite_contours and
                    self.save_path_contours.exists()
            ):
                result = axipy.show_dialog(
                    title=self.plugin.title,
                    text=f"Файл '{self.save_path_contours}' уже существует. Перезаписать?",
                    buttons=axipy.DlgButtons.YES_NO,
                    default_button=DlgButtons.YES,
                    icon=DlgIcon.WARNING,
                )
                if result != DlgButtons.YES:
                    return None

            worker = Worker(
                source_table=axipy.data_manager.find(self.cb_choose_source.currentText()),
                nodes_path=self.save_path_nodes,
                nodes_style=self.style_button.style,
                number_each_hole=self.chk_number_each_hole.isChecked(),
                number_each_collection_item=self.chk_number_each_collection_item.isChecked(),
                nodes_starting_number=self.spin_box_vertices.value(),
                is_contours_checked=self.gb_create_table_contours.isChecked(),
                contours_path=self.save_path_contours,
                number_each_hole_contours=self.chk_number_each_hole_contours.isChecked(),
                number_each_collection_item_contours=self.chk_number_each_collection_item_contours.isChecked(),
                contours_starting_number=self.spin_box_contours.value(),
            )
            worker.run_and_get()
            self.save_settings()

        super().done(return_code)

    def check_if_out_file_path_opened(self, path: Path) -> bool:
        for obj in axipy.data_manager.all_objects:
            props = obj.properties
            file_name = props.get("fileName", None)
            if (file_name is not None) and (Path(file_name) == path):
                return True

        return False

    def save_settings(self) -> None:
        settings = self.plugin.settings
        settings.setValue(SettingsKeys.NODES_PATH, str(self.save_path_nodes.parent))
        settings.setValue(SettingsKeys.NODES_STYLE, self.style_button.style.to_mapinfo())
        settings.setValue(SettingsKeys.NUMBER_EACH_HOLE, self.chk_number_each_hole.isChecked())
        settings.setValue(SettingsKeys.NUMBER_EACH_COLLECTION_ITEM, self.chk_number_each_collection_item.isChecked())
        settings.setValue(SettingsKeys.NODES_STARTING_NUMBER, self.spin_box_vertices.value())
        settings.setValue(SettingsKeys.IS_CONTOURS_CHECKED, self.gb_create_table_contours.isChecked())
        settings.setValue(SettingsKeys.CONTOURS_PATH, str(self.save_path_contours.parent))
        settings.setValue(SettingsKeys.NUMBER_EACH_HOLE_CONTOURS, self.chk_number_each_hole_contours.isChecked())
        settings.setValue(SettingsKeys.NUMBER_EACH_COLLECTION_ITEM_CONTOURS,
                          self.chk_number_each_collection_item_contours.isChecked())
        settings.setValue(SettingsKeys.CONTOURS_STARTING_NUMBER, self.spin_box_contours.value())

    def _to_bool(self, v: str) -> bool:
        if isinstance(v, bool):
            return v
        return True if v == "true" else False

    def restore_settings(self) -> None:
        settings = self.plugin.settings
        if len(settings.allKeys()) == 0:
            return None

        self.save_path_nodes = Path(settings.value(SettingsKeys.NODES_PATH)) / self.DEFAULT_NODES_NAME

        self._ask_overwrite_nodes = True
        self.style_button.style = axipy.Style.from_mapinfo(settings.value(SettingsKeys.NODES_STYLE))
        self.chk_number_each_hole.setChecked(self._to_bool(settings.value(SettingsKeys.NUMBER_EACH_HOLE)))
        self.chk_number_each_collection_item.setChecked(
            self._to_bool(settings.value(SettingsKeys.NUMBER_EACH_COLLECTION_ITEM)))
        self.spin_box_vertices.setValue(int(settings.value(SettingsKeys.NODES_STARTING_NUMBER)))
        self.gb_create_table_contours.setChecked(self._to_bool(settings.value(SettingsKeys.IS_CONTOURS_CHECKED)))

        self.save_path_contours = Path(settings.value(SettingsKeys.CONTOURS_PATH)) / self.DEFAULT_CONTOURS_NAME

        self._ask_overwrite_contours = True
        self.chk_number_each_hole_contours.setChecked(
            self._to_bool(settings.value(SettingsKeys.NUMBER_EACH_HOLE_CONTOURS)))
        self.chk_number_each_collection_item_contours.setChecked(
            self._to_bool(settings.value(SettingsKeys.NUMBER_EACH_COLLECTION_ITEM_CONTOURS)))
        self.spin_box_contours.setValue(int(settings.value(SettingsKeys.CONTOURS_STARTING_NUMBER)))

    @Slot()
    def slot_save_file_dialog_vertices(self) -> None:
        self._save_file_dialog(WorkType.VERTICES)

    @Slot()
    def slot_save_file_dialog_contours(self) -> None:
        self._save_file_dialog(WorkType.CONTOURS)

    def _save_file_dialog(self, work_type: WorkType) -> None:
        if work_type == WorkType.VERTICES:
            title = "Выбор пути сохранения таблицы вершин"
            folder = self.save_path_nodes.parent
        elif work_type == WorkType.CONTOURS:
            title = "Выбор пути сохранения таблицы контуров"
            folder = self.save_path_contours.parent
        else:
            raise ValueError(work_type)

        path = axipy.save_file_dialog("MapInfo Tab (*.tab)", title, folder)
        if path is None:
            return None
        path = cast(Path, path)
        axipy.CurrentSettings.LastSavePath = path.parent

        if work_type == WorkType.VERTICES:
            self.save_path_nodes = path
            self._ask_overwrite_nodes = False
        elif work_type == WorkType.CONTOURS:
            self.save_path_contours = path
            self._ask_overwrite_contours = False
        else:
            raise ValueError(work_type)
