from os import path
from pathlib import Path
from typing import List, Optional, Union, cast

from axipy.cpp_core_dp import ShadowConverter
from axipy.cs import CoordSystem
from axipy.da.data_object import Table
from PySide2.QtCore import QPoint, QPointF

from ..schema import Schema
from .data_provider import DataProvider
from .source import Destination, Source

__all__: List[str] = [
    "TabSource",
    "TabDestination",
    "TabDataProvider",
]


class TabSource(Source):
    pass


class TabDestination(Destination):
    pass


class TabDataProvider(DataProvider):
    """
    Провайдер MapInfo.

    Note:
        Ссылку на провайдер можно получить через глобальную переменную :attr:`axipy.provider_manager.tab`.
    """

    @staticmethod
    def _identifier() -> str:
        return "TabDataProvider"

    def get_source(self, filepath: Union[str, Path], alias: Optional[str] = None) -> Source:
        """
        Создает источник данных.

        Args:
            filepath: Путь к файлу.
            alias: Псевдоним для открываемой таблицы.
        """
        return TabSource(
            Source._provider(self.id),
            Source._table_file(filepath),
            Source._alias(alias),
        )

    def open(self, filepath: str, alias: Optional[str] = None) -> Table:
        """
        Открывает объект данных.

        Args:
            filepath: Путь к файлу.
            alias: Псевдоним для открываемой таблицы.
        """
        return cast(Table, self.get_source(filepath, alias).open())

    def get_destination(self, filepath: str, schema: Schema) -> Destination:
        """
        Создает назначение объекта данных.

        Args:
            filepath: Путь к файлу.
            schema: Схема таблицы.
        """
        return TabDestination(schema, Source._provider(self.id), Source._table_file(filepath))

    def create_open(self, filepath: str, schema: Schema) -> Table:
        """
        Создает и открывает объект данных.

        Args:
            filepath: Путь к файлу.
            schema: Схема таблицы.
        """
        return cast(Table, self.get_destination(filepath, schema).create_open())

    def __check_tab_file_suffix(self, filepath: str) -> None:
        _, ext = path.splitext(filepath)
        if ext.lower() != ".tab":
            raise ValueError(f"Файл '{filepath}' должен иметь расширение 'tab'")

    def __check_tab_file_exists(self, filepath: str) -> None:
        if not path.exists(filepath):
            raise FileNotFoundError(f"Файл '{filepath}' не существует")
        self.__check_tab_file_suffix(filepath)

    def __ensure_string(self, file_path: Union[str, Path]) -> str:
        if isinstance(file_path, Path):
            return str(file_path)
        return file_path

    def change_coordsystem(self, filepath: Union[str, Path], coordsystem: CoordSystem) -> None:
        """
        Изменяет координатную систему в TAB файле без изменения самих данных. Меняется
        непосредственно сам файл, так что рекомендуется сделать копию.

        Args:
            filepath: Путь к файлу TAB (имя файла).
            coordsystem: Новое значение СК.

        Raises:
            RuntimeError: При возникновении ошибки

        .. literalinclude:: /../../tests/doc_examples/da/test_example_dp.py
            :caption: Пример использования
            :pyobject: test_run_example_change_cs
            :lines: 2, 4-
            :dedent: 4
        """
        filepath = self.__ensure_string(filepath)
        self.__check_tab_file_exists(filepath)
        convert_data = {
            "openWith": self.id,
            "src": filepath,
            "dest_prj": coordsystem.prj,
        }
        ShadowConverter.convertTabToTab(convert_data)

    def _coord_to_int(self, filepath: str, p_in: QPointF) -> QPoint:
        self.__check_tab_file_exists(filepath)
        convert_data = {
            "openWith": self.id,
            "src": filepath,
            "store_coord_value_x": p_in.x(),
            "store_coord_value_y": p_in.y(),
            "get_store_coord": True,
        }
        return ShadowConverter.mapStoreCoordConvert(convert_data)

    def _coord_from_int(self, filepath: str, p_in: QPoint) -> QPointF:
        self.__check_tab_file_exists(filepath)
        convert_data = {
            "openWith": self.id,
            "src": filepath,
            "store_coord_value_x": p_in.x(),
            "store_coord_value_y": p_in.y(),
            "get_store_coord": False,
        }
        return ShadowConverter.mapStoreCoordConvert(convert_data)

    def remove_table_files(self, filepath: Union[str, Path]) -> None:
        """
        Удаляет все связанные файлы с данным файлом в файловой системе.

        Args:
            filepath: Путь к файлу TAB (имя файла).

        Raises:
            RuntimeError: При возникновении ошибки

        .. literalinclude:: /../../tests/doc_examples/da/test_example_dp.py
            :caption: Пример использования
            :pyobject: test_run_example_remove_files
            :lines: 2,4-
            :dedent: 4
        """
        filepath = self.__ensure_string(filepath)
        self.__check_tab_file_exists(filepath)
        convert_data = {"openWith": self.id, "src": filepath}
        ShadowConverter.removeSourceFiles(convert_data)

    def copy_table_files(self, src_filepath: Union[str, Path], dest_filepath: Union[str, Path]) -> None:
        """
        Копирует все связанные файлы с данным файлом в файловой системе под новым
        именем.

        Args:
            src_filepath: Путь к исходному файлу TAB (имя файла).
            dest_filepath: Путь к выходному файлу TAB (имя файла).

        Raises:
            RuntimeError: При возникновении ошибки

        .. literalinclude:: /../../tests/doc_examples/da/test_example_dp.py
            :caption: Пример использования
            :pyobject: test_run_example_copy_files
            :lines: 2,3,6-
            :dedent: 4
        """
        src_filepath = self.__ensure_string(src_filepath)
        dest_filepath = self.__ensure_string(dest_filepath)

        self.__check_tab_file_exists(src_filepath)
        self.__check_tab_file_suffix(dest_filepath)

        if path.exists(dest_filepath):
            raise FileExistsError(f"Файл '{dest_filepath}' уже существует")

        convert_data = {"openWith": self.id, "src": src_filepath, "dest": dest_filepath}
        ShadowConverter.copyTabFiles(convert_data)

    def rename_table_files(self, src_filepath: Union[str, Path], dest_filepath: Union[str, Path]) -> None:
        """
        Переименовывает файл и все связанные файлы с ним.

        Args:
            src_filepath: Путь к исходному файлу TAB (имя файла).
            dest_filepath: Путь к новому имени файла TAB (имя файла). Файл не должен существовать.

        Raises:
            RuntimeError: При возникновении ошибки

        .. literalinclude:: /../../tests/doc_examples/da/test_example_dp.py
            :caption: Пример использования
            :pyobject: test_run_example_rename_files
            :lines: 2,3,6-
            :dedent: 4
        """
        src_filepath = self.__ensure_string(src_filepath)
        dest_filepath = self.__ensure_string(dest_filepath)

        self.copy_table_files(src_filepath, dest_filepath)
        self.remove_table_files(src_filepath)
