#include "glue/CCharacterCreationScript.hpp"
#include "client/ClientServices.hpp"
#include "component/CCharacterComponent.hpp"
#include "db/Db.hpp"
#include "glue/CCharacterCreation.hpp"
#include "net/Connection.hpp"
#include "object/client/CGUnit_C.hpp"
#include "ui/FrameScript.hpp"
#include "ui/Types.hpp"
#include "ui/game/CGLookingForGroup.hpp"
#include "ui/simple/CSimpleModelFFX.hpp"
#include "util/Lua.hpp"
#include "util/Unimplemented.hpp"
#include <cstdint>

int32_t Script_SetCharCustomizeFrame(lua_State* L) {
    if (!lua_isstring(L, 1)) {
        luaL_error(L, "Usage: SetCharCustomizeFrame(\"frameName\")");
        return 0;
    }

    auto type = CSimpleModel::GetObjectType();
    auto frame = static_cast<CSimpleModelFFX*>(CScriptObject::GetScriptObjectByName(lua_tostring(L, 1), type));

    if (frame) {
        CCharacterCreation::s_charCustomizeFrame = frame;
    }

    return 0;
}

int32_t Script_SetCharCustomizeBackground(lua_State* L) {
    if (!lua_isstring(L, 1)) {
        luaL_error(L, "Usage: SetCharCustomizeBackground(\"filename\")");

        return 0;
    }

    auto filename = lua_tostring(L, 1);

    CCharacterCreation::SetCharCustomizeModel(filename);

    return 0;
}

int32_t Script_ResetCharCustomize(lua_State* L) {
    CCharacterCreation::ResetCharCustomizeInfo();

    return 0;
}

int32_t Script_GetNameForRace(lua_State* L) {
    auto raceRec = g_chrRacesDB.GetRecord(CCharacterCreation::s_character->m_data.raceID);
    auto displayName = CGUnit_C::GetDisplayRaceNameFromRecord(
        raceRec,
        static_cast<UNIT_SEX>(CCharacterCreation::s_character->m_data.sexID),
        nullptr
    );

    if (raceRec && displayName) {
        lua_pushstring(L, displayName);
        lua_pushstring(L, raceRec->m_clientFileString);
    } else {
        lua_pushnil(L);
        lua_pushnil(L);
    }

    return 2;
}

int32_t Script_GetFactionForRace(lua_State* L) {
    if (!lua_isnumber(L, 1)) {
        luaL_error(L, "Usage: GetFactionForRace(index)");
        return 0;
    }

    auto raceIndex = static_cast<int32_t>(lua_tonumber(L, 1)) - 1;
    auto raceID = raceIndex >= CCharacterCreation::s_races.Count() ? 0 : CCharacterCreation::s_races[raceIndex];
    auto raceRec = g_chrRacesDB.GetRecord(raceID);

    if (!raceRec) {
        lua_pushnil(L);
        lua_pushnil(L);

        return 2;
    }

    auto factionTemplateRec = g_factionTemplateDB.GetRecord(raceRec->m_factionID);

    if (!factionTemplateRec) {
        lua_pushnil(L);
        lua_pushnil(L);

        return 2;
    }

    FactionGroupRec* matchingGroup = nullptr;

    for (auto group = 0; group < g_factionGroupDB.GetNumRecords(); group++) {
        auto factionGroupRec = g_factionGroupDB.GetRecordByIndex(group);

        if (!factionGroupRec || !factionGroupRec->m_maskID) {
            continue;
        }

        bool templateMatch = (1 << factionGroupRec->m_maskID) & factionTemplateRec->m_factionGroup;

        if (templateMatch) {
            matchingGroup = factionGroupRec;
            break;
        }
    }

    if (matchingGroup) {
        lua_pushstring(L, matchingGroup->m_name);
        lua_pushstring(L, matchingGroup->m_internalName);
    } else {
        lua_pushnil(L);
        lua_pushnil(L);
    }

    return 2;
}

int32_t Script_GetAvailableRaces(lua_State* L) {
    for (int32_t i = 0; i < CCharacterCreation::s_races.Count(); i++) {
        auto raceID = CCharacterCreation::s_races[i];
        auto raceRec = g_chrRacesDB.GetRecord(raceID);

        auto displayName = CGUnit_C::GetDisplayRaceNameFromRecord(
            raceRec,
            static_cast<UNIT_SEX>(CCharacterCreation::s_character->m_data.sexID),
            nullptr
        );
        lua_pushstring(L, displayName);

        auto clientFileString = raceRec ? raceRec->m_clientFileString : nullptr;
        lua_pushstring(L, clientFileString);

        if (raceRec) {
            int32_t accountExpansion = ClientServices::Connection()->m_accountExpansion;
            int32_t requiredExpansion = raceRec->m_requiredExpansion;
            lua_pushnumber(L, accountExpansion >= requiredExpansion);
        } else {
            lua_pushnumber(L, 0);
        }
    }

    return CCharacterCreation::s_races.Count() * 3;
}

int32_t Script_GetAvailableClasses(lua_State* L) {
    for (int32_t i = 0; i < g_chrClassesDB.GetNumRecords(); i++) {
        auto classRec = g_chrClassesDB.GetRecordByIndex(i);

        auto displayName = CGUnit_C::GetDisplayClassNameFromRecord(
            classRec,
            static_cast<UNIT_SEX>(CCharacterCreation::s_character->m_data.sexID),
            nullptr
        );

        int32_t accountExpansion = ClientServices::Connection()->m_accountExpansion;
        int32_t requiredExpansion = classRec->m_requiredExpansion;

        if (displayName) {
            lua_pushstring(L, displayName);
            lua_pushstring(L, classRec->m_filename);
            lua_pushnumber(L, accountExpansion >= requiredExpansion);
        } else {
            lua_pushnil(L);
            lua_pushnil(L);
            lua_pushnil(L);
        }
    }

    return g_chrClassesDB.GetNumRecords() * 3;
}

int32_t Script_GetClassesForRace(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_GetHairCustomization(lua_State* L) {
    auto raceIndex = CCharacterCreation::s_raceIndex;
    auto raceID = raceIndex >= CCharacterCreation::s_races.Count() ? 0 : CCharacterCreation::s_races[raceIndex];
    auto raceRec = g_chrRacesDB.GetRecord(raceID);

    if (raceRec) {
        lua_pushstring(L, raceRec->m_hairCustomization);
    } else {
        lua_pushstring(L, "NORMAL");
    }

    return 1;
}

int32_t Script_GetFacialHairCustomization(lua_State* L) {
    auto raceIndex = CCharacterCreation::s_raceIndex;
    auto raceID = raceIndex >= CCharacterCreation::s_races.Count() ? 0 : CCharacterCreation::s_races[raceIndex];
    auto raceRec = g_chrRacesDB.GetRecord(raceID);

    if (raceRec) {
        lua_pushstring(L, raceRec->m_facialHairCustomization[CCharacterCreation::s_character->m_data.sexID]);
    } else {
        lua_pushstring(L, "NORMAL");
    }

    return 1;
}

int32_t Script_GetSelectedRace(lua_State* L) {
    lua_pushnumber(L, CCharacterCreation::s_raceIndex + 1);

    return 1;
}

int32_t Script_GetSelectedSex(lua_State* L) {
    lua_pushnumber(L, g_glueFrameScriptGenders[CCharacterCreation::s_character->m_data.sexID]);

    return 1;
}

int32_t Script_GetSelectedClass(lua_State* L) {
    auto classRec = g_chrClassesDB.GetRecord(CCharacterCreation::s_selectedClassID);

    if (!classRec) {
        return 0;
    }

    auto displayName = CGUnit_C::GetDisplayClassNameFromRecord(
        classRec,
        static_cast<UNIT_SEX>(CCharacterCreation::s_character->m_data.sexID),
        nullptr
    );

    lua_pushstring(L, displayName);
    lua_pushstring(L, classRec->m_filename);

    int32_t classIndex = 0;

    for (auto i = 0; i < g_chrClassesDB.GetNumRecords(); i++) {
        auto rec = g_chrClassesDB.GetRecordByIndex(i);
        if (rec->m_ID == CCharacterCreation::s_selectedClassID) {
            classIndex = i;
        }
    }

    lua_pushnumber(L, classIndex + 1);

    auto classRoles = CGLookingForGroup::GetClassRoles(CCharacterCreation::s_selectedClassID);

    lua_pushboolean(L, classRoles & CLASS_ROLE_TANK);
    lua_pushboolean(L, classRoles & CLASS_ROLE_HEALER);
    lua_pushboolean(L, classRoles & CLASS_ROLE_DAMAGE);

    return 6;
}

int32_t Script_SetSelectedRace(lua_State* L) {
    if (!lua_isnumber(L, 1)) {
        luaL_error(L, "Usage: SetSelectedRace(index)");
        return 0;
    }

    auto raceIndex = lua_tonumber(L, 1) - 1;

    CCharacterCreation::SetSelectedRace(raceIndex);

    return 0;
}

int32_t Script_SetSelectedSex(lua_State* L) {
    if (!lua_isnumber(L, 1)) {
        luaL_error(L, "Usage: SetSelectedSex(index)");
        return 0;
    }

    auto sexIndex = lua_tointeger(L, 1);

    for (int32_t sexID = 0; sexID < UNITSEX_LAST; sexID++) {
        if (sexIndex == g_glueFrameScriptGenders[sexID]) {
            CCharacterCreation::SetSelectedSex(sexID);
        }
    }

    return 0;
}

int32_t Script_SetSelectedClass(lua_State* L) {
    if (!lua_isnumber(L, 1)) {
        luaL_error(L, "Usage: SetSelectedClass(index)");
        return 0;
    }

    int32_t classIndex = lua_tonumber(L, 1) - 1;

    auto classRec = g_chrClassesDB.GetRecordByIndex(classIndex);

    CCharacterCreation::SetSelectedClass(classRec ? classRec->GetID() : 0);

    return 0;
}

int32_t Script_UpdateCustomizationBackground(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_UpdateCustomizationScene(lua_State* L) {
    CCharacterCreation::s_character->RenderPrep(0);

    return 0;
}

int32_t Script_CycleCharCustomization(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_RandomizeCharCustomization(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_GetCharacterCreateFacing(lua_State* L) {
    lua_pushnumber(L, CCharacterCreation::s_charFacing * 57.29578);

    return 1;
}

int32_t Script_SetCharacterCreateFacing(lua_State* L) {
    if (!lua_isnumber(L, 1)) {
        luaL_error(L, "Usage: SetCharacterCreateFacing(degrees)");
        return 0;
    }

    auto orientation = lua_tonumber(L, 1) * 0.017453292;

    CCharacterCreation::SetFacing(orientation);

    return 1;
}

int32_t Script_GetRandomName(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_CreateCharacter(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_CustomizeExistingCharacter(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_PaidChange_GetPreviousRaceIndex(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_PaidChange_GetCurrentRaceIndex(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_PaidChange_GetCurrentClassIndex(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_PaidChange_GetName(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_IsRaceClassValid(lua_State* L) {
    if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2)) {
        luaL_error(L, "Usage: IsRaceClassValid(raceIndex, classIndex)");
        return 0;
    }

    int32_t raceIndex = lua_tointeger(L, 1) - 1;
    int32_t classIndex = lua_tointeger(L, 2) - 1;

    auto classRec = g_chrClassesDB.GetRecordByIndex(classIndex);

    if (!classRec) {
        return 0;
    }

    auto raceID = raceIndex >= CCharacterCreation::s_races.Count() ? 0 : CCharacterCreation::s_races[raceIndex];
    auto classID = classRec->m_ID;

    if (CCharacterCreation::IsRaceClassValid(raceID, classID)) {
        lua_pushnumber(L, 1.0);
    } else {
        lua_pushnil(L);
    }

    return 1;
}

int32_t Script_IsRaceClassRestricted(lua_State* L) {
    WHOA_UNIMPLEMENTED(0);
}

int32_t Script_GetCreateBackgroundModel(lua_State* L) {
    if (SFile::IsStreamingTrial()) {
        lua_pushstring(L, "CharacterSelect");

        return 1;
    }

    if (CCharacterCreation::s_selectedClassID == 6) {
        auto classRec = g_chrClassesDB.GetRecord(6);

        if (classRec) {
            lua_pushstring(L, classRec->m_filename);

            return 1;
        }
    } else {
        auto raceID = CCharacterCreation::s_character->m_data.raceID;

        if (raceID == 7) {
            raceID = 3;
        } else if (raceID == 8) {
            raceID = 2;
        }

        auto raceRec = g_chrRacesDB.GetRecord(raceID);

        if (raceRec) {
            lua_pushstring(L, raceRec->m_clientFileString);

            return 1;
        }
    }

    // Race or class background not found
    lua_pushstring(L, "");

    return 1;
}

static FrameScript_Method s_ScriptFunctions[NUM_SCRIPT_FUNCTIONS_CHAR_CREATE] = {
    { "SetCharCustomizeFrame",          &Script_SetCharCustomizeFrame },
    { "SetCharCustomizeBackground",     &Script_SetCharCustomizeBackground },
    { "ResetCharCustomize",             &Script_ResetCharCustomize },
    { "GetNameForRace",                 &Script_GetNameForRace },
    { "GetFactionForRace",              &Script_GetFactionForRace },
    { "GetAvailableRaces",              &Script_GetAvailableRaces },
    { "GetAvailableClasses",            &Script_GetAvailableClasses },
    { "GetClassesForRace",              &Script_GetClassesForRace },
    { "GetHairCustomization",           &Script_GetHairCustomization },
    { "GetFacialHairCustomization",     &Script_GetFacialHairCustomization },
    { "GetSelectedRace",                &Script_GetSelectedRace },
    { "GetSelectedSex",                 &Script_GetSelectedSex },
    { "GetSelectedClass",               &Script_GetSelectedClass },
    { "SetSelectedRace",                &Script_SetSelectedRace },
    { "SetSelectedSex",                 &Script_SetSelectedSex },
    { "SetSelectedClass",               &Script_SetSelectedClass },
    { "UpdateCustomizationBackground",  &Script_UpdateCustomizationBackground },
    { "UpdateCustomizationScene",       &Script_UpdateCustomizationScene },
    { "CycleCharCustomization",         &Script_CycleCharCustomization },
    { "RandomizeCharCustomization",     &Script_RandomizeCharCustomization },
    { "GetCharacterCreateFacing",       &Script_GetCharacterCreateFacing },
    { "SetCharacterCreateFacing",       &Script_SetCharacterCreateFacing },
    { "GetRandomName",                  &Script_GetRandomName },
    { "CreateCharacter",                &Script_CreateCharacter },
    { "CustomizeExistingCharacter",     &Script_CustomizeExistingCharacter },
    { "PaidChange_GetPreviousRaceIndex", &Script_PaidChange_GetPreviousRaceIndex },
    { "PaidChange_GetCurrentRaceIndex", &Script_PaidChange_GetCurrentRaceIndex },
    { "PaidChange_GetCurrentClassIndex", &Script_PaidChange_GetCurrentClassIndex },
    { "PaidChange_GetName",             &Script_PaidChange_GetName },
    { "IsRaceClassValid",               &Script_IsRaceClassValid },
    { "IsRaceClassRestricted",          &Script_IsRaceClassRestricted },
    { "GetCreateBackgroundModel",       &Script_GetCreateBackgroundModel }
};

void CharacterCreateRegisterScriptFunctions() {
    for (int32_t i = 0; i < NUM_SCRIPT_FUNCTIONS_CHAR_CREATE; ++i) {
        FrameScript_RegisterFunction(
            s_ScriptFunctions[i].name,
            s_ScriptFunctions[i].method
        );
    }
}
