#include "glue/CCharacterSelection.hpp"
#include "client/ClientServices.hpp"
#include "component/CCharacterComponent.hpp"
#include "db/Db.hpp"
#include "glue/CGlueLoading.hpp"
#include "glue/CGlueMgr.hpp"
#include "glue/CRealmList.hpp"
#include "glue/Types.hpp"
#include "model/CM2Shared.hpp"
#include "net/Connection.hpp"
#include "ui/simple/CSimpleModelFFX.hpp"

int32_t CCharacterSelection::s_characterCount;
TSGrowableArray<CharacterSelectionDisplay> CCharacterSelection::s_characterList;
float CCharacterSelection::s_charFacing;
int32_t CCharacterSelection::s_enterWorldIndex;
CSimpleModelFFX* CCharacterSelection::s_modelFrame;
uint32_t CCharacterSelection::s_restrictHuman;
uint32_t CCharacterSelection::s_restrictDwarf;
uint32_t CCharacterSelection::s_restrictGnome;
uint32_t CCharacterSelection::s_restrictNightElf;
uint32_t CCharacterSelection::s_restrictDraenei;
uint32_t CCharacterSelection::s_restrictOrc;
uint32_t CCharacterSelection::s_restrictTroll;
uint32_t CCharacterSelection::s_restrictTauren;
uint32_t CCharacterSelection::s_restrictUndead;
uint32_t CCharacterSelection::s_restrictBloodElf;
int32_t CCharacterSelection::s_selectionIndex;

void CCharacterSelection::ClearCharacterList() {
    CCharacterSelection::s_characterList.SetCount(0);

    CCharacterSelection::ClearCharacterModel();

    CCharacterSelection::s_selectionIndex = 0;
    CCharacterSelection::ShowCharacter();

    FrameScript_SignalEvent(UPDATE_SELECTED_CHARACTER, "%d", CCharacterSelection::s_selectionIndex + 1);

    if (CCharacterSelection::s_modelFrame) {
        CCharacterSelection::s_modelFrame->SetCameraByIndex(0);
    }

    FrameScript_SignalEvent(CHARACTER_LIST_UPDATE, nullptr);
}

void CCharacterSelection::ClearCharacterModel() {
    if (!CCharacterSelection::s_modelFrame) {
        return;
    }

    auto model = CCharacterSelection::s_modelFrame->m_model;

    if (model) {
        model->DetachAllChildrenById(0);

        // Might be an inlined function (perhaps ClearPetModel)
        model->DetachAllChildrenById(1);
    }
}

void CCharacterSelection::EnumerateCharactersCallback(const CHARACTER_INFO& info, void* param) {
    auto display = CCharacterSelection::s_characterList.New();
    display->m_info = info;

    // TODO
}

const CharacterSelectionDisplay* CCharacterSelection::GetCharacterDisplay(int32_t index) {
    if (index < 0 || index >= CCharacterSelection::s_characterList.Count()) {
        return nullptr;
    }

    return &CCharacterSelection::s_characterList[index];
}

const CharacterSelectionDisplay* CCharacterSelection::GetSelectedCharacter() {
    if (CCharacterSelection::s_selectionIndex < 0 || CCharacterSelection::s_selectionIndex >= CCharacterSelection::s_characterList.Count()) {
        return nullptr;
    }

    return &CCharacterSelection::s_characterList[CCharacterSelection::s_selectionIndex];
}

void CCharacterSelection::OnGetCharacterList() {
    CCharacterSelection::s_characterList.SetCount(0);

    CCharacterSelection::ClearCharacterModel();

    CGlueMgr::GetCharacterList();
}

void CCharacterSelection::RenderPrep() {
    auto character = CCharacterSelection::GetSelectedCharacter();
    if (!character) {
        return;
    }

    auto component = character->m_component;
    if (!component) {
        return;
    }

    component->RenderPrep(0);
}

void CCharacterSelection::SetBackgroundModel(const char* modelPath) {
    if (!CCharacterSelection::s_modelFrame || !modelPath || !*modelPath) {
        return;
    }

    auto model = CCharacterSelection::s_modelFrame->m_model;

    // Check if already set
    if (model && !SStrCmpI(modelPath, model->m_shared->m_filePath, STORM_MAX_STR)) {
        return;
    }

    CCharacterSelection::s_modelFrame->SetModel(modelPath);

    // TODO BYTE1(CCharacterSelection::m_modelFrame->simplemodelffx_dword510[3]) = 1;

    model = CCharacterSelection::s_modelFrame->m_model;

    if (model) {
        // TODO lighting callback + arg

        model->IsDrawable(1, 1);
    }
}

void CCharacterSelection::SetFacing(float facing) {
    if (!CCharacterSelection::s_characterCount) {
        return;
    }

    CCharacterSelection::s_charFacing = facing;

    if (!CCharacterSelection::s_characterList.Count()) {
        return;
    }

    auto component = CCharacterSelection::s_characterList[CCharacterSelection::s_selectionIndex].m_component;
    auto model = component->m_data.model;

    if (!model) {
        return;
    }

    C3Vector position = { 0.0f, 0.0f, 0.0f };
    model->SetWorldTransform(position, facing, 1.0f);
}

void CCharacterSelection::ShowCharacter() {
    if (CCharacterSelection::s_selectionIndex < 0 || CCharacterSelection::s_selectionIndex >= CCharacterSelection::s_characterList.Count()) {
        return;
    }

    CCharacterSelection::ClearCharacterModel();

    CCharacterSelection::s_charFacing = 0.0f;

    auto character = &CCharacterSelection::s_characterList[CCharacterSelection::s_selectionIndex];

    // Create character component and pet model

    if (!character->m_component) {
        character->CreateModelData();
    }

    if (!character->m_component) {
        return;
    }

    // Attach character display to model frame

    auto parent = CCharacterSelection::s_modelFrame->m_model;

    if (!parent) {
        return;
    }

    auto characterModel = character->m_component->m_data.model;
    auto petModel = character->m_petModel;

    characterModel->AttachToParent(parent, 0, nullptr, 0);

    CCharacterSelection::SetFacing(0.0f);

    if (petModel) {
        petModel->AttachToParent(parent, 1, nullptr, 0);
    }

    // TODO a2 may be 1 in some circumstances
    CGlueLoading::StartLoad(character->m_component, 0);
}

void CCharacterSelection::Shutdown() {
    // TODO

    CCharacterSelection::s_characterList.Clear();

    // TODO
}

void CCharacterSelection::UpdateCharacterList() {
    // TODO

    CCharacterSelection::s_characterList.SetCount(0);

    CCharacterSelection::s_restrictHuman = 0;
    CCharacterSelection::s_restrictDwarf = 0;
    CCharacterSelection::s_restrictGnome = 0;
    CCharacterSelection::s_restrictNightElf = 0;
    CCharacterSelection::s_restrictDraenei = 0;
    CCharacterSelection::s_restrictOrc = 0;
    CCharacterSelection::s_restrictTroll = 0;
    CCharacterSelection::s_restrictTauren = 0;
    CCharacterSelection::s_restrictUndead = 0;
    CCharacterSelection::s_restrictBloodElf = 0;

    // Enumerate characters

    ClientServices::Connection()->EnumerateCharacters(CCharacterSelection::EnumerateCharactersCallback, nullptr);

    // No characters

    if (CCharacterSelection::s_characterList.Count() == 0) {
        CCharacterSelection::s_selectionIndex = 0;
        CCharacterSelection::ShowCharacter();

        FrameScript_SignalEvent(8, "%d", CCharacterSelection::s_selectionIndex + 1);

        CCharacterSelection::ClearCharacterModel();

        FrameScript_SignalEvent(7, nullptr);

        return;
    }

    // At least one character

    CCharacterSelection::s_restrictHuman = ClientServices::Connection()->m_restrictHuman;
    CCharacterSelection::s_restrictDwarf = ClientServices::Connection()->m_restrictDwarf;
    CCharacterSelection::s_restrictGnome = ClientServices::Connection()->m_restrictGnome;
    CCharacterSelection::s_restrictNightElf = ClientServices::Connection()->m_restrictNightElf;
    CCharacterSelection::s_restrictDraenei = ClientServices::Connection()->m_restrictDraenei;
    CCharacterSelection::s_restrictOrc = ClientServices::Connection()->m_restrictOrc;
    CCharacterSelection::s_restrictTroll = ClientServices::Connection()->m_restrictTroll;
    CCharacterSelection::s_restrictTauren = ClientServices::Connection()->m_restrictTauren;
    CCharacterSelection::s_restrictUndead = ClientServices::Connection()->m_restrictUndead;
    CCharacterSelection::s_restrictBloodElf = ClientServices::Connection()->m_restrictBloodElf;

    CRealmList::s_preferredCategory = 0;

    // TODO g_lastCharacterIndex stuff
    int32_t selectionIndex = 0;

    CCharacterSelection::s_selectionIndex = selectionIndex;
    CCharacterSelection::ShowCharacter();

    FrameScript_SignalEvent(8, "%d", CCharacterSelection::s_selectionIndex + 1);
    FrameScript_SignalEvent(7, nullptr);
}
