#include "glue/CGlueMgr.hpp"
#include "glue/CRealmList.hpp"
#include "client/Client.hpp"
#include "client/ClientServices.hpp"
#include "gx/Coordinate.hpp"
#include "gx/Device.hpp"
#include "gx/CGVideoOptions.hpp"
#include "math/Utils.hpp"
#include "net/Connection.hpp"
#include "net/Login.hpp"
#include "sound/SI2.hpp"
#include "ui/CSimpleModelFFX.hpp"
#include "ui/CSimpleTop.hpp"
#include "ui/FrameScript.hpp"
#include "ui/FrameXML.hpp"
#include "ui/Interface.hpp"
#include "ui/ScriptFunctions.hpp"
#include "console/CVar.hpp"
#include "util/Filesystem.hpp"
#include "util/SysMessage.hpp"
#include <cstdio>
#include <common/MD5.hpp>

unsigned char InterfaceKey[256] = {
    0xC3, 0x5B, 0x50, 0x84, 0xB9, 0x3E, 0x32, 0x42, 0x8C, 0xD0, 0xC7, 0x48, 0xFA, 0x0E, 0x5D, 0x54,
    0x5A, 0xA3, 0x0E, 0x14, 0xBA, 0x9E, 0x0D, 0xB9, 0x5D, 0x8B, 0xEE, 0xB6, 0x84, 0x93, 0x45, 0x75,
    0xFF, 0x31, 0xFE, 0x2F, 0x64, 0x3F, 0x3D, 0x6D, 0x07, 0xD9, 0x44, 0x9B, 0x40, 0x85, 0x59, 0x34,
    0x4E, 0x10, 0xE1, 0xE7, 0x43, 0x69, 0xEF, 0x7C, 0x16, 0xFC, 0xB4, 0xED, 0x1B, 0x95, 0x28, 0xA8,
    0x23, 0x76, 0x51, 0x31, 0x57, 0x30, 0x2B, 0x79, 0x08, 0x50, 0x10, 0x1C, 0x4A, 0x1A, 0x2C, 0xC8,
    0x8B, 0x8F, 0x05, 0x2D, 0x22, 0x3D, 0xDB, 0x5A, 0x24, 0x7A, 0x0F, 0x13, 0x50, 0x37, 0x8F, 0x5A,
    0xCC, 0x9E, 0x04, 0x44, 0x0E, 0x87, 0x01, 0xD4, 0xA3, 0x15, 0x94, 0x16, 0x34, 0xC6, 0xC2, 0xC3,
    0xFB, 0x49, 0xFE, 0xE1, 0xF9, 0xDA, 0x8C, 0x50, 0x3C, 0xBE, 0x2C, 0xBB, 0x57, 0xED, 0x46, 0xB9,
    0xAD, 0x8B, 0xC6, 0xDF, 0x0E, 0xD6, 0x0F, 0xBE, 0x80, 0xB3, 0x8B, 0x1E, 0x77, 0xCF, 0xAD, 0x22,
    0xCF, 0xB7, 0x4B, 0xCF, 0xFB, 0xF0, 0x6B, 0x11, 0x45, 0x2D, 0x7A, 0x81, 0x18, 0xF2, 0x92, 0x7E,
    0x98, 0x56, 0x5D, 0x5E, 0x69, 0x72, 0x0A, 0x0D, 0x03, 0x0A, 0x85, 0xA2, 0x85, 0x9C, 0xCB, 0xFB,
    0x56, 0x6E, 0x8F, 0x44, 0xBB, 0x8F, 0x02, 0x22, 0x68, 0x63, 0x97, 0xBC, 0x85, 0xBA, 0xA8, 0xF7,
    0xB5, 0x40, 0x68, 0x3C, 0x77, 0x86, 0x6F, 0x4B, 0xD7, 0x88, 0xCA, 0x8A, 0xD7, 0xCE, 0x36, 0xF0,
    0x45, 0x6E, 0xD5, 0x64, 0x79, 0x0F, 0x17, 0xFC, 0x64, 0xDD, 0x10, 0x6F, 0xF3, 0xF5, 0xE0, 0xA6,
    0xC3, 0xFB, 0x1B, 0x8C, 0x29, 0xEF, 0x8E, 0xE5, 0x34, 0xCB, 0xD1, 0x2A, 0xCE, 0x79, 0xC3, 0x9A,
    0x0D, 0x36, 0xEA, 0x01, 0xE0, 0xAA, 0x91, 0x20, 0x54, 0xF0, 0x72, 0xD8, 0x1E, 0xC7, 0x89, 0xD2
};

int32_t CGlueMgr::m_acceptedEULA = 1; // TODO
int32_t CGlueMgr::m_acceptedTerminationWithoutNotice;
int32_t CGlueMgr::m_acceptedTOS = 1; // TODO
int32_t CGlueMgr::m_processServerAlert = 1;
int32_t CGlueMgr::m_pendingTimerAlert;
int32_t CGlueMgr::m_accountMsgAvailable;
char CGlueMgr::m_accountName[1280];
float CGlueMgr::m_aspect;
bool CGlueMgr::m_authenticated;
char CGlueMgr::m_currentScreen[64];
MipBits* CGlueMgr::m_cursorMipBits;
int32_t CGlueMgr::m_disconnectPending;
int32_t CGlueMgr::m_displayingQueueDialog;
CGlueMgr::GLUE_IDLE_STATE CGlueMgr::m_idleState;
int32_t CGlueMgr::m_initialized;
int32_t CGlueMgr::m_lastLoginResult;
int32_t CGlueMgr::m_lastLoginState;
int32_t CGlueMgr::m_loginResult;
int32_t CGlueMgr::m_loginState;
int32_t CGlueMgr::m_matrixChallengeCount;
int32_t CGlueMgr::m_matrixRemaining;
int32_t CGlueMgr::m_reconnect;
int32_t CGlueMgr::m_reload;
int32_t CGlueMgr::m_scandllOkayToLogIn = 1; // TODO
float CGlueMgr::m_screenHeight;
float CGlueMgr::m_screenWidth;
int32_t CGlueMgr::m_showedDisconnect;
CSimpleTop* CGlueMgr::m_simpleTop;
int32_t CGlueMgr::m_suspended;

int32_t CGlueMgr::m_surveyTimer;
int32_t CGlueMgr::m_executedSurvey;
int32_t CGlueMgr::m_surveyDownload;
int32_t CGlueMgr::m_patchDownload;
bool CGlueMgr::m_deleteLocalPatch;


float CalculateAspectRatio() {
    auto widescreenVar = CVar::Lookup("widescreen");
    auto resolutionVar = CVar::Lookup("gxResolution");

    int32_t width = 4;
    int32_t height = 3;

    if (widescreenVar && widescreenVar->GetInt() && resolutionVar) {
        char separator;
        sscanf(resolutionVar->GetString(), "%d%c%d", &width, &separator, &height);
    }

    return static_cast<float>(width) / static_cast<float>(height);
}

void CGlueMgr::ChangeRealm(const REALM_INFO* realmInfo) {
    if (!realmInfo) {
        return;
    }

    ClientServices::SelectRealm(realmInfo->name);

    if (ClientServices::Connection()->IsConnected()) {
        CGlueMgr::m_disconnectPending = 1;
        CGlueMgr::m_reconnect = 1;

        ClientServices::Connection()->Disconnect();

        return;
    }

    CGlueMgr::m_idleState = IDLE_ACCOUNT_LOGIN;
    CGlueMgr::m_showedDisconnect = 0;

    auto text = FrameScript_GetText("GAME_SERVER_LOGIN", -1, GENDER_NOT_APPLICABLE);
    FrameScript_SignalEvent(3, "%s%s", "CANCEL", text);

    ClientServices::Connection()->Connect();
}

void CGlueMgr::DisplayLoginStatus() {
    // No change
    if (CGlueMgr::m_lastLoginState == CGlueMgr::m_loginState && CGlueMgr::m_lastLoginResult == CGlueMgr::m_loginResult) {
        return;
    }

    CGlueMgr::m_lastLoginState = CGlueMgr::m_loginState;
    CGlueMgr::m_lastLoginResult = CGlueMgr::m_loginResult;

    // Authentication failure
    if (CGlueMgr::m_loginState == LOGIN_STATE_FAILED) {
        // Unknown result
        if (CGlueMgr::m_loginResult >= LOGIN_RESULT_MAX) {
            return;
        }

        // Parental control
        if (CGlueMgr::m_loginResult == LOGIN_PARENTALCONTROL) {
            auto resultStr = Grunt::g_LoginResultStringNames[LOGIN_PARENTALCONTROL];
            auto resultText = FrameScript_GetText(resultStr, -1, GENDER_NOT_APPLICABLE);
            FrameScript_SignalEvent(3, "%s%s%s", "PARENTAL_CONTROL", resultText, "AUTH_PARENTAL_CONTROL_URL");

            return;
        }

        // Other failures
        auto resultStr = Grunt::g_LoginResultStringNames[CGlueMgr::m_loginResult];
        auto resultText = FrameScript_GetText(resultStr, -1, GENDER_NOT_APPLICABLE);

        const char* buttonText;
        if (false /* TODO SStrStrUTF8I(resultText, "<HTML>") */) {
            buttonText = CGlueMgr::m_loginResult == LOGIN_FAILED
                             ? "CONNECTION_HELP_HTML"
                             : "OKAY_HTML";
        } else {
            buttonText = CGlueMgr::m_loginResult == LOGIN_FAILED
                             ? "CONNECTION_HELP"
                             : "OKAY";
        }

        FrameScript_SignalEvent(3, "%s%s", buttonText, resultText);

        return;
    }

    // Authentication in progress or success
    if (CGlueMgr::m_loginState != LOGIN_STATE_DISCONNECTED && CGlueMgr::m_loginState != LOGIN_STATE_SURVEY) {
        auto v3 = CGlueMgr::m_loginState < LOGIN_STATE_MAX;
        if (CGlueMgr::m_loginState == LOGIN_STATE_MATRIX || CGlueMgr::m_loginState == LOGIN_STATE_MATRIX_WAIT) {
            v3 = false;
        }

        if (CGlueMgr::m_loginState != LOGIN_STATE_TOKEN && CGlueMgr::m_loginState != LOGIN_STATE_TOKEN_WAIT && v3) {
            auto stateStr = Grunt::g_LoginStateStringNames[CGlueMgr::m_loginState];
            auto stateText = FrameScript_GetText(stateStr, -1, GENDER_NOT_APPLICABLE);
            FrameScript_SignalEvent(3, "%s%s", "CANCEL", stateText);
        }
    }
}

int32_t CGlueMgr::HandleDisplaySizeChanged(const CSizeEvent& event) {
    if (
        CGlueMgr::m_screenWidth > 0
        && CGlueMgr::m_screenWidth == event.w
        && CGlueMgr::m_screenHeight > 0
        && CGlueMgr::m_screenHeight == event.h
    ) {
        return 1;
    }

    CGlueMgr::m_screenWidth = event.w;
    CGlueMgr::m_screenHeight = event.h;
    float aspect = CalculateAspectRatio();

    auto glueParent = static_cast<CSimpleFrame*>(CScriptObject::GetScriptObjectByName("GlueParent", CSimpleFrame::GetObjectType()));
    if (glueParent) {
        glueParent->SetFrameScale(1.0f, true);
        CLayoutFrame::ResizePending();
    }

    if (AreEqual(CGlueMgr::m_aspect, aspect, 0.0099999998)) {
        return 1;
    }

    CGlueMgr::m_aspect = aspect;
    CGlueMgr::m_reload = 1;

    return 1;
}

void CGlueMgr::GetRealmList(bool showProgress) {
    CGlueMgr::m_idleState = IDLE_REALM_LIST;
    CGlueMgr::m_showedDisconnect = 0;
    if (showProgress) {
        auto text = FrameScript_GetText("REALM_LIST_IN_PROGRESS", -1, FRAMESCRIPT_GENDER::GENDER_NOT_APPLICABLE);
        FrameScript_SignalEvent(3, "%s%s", "CANCEL", text);
    }
    ClientServices::GetRealmList();
}

void CGlueMgr::GetCharacterList() {
    if (CGlueMgr::m_idleState != IDLE_WORLD_LOGIN) {
        CGlueMgr::m_idleState = IDLE_CHARACTER_LIST;
        auto text = FrameScript_GetText("CHAR_LIST_RETRIEVING", -1, GENDER_NOT_APPLICABLE);
        FrameScript_SignalEvent(3, "%s%s", "CANCEL", text);
        ClientServices::GetCharacterList();
    }
}

// TODO a1: const EVENT_DATA_IDLE*
int32_t CGlueMgr::Idle(const void* a1, void* a2) {
    // TODO:
    // if (gxDevice->IsStereoEnabled) {
    //     CGlueMgr::SetUIDepth(gxDevice->StereoGetConvergence);
    // }

    auto loginConnection = ClientServices::LoginConnection();
    if (loginConnection) {
        // Virtual call (loginConnection + 184) leads to nullsub
        // Checked by tracing in debugger
    }

    if (CGlueMgr::m_processServerAlert) {
        // TODO:
        // v2 = SStrLen("SERVERALERT:");
        // FrameScript_SignalEvent(0x15u, "%s", &CGlueMgr::m_serverAlert[(_DWORD)&v2[CGlueMgr::m_serverAlert[0] != -17 ? 0 : 3]]);
        CGlueMgr::m_processServerAlert = 0;
    }

    if (CGlueMgr::m_pendingTimerAlert) {
        FrameScript_SignalEvent(0x21u, "%d", CGlueMgr::m_pendingTimerAlert);
        CGlueMgr::m_pendingTimerAlert = 0;
    }

    // TODO: CKBPage::UpdateLoadingQueue();

    if (CGlueMgr::m_idleState == IDLE_NONE) {
        if (CGlueMgr::m_reload) {
            if (!CGlueMgr::m_suspended) {
                CGlueMgr::Suspend();
                CGlueMgr::Resume();
                // TODO Sub4DA360();
                CGlueMgr::SetScreen(CGlueMgr::m_currentScreen);
            }

            CGlueMgr::m_reload = 0;
        }

        if (CGlueMgr::m_accountMsgAvailable) {
            FrameScript_SignalEvent(0x22u, 0);
            CGlueMgr::m_accountMsgAvailable = 0;
        }

        // TODO CGlueMgr::HandleBattlenetDisconnect();

        return 1;
    }

    // TODO: LOOP { ConsoleWrite(GRUNT DEBUG MESSAGE) }

    WOWCS_OPS op;
    const char* msg;
    int32_t result;
    int32_t errorCode;
    int32_t complete = ClientServices::Connection()->PollStatus(op, &msg, result, errorCode);

    switch (CGlueMgr::m_idleState) {
    case IDLE_LOGIN_SERVER_LOGIN: {
        CGlueMgr::PollLoginServerLogin();
        break;
    }

    case IDLE_ACCOUNT_LOGIN: {
        CGlueMgr::PollAccountLogin(errorCode, msg, complete, result, op);
        break;
    }

    case IDLE_CHARACTER_LIST: {
        CGlueMgr::PollCharacterList(errorCode, msg, complete, result, op);
        break;
    }

    case IDLE_12: {
        if (CGlueMgr::m_patchDownload) {
            CGlueMgr::PatchDownloadIdle();
        } else if (CGlueMgr::m_surveyDownload) {
            CGlueMgr::SurveyDownloadIdle();
        }
        break;
    }

    case IDLE_13: {
        CGlueMgr::PollUserSurvey();
        break;
    }

    // TODO other idle states

    default:
        break;
    }

    return 1;
}

void CGlueMgr::Initialize() {
    CGlueMgr::m_initialized = 1;

    // TODO
    // - cvar stuff (tou, etc)

    CRect windowSize;
    g_theGxDevicePtr->CapsWindowSize(windowSize);

    CSizeEvent sizeEvent;
    sizeEvent.id = -1;
    sizeEvent.w = windowSize.maxX - windowSize.minX;
    sizeEvent.h = windowSize.maxY - windowSize.minY;

    CGlueMgr::HandleDisplaySizeChanged(sizeEvent);

    FrameXML_RegisterFactory("ModelFFX", &CSimpleModelFFX::Create, 0);

    CGlueMgr::Resume();

    CRealmList::Initialize();

    EventRegisterEx(EVENT_ID_IDLE, &CGlueMgr::Idle, 0, 0.0);

    // TODO
    // CGLCD::Initialize();
    // sub_552380();

    // TODO
    // - options changed warning stuff

    // TODO
    // AccountDataInitializeBasicSystem();
}

void CGlueMgr::LoginServerLogin(const char* accountName, const char* password) {
    if (!CGlueMgr::m_scandllOkayToLogIn || !CGlueMgr::m_acceptedTOS || !CGlueMgr::m_acceptedEULA || CGlueMgr::m_idleState != IDLE_NONE) {
        return;
    }

    if (!accountName || !*accountName) {
        auto prompt = FrameScript_GetText("LOGIN_ENTER_NAME", -1, GENDER_NOT_APPLICABLE);
        FrameScript_SignalEvent(3, "%s%s", "OKAY", prompt);

        return;
    }

    if (!password || !*password) {
        auto prompt = FrameScript_GetText("LOGIN_ENTER_PASSWORD", -1, GENDER_NOT_APPLICABLE);
        FrameScript_SignalEvent(3, "%s%s", "OKAY", prompt);

        return;
    }

    CGlueMgr::m_loginResult = -1;
    CGlueMgr::m_loginState = -1;
    CGlueMgr::m_lastLoginResult = -1;
    CGlueMgr::m_lastLoginState = -1;
    CGlueMgr::m_authenticated = false;
    CGlueMgr::m_matrixRemaining = 0;
    CGlueMgr::m_idleState = IDLE_LOGIN_SERVER_LOGIN;
    CGlueMgr::m_showedDisconnect = 0;

    char* dest = CGlueMgr::m_accountName;
    for (const char* src = accountName; *src && dest < CGlueMgr::m_accountName + sizeof(CGlueMgr::m_accountName); src++, dest++) {
        if (src[0] == '|' && src[1] == '|') {
            src++;
        }

        *dest = *src;
    }
    *dest = '\0';

    SStrUpper(CGlueMgr::m_accountName);

    // Show prompt
    FrameScript_SignalEvent(3, "%s", "CANCEL");

    ClientServices::Logon(CGlueMgr::m_accountName, password);

    // Zero out password
    memset(const_cast<char*>(password), 0, SStrLen(password));
}

void CGlueMgr::QuitGame() {
    ClientPostClose(0);
}

void CGlueMgr::PollAccountLogin(int32_t errorCode, const char* msg, int32_t complete, int32_t result, WOWCS_OPS op) {
    auto login = ClientServices::LoginConnection();

    if (login->GetLoginServerType() == 1 && CGlueMgr::m_loginState == LOGIN_STATE_FAILED) {
        CGlueMgr::DisplayLoginStatus();

        ClientServices::Connection()->Cancel(2);
        login->Logoff();

        CGlueMgr::m_idleState = IDLE_NONE;
        CGlueMgr::m_showedDisconnect = 0;

        return;
    }

    if (errorCode == 27) {
        // TODO AUTH_WAIT_QUEUE
    } else {
        if (CGlueMgr::m_displayingQueueDialog) {
            FrameScript_SignalEvent(3, "%s", "CANCEL");
            CGlueMgr::m_displayingQueueDialog = 0;
        }

        FrameScript_SignalEvent(4, "%s", msg);
    }

    if (!complete) {
        return;
    }

    if (result == 0) {
        if (errorCode != 2) {
            // TODO
            // Select Error Description with or without URL
        }

        CGlueMgr::m_idleState = IDLE_NONE;
        CGlueMgr::m_showedDisconnect = 0;

        if (errorCode == 2) {
            CGlueMgr::m_disconnectPending = 1;
            ClientServices::Connection()->Disconnect();
        }

        if (errorCode != 13) {
            // TODO CCharacterSelection::ClearCharacterList();

            if (ClientServices::GetInstance()->m_realmList.Count()) {
                FrameScript_SignalEvent(5, nullptr);
                CRealmList::UpdateList();
            } else {
                CGlueMgr::GetRealmList(true);
            }

            return;
        }

        if (!SStrCmpI(CGlueMgr::m_currentScreen, "charselect", STORM_MAX_STR)) {
            CGlueMgr::SetScreen("login");
            return;
        }

        return;
    }

    if (op == COP_CONNECT) {
        // TODO: Correct arguments (they're not used inside method)
        ClientServices::Connection()->AccountLogin("", "", 0, 0);
        return;
    }

    CGlueMgr::SetScreen("charselect");
}

void CGlueMgr::PollLoginServerLogin() {
    if (CGlueMgr::m_loginState != LOGIN_STATE_PIN_WAIT) {
        CGlueMgr::DisplayLoginStatus();
    }

    // Open new client connection after successful authentication
    if (CGlueMgr::m_authenticated) {
        CGlueMgr::m_idleState = IDLE_NONE;
        CGlueMgr::m_showedDisconnect = 0;
        CGlueMgr::Sub4D8BA0();
        CGlueMgr::m_authenticated = false;

        return;
    }

    switch (CGlueMgr::m_loginState) {
    case LOGIN_STATE_FAILED: {
        ClientServices::LoginConnection()->Logoff();
        CGlueMgr::m_idleState = IDLE_NONE;
        CGlueMgr::m_showedDisconnect = 0;
        break;
    }

    case LOGIN_STATE_DOWNLOADFILE: {
        // TODO
        // Get String from Server's answer
        // v14 = (char *)(ClientServices::LoginConnection() + 3928);
        const char* v14 = "";
        if (!SStrCmpI(v14, "Patch", STORM_MAX_STR)) {
            CGlueMgr::PatchDownloadStart();
        }
        if (!SStrCmpI(v14, "Survey", STORM_MAX_STR)) {
            CGlueMgr::SurveyDownloadStart();
        }
        break;
    }

    case LOGIN_STATE_PIN: {
        FrameScript_SignalEvent(5, nullptr);
        // TODO
        // Calling GruntLogin::GetPinInfo
        // v9 = (unsigned __int8 *)(*(int (__thiscall **)(int))(*(_DWORD *)v8 + 136))(v8);
        int32_t v9[10] = {};
        FrameScript_SignalEvent(26, "%d%d%d%d%d%d%d%d%d%d", v9[0], v9[1], v9[2], v9[3], v9[4], v9[5], v9[6], v9[7], v9[8], v9[9]);
        CGlueMgr::m_loginState = LOGIN_STATE_PIN_WAIT;
        break;
    }

    case LOGIN_STATE_MATRIX: {
        // TODO
        // Calling GruntLogin::GetMatrixInfo
        // (*(void (__thiscall **)(int, int *, int *, int *, int *, unsigned __int8 *, _DWORD **))(*(_DWORD *)v10 + 160))(v10, &v73, &v69, &v72, &v70, &v77, &v75);
        CGlueMgr::m_matrixChallengeCount = 0;
        CGlueMgr::m_matrixRemaining = 0;
        CGlueMgr::m_loginState = LOGIN_STATE_MATRIX_WAIT;
        FrameScript_SignalEvent(5, 0);
        // TODO: FrameScript_SignalEvent(0x1Cu, "%d%d%d%d%b%d", v73, v69, v72, v70, v77, v75);
        break;
    }

    case LOGIN_STATE_TOKEN: {
        CGlueMgr::m_loginState = LOGIN_STATE_TOKEN_WAIT;
        FrameScript_SignalEvent(5, 0);
        FrameScript_SignalEvent(38, 0);
        break;
    }

    case LOGIN_STATE_CHECKINGVERSIONS: {
        uint8_t versionChecksum[20];
        // TODO
        // uint8_t* versionChallenge = ClientServices::LoginConnection()->GetVersionChallenge();
        // ChecksumExecutables(versionChallenge, 16, versionChecksum);
        ClientServices::LoginConnection()->ProveVersion(versionChecksum);

        break;
    }

    default:
        break;
    }
}

void CGlueMgr::PollCharacterList(int32_t errorCode, const char* msg, int32_t complete, int32_t result, WOWCS_OPS op) {
    FrameScript_SignalEvent(4, "%s", msg);

    // TODO: if (CGlueMgr::HandleBattlenetDisconnect())
    if (false) {
        CGlueMgr::m_idleState = IDLE_NONE;
        CGlueMgr::m_showedDisconnect = 0;
    }

    if (!complete) {
        return;
    }

    if (!result) {
        if (errorCode == 2) {
            // TODO CCharacterSelection::ClearCharacterList();
            CGlueMgr::GetRealmList(true);
        } else {
            FrameScript_SignalEvent(3, "%s%s", "OKAY", msg);
            CGlueMgr::m_idleState = IDLE_NONE;
            CGlueMgr::m_showedDisconnect = 0;
        }
        return;
    }

    CGlueMgr::m_idleState = IDLE_NONE;
    CGlueMgr::m_showedDisconnect = 0;
    FrameScript_SignalEvent(5, nullptr);
    // TODO: sub_4E4610();
    if (!CGlueMgr::m_accountMsgAvailable) {
        return;
    }

    FrameScript_SignalEvent(34, 0);
    CGlueMgr::m_accountMsgAvailable = 0;
}

void CGlueMgr::PollUserSurvey() {
    if (CGlueMgr::m_surveyDownload && false /* virtual call */) {
        if (CGlueMgr::m_executedSurvey) {
            // TODO
        } else {
            CGlueMgr::m_executedSurvey = 1;
            if (CGlueMgr::SurveyExecute()) {
                auto text = FrameScript_GetText("LOGIN_STATE_SURVEY", -1, FRAMESCRIPT_GENDER::GENDER_NOT_APPLICABLE);
                FrameScript_SignalEvent(3, "%s%s", "CANCEL", text);
                CGlueMgr::SurveySendResults();
                // TODO: CGlueMgr::m_surveyTimer = OsGetAsyncTimeMs();
            }
        }
    }
}

void CGlueMgr::InitCursor() {
    uint32_t width;
    uint32_t height;
    PIXEL_FORMAT format = PIXEL_ARGB8888;

    CGlueMgr::m_cursorMipBits = TextureLoadImage("Interface\\Cursor\\Point.blp", &width, &height, &format, nullptr, nullptr, nullptr, 0);

    STORM_ASSERT(width == 32);
    STORM_ASSERT(height == 32);
    STORM_ASSERT(format == PIXEL_ARGB8888);

    CGlueMgr::m_simpleTop->SetCursor(CGlueMgr::m_cursorMipBits);
}

void CGlueMgr::Resume() {
    // TODO
    // CGlueMgr::m_disconnectPending = 0;
    // CGlueMgr::m_reconnect = 0;

    CGlueMgr::m_idleState = IDLE_NONE;

    // TODO
    // CGlueMgr::m_showedDisconnect = 0;
    // CGlueMgr::m_characterInfo = 0;

    CGlueMgr::m_suspended = 0;
    CGlueMgr::m_reload = 0;

    CRect screenRect;
    g_theGxDevicePtr->CapsWindowSizeInScreenCoords(screenRect);
    CGlueMgr::m_screenWidth = screenRect.maxX - screenRect.minX;
    CGlueMgr::m_screenHeight = screenRect.maxY - screenRect.minY;
    CGlueMgr::m_aspect = CalculateAspectRatio();
    CoordinateSetAspectRatio(CGlueMgr::m_aspect);

    // Create CSimpleTop
    auto m = SMemAlloc(sizeof(CSimpleTop), __FILE__, __LINE__, 0x0);
    auto top = new (m) CSimpleTop();

    CGlueMgr::m_simpleTop = top;
    CGlueMgr::m_simpleTop->m_displaySizeCallback = &CGlueMgr::HandleDisplaySizeChanged;

    CGlueMgr::InitCursor();

    // TODO
    // - setting mouse mode

    FrameScript_Flush();

    SystemRegisterFunctions();
    RegisterSimpleFrameScriptMethods();
    GlueScriptEventsRegisterFunctions();
    CharSelectRegisterScriptFunctions();
    CharacterCreateRegisterScriptFunctions();
    RealmListRegisterScriptFunctions();
    SI2::RegisterScriptFunctions();

    // TODO
    // AccountMsg_RegisterScriptFunctions();

    CGVideoOptions::RegisterScriptFunctions();

    // TODO
    // FrameScript::s_scriptFunctionsLoaded = 1;

    FrameScript_CreateEvents(g_glueScriptEvents, NUM_GLUE_SCRIPT_EVENTS);

    OsCreateDirectory("Logs", 0);

    CWOWClientStatus status;

    if (!SLogCreate("Logs\\GlueXML.log", 0, &status.m_logFile)) {
        SysMsgPrintf(SYSMSG_WARNING, "Cannot create WOWClient log file \"%s\"!", "Logs\\GlueXML.log");
    }

    DeleteInterfaceFiles();

    MD5_CTX md5;
    unsigned char digest1[16];
    unsigned char digest2[16];

    int32_t v8;
    unsigned char* v9;
    unsigned char* v10;

    MD5Init(&md5);

    switch (FrameXML_CheckSignature("Interface\\GlueXML\\GlueXML.toc", 0, InterfaceKey, digest1)) {
        case 0:
            status.Add(STATUS_WARNING, "GlueXML missing signature");
            ClientPostClose(9);
            return;

        case 1:
            status.Add(STATUS_WARNING, "GlueXML has corrupt signature");
            ClientPostClose(9);
            return;

        case 2:
            status.Add(STATUS_WARNING, "GlueXML is modified or corrupt");
            ClientPostClose(9);
            return;

        case 3:
            FrameXML_FreeHashNodes();
            FrameXML_CreateFrames("Interface\\GlueXML\\GlueXML.toc", 0, &md5, &status);

            MD5Final(digest2, &md5);

            v8 = 16;
            v9 = digest2;
            v10 = digest1;

            break;

        default:
            ClientPostClose(9);
            return;
    }

    // TODO
    // - some kind of digest validation?

    FrameScript_SignalEvent(22, nullptr);

    // TODO
    // CGlueMgr::InitializeFFX();

    // TODO
    // ClientServices::SetMessageHandler(SMSG_CHARACTER_RENAME_RESULT, CGlueMgr::OnCharRenameResult, 0);
    // ClientServices::SetMessageHandler(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, CGlueMgr::OnCharDeclineResult, 0);
    // ClientServices::SetMessageHandler(SMSG_CHAR_CUSTOMIZE, CGlueMgr::OnCharCustomizeResult, 0);
    // ClientServices::SetMessageHandler(SMSG_REALM_SPLIT, CGlueMgr::OnRealmSplitMsg, 0);
    // ClientServices::SetMessageHandler(SMSG_KICK_REASON, CGlueMgr::OnKickReasonMsg, 0);
    // ClientServices::SetMessageHandler(SMSG_CHAR_FACTION_CHANGE, CGlueMgr::OnCharFactionChangeResult, 0);

    // TODO
    // CGlueMgr::m_pendingServerAlert = 1;
    // CGlueMgr::m_processServerAlert = 0;
    // CGlueMgr::m_serverAlert[0] = 0;
    // v21 = (const char *)ClientServices::GetServerAlertURL();
    // v22 = (const CHAR *)FrameScript_GetText(v21, -1, 0);
    // if (!OsURLDownload(v22, CGlueMgr::ServerAlertURLCallback, 0)) {
    //     CGlueMgr::m_pendingServerAlert = 0;
    // }
}

void CGlueMgr::SetCurrentAccount(const char* accountName) {
    SStrCopy(CGlueMgr::m_accountName, accountName, sizeof(CGlueMgr::m_accountName));
    SStrUpper(CGlueMgr::m_accountName);
}

void CGlueMgr::SetLoginStateAndResult(LOGIN_STATE state, LOGIN_RESULT result, const char* addrStr, const char* stateStr, const char* resultStr, uint8_t flags) {
    // TODO
    // CGlueMgr::LogConnectionStatus("GRUNT: state: %s result: %s %s", a4);

    if (flags & 0x1) {
        CGlueMgr::m_accountMsgAvailable = 1;
    }

    if (state == LOGIN_STATE_AUTHENTICATED) {
        CGlueMgr::m_authenticated = true;
    }

    CGlueMgr::m_loginState = state;
    CGlueMgr::m_loginResult = result;
}

void CGlueMgr::SetScreen(const char* screen) {
    FrameScript_SignalEvent(0, "%s", screen);
}

void CGlueMgr::StatusDialogClick() {
    if (!SStrCmpI(CGlueMgr::m_currentScreen, "patchdownload", STORM_MAX_STR)) {
        CGlueMgr::SetScreen("login");
    }

    switch (CGlueMgr::m_idleState) {
        case IDLE_NONE: {
            ClientServices::Connection()->Cleanup();

            break;
        }

        case IDLE_LOGIN_SERVER_LOGIN: {
            ClientServices::LoginConnection()->Logoff();

            CGlueMgr::m_showedDisconnect = 0;
            CGlueMgr::m_idleState = IDLE_NONE;

            break;
        }

        case IDLE_ACCOUNT_LOGIN:
        case IDLE_CHARACTER_LIST: {
            ClientServices::Connection()->Cancel(2);

            break;
        }

        case IDLE_REALM_LIST:
        case IDLE_5:
        case IDLE_6:
        case IDLE_10: {
            ClientServices::Connection()->Cancel(2);

            CGlueMgr::m_showedDisconnect = 0;
            CGlueMgr::m_idleState = IDLE_NONE;

            break;
        }

        case IDLE_7:
        case IDLE_8:
        case IDLE_9: {
            CGlueMgr::m_showedDisconnect = 0;
            CGlueMgr::m_idleState = IDLE_NONE;

            break;
        }

        case IDLE_WORLD_LOGIN: {
            CGlueMgr::m_showedDisconnect = 0;
            CGlueMgr::m_idleState = IDLE_NONE;
            CGlueMgr::GetCharacterList();

            break;
        }

        case IDLE_12:
        case IDLE_13: {
            if (CGlueMgr::m_surveyDownload) {
                CGlueMgr::SurveyDownloadCancel();
                CGlueMgr::m_showedDisconnect = 0;
                CGlueMgr::m_idleState = IDLE_NONE;
            }
            break;
        }
    }
}

void CGlueMgr::Sub4D8BA0() {
    if (CGlueMgr::m_idleState != IDLE_NONE) {
        return;
    }

    CGlueMgr::m_idleState = IDLE_ACCOUNT_LOGIN;
    CGlueMgr::m_showedDisconnect = 0;
    ClientServices::Connection()->Connect();
}

void CGlueMgr::Suspend() {
    CGlueMgr::m_suspended = 1;

    // TODO

    if (CGlueMgr::m_simpleTop) {
        delete CGlueMgr::m_simpleTop;
        CGlueMgr::m_simpleTop = nullptr;
    }

    // TODO

    FrameXML_FreeHashNodes();

    // TODO
}

void CGlueMgr::UpdateCurrentScreen(const char* screen) {
    // TODO

    SStrCopy(CGlueMgr::m_currentScreen, screen, sizeof(CGlueMgr::m_currentScreen));

    // TODO
}

void CGlueMgr::SurveyDownloadStart() {
}

void CGlueMgr::SurveyDownloadCancel() {
}

void CGlueMgr::SurveyDownloadIdle() {
}

bool CGlueMgr::SurveyExecute() {
    return false;
}

void CGlueMgr::SurveySendResults() {
}

void CGlueMgr::PatchDownloadStart() {
}

void CGlueMgr::PatchDownloadIdle() {
}
