from __future__ import annotations

from collections import namedtuple
from os.path import basename
from typing import TYPE_CHECKING, Dict, Optional

from ..helpers import ImportHelper
from ..streams import EndianBinaryReader, EndianBinaryWriter

if TYPE_CHECKING:
    from ..environment import Environment

DirectoryInfo = namedtuple("DirectoryInfo", "path offset size")


class File:
    name: str
    files: Dict[str, File]
    environment: Environment
    cab_file: str
    is_changed: bool
    signature: str
    packer: str
    is_dependency: bool
    parent: Optional[File]

    def __init__(
        self,
        parent: Optional[File] = None,
        name: Optional[str] = None,
        is_dependency: bool = False,
    ):
        self.files = {}
        self.is_changed = False
        self.cab_file = "CAB-UnityPy_Mod.resS"
        self.parent = parent
        self.environment = getattr(parent, "environment", parent) if parent else None
        self.name = basename(name) if isinstance(name, str) else ""
        self.is_dependency = is_dependency

    def get_assets(self):
        if isinstance(self, SerializedFile.SerializedFile):
            return self

        for f in self.files.values():
            if isinstance(f, (BundleFile.BundleFile, WebFile.WebFile)):
                for asset in f.get_assets():
                    yield asset
            elif isinstance(f, SerializedFile.SerializedFile):
                yield f

    def get_filtered_objects(self, obj_types: Optional[list] = None):
        if obj_types is None or len(obj_types) == 0:
            return self.get_objects()
        for f in self.files.values():
            if isinstance(f, (BundleFile.BundleFile, WebFile.WebFile)):
                for obj in f.objects:
                    if obj.type in obj_types:
                        yield obj
            elif isinstance(f, SerializedFile.SerializedFile):
                for obj in f.objects.values():
                    if obj.type in obj_types:
                        yield obj

    def get_objects(self):
        for f in self.files.values():
            if isinstance(f, (BundleFile.BundleFile, WebFile.WebFile)):
                for obj in f.objects:
                    yield obj
            elif isinstance(f, SerializedFile.SerializedFile):
                for obj in f.objects.values():
                    yield obj
            elif isinstance(f, ObjectReader.ObjectReader):
                yield f

    def read_files(self, reader: EndianBinaryReader, files: list):
        # read file data and convert it
        for node in files:
            reader.Position = node.offset
            name = node.path
            node_reader = EndianBinaryReader(reader.read(node.size), offset=(reader.BaseOffset + node.offset))
            f = ImportHelper.parse_file(node_reader, self, name, is_dependency=self.is_dependency)

            if isinstance(f, (EndianBinaryReader, SerializedFile.SerializedFile)):
                if self.environment:
                    self.environment.register_cab(name, f)

            # required for BundleFiles
            f.flags = getattr(node, "flags", 0)
            self.files[name] = f

    def get_writeable_cab(self, name: Optional[str] = None):
        """
        Creates a new cab file in the bundle that contains the given data.
        This is useful for asset types that use resource files.
        """

        if not name:
            name = self.cab_file

        if not name:
            return None

        if name in self.files:
            if isinstance(self.files[name], EndianBinaryWriter):
                return self.files[name]
            else:
                raise ValueError("This cab already exists and isn't an EndianBinaryWriter")

        writer = EndianBinaryWriter()
        # try to find another resource file to copy the flags from
        for fname, f in self.files.items():
            if fname.endswith(".resS"):
                writer.flags = f.flags
                writer.endian = f.endian
                break
        else:
            writer.flags = 0
        writer.name = name
        self.files[name] = writer
        return writer

    @property
    def container(self):
        return {path: obj for f in self.files.values() if isinstance(f, File) for path, obj in f.container.items()}

    def get(self, key, default=None):
        return getattr(self, key, default)

    def keys(self):
        return self.files.keys()

    def items(self):
        return self.files.items()

    def values(self):
        return self.files.values()

    def __getitem__(self, item):
        return self.files[item]

    def __repr__(self):
        return f"<{self.__class__.__name__}>"

    def mark_changed(self):
        if isinstance(self.parent, File):
            self.parent.mark_changed()
        self.is_changed = True


# recursive import requires the import down here
from . import BundleFile, ObjectReader, SerializedFile, WebFile  # noqa: E402
