from typing import Optional

from ..helpers import CompressionHelper
from ..streams import EndianBinaryReader, EndianBinaryWriter
from . import File


class WebFile(File.File):
    """A package which can hold other WebFiles, Bundles and SerialiedFiles.
    It may be compressed via gzip or brotli.

    files -- list of all files in the WebFile
    """

    def __init__(self, reader: EndianBinaryReader, parent: File, name=None, **kwargs):
        """Constructor Method"""
        super().__init__(parent=parent, name=name, **kwargs)

        # check compression
        magic = reader.read_bytes(2)
        reader.Position = 0

        if magic == CompressionHelper.GZIP_MAGIC:
            self.packer = "gzip"
            data = CompressionHelper.decompress_gzip(reader.bytes)
            reader = EndianBinaryReader(data, endian="<")
        else:
            reader.Position = 0x20
            magic = reader.read_bytes(6)
            reader.Position = 0
            if CompressionHelper.BROTLI_MAGIC == magic:
                self.packer = "brotli"
                data = CompressionHelper.decompress_brotli(reader.bytes)
                reader = EndianBinaryReader(data, endian="<")
            else:
                self.packer = "none"
                reader.endian = "<"

        # signature check
        signature = reader.read_string_to_null()
        if not signature.startswith(("UnityWebData", "TuanjieWebData")):
            raise ValueError(f"Invalid WebFile signature: {signature!r}. Expected 'UnityWebData' or 'TuanjieWebData'.")
        self.signature = signature

        # read header -> contains file headers
        head_length = reader.read_int()

        files = []
        while reader.Position < head_length:
            offset = reader.read_int()
            length = reader.read_int()
            path_length = reader.read_int()
            name = bytes(reader.read_bytes(path_length)).decode("utf-8")
            files.append(File.DirectoryInfo(name, offset, length))

        self.read_files(reader, files)

    def save(
        self,
        files: Optional[dict] = None,
        packer: str = "none",
        signature: str = "UnityWebData1.0",
    ) -> bytes:
        # solve defaults
        if not files:
            files = self.files
        if not packer:
            packer = self.packer

        # get raw data
        files = {name: f.bytes if isinstance(f, EndianBinaryReader) else f.save() for name, f in files.items()}

        # create writer
        writer = EndianBinaryWriter(endian="<")
        # signature
        writer.write_string_to_null(signature)

        # data offset
        offset = sum(
            [
                writer.Position,  # signature
                sum(len(path.encode("utf-8")) for path in files.keys()),  # path of each file
                4 * 3 * len(files),  # 3 ints per file
                4,  # offset int
            ]
        )

        writer.write_int(offset)

        # 1. file headers
        for name, data in files.items():
            # offset
            writer.write_int(offset)
            # length
            length = len(data)
            writer.write_int(length)
            offset += length
            # path
            enc_path = name.encode("utf-8")
            writer.write_int(len(enc_path))
            writer.write(enc_path)

        # 2. file data
        for data in files.values():
            writer.write(data)

        if packer == "gzip":
            return CompressionHelper.compress_gzip(writer.bytes)
        elif packer == "brotli":
            return CompressionHelper.compress_brotli(writer.bytes)
        else:
            return writer.bytes
