import os.path
import pathlib
from enum import Enum
from typing import Any, Dict, List, NoReturn, Optional, cast

from axipy.cpp_core_dp import ShadowConverter
from axipy.cs import CoordSystem
from axipy.da.data_object import Table

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

__all__: List[str] = [
    "DwgSource",
    "DwgDestination",
    "DwgDataProvider",
    "DwgFileVersion",
    "DwgFileFormat",
    "DwgPalette",
]


class DwgSource(Source):
    pass


class DwgDestination(Destination):
    pass


class DwgFileFormat(int, Enum):
    """
    Формат файла AutoCAD.

    Используется при задании параметра в функции :meth:`DwgDataProvider.convert_file`
    """

    Dwg = 0
    """Файл DWG."""
    Dxf = 1
    """Текстовый файл DXF."""
    Dxb = 2
    """Бинарный файл DXF."""
    Auto = 3
    """Явно не задано."""


class DwgFileVersion(int, Enum):
    """
    Версия файла DWG AutoCAD.

    Используется при задании параметра в функции :meth:`DwgDataProvider.convert_file`
    """

    AutoCAD_R9 = 11
    """AutoCAD Release 9."""
    AutoCAD_R10 = 13
    """AutoCAD Release 10."""
    AutoCAD_R11_12 = 16
    """AutoCAD Release 11-12."""
    AutoCAD_R13 = 19
    """AutoCAD Release 13."""
    AutoCAD_R14 = 21
    """AutoCAD Release 14."""
    AutoCAD_2000 = 23
    """AutoCAD 2000-2002."""
    AutoCAD_2004 = 25
    """AutoCAD 2004-2006."""
    AutoCAD_2007 = 27
    """AutoCAD 2007-2009."""
    AutoCAD_2010 = 29
    """AutoCAD 2010-2012."""
    AutoCAD_2013 = 31
    """AutoCAD 2013-2016."""


class DwgPalette(int, Enum):
    """
    Палитра при работе с файлами AutoCAD.

    Используется при задании параметра в функции :meth:`DwgDataProvider.set_palette`
    """

    Light = 1
    """Светлая тема."""
    Dark = 2
    """Темная тема."""


class DwgDataProvider(DataProvider):
    """
    Провайдер для источников формата AutoCAD.

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

    :any:`export_from_acad_index`

    :any:`export_to_acad_index`
    """

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

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

        Args:
          filename: Имя файла.
          alias: Псевдоним для открываемой таблицы.
        """
        return DwgSource(Source._provider(self.id), Source._alias(alias), {"src": filename})

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

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

    def __ext_to_type(self, path: str) -> DwgFileFormat:
        ext = pathlib.Path(path).suffix
        if ext is not None:
            e = ext[1:].lower()
            if e == "dwg":
                return DwgFileFormat.Dwg
            elif e == "dxf":
                return DwgFileFormat.Dxf
            elif e == "dxb":
                return DwgFileFormat.Dxb
        return DwgFileFormat.Auto

    # noinspection PyShadowingBuiltins
    def get_destination(
        self,
        filepath: str,
        schema: Schema,
        layer_name: Optional[str] = None,
        version: DwgFileVersion = DwgFileVersion.AutoCAD_2013,
        format: DwgFileFormat = DwgFileFormat.Auto,
        coordsystem: Optional[CoordSystem] = None,
        open_mode: OpenMode = OpenMode.Create,
        attributes: bool = True,
    ) -> Destination:
        """
        Создает назначение объекта данных.

        Args:
            filepath: Путь к результирующему файлу.
            schema: Схема таблицы.
            layer_name: Наименование слоя, в который будет экспортироваться данные. Если None, данные будут добавлены в слой '0'
            version: Версия выходного файла
            format: Формат выходного файла
            coordsystem: Система координат, в которой необходимо получить результат. Если не указана, берется из схемы.
            open_mode: Режим открытия файла. В случае :attr:`OpenMode.Append` будет производиться дополнение к существующему файлу.
            attributes: Экспортировать атрибуты.
        """
        if format == DwgFileFormat.Auto:
            format = self.__ext_to_type(filepath)
        pars: Dict[str, Any] = {"version": int(version), "format": int(format)}
        if coordsystem is not None:
            pars["prj"] = coordsystem.prj
        elif schema.coordsystem is not None:
            pars["prj"] = schema.coordsystem.prj
        if open_mode == OpenMode.Append:
            pars["append"] = True
        else:
            pars["create"] = True
        pars["attributes"] = attributes
        if layer_name is not None:
            pars["obj"] = layer_name
        return DwgDestination(schema, Source._provider(self.id), Source._table_file(filepath), pars)

    def create_open(self) -> NoReturn:
        """
        Attention:
            Не поддерживается.

        Raises:
            NotImplementedError
        """
        raise NotImplementedError

    def file_extensions(self) -> List[str]:
        return list(filter(lambda v: v in ["dwg", "dxf"], super().file_extensions()))

    def convert_file(
        self,
        src_filepath: str,
        dest_filepath: str,
        out_version: DwgFileVersion = DwgFileVersion.AutoCAD_2010,
        out_format: DwgFileFormat = DwgFileFormat.Auto,
    ) -> None:
        """
        Производит конвертацию исходный файл текущего провайдера в другой формат этого
        же провайдера.

        Args:
            src_filepath: Путь к исходному файлу (имя файла).
            dest_filepath: Путь к выходному файлу (имя файла).
            out_version: Версия выходного файла.
            out_format: Формат выходного файла.

        .. code-block:: python

            input_file = 'filename_in.dwg'
            output_file = 'filename_out.dxf'
            provider_manager.dwg.convert_file(input_file, output_file, out_format = DwgFileFormat.Dxf,
                out_version = DwgFileVersion.AutoCAD_2010)
        """
        if not os.path.exists(src_filepath):
            raise FileNotFoundError(f"Файл '{src_filepath}' не существует")
        if out_format == DwgFileFormat.Auto:
            out_format = self.__ext_to_type(dest_filepath)

        convert_data = {
            "openWith": self.id,
            "src": src_filepath,
            "dest": dest_filepath,
            "out_format": int(out_format),
            "out_version": int(out_version),
        }
        ShadowConverter.convertAcadFile(convert_data)

    def set_palette(self, palette: DwgPalette) -> None:
        """
        Устанавливает текущую палитру.

        Args:
            palette: Индекс палитры.
        """
        data = {
            "openWith": self.id,
            "num_palette": int(palette),
        }
        ShadowConverter.setAcadPalette(data)
