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

import enum
import struct
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ..extase.memorymap import MemoryContext

from ..extase import (
    File,
    Resource,
    RESOURCE_PARSE_SUCCESS,
    ResourceParseInProgress,
    ResourceParseWaiting,
)
from ..extase.cdata_resources import (
    CDataArrayResource,
    CDataExt_Value,
    CDataExtWriteContext,
)

from .. import oot64_data

from . import scene_rooms_resources
from . import collision_resources
from . import room_shape_resources
from . import misc_resources


def _SHIFTR(v: int, s: int, w: int):
    assert isinstance(v, int)
    assert isinstance(s, int)
    assert isinstance(w, int)
    assert v >= 0
    assert s >= 0
    assert w >= 1
    return (v >> s) & ((1 << w) - 1)


VERBOSE_NOT_FULLY_PARSED_SCENECMD = False


class SceneCmdId(enum.Enum):
    # keep the SCENE_CMD_ID_ prefix for grepability
    SCENE_CMD_ID_PLAYER_ENTRY_LIST = 0
    SCENE_CMD_ID_ACTOR_LIST = enum.auto()
    SCENE_CMD_ID_UNUSED_2 = enum.auto()
    SCENE_CMD_ID_COLLISION_HEADER = enum.auto()
    SCENE_CMD_ID_ROOM_LIST = enum.auto()
    SCENE_CMD_ID_WIND_SETTINGS = enum.auto()
    SCENE_CMD_ID_SPAWN_LIST = enum.auto()
    SCENE_CMD_ID_SPECIAL_FILES = enum.auto()
    SCENE_CMD_ID_ROOM_BEHAVIOR = enum.auto()
    SCENE_CMD_ID_UNDEFINED_9 = enum.auto()
    SCENE_CMD_ID_ROOM_SHAPE = enum.auto()
    SCENE_CMD_ID_OBJECT_LIST = enum.auto()
    SCENE_CMD_ID_LIGHT_LIST = enum.auto()
    SCENE_CMD_ID_PATH_LIST = enum.auto()
    SCENE_CMD_ID_TRANSITION_ACTOR_LIST = enum.auto()
    SCENE_CMD_ID_LIGHT_SETTINGS_LIST = enum.auto()
    SCENE_CMD_ID_TIME_SETTINGS = enum.auto()
    SCENE_CMD_ID_SKYBOX_SETTINGS = enum.auto()
    SCENE_CMD_ID_SKYBOX_DISABLES = enum.auto()
    SCENE_CMD_ID_EXIT_LIST = enum.auto()
    SCENE_CMD_ID_END = enum.auto()
    SCENE_CMD_ID_SOUND_SETTINGS = enum.auto()
    SCENE_CMD_ID_ECHO_SETTINGS = enum.auto()
    SCENE_CMD_ID_CUTSCENE_DATA = enum.auto()
    SCENE_CMD_ID_ALTERNATE_HEADER_LIST = enum.auto()
    SCENE_CMD_ID_MISC_SETTINGS = enum.auto()


scene_cmd_macro_name_by_cmd_id = {
    SceneCmdId.SCENE_CMD_ID_PLAYER_ENTRY_LIST: "SCENE_CMD_PLAYER_ENTRY_LIST",
    SceneCmdId.SCENE_CMD_ID_ACTOR_LIST: "SCENE_CMD_ACTOR_LIST",
    SceneCmdId.SCENE_CMD_ID_UNUSED_2: "SCENE_CMD_UNUSED_02",
    SceneCmdId.SCENE_CMD_ID_COLLISION_HEADER: "SCENE_CMD_COL_HEADER",
    SceneCmdId.SCENE_CMD_ID_ROOM_LIST: "SCENE_CMD_ROOM_LIST",
    SceneCmdId.SCENE_CMD_ID_WIND_SETTINGS: "SCENE_CMD_WIND_SETTINGS",
    SceneCmdId.SCENE_CMD_ID_SPAWN_LIST: "SCENE_CMD_SPAWN_LIST",
    SceneCmdId.SCENE_CMD_ID_SPECIAL_FILES: "SCENE_CMD_SPECIAL_FILES",
    SceneCmdId.SCENE_CMD_ID_ROOM_BEHAVIOR: "SCENE_CMD_ROOM_BEHAVIOR",
    SceneCmdId.SCENE_CMD_ID_UNDEFINED_9: "SCENE_CMD_UNK_09",
    SceneCmdId.SCENE_CMD_ID_ROOM_SHAPE: "SCENE_CMD_ROOM_SHAPE",
    SceneCmdId.SCENE_CMD_ID_OBJECT_LIST: "SCENE_CMD_OBJECT_LIST",
    SceneCmdId.SCENE_CMD_ID_LIGHT_LIST: "SCENE_CMD_LIGHT_LIST",
    SceneCmdId.SCENE_CMD_ID_PATH_LIST: "SCENE_CMD_PATH_LIST",
    SceneCmdId.SCENE_CMD_ID_TRANSITION_ACTOR_LIST: "SCENE_CMD_TRANSITION_ACTOR_LIST",
    SceneCmdId.SCENE_CMD_ID_LIGHT_SETTINGS_LIST: "SCENE_CMD_ENV_LIGHT_SETTINGS",
    SceneCmdId.SCENE_CMD_ID_TIME_SETTINGS: "SCENE_CMD_TIME_SETTINGS",
    SceneCmdId.SCENE_CMD_ID_SKYBOX_SETTINGS: "SCENE_CMD_SKYBOX_SETTINGS",
    SceneCmdId.SCENE_CMD_ID_SKYBOX_DISABLES: "SCENE_CMD_SKYBOX_DISABLES",
    SceneCmdId.SCENE_CMD_ID_EXIT_LIST: "SCENE_CMD_EXIT_LIST",
    SceneCmdId.SCENE_CMD_ID_END: "SCENE_CMD_END",
    SceneCmdId.SCENE_CMD_ID_SOUND_SETTINGS: "SCENE_CMD_SOUND_SETTINGS",
    SceneCmdId.SCENE_CMD_ID_ECHO_SETTINGS: "SCENE_CMD_ECHO_SETTINGS",
    SceneCmdId.SCENE_CMD_ID_CUTSCENE_DATA: "SCENE_CMD_CUTSCENE_DATA",
    SceneCmdId.SCENE_CMD_ID_ALTERNATE_HEADER_LIST: "SCENE_CMD_ALTERNATE_HEADER_LIST",
    SceneCmdId.SCENE_CMD_ID_MISC_SETTINGS: "SCENE_CMD_MISC_SETTINGS",
}


class SceneCommandsResource(Resource, can_size_be_unknown=True):
    def __init__(self, file: File, range_start: int, name: str):
        super().__init__(file, range_start, None, name)
        self.parsed_commands: set[SceneCmdId] = set()
        self.player_entry_list_length = None
        self.room_list_length = None
        self.exit_list_length = None

    def try_parse_data(self, memory_context: "MemoryContext"):
        assert self.file.data is not None
        data = self.file.data[self.range_start :]

        new_progress_done = []
        waiting_for = []

        offset = 0
        cmd_id = None
        end_offset = None

        found_commands: set[SceneCmdId] = set()

        while offset + 8 <= len(data):
            (cmd_id_int, data1, pad2, data2_I) = struct.unpack_from(
                ">BBHI", data, offset
            )
            (_, data2_H0, data2_H1) = struct.unpack_from(">IHH", data, offset)
            (_, data2_B0, data2_B1, data2_B2, data2_B3) = struct.unpack_from(
                ">IBBBB", data, offset
            )

            offset += 8
            cmd_id = SceneCmdId(cmd_id_int)
            assert pad2 == 0

            found_commands.add(cmd_id)

            if cmd_id == SceneCmdId.SCENE_CMD_ID_END:
                assert data1 == 0
                assert data2_I == 0
                end_offset = offset
                self.parsed_commands.add(cmd_id)
                break

            if cmd_id in self.parsed_commands:
                continue

            if cmd_id == SceneCmdId.SCENE_CMD_ID_ACTOR_LIST:
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.ActorEntryListResource,
                    lambda file, offset: scene_rooms_resources.ActorEntryListResource(
                        file, offset, f"{self.name}_{data2_I:08X}_ActorEntryList"
                    ),
                )
                resource.set_length(data1)
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(("reported ActorEntryListResource", cmd_id))

            if cmd_id == SceneCmdId.SCENE_CMD_ID_OBJECT_LIST:
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.ObjectListResource,
                    lambda file, offset: scene_rooms_resources.ObjectListResource(
                        file, offset, f"{self.name}_{data2_I:08X}_ObjectList"
                    ),
                )
                resource.set_length(data1)
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(("reported ObjectListResource", cmd_id))

            if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_SHAPE:
                room_shape_resources.report_room_shape_at_segmented(
                    self, memory_context, data2_I, self.name
                )
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(("reported room shape resource", cmd_id))

            if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_LIST:
                self.room_list_length = data1
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.RoomListResource,
                    lambda file, offset: scene_rooms_resources.RoomListResource(
                        file, offset, f"{self.name}_{data2_I:08X}_RoomList"
                    ),
                )
                resource.set_length(data1)
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(("reported RoomListResource", cmd_id))

            if cmd_id == SceneCmdId.SCENE_CMD_ID_COLLISION_HEADER:
                assert data1 == 0
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    collision_resources.CollisionResource,
                    lambda file, offset: collision_resources.CollisionResource(
                        file, offset, f"{self.name}_{data2_I:08X}_Col"
                    ),
                )
                new_progress_done.append(("reported CollisionResource", cmd_id))
                if resource.is_data_parsed:
                    self.exit_list_length = resource.length_exitList
                    self.parsed_commands.add(cmd_id)
                    new_progress_done.append(
                        ("set self.exit_list_length from CollisionResource", cmd_id)
                    )
                else:
                    waiting_for.append(
                        (
                            "CollisionResource to be parsed to set self.exit_list_length",
                            cmd_id,
                            resource,
                        )
                    )

            if cmd_id == SceneCmdId.SCENE_CMD_ID_SPAWN_LIST:
                assert data1 == 0
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.SpawnListResource,
                    lambda file, offset: scene_rooms_resources.SpawnListResource(
                        file, offset, f"{self.name}_{data2_I:08X}_SpawnList"
                    ),
                )
                new_progress_done.append(("reported SpawnListResource", cmd_id))
                if (
                    self.player_entry_list_length is not None
                    and self.room_list_length is not None
                ):
                    resource.player_entry_list_length = self.player_entry_list_length
                    resource.room_list_length = self.room_list_length
                    self.parsed_commands.add(cmd_id)
                    new_progress_done.append(
                        ("passed lengths to SpawnListResource", cmd_id)
                    )
                else:
                    waiting_for.append(
                        (
                            "self.player_entry_list_length and self.room_list_length"
                            " to pass to SpawnListResource",
                            cmd_id,
                        )
                    )

            if cmd_id == SceneCmdId.SCENE_CMD_ID_PLAYER_ENTRY_LIST:
                self.player_entry_list_length = data1
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.ActorEntryListResource,
                    lambda file, offset: scene_rooms_resources.ActorEntryListResource(
                        file, offset, f"{self.name}_{data2_I:08X}_PlayerEntryList"
                    ),
                )
                resource.set_length(data1)
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(("reported ActorEntryListResource", cmd_id))

            if cmd_id == SceneCmdId.SCENE_CMD_ID_EXIT_LIST:
                assert data1 == 0
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.ExitListResource,
                    lambda file, offset: scene_rooms_resources.ExitListResource(
                        file, offset, f"{self.name}_{data2_I:08X}_ExitList"
                    ),
                )
                new_progress_done.append(("reported ExitListResource", cmd_id))
                if self.exit_list_length is not None:
                    # TODO this doesnt work very well, eg need to trim to avoid overlaps
                    length = self.exit_list_length
                    # blindly align length to 2 (could/should check for zeros)
                    length = max(2, (length + 1) // 2 * 2)
                    # trim based on overlaps
                    while True:
                        _, other_resource = resource.file.get_resource_at(
                            resource.range_start
                            + length * resource.elem_cdata_ext.size
                            - 1
                        )
                        if other_resource is resource:
                            break
                        length -= 2
                    assert length > 0
                    resource.set_length(length)
                    self.parsed_commands.add(cmd_id)
                    new_progress_done.append(
                        ("passed length to ExitListResource", cmd_id, resource)
                    )
                else:
                    waiting_for.append(
                        (
                            "self.exit_list_length to (guess a length to) pass to ExitListResource",
                            cmd_id,
                        )
                    )

            if cmd_id == SceneCmdId.SCENE_CMD_ID_LIGHT_SETTINGS_LIST:
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.EnvLightSettingsListResource,
                    lambda file, offset: scene_rooms_resources.EnvLightSettingsListResource(
                        file, offset, f"{self.name}_{data2_I:08X}_EnvLightSettingsList"
                    ),
                )
                resource.set_length(data1)
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(
                    ("reported EnvLightSettingsListResource", cmd_id)
                )

            if cmd_id == SceneCmdId.SCENE_CMD_ID_TRANSITION_ACTOR_LIST:
                resource = memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.TransitionActorEntryListResource,
                    lambda file, offset: scene_rooms_resources.TransitionActorEntryListResource(
                        file,
                        offset,
                        f"{self.name}_{data2_I:08X}_TransitionActorEntryList",
                    ),
                )
                resource.set_length(data1)
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(
                    ("reported TransitionActorEntryListResource", cmd_id)
                )

            if cmd_id == SceneCmdId.SCENE_CMD_ID_PATH_LIST:
                assert data1 == 0
                memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    scene_rooms_resources.PathListResource,
                    lambda file, offset: scene_rooms_resources.PathListResource(
                        file, offset, f"{self.name}_{data2_I:08X}_PathList"
                    ),
                )
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(("reported PathListResource", cmd_id))

            if cmd_id == SceneCmdId.SCENE_CMD_ID_ALTERNATE_HEADER_LIST:
                assert data1 == 0
                memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    AltHeadersResource,
                    lambda file, offset: AltHeadersResource(
                        file, offset, f"{self.name}_{data2_I:08X}_AltHeaders"
                    ),
                )
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(("reported AltHeadersResource", cmd_id))

            if cmd_id == SceneCmdId.SCENE_CMD_ID_CUTSCENE_DATA:
                assert data1 == 0
                memory_context.report_resource_at_segmented(
                    self,
                    data2_I,
                    misc_resources.CutsceneResource,
                    lambda file, offset: misc_resources.CutsceneResource(
                        file, offset, f"{self.name}_{data2_I:08X}_Cs"
                    ),
                )
                self.parsed_commands.add(cmd_id)
                new_progress_done.append(("reported CutsceneResource", cmd_id))

        if cmd_id != SceneCmdId.SCENE_CMD_ID_END:
            raise Exception("reached end of data without encountering end marker")
        assert end_offset is not None

        # Nothing to parse for these commands
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_SOUND_SETTINGS)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_MISC_SETTINGS)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_SPECIAL_FILES)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_SKYBOX_SETTINGS)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_TIME_SETTINGS)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_ROOM_BEHAVIOR)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_ECHO_SETTINGS)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_SKYBOX_DISABLES)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_UNDEFINED_9)
        found_commands.discard(SceneCmdId.SCENE_CMD_ID_WIND_SETTINGS)

        self.range_end = self.range_start + end_offset
        assert self.parsed_commands.issubset(found_commands)

        if found_commands - self.parsed_commands:
            if VERBOSE_NOT_FULLY_PARSED_SCENECMD:
                print(
                    "NOT FULLY PARSED:",
                    self,
                    "Found commands:",
                    found_commands,
                    "Parsed commands:",
                    self.parsed_commands,
                    "FOUND BUT NOT PARSED:",
                    found_commands - self.parsed_commands,
                )

        if waiting_for:
            if new_progress_done:
                raise ResourceParseInProgress(
                    new_progress_done=new_progress_done, waiting_for=waiting_for
                )
            else:
                raise ResourceParseWaiting(waiting_for=waiting_for)

        assert self.parsed_commands == found_commands
        return RESOURCE_PARSE_SUCCESS

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

    def write_extracted(self, memory_context):
        assert self.file.data is not None
        data = self.file.data[self.range_start : self.range_end]
        with self.extract_to_path.open("w") as f:
            if not self.braces_in_source:
                f.write("{\n")
            for offset in range(0, len(data), 8):
                (cmd_id_int, data1, pad2, data2_I) = struct.unpack_from(
                    ">BBHI", data, offset
                )
                (_, data2_B0, data2_B1, data2_B2, data2_B3) = struct.unpack_from(
                    ">IBBBB", data, offset
                )
                (_, data2_b0, data2_b1, data2_b2, data2_b3) = struct.unpack_from(
                    ">Ibbbb", data, offset
                )
                cmd_id = SceneCmdId(cmd_id_int)
                f.write(" " * 4)
                f.write(scene_cmd_macro_name_by_cmd_id[cmd_id])
                f.write("(")
                if cmd_id == SceneCmdId.SCENE_CMD_ID_PLAYER_ENTRY_LIST:
                    address = data2_I
                    f.write(
                        memory_context.get_c_expression_length_at_segmented(address)
                    )
                    f.write(", ")
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_ACTOR_LIST:
                    address = data2_I
                    f.write(
                        memory_context.get_c_expression_length_at_segmented(address)
                    )
                    f.write(", ")
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_UNUSED_2:
                    raise NotImplementedError(cmd_id)
                if cmd_id == SceneCmdId.SCENE_CMD_ID_COLLISION_HEADER:
                    assert data1 == 0
                    address = data2_I
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_LIST:
                    address = data2_I
                    f.write(
                        memory_context.get_c_expression_length_at_segmented(address)
                    )
                    f.write(", ")
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_WIND_SETTINGS:
                    assert data1 == 0
                    xDir = data2_b0
                    yDir = data2_b1
                    zDir = data2_b2
                    strength = data2_B3
                    f.write(f"{xDir}, {yDir}, {zDir}, {strength}")
                if cmd_id == SceneCmdId.SCENE_CMD_ID_SPAWN_LIST:
                    assert data1 == 0
                    address = data2_I
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_SPECIAL_FILES:
                    naviQuestHintFileId = data1
                    keepObjectId = data2_I
                    f.write(
                        f"{oot64_data.get_navi_quest_hint_file_id_name(naviQuestHintFileId)}, "
                        f"{oot64_data.get_object_id_name(keepObjectId)}"
                    )
                if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_BEHAVIOR:
                    gpFlags1 = data1
                    gpFlags2 = data2_I
                    behaviorType1 = gpFlags1
                    behaviorType2 = _SHIFTR(gpFlags2, 0, 8)
                    lensMode = _SHIFTR(gpFlags2, 8, 1)
                    disableWarpSongs = _SHIFTR(gpFlags2, 10, 1)
                    behaviorType1_name = oot64_data.get_room_behavior_type1_name(
                        behaviorType1
                    )
                    behaviorType2_name = oot64_data.get_room_behavior_type2_name(
                        behaviorType2
                    )
                    lensMode_name = oot64_data.get_lens_mode_name(lensMode)
                    disableWarpSongs_name = (
                        "true /* no warp songs */"
                        if disableWarpSongs
                        else "false /* warp songs enabled */"
                    )
                    f.write(
                        f"{behaviorType1_name}, {behaviorType2_name}, {lensMode_name}, {disableWarpSongs_name}"
                    )
                if cmd_id == SceneCmdId.SCENE_CMD_ID_UNDEFINED_9:
                    assert data1 == 0
                    assert data2_I == 0
                if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_SHAPE:
                    assert data1 == 0  # TODO these asserts should be done on parse?
                    address = data2_I
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_OBJECT_LIST:
                    address = data2_I
                    f.write(
                        memory_context.get_c_expression_length_at_segmented(address)
                    )
                    f.write(", ")
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_LIGHT_LIST:
                    raise NotImplementedError(cmd_id)
                if cmd_id == SceneCmdId.SCENE_CMD_ID_PATH_LIST:
                    assert data1 == 0
                    address = data2_I
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_TRANSITION_ACTOR_LIST:
                    address = data2_I
                    f.write(
                        memory_context.get_c_expression_length_at_segmented(address)
                    )
                    f.write(", ")
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_LIGHT_SETTINGS_LIST:
                    address = data2_I
                    f.write(
                        memory_context.get_c_expression_length_at_segmented(address)
                    )
                    f.write(", ")
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_TIME_SETTINGS:
                    assert data1 == 0
                    hour = data2_B0
                    min_ = data2_B1
                    timeSpeed = data2_B2
                    assert data2_B3 == 0

                    hour_str = "0xFF" if hour == 0xFF else str(hour)
                    min_str = "0xFF" if min_ == 0xFF else str(min_)
                    timeSpeed_str = "0xFF" if timeSpeed == 0xFF else str(timeSpeed)

                    if hour == 0xFF or min_ == 0xFF:
                        f.write("/* don't set time */ ")
                    f.write(f"{hour_str}, {min_str}, {timeSpeed_str}")
                    if timeSpeed == 0xFF or timeSpeed == 0:
                        f.write(" /* time doesn't move */")
                if cmd_id == SceneCmdId.SCENE_CMD_ID_SKYBOX_SETTINGS:
                    assert data1 == 0
                    skyboxId = data2_B0
                    skyboxConfig = data2_B1
                    envLightMode = data2_B2
                    assert data2_B3 == 0
                    f.write(
                        f"{oot64_data.get_skybox_id(skyboxId)}, "
                        f"{skyboxConfig}, "
                        f"{oot64_data.get_light_mode(envLightMode)}"
                    )
                if cmd_id == SceneCmdId.SCENE_CMD_ID_SKYBOX_DISABLES:
                    assert data1 == 0
                    skyboxDisabled = data2_B0
                    sunMoonDisabled = data2_B1
                    assert data2_B2 == data2_B3 == 0
                    skyboxDisabled_name = (
                        "true /* no skybox */"
                        if skyboxDisabled
                        else "false /* skybox enabled */"
                    )
                    sunMoonDisabled_name = (
                        "true /* no sun/moon */"
                        if sunMoonDisabled
                        else "false /* sun/moon enabled */"
                    )
                    f.write(f"{skyboxDisabled_name}, {sunMoonDisabled_name}")
                if cmd_id == SceneCmdId.SCENE_CMD_ID_EXIT_LIST:
                    assert data1 == 0
                    address = data2_I
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_END:
                    assert data1 == 0
                    assert data2_I == 0
                if cmd_id == SceneCmdId.SCENE_CMD_ID_SOUND_SETTINGS:
                    specId = data1
                    assert data2_B0 == 0
                    assert data2_B1 == 0
                    natureAmbienceId = data2_B2
                    seqId = data2_B3
                    natureAmbienceId_name = oot64_data.get_nature_ambience_id_name(
                        natureAmbienceId
                    )
                    seqId_name = oot64_data.get_sequence_id_name(seqId)
                    f.write(f"{specId}, {natureAmbienceId_name}, {seqId_name}")
                if cmd_id == SceneCmdId.SCENE_CMD_ID_ECHO_SETTINGS:
                    assert data1 == 0
                    assert data2_B0 == data2_B1 == data2_B2 == 0
                    echo = data2_B3
                    f.write(f"{echo}")
                if cmd_id == SceneCmdId.SCENE_CMD_ID_CUTSCENE_DATA:
                    assert data1 == 0
                    address = data2_I
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_ALTERNATE_HEADER_LIST:
                    assert data1 == 0
                    address = data2_I
                    f.write(memory_context.get_c_reference_at_segmented(address))
                if cmd_id == SceneCmdId.SCENE_CMD_ID_MISC_SETTINGS:
                    sceneCamType = data1
                    worldMapLocation = data2_I
                    sceneCamType_name = oot64_data.get_scene_cam_type_name(sceneCamType)
                    f.write(f"{sceneCamType_name}, {worldMapLocation}")

                f.write("),\n")

            if not self.braces_in_source:
                f.write("}\n")

    def get_c_reference(self, resource_offset: int):
        if resource_offset == 0:
            return self.symbol_name
        else:
            raise ValueError

    def get_c_includes(self):
        return (
            "array_count.h",
            "object.h",  # for OBJECT_*
            # TODO these are not always needed:
            "sequence.h",  # for NATURE_ID_* and NA_BGM_*
            "skybox.h",  # for SKYBOX_*
        )

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


class AltHeadersResource(CDataArrayResource):
    def report_elem(resource, memory_context: "MemoryContext", v):
        assert isinstance(v, int)
        address = v
        if address != 0:
            memory_context.report_resource_at_segmented(
                resource,
                address,
                SceneCommandsResource,
                lambda file, offset: SceneCommandsResource(
                    file, offset, f"{resource.name}_{address:08X}_Cmds"
                ),
            )

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

    elem_cdata_ext = (
        CDataExt_Value("I").set_report(report_elem).set_write(write_elem)
    )  # SceneCmd*

    def try_parse_data(self, memory_context):
        assert self.file.data is not None
        length = 0
        for i, (v,) in enumerate(
            struct.iter_unpack(">I", self.file.data[self.range_start :])
        ):
            if v == 0:
                if i < 3:
                    length = i + 1
                    continue
                else:
                    break

            try:
                resolve_result = memory_context.resolve_segmented(v)
            except:
                break
            data_may_be_scenecmds = False
            for j in range(
                resolve_result.file_offset, len(resolve_result.file.data), 8
            ):
                cmd_id_int = resolve_result.file.data[j]
                try:
                    cmd_id = SceneCmdId(cmd_id_int)
                except ValueError:
                    break
                if cmd_id == SceneCmdId.SCENE_CMD_ID_END:
                    data_may_be_scenecmds = True
                    break
            if not data_may_be_scenecmds:
                break
            length = i + 1
        assert length > 0
        self.set_length(length)
        return super().try_parse_data(memory_context)

    def get_c_declaration_base(self):
        return f"SceneCmd* {self.symbol_name}[]"

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