import os

from .async_utils import aio_replace_all, aio_re_find_all
from .utils import Utils


class MDLImportItem:
    def __init__(self):
        self.import_clause = ""     # The import clause like `import xx`
        self.import_package = ""    # The package name without import directive
        self.package_path = ""      # The package path converted from import package
        self.module_preset = False  # If it's preset.

    def __repr__(self):
        s = f"<'import_clause': {self.import_clause},"
        s += f"'import_package': {self.import_package},"
        s += f"'packag_path': {self.package_path}>"

        return s


class MDLParser:
    """A parser of mdl file, for get the relative texture files.

    Args:
        mdl_file_path (str): Human readable string describing the exception.
        mdl_content(bytes): MDL content in bytes array.
    """

    def __init__(self, mdl_file_path, mdl_content):
        self._parsed = False
        self._mdl_file_path = mdl_file_path
        self._mdl_content = mdl_content
        if type(self._mdl_content) == bytes:
            self._mdl_content = self._mdl_content.decode()
        self._raw_to_absolute_texture_paths = {}
        self._mdl_imports = {}

    @property
    def content(self):
        return self._mdl_content

    async def parse(self):
        if not self._parsed:
            self._parsed = True
            texture_paths, self._mdl_imports = await self._parse_internal()

            absolute_mdl_basepath = os.path.dirname(self._mdl_file_path)
            if absolute_mdl_basepath != "/":
                absolute_mdl_basepath += "/"

            for raw_texture_path in texture_paths:
                if not raw_texture_path.strip():
                    continue

                if raw_texture_path[0] == '/':
                    texture_path = "." + raw_texture_path
                else:
                    texture_path = raw_texture_path

                absolute_texture_path = Utils.compute_absolute_path(absolute_mdl_basepath, texture_path)
                self._raw_to_absolute_texture_paths[raw_texture_path] = absolute_texture_path

            for mdl_import in self._mdl_imports:
                mdl_import.package_path = Utils.compute_absolute_path(
                    absolute_mdl_basepath, mdl_import.package_path
                )

        return self._raw_to_absolute_texture_paths, self._mdl_imports

    async def _parse_internal(self):
        if not self._mdl_content:
            return [], []

        texture_paths = await aio_re_find_all(
            'texture_2d[ \t]*\([ \t]*"(.*?)"', self._mdl_content
        )

        texture_paths.extend(
            await aio_re_find_all(
                'texture_3d[ \t]*\([ \t]*"(.*?)"', self._mdl_content
            )
        )

        texture_paths.extend(
            await aio_re_find_all(
                'texture_cube[ \t]*\([ \t]*"(.*?)"', self._mdl_content
            )
        )

        texture_paths.extend(
            await aio_re_find_all(
                'texture_ptex[ \t]*\([ \t]*"(.*?)"', self._mdl_content
            )
        )

        mdl_packages = await aio_re_find_all("import[ \t]+(.*)::.*;", self._mdl_content)
        mdl_imports = await aio_re_find_all("(import[ \t]+.*::.*);", self._mdl_content)
        
        mdl_packages += await aio_re_find_all("using[ \t]+(.*)[ \t]+import.*;", self._mdl_content)
        mdl_imports += await aio_re_find_all("(using[ \t]+.*import.*);", self._mdl_content)

        mdl_packages += await aio_re_find_all("=[ \t]+(.*)::.*", self._mdl_content)
        mdl_imports += await aio_re_find_all("(=[ \t]+.*::.*)", self._mdl_content)

        mdl_paths = await self._convert_packages(mdl_packages)

        import_items = []
        for clause, package, path in zip(mdl_imports, mdl_packages, mdl_paths):
            item = MDLImportItem()
            item.import_clause = clause
            item.import_package = package
            item.package_path = path
            if clause.startswith("="):
                item.module_preset = True
            import_items.append(item)

        return texture_paths, import_items

    async def _convert_packages(self, packages):
        paths = []
        for mdl_package in packages:
            path = await aio_replace_all(mdl_package, "::", "/")
            if path:
                paths.append(path + ".mdl")

        return paths
