# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0

import enum
from typing import TYPE_CHECKING

from .. import oot64_data

if TYPE_CHECKING:
    from ..extase.memorymap import MemoryContext


from ..extase import (
    Resource,
    File,
    RESOURCE_PARSE_SUCCESS,
)
from ..extase.cdata_resources import (
    CDataResource,
    CDataArrayNamedLengthResource,
    CDataExt_Struct,
    CDataExt_Value,
    CDataExtWriteContext,
    cdata_ext_Vec3s,
)

from . import dlist_resources


class RoomShapeType(enum.Enum):
    ROOM_SHAPE_TYPE_NORMAL = 0
    ROOM_SHAPE_TYPE_IMAGE = enum.auto()
    ROOM_SHAPE_TYPE_CULLABLE = enum.auto()


class RoomShapeImageAmountType(enum.Enum):
    ROOM_SHAPE_IMAGE_AMOUNT_SINGLE = 1
    ROOM_SHAPE_IMAGE_AMOUNT_MULTI = enum.auto()


def report_room_shape_at_segmented(
    reporter: Resource, memory_context: "MemoryContext", address: int, name_base: str
):
    def new_resource_pointed_to(file: File, offset: int):
        resource_type = get_room_shape_resource_type(file, offset)
        name_suffix = resource_type.__name__.removesuffix("Resource")
        return resource_type(
            file,
            offset,
            f"{name_base}_{address:08X}_{name_suffix}",
        )

    memory_context.report_resource_at_segmented(
        reporter, address, Resource, new_resource_pointed_to
    )


def get_room_shape_resource_type(file: File, offset: int):
    assert file.data is not None

    room_shape_type_int = file.data[offset]
    room_shape_type = RoomShapeType(room_shape_type_int)

    resource_type = None

    if room_shape_type == RoomShapeType.ROOM_SHAPE_TYPE_NORMAL:
        resource_type = RoomShapeNormalResource

    if room_shape_type == RoomShapeType.ROOM_SHAPE_TYPE_IMAGE:
        room_shape_image_amount_type_int = file.data[offset + 1]
        room_shape_image_amount_type = RoomShapeImageAmountType(
            room_shape_image_amount_type_int
        )
        if (
            room_shape_image_amount_type
            == RoomShapeImageAmountType.ROOM_SHAPE_IMAGE_AMOUNT_SINGLE
        ):
            resource_type = RoomShapeImageSingleResource
        if (
            room_shape_image_amount_type
            == RoomShapeImageAmountType.ROOM_SHAPE_IMAGE_AMOUNT_MULTI
        ):
            resource_type = RoomShapeImageMultiResource

    if room_shape_type == RoomShapeType.ROOM_SHAPE_TYPE_CULLABLE:
        resource_type = RoomShapeCullableResource

    assert resource_type is not None

    return resource_type


cdata_ext_RoomShapeBase = CDataExt_Struct(
    (
        (
            "type",
            CDataExt_Value("B").set_write_str_v(oot64_data.get_room_shape_type_name),
        ),
    )
)

cdata_ext_RoomShapeDListsEntry = CDataExt_Struct(
    (
        ("opa", dlist_resources.cdata_ext_gfx_segmented),
        ("xlu", dlist_resources.cdata_ext_gfx_segmented),
    )
)


# TODO check if this even needs a named length
class RoomShapeNormalEntryArrayResource(CDataArrayNamedLengthResource):
    elem_cdata_ext = cdata_ext_RoomShapeDListsEntry

    def get_c_declaration_base(self):
        return f"RoomShapeDListsEntry {self.symbol_name}[{self.length_name}]"

    def get_h_includes(self):
        return ("room.h",)


class RoomShapeNormalResource(CDataResource):
    def write_numEntries(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        address = resource.cdata_unpacked["entries"]
        assert isinstance(address, int)
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_expression_length_at_segmented(address))
        return True

    def report_entries(resource, memory_context: "MemoryContext", v):
        assert isinstance(v, int)
        address = v
        entries_resource = memory_context.report_resource_at_segmented(
            resource,
            address,
            RoomShapeNormalEntryArrayResource,
            lambda file, offset: RoomShapeNormalEntryArrayResource(
                file, offset, f"{resource.name}_{address:08X}_DListsEntries"
            ),
        )
        assert isinstance(entries_resource, RoomShapeNormalEntryArrayResource)
        numEntries = resource.cdata_unpacked["numEntries"]
        assert isinstance(numEntries, int)
        entries_resource.set_length(numEntries)

    def write_entries(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        assert isinstance(v, int)
        address = v
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_reference_at_segmented(address))
        return True

    def write_entriesEnd(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        address = resource.cdata_unpacked["entries"]
        assert isinstance(address, int)
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_reference_at_segmented(address))
        wctx.f.write(" + ")
        wctx.f.write(memory_context.get_c_expression_length_at_segmented(address))
        return True

    cdata_ext = CDataExt_Struct(
        (
            ("base", cdata_ext_RoomShapeBase),
            ("numEntries", CDataExt_Value("B").set_write(write_numEntries)),
            ("pad2", CDataExt_Value.pad16),
            (
                "entries",
                CDataExt_Value("I").set_report(report_entries).set_write(write_entries),
            ),
            (
                "entriesEnd",
                CDataExt_Value("I").set_write(write_entriesEnd),
            ),
        )
    )

    def get_c_declaration_base(self):
        return f"RoomShapeNormal {self.symbol_name}"

    def get_c_reference(self, resource_offset: int):
        if resource_offset == 0:
            return f"&{self.symbol_name}"
        else:
            raise ValueError

    def get_c_includes(self):
        return ("array_count.h",)

    def get_h_includes(self):
        return ("room.h",)


class RoomShapeDListsEntryResource(CDataResource):
    cdata_ext = cdata_ext_RoomShapeDListsEntry

    def get_c_declaration_base(self):
        return f"RoomShapeDListsEntry {self.symbol_name}"

    def get_c_reference(self, resource_offset):
        if resource_offset == 0:
            return f"&{self.symbol_name}"
        else:
            raise ValueError

    def get_h_includes(self):
        return ("room.h",)


def report_RoomShapeImageBase_entry(resource, memory_context: "MemoryContext", v):
    assert isinstance(v, int)
    address = v
    memory_context.report_resource_at_segmented(
        resource,
        address,
        RoomShapeDListsEntryResource,
        lambda file, offset: RoomShapeDListsEntryResource(
            file, offset, f"{resource.name}_{address:08X}_RoomShapeDListsEntry"
        ),
    )


def write_RoomShapeImageBase_entry(
    resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
    assert isinstance(v, int)
    address = v
    wctx.f.write(wctx.line_prefix)
    wctx.f.write(memory_context.get_c_reference_at_segmented(address))
    return True


cdata_ext_RoomShapeImageBase = CDataExt_Struct(
    (
        ("base", cdata_ext_RoomShapeBase),
        (
            "amountType",
            CDataExt_Value("B").set_write_str_v(
                oot64_data.get_room_shape_image_amount_type_name
            ),
        ),
        ("pad2", CDataExt_Value.pad16),
        (
            "entry",
            (
                CDataExt_Value("I")
                .set_report(report_RoomShapeImageBase_entry)
                .set_write(write_RoomShapeImageBase_entry)
            ),
        ),  # RoomShapeDListsEntry*
    )
)


class JFIFResource(Resource):
    def __init__(self, file, range_start, name):
        super().__init__(file, range_start, range_start + 320 * 240 * 2, name)

    def try_parse_data(self, memory_context):
        return RESOURCE_PARSE_SUCCESS

    needs_build = True
    extracted_path_suffix = ""

    def get_filename_stem(self):
        return f"{self.name}.jpg"

    def write_extracted(self, memory_context):
        assert self.file.data is not None
        # TODO trim zeros at the end of the data
        self.extract_to_path.write_bytes(
            self.file.data[self.range_start : self.range_end]
        )

    def get_c_declaration_base(self):
        return f"u64 {self.symbol_name}[SCREEN_WIDTH * SCREEN_HEIGHT * G_IM_SIZ_16b_BYTES / sizeof(u64)]"

    def get_c_reference(self, resource_offset: int):
        if resource_offset == 0:
            return f"&{self.symbol_name}"
        else:
            raise ValueError

    def get_h_includes(self):
        return (
            "ultra64.h",
            "gfx.h",  # for SCREEN_WIDTH, SCREEN_HEIGHT
        )


class RoomShapeImageSingleResource(CDataResource):
    def report_source(resource, memory_context: "MemoryContext", v):
        assert isinstance(v, int)
        address = v
        memory_context.report_resource_at_segmented(
            resource,
            address,
            JFIFResource,
            lambda file, offset: JFIFResource(
                file,
                offset,
                f"{resource.name}_{address:08X}_JFIF",
            ),
        )

    def write_source(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        assert isinstance(v, int)
        address = v
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_reference_at_segmented(address))
        return True

    cdata_ext = CDataExt_Struct(
        (
            ("base", cdata_ext_RoomShapeImageBase),
            (
                "source",
                CDataExt_Value("I").set_report(report_source).set_write(write_source),
            ),
            ("unk_0C", CDataExt_Value.u32),
            ("tlut", CDataExt_Value.pointer),  # TODO
            ("width", CDataExt_Value.u16),
            ("height", CDataExt_Value.u16),
            ("fmt", CDataExt_Value.u8),  # TODO
            ("siz", CDataExt_Value.u8),  # TODO
            ("tlutMode", CDataExt_Value.u16),
            ("tlutCount", CDataExt_Value.u16),
            ("pad1E", CDataExt_Value.pad16),
        )
    )

    def get_c_declaration_base(self):
        return f"RoomShapeImageSingle {self.symbol_name}"

    def get_c_reference(self, resource_offset: int):
        if resource_offset == 0:
            return f"&{self.symbol_name}"
        else:
            raise ValueError

    def get_h_includes(self):
        return ("room.h",)


class RoomShapeImageMultiBgEntryArrayResource(CDataArrayNamedLengthResource):
    def report_source(resource, memory_context: "MemoryContext", v):
        assert isinstance(v, int)
        address = v
        memory_context.report_resource_at_segmented(
            resource,
            address,
            JFIFResource,
            lambda file, offset: JFIFResource(
                file,
                offset,
                f"{resource.name}_{address:08X}_JFIF",
            ),
        )

    def write_source(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        assert isinstance(v, int)
        address = v
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_reference_at_segmented(address))
        return True

    elem_cdata_ext = CDataExt_Struct(
        (
            ("unk_00", CDataExt_Value.u16),
            ("bgCamIndex", CDataExt_Value.u8),
            ("pad3", CDataExt_Value.pad8),
            (
                "source",
                CDataExt_Value("I").set_report(report_source).set_write(write_source),
            ),
            ("unk_0C", CDataExt_Value.u32),
            ("tlut", CDataExt_Value.pointer),  # TODO
            ("width", CDataExt_Value.u16),
            ("height", CDataExt_Value.u16),
            ("fmt", CDataExt_Value.u8),  # TODO
            ("siz", CDataExt_Value.u8),  # TODO
            ("tlutMode", CDataExt_Value.u16),
            ("tlutCount", CDataExt_Value.u16),
            ("pad1A", CDataExt_Value.pad16),
        )
    )

    def get_c_declaration_base(self):
        return f"RoomShapeImageMultiBgEntry {self.name}[{self.length_name}]"

    def get_h_includes(self):
        return ("room.h",)


class RoomShapeImageMultiResource(CDataResource):
    def write_numBackgrounds(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        address = resource.cdata_unpacked["backgrounds"]
        assert isinstance(address, int)
        backgrounds_resource = memory_context.resolve_segmented(address).get_resource(
            RoomShapeImageMultiBgEntryArrayResource
        )
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(backgrounds_resource.length_name)
        return True

    def report_backgrounds(resource, memory_context: "MemoryContext", v):
        assert isinstance(v, int)
        address = v
        backgrounds_resource = memory_context.report_resource_at_segmented(
            resource,
            address,
            RoomShapeImageMultiBgEntryArrayResource,
            lambda file, offset: RoomShapeImageMultiBgEntryArrayResource(
                file,
                offset,
                f"{resource.name}_{address:08X}_RoomShapeImageMultiBgEntries",
            ),
        )
        backgrounds_resource.set_length(resource.cdata_unpacked["numBackgrounds"])

    def write_backgrounds(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        assert isinstance(v, int)
        address = v
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_reference_at_segmented(address))
        return True

    cdata_ext = CDataExt_Struct(
        (
            ("base", cdata_ext_RoomShapeImageBase),
            ("numBackgrounds", CDataExt_Value("B").set_write(write_numBackgrounds)),
            ("pad9", CDataExt_Value.pad8),
            ("padA", CDataExt_Value.pad16),
            (
                "backgrounds",
                (
                    CDataExt_Value("I")
                    .set_report(report_backgrounds)
                    .set_write(write_backgrounds)
                ),
            ),  # RoomShapeImageMultiBgEntry*
        )
    )

    def get_c_declaration_base(self):
        return f"RoomShapeImageMulti {self.symbol_name}"

    def get_c_reference(self, resource_offset: int):
        if resource_offset == 0:
            return f"&{self.symbol_name}"
        else:
            raise ValueError

    def get_h_includes(self):
        return ("room.h",)


class RoomShapeCullableEntryArrayResource(CDataArrayNamedLengthResource):
    elem_cdata_ext = CDataExt_Struct(
        (
            ("boundsSphereCenter", cdata_ext_Vec3s),
            ("boundsSphereRadius", CDataExt_Value.s16),
            ("opa", dlist_resources.cdata_ext_gfx_segmented),
            ("xlu", dlist_resources.cdata_ext_gfx_segmented),
        )
    )

    def get_c_declaration_base(self):
        return f"RoomShapeCullableEntry {self.symbol_name}[{self.length_name}]"

    def get_h_includes(self):
        return ("room.h",)


class RoomShapeCullableResource(CDataResource):
    def write_numEntries(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        address = resource.cdata_unpacked["entries"]
        assert isinstance(address, int)
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_expression_length_at_segmented(address))
        return True

    def report_entries(resource, memory_context: "MemoryContext", v):
        assert isinstance(v, int)
        address = v
        entries_resource = memory_context.report_resource_at_segmented(
            resource,
            address,
            RoomShapeCullableEntryArrayResource,
            lambda file, offset: RoomShapeCullableEntryArrayResource(
                file, offset, f"{resource.name}_{address:08X}_CullableEntries"
            ),
        )
        assert isinstance(entries_resource, RoomShapeCullableEntryArrayResource)
        numEntries = resource.cdata_unpacked["numEntries"]
        assert isinstance(numEntries, int)
        entries_resource.set_length(numEntries)

    def write_entries(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        assert isinstance(v, int)
        address = v
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_reference_at_segmented(address))
        return True

    def write_entriesEnd(
        resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
    ):
        address = resource.cdata_unpacked["entries"]
        assert isinstance(address, int)
        wctx.f.write(wctx.line_prefix)
        wctx.f.write(memory_context.get_c_reference_at_segmented(address))
        wctx.f.write(" + ")
        wctx.f.write(memory_context.get_c_expression_length_at_segmented(address))
        return True

    cdata_ext = CDataExt_Struct(
        (
            ("base", cdata_ext_RoomShapeBase),
            ("numEntries", CDataExt_Value("B").set_write(write_numEntries)),
            ("pad2", CDataExt_Value.pad16),
            (
                "entries",
                CDataExt_Value("I").set_report(report_entries).set_write(write_entries),
            ),
            (
                "entriesEnd",
                CDataExt_Value("I").set_write(write_entriesEnd),
            ),
        )
    )

    def get_c_declaration_base(self):
        return f"RoomShapeCullable {self.symbol_name}"

    def get_c_reference(self, resource_offset: int):
        if resource_offset == 0:
            return f"&{self.symbol_name}"
        else:
            raise ValueError

    def get_h_includes(self):
        return ("room.h",)
