#include "2s2h/resource/importer/CutsceneFactory.h"
#include "2s2h/resource/type/Cutscene.h"

extern "C" {
#include "z64cutscene.h"
}

namespace SOH {

static inline uint32_t read_CMD_BBBB(std::shared_ptr<Ship::BinaryReader> reader) {
    uint32_t v;
    reader->Read((char*)&v, sizeof(uint32_t));

    return v;
}

static inline uint32_t read_CMD_BBH(std::shared_ptr<Ship::BinaryReader> reader) {
    uint32_t v;
    reader->Read((char*)&v, sizeof(uint32_t));

    // swap the half word to match endianness
    if (reader->GetEndianness() != Ship::Endianness::Native) {
        uint8_t* b = (uint8_t*)&v;
        uint8_t tmp = b[2];
        b[2] = b[3];
        b[3] = tmp;
    }

    return v;
}

static inline uint32_t read_CMD_HBB(std::shared_ptr<Ship::BinaryReader> reader) {
    uint32_t v;
    reader->Read((char*)&v, sizeof(uint32_t));

    // swap the half word to match endianness
    if (reader->GetEndianness() != Ship::Endianness::Native) {
        uint8_t* b = (uint8_t*)&v;
        uint8_t tmp = b[0];
        b[0] = b[1];
        b[1] = tmp;
    }

    return v;
}

static inline uint32_t read_CMD_HH(std::shared_ptr<Ship::BinaryReader> reader) {
    uint32_t v;
    reader->Read((char*)&v, sizeof(uint32_t));

    // swap the half words to match endianness
    if (reader->GetEndianness() != Ship::Endianness::Native) {
        uint8_t* b = (uint8_t*)&v;
        uint8_t tmp = b[0];
        b[0] = b[1];
        b[1] = tmp;
        tmp = b[2];
        b[2] = b[3];
        b[3] = tmp;
    }

    return v;
}

std::shared_ptr<Ship::IResource>
ResourceFactoryBinaryCutsceneV0::ReadResource(std::shared_ptr<Ship::File> file,
                                              std::shared_ptr<Ship::ResourceInitData> initData) {
    if (!FileHasValidFormatAndReader(file, initData)) {
        return nullptr;
    }

    auto cutscene = std::make_shared<Cutscene>(initData);
    auto reader = std::get<std::shared_ptr<Ship::BinaryReader>>(file->Reader);

    uint32_t numEntries = reader->ReadUInt32();
    cutscene->commands.reserve(numEntries);

    cutscene->numCommands = reader->ReadUInt32();
    cutscene->commands.push_back(cutscene->numCommands);

    cutscene->endFrame = reader->ReadUInt32();
    cutscene->commands.push_back(cutscene->endFrame);

    // BENTODO detect the game
    while (true) {
        uint32_t command = reader->ReadUInt32();
        cutscene->commands.push_back(command);

        if (((command >= CS_CMD_ACTOR_CUE_100) && (command <= CS_CMD_ACTOR_CUE_149)) ||
            (command == CS_CMD_ACTOR_CUE_201) ||
            ((command >= CS_CMD_ACTOR_CUE_450) && (command <= CS_CMD_ACTOR_CUE_599))) {
            goto actorCue;
        }

        switch (command) {
            case CS_CMD_TEXT: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                }
                break;
            }
            case CS_CMD_CAMERA_SPLINE: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                while (1) {
                    // We need to store the first half of the header to get the number of entries.
                    uint32_t header1 = read_CMD_HH(reader);

                    uint32_t numEntries = header1 & 0xFFFF;

                    cutscene->commands.push_back(header1);
                    if (numEntries == 0xFFFF) {
                        break; // Last command is HH(0xFFFF, 0004) which was already read in and pushed into the vector
                    }
                    cutscene->commands.push_back(read_CMD_HH(reader));

                    for (size_t j = 0; j < numEntries * 2; j++) {
                        cutscene->commands.push_back(read_CMD_BBH(reader));
                        cutscene->commands.push_back(read_CMD_HH(reader));
                        cutscene->commands.push_back(read_CMD_HH(reader));
                    }

                    for (size_t j = 0; j < numEntries; j++) {
                        cutscene->commands.push_back(read_CMD_HH(reader));
                        cutscene->commands.push_back(read_CMD_HH(reader));
                    }
                }

                // for (uint32_t i = 0; i < (size / 4); i++) {

                // cutscene->commands.push_back(read_CMD_HH(reader));
                //}
                break;
            }
            case CS_CMD_MISC:
            case CS_CMD_LIGHT_SETTING:
            case CS_CMD_TRANSITION:
            case CS_CMD_MOTION_BLUR:
            case CS_CMD_GIVE_TATL:
            case CS_CMD_START_SEQ:
            case CS_CMD_STOP_SEQ:
            case CS_CMD_SFX_REVERB_INDEX_2:
            case CS_CMD_SFX_REVERB_INDEX_1:
            case CS_CMD_MODIFY_SEQ:
            case CS_CMD_START_AMBIENCE:
            case CS_CMD_FADE_OUT_AMBIENCE:
            case CS_CMD_DESTINATION:
            case CS_CMD_CHOOSE_CREDITS_SCENES:
            default: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                }
                break;
            }
            case CS_CMD_TRANSITION_GENERAL: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HBB(reader));
                    cutscene->commands.push_back(read_CMD_BBBB(reader));
                }
                break;
            }
            case CS_CMD_FADE_OUT_SEQ: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case CS_CMD_TIME: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HBB(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case CS_CMD_PLAYER_CUE: {
            actorCue:
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);
                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case CS_CMD_RUMBLE: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HBB(reader));
                    cutscene->commands.push_back(read_CMD_BBBB(reader));
                }
                break;
            }
            case 0xFFFFFFFF: {
                cutscene->commands.push_back(reader->ReadUInt32());
                return cutscene;
            }
        }
    }

    return cutscene;
}

#if 0
void Ship::CutsceneFactoryV0::ParseFileBinaryOoT(std::shared_ptr<BinaryReader> reader,
                                                std::shared_ptr<Cutscene> cutscene) {
    while (true) {
        uint32_t commandId = reader->ReadUInt32();
        cutscene->commands.push_back(commandId);

        switch (commandId) {
            case (uint32_t)CutsceneCommands::SetCameraPos: {
                cutscene->commands.push_back(read_CMD_HH(reader));
                cutscene->commands.push_back(read_CMD_HH(reader));

                while (true) {
                    uint32_t val = read_CMD_BBH(reader);
                    int8_t continueFlag = ((int8_t*)&val)[0];

                    cutscene->commands.push_back(val);
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));

                    if (continueFlag == -1) {
                        break;
                    }
                }
            } break;
            case (uint32_t)CutsceneCommands::SetCameraFocus: {
                cutscene->commands.push_back(read_CMD_HH(reader));
                cutscene->commands.push_back(read_CMD_HH(reader));

                while (true) {
                    uint32_t val = read_CMD_BBH(reader);
                    int8_t continueFlag = ((int8_t*)&val)[0];

                    cutscene->commands.push_back(val);
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));

                    if (continueFlag == -1) {
                        break;
                    }
                }
                break;
            }
            case (uint32_t)CutsceneCommands::SpecialAction: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case (uint32_t)CutsceneCommands::SetLighting: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case (uint32_t)CutsceneCommands::SetCameraPosLink: {
                cutscene->commands.push_back(read_CMD_HH(reader));
                cutscene->commands.push_back(read_CMD_HH(reader));

                while (true) {
                    uint32_t val = read_CMD_BBH(reader);
                    int8_t continueFlag = ((int8_t*)&val)[0];

                    cutscene->commands.push_back(val);
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));

                    if (continueFlag == -1) {
                        break;
                    }
                }
                break;
            }
            case (uint32_t)CutsceneCommands::SetCameraFocusLink: {
                cutscene->commands.push_back(read_CMD_HH(reader));
                cutscene->commands.push_back(read_CMD_HH(reader));

                while (true) {
                    uint32_t val = read_CMD_BBH(reader);
                    int8_t continueFlag = ((int8_t*)&val)[0];

                    cutscene->commands.push_back(val);
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));

                    if (continueFlag == -1) {
                        break;
                    }
                }
                break;
            }
            case (uint32_t)CutsceneCommands::Cmd09: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HBB(reader));
                    cutscene->commands.push_back(read_CMD_BBH(reader));
                }
                break;
            }
            case 0x15:
            case (uint32_t)CutsceneCommands::Unknown: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
            } break;
            case (uint32_t)CutsceneCommands::Textbox: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                }
                break;
            }
            case (uint32_t)CutsceneCommands::SetActorAction0:
            case (uint32_t)CutsceneCommands::SetActorAction1:
            case 17:
            case 18:
            case 23:
            case 34:
            case 39:
            case 46:
            case 76:
            case 85:
            case 93:
            case 105:
            case 107:
            case 110:
            case 119:
            case 123:
            case 138:
            case 139:
            case 144:
            case (uint32_t)CutsceneCommands::SetActorAction2:
            case 16:
            case 24:
            case 35:
            case 40:
            case 48:
            case 64:
            case 68:
            case 70:
            case 78:
            case 80:
            case 94:
            case 116:
            case 118:
            case 120:
            case 125:
            case 131:
            case 141:
            case (uint32_t)CutsceneCommands::SetActorAction3:
            case 36:
            case 41:
            case 50:
            case 67:
            case 69:
            case 72:
            case 74:
            case 81:
            case 106:
            case 117:
            case 121:
            case 126:
            case 132:
            case (uint32_t)CutsceneCommands::SetActorAction4:
            case 37:
            case 42:
            case 51:
            case 53:
            case 63:
            case 65:
            case 66:
            case 75:
            case 82:
            case 108:
            case 127:
            case 133:
            case (uint32_t)CutsceneCommands::SetActorAction5:
            case 38:
            case 43:
            case 47:
            case 54:
            case 79:
            case 83:
            case 128:
            case 135:
            case (uint32_t)CutsceneCommands::SetActorAction6:
            case 55:
            case 77:
            case 84:
            case 90:
            case 129:
            case 136:
            case (uint32_t)CutsceneCommands::SetActorAction7:
            case 52:
            case 57:
            case 58:
            case 88:
            case 115:
            case 130:
            case 137:
            case (uint32_t)CutsceneCommands::SetActorAction8:
            case 60:
            case 89:
            case 111:
            case 114:
            case 134:
            case 142:
            case (uint32_t)CutsceneCommands::SetActorAction9:
            case (uint32_t)CutsceneCommands::SetActorAction10: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                }

                break;
            }
            case (uint32_t)CutsceneCommands::SetSceneTransFX: {
                cutscene->commands.push_back(reader->ReadUInt32());
                cutscene->commands.push_back(read_CMD_HH(reader));
                cutscene->commands.push_back(read_CMD_HH(reader));
                break;
            }
            case (uint32_t)CutsceneCommands::PlayBGM: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case (uint32_t)CutsceneCommands::StopBGM: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case (uint32_t)CutsceneCommands::FadeBGM: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case (uint32_t)CutsceneCommands::SetTime: {
                uint32_t size = reader->ReadUInt32();
                cutscene->commands.push_back(size);

                for (uint32_t i = 0; i < size; i++) {
                    cutscene->commands.push_back(read_CMD_HH(reader));
                    cutscene->commands.push_back(read_CMD_HBB(reader));
                    cutscene->commands.push_back(reader->ReadUInt32());
                }
                break;
            }
            case (uint32_t)CutsceneCommands::Terminator: {
                cutscene->commands.push_back(reader->ReadUInt32());
                cutscene->commands.push_back(read_CMD_HH(reader));
                cutscene->commands.push_back(read_CMD_HH(reader));
                break;
            }
            case 0xFFFFFFFF: // CS_END
            {
                cutscene->commands.push_back(reader->ReadUInt32());
                return;
            }
            default:
                SPDLOG_TRACE("CutsceneV0: Unknown command {}\n", commandId);
                // error?
                break;
        }
    }
}
#endif
} // namespace SOH
