#include "pch.h"
#include "vehicle.h"
#include <CSprite.h>
#include "overlay.h"

#ifdef GTASA
#include "teleport.h"
#include "weapon.h"
#include "vehicle.h"
#endif

void Overlay::Init()
{
    // TODO: use structs
    // Load config data
    m_bCoord = gConfig.Get("Overlay.ShowCoordinates", false);
    m_bCpuUsage = gConfig.Get("Overlay.ShowCPUUsage", false);
    m_bFPS = gConfig.Get("Overlay.ShowFPS", false);
    m_bLocName = gConfig.Get("Overlay.ShowLocationName", false);
    m_bModelInfo = gConfig.Get("Overlay.ShowModelInfo", false);
    m_bPedTasks = gConfig.Get("Overlay.ShowPedTasks", false);
    m_bTransparent = gConfig.Get("Overlay.Transparent", false);
    m_bMemUsage = gConfig.Get("Overlay.ShowMemoryUsage", false);
    m_bVehHealth = gConfig.Get("Overlay.ShowVehicleHealth", false);
    m_bVehSpeed = gConfig.Get("Overlay.ShowVehicleSpeed", false);
    m_nSelectedPos = (DisplayPos)gConfig.Get("Overlay.SelectedPosition", (int)DisplayPos::BOTTOM_RIGHT);
    m_fPos.x = gConfig.Get("Overlay.PosX", 0);
    m_fPos.y = gConfig.Get("Overlay.PosY", 0);
    m_fTextCol[0] = gConfig.Get("Overlay.TextColor.Red", 1.0f);
    m_fTextCol[1] = gConfig.Get("Overlay.TextColor.Green", 1.0f);
    m_fTextCol[2] = gConfig.Get("Overlay.TextColor.Blue", 1.0f);
    m_fTextCol[3] = gConfig.Get("Overlay.TextColor.Alpha", 1.0f);

    Util::GetCPUUsageInit();
    MEMORYSTATUSEX memInfo;
    memInfo.dwLength = sizeof(MEMORYSTATUSEX);
    GlobalMemoryStatusEx(&memInfo);

    m_nTotalMem = static_cast<int>(memInfo.ullTotalPhys * 1e-6); // Bytes -> MegaBytes

    ThiscallEvent<AddressList<BY_GAME(0x5343B2, 0x48882E, 0x474BC0), H_CALL>, PRIORITY_BEFORE, ArgPickN<CEntity*, 0>, void(CEntity*)> preRenderEntityEvent;

#ifndef GTASA
    patch::Nop(BY_GAME(NULL, 0x488828, 0x474BBA), 4);
#endif

    // Directly drawing here seems to crash renderer?
    preRenderEntityEvent += [](CEntity *pEnt)
    {   
        CPlayerPed *player = FindPlayerPed();
        if (player != pEnt)
        {
            CVector coord = pEnt->GetPosition();
            CVector plaPos = player->GetPosition();

            CColPoint outColPoint;
            if (BY_GAME(pEnt->m_bIsVisible, pEnt->IsVisible(), pEnt->IsVisible()))
            {
                m_EntityList.push_back(pEnt);
            }
    #ifdef GTAVC
            if (CModelInfo::GetModelInfo(pEnt->m_nModelIndex)->m_nNum2dEffects > 0)
            {
                pEnt->ProcessLightsForEntity();
            }
    #elif GTA3
        // if (CModelInfo::ms_modelInfoPtrs[pEnt->m_nModelIndex]->m_nNum2dEffects > 0)
        // {
        //     pEnt->ProcessLightsForEntity();
        // }
    #endif
        }
    };
}

void Overlay::ProcessModelInfo()
{
    if (m_bModelInfo)
    {
        ImDrawList *pDrawList = ImGui::GetWindowDrawList();
        CPlayerPed *player = FindPlayerPed();
        for (CEntity *pEnt : m_EntityList)
        {   
            if (pEnt == player)
            {
                continue;
            }

            CVector coord = BY_GAME(,,*)pEnt->GetBoundCentre();
            float distance = DistanceBetweenPoints(coord, player->GetPosition());
            RwV3d screen;
            CVector2D size;
            if (distance < m_fMaxDistance &&
#ifdef GTASA                
            CSprite::CalcScreenCoors(coord.ToRwV3d(), &screen, &size.x, &size.y, true, true)
#else 
            CSprite::CalcScreenCoors(coord.ToRwV3d(), &screen, &size.x, &size.y, true)
#endif
)
            {
                bool skip = false;
                uint model = pEnt->m_nModelIndex;
                std::string text = std::to_string(model);
                ImU32 col = ImGui::ColorConvertFloat4ToU32(distance < m_fMaxDistance/2 ? ImVec4(1.0f, 1.0f, 1.0f, 1.00f) : ImVec4(0.35f, 0.33f, 0.3f, 1.00f));
#ifdef GTASA
                if (pEnt->m_nType == ENTITY_TYPE_VEHICLE)
                {
                    text = std::format("{}\n{}", model, Vehicle::GetNameFromModel(model));
                }
                else if (pEnt->m_nType == ENTITY_TYPE_PED)
                {
                    CPed *ped = static_cast<CPed*>(pEnt);
                    if (BY_GAME(ped->m_nPedFlags.bInVehicle, ped->m_bInVehicle, ped->m_bInVehicle))
                    {
                        skip = true;
                    }
                }
#endif

                if (!skip)
                {
                    pDrawList->AddText(ImVec2(screen.x, screen.y), col, text.c_str());
                }
            }
        }
    }
}

void Overlay::ProcessPedTasks()
{
    if (m_bPedTasks)
    {
        ImDrawList *pDrawList = ImGui::GetWindowDrawList();
        CPlayerPed *player = FindPlayerPed();
        for (CEntity *pEnt : m_EntityList)
        {   
            if (pEnt == player || pEnt->m_nType != ENTITY_TYPE_PED)
            {
                continue;
            }
            CPed *pPed = static_cast<CPed*>(pEnt);
            CVector coord = BY_GAME(,,*)pPed->GetBoundCentre();
            float distance = DistanceBetweenPoints(coord, player->GetPosition());
            RwV3d screen;
            CVector2D size;
            if (distance < m_fMaxDistance 
            && CSprite::CalcScreenCoors(coord.ToRwV3d(), &screen, &size.x, &size.y, true, true))
            {
                ImU32 col = ImGui::ColorConvertFloat4ToU32(distance < m_fMaxDistance/2 ? ImVec4(1.0f, 1.0f, 1.0f, 1.00f) : ImVec4(0.35f, 0.33f, 0.3f, 1.00f));
                float height = ImGui::GetTextLineHeight();
                screen.y -= 2 * height;

                for (size_t i = 0; i != TASK_SECONDARY_MAX; ++i)
                {
                    CTask *pTask = pPed->m_pIntelligence->m_TaskMgr.m_aSecondaryTasks[i];
                    if (pTask)
                    {
                        const char *name = taskNames[pTask->GetId()];
                        pDrawList->AddText(ImVec2(screen.x, screen.y), col, name);
                        screen.y -= height;
                    }
                }
                for (size_t i = 0; i != TASK_PRIMARY_MAX; ++i)
                {
                    CTask *pTask = pPed->m_pIntelligence->m_TaskMgr.m_aPrimaryTasks[i];
                    if (pTask)
                    {
                        const char *name = taskNames[pTask->GetId()];
                        pDrawList->AddText(ImVec2(screen.x, screen.y), col, name);
                        screen.y -= height;
                    }
                }
            }
        }
    }
}

void Overlay::Draw()
{
    if (FrontEndMenuManager.m_bMenuActive)
    {
        return;
    }

    ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove 
                            | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNavFocus
                            | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing;
    ImGui::SetNextWindowPos(ImVec2(0, 0));
    ImGui::SetNextWindowSize(ImVec2(screen::GetScreenWidth(), screen::GetScreenHeight()));
    ImGui::SetNextWindowBgAlpha(0.0f);
    if (ImGui::Begin("##Overlay", NULL, flags))
    {
        ProcessModelInfo();
        ProcessPedTasks();
        ImGui::End();
    }

    ProcessInfoBox();
    ProcessCmdBar();

    // clear the list here
    m_EntityList.clear();
}

void Overlay::ProcessCmdBar()
{
    if (m_bCmdBar)
    {
        int resX = static_cast<int>(screen::GetScreenWidth());
        int resY = static_cast<int>(screen::GetScreenHeight());

        ImGui::SetNextWindowPos(ImVec2(0, resY - 40), ImGuiCond_Always);
        ImGui::SetNextWindowSize(ImVec2(resX, 40));

        ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration + ImGuiWindowFlags_AlwaysAutoResize +
                                ImGuiWindowFlags_NoSavedSettings
                                + ImGuiWindowFlags_NoMove;
        if (ImGui::Begin("CmdBar", nullptr, flags))
        {
            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, resY / 130));
            ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(3, 3));

            ImGui::SetNextItemWidth(ImGui::GetContentRegionMax().x);

            static char buf[INPUT_BUFFER_SIZE] = "";
            if (ImGui::InputTextWithHint("##TEXTFIELD", "Enter command", buf, INPUT_BUFFER_SIZE, ImGuiInputTextFlags_EnterReturnsTrue))
            {
                ProcessCommands(std::string(buf));
                m_bCmdBar = false;
                strcpy(buf, "");
            }
            if (!ImGui::IsAnyItemActive())
            {
                ImGui::SetKeyboardFocusHere(-1);
            }
            ImGui::PopStyleVar(2);
            ImGui::End();
        }
    }
}

void Overlay::ProcessCommands(std::string&& str)
{
    std::stringstream ss(str);

    std::string command;
    ss >> command;

    if (command == "armour")
    {
        try
        {
            std::string temp;
            ss >> temp;
            FindPlayerPed()->m_fArmour = std::stof(temp);
        }
        catch (...)
        {
            Util::SetMessage(TEXT("Menu.InvalidValue"));
        }
    }

    if (command == "hp")
    {
        try
        {
            std::string temp;
            ss >> temp;
            FindPlayerPed()->m_fHealth = std::stof(temp);
        }
        catch (...)
        {
            Util::SetMessage(TEXT("Menu.InvalidValue"));
        }
    }

    if (command == "time")
    {
        try
        {
            std::string temp;
            ss >> temp;
            CClock::ms_nGameClockHours = std::stoi(temp);

            ss >> temp;
            CClock::ms_nGameClockMinutes = std::stoi(temp);
        }
        catch (...)
        {
            Util::SetMessage(TEXT("Menu.InvalidValue"));
        }
    }

#ifdef GTASA
    if (command == "tp")
    {
        try
        {
            CVector pos;
            std::string temp;

            ss >> temp;
            pos.x = std::stof(temp);

            ss >> temp;
            pos.y = std::stof(temp);

            ss >> temp;
            pos.z = std::stof(temp);

            Teleport::WarpPlayer(pos);
        }
        catch (...)
        {
            Util::SetMessage(TEXT("Menu.InvalidLocation"));
        }
    }

    if (command == "wep")
    {
        std::string wep_name;
        ss >> wep_name;

        if (wep_name == "jetpack")
        {
            std::string weapon = "-1";
            Weapon::GiveWeaponToPlayer(weapon);
            Util::SetMessage(TEXT("Menu.WeaponSpawned"));
        }
        else
        {
            eWeaponType weapon = CWeaponInfo::FindWeaponType((char*)wep_name.c_str());
            std::string weapon_name = std::to_string(weapon);
            CWeaponInfo* pweaponinfo = CWeaponInfo::GetWeaponInfo(weapon, 1);

            if (wep_name != "" && pweaponinfo->m_nModelId1 != -1)
            {
                Weapon::GiveWeaponToPlayer(weapon_name);
                Util::SetMessage(TEXT("Menu.WeaponSpawned"));
            }
            else
                Util::SetMessage(TEXT("Menu.InvalidComamnd"));
        }

        return;
    }
    if (command == "veh")
    {
        std::string veh_name;
        ss >> veh_name;

        int model = Vehicle::GetModelFromName(veh_name.c_str());
        if (model != 0)
        {
            std::string smodel = std::to_string(model);
            Vehicle::SpawnVehicle(smodel);
            Util::SetMessage(TEXT("Menu.VehicleSpawned"));
        }
        else
            Util::SetMessage(TEXT("Menu.InvalidComamnd"));
    }
#endif
}

void Overlay::ProcessInfoBox()
{
    CPlayerPed* pPlayer = FindPlayerPed();
    if (pPlayer)
    {
        bool m_bShowMenu = m_bCoord || m_bFPS || m_bLocName || m_bCpuUsage || m_bMemUsage ||
                           ((m_bVehHealth || m_bVehSpeed) && pPlayer && pPlayer->m_pVehicle && pPlayer->m_pVehicle->m_pDriver == pPlayer);

        const float offset = 10.0f;
        ImGuiIO& io = ImGui::GetIO();
        ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
                                        ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;

        if (m_nSelectedPos == DisplayPos::CUSTOM)
        {
            if (m_fPos.x != NULL && m_fPos.y != NULL)
            {
                gConfig.Set("Overlay.PosX", m_fPos.x);
                gConfig.Set("Overlay.PosY", m_fPos.y);
                ImGui::SetNextWindowPos(m_fPos, ImGuiCond_Once);
            }
        }
        else
        {
            window_flags |= ImGuiWindowFlags_NoMove;
            ImVec2 pos, pivot;

            if (m_nSelectedPos == DisplayPos::TOP_LEFT)
            {
                pos = ImVec2(offset, offset);
                pivot = ImVec2(0.0f, 0.0f);
            }

            if (m_nSelectedPos == DisplayPos::TOP_RIGHT)
            {
                pos = ImVec2(io.DisplaySize.x - offset, offset);
                pivot = ImVec2(1.0f, 0.0f);
            }

            if (m_nSelectedPos == DisplayPos::BOTTOM_LEFT)
            {
                pos = ImVec2(offset, io.DisplaySize.y - offset);
                pivot = ImVec2(0.0f, 1.0f);
            }

            if (m_nSelectedPos == DisplayPos::BOTTOM_RIGHT)
            {
                pos = ImVec2(io.DisplaySize.x - offset, io.DisplaySize.y - offset);
                pivot = ImVec2(1.0f, 1.0f);
            }

            ImGui::SetNextWindowPos(pos, ImGuiCond_Always, pivot);
        }

        ImGui::SetNextWindowBgAlpha(m_bTransparent ? 0.0f : 0.5f);
        ImGui::PushStyleColor(ImGuiCol_Text, *(ImVec4*)&m_fTextCol);
        if (m_bShowMenu && ImGui::Begin("Overlay", nullptr, window_flags))
        {
            CVector pos{0,0,0};
            pos = pPlayer->GetPosition();

            size_t game_ms = CTimer::m_snTimeInMilliseconds;
            static size_t interval = 0;
            static float cpuUsage = 0, memUsage = 0;
            if (game_ms - interval > 1000)
            {
                cpuUsage = static_cast<float>(Util::GetCurrentCPUUsage());

                MEMORYSTATUSEX memInfo;
                memInfo.dwLength = sizeof(MEMORYSTATUSEX);
                GlobalMemoryStatusEx(&memInfo);
                int mUsedRam = static_cast<int>((memInfo.ullTotalPhys - memInfo.ullAvailPhys) * 1e-6);
                memUsage = 100.0f * (static_cast<float>(mUsedRam) / static_cast<float>(m_nTotalMem));

                m_nFPS = static_cast<size_t>(BY_GAME(CTimer::game_FPS, io.Framerate, io.Framerate));
                interval = game_ms;
            }

            if (m_bCoord)
            {
                ImGui::Text(TEXT("Menu.Coords"), pos.x, pos.y, pos.z);
            }

            if (m_bCpuUsage)
            {
                ImGui::Text(TEXT("Menu.CPUUsage"), cpuUsage);
            }

            if (m_bFPS)
            {
                ImGui::Text(TEXT("Menu.Frames"), m_nFPS);
            }

            if (m_bLocName)
            {
                ImGui::Text(TEXT("Menu.Location"), Util::GetLocationName(&pos).c_str());
            }

            if (m_bMemUsage)
            {
                ImGui::Text(TEXT("Menu.RAMUsage"), memUsage);
            }

            if (pPlayer->m_pVehicle && pPlayer->m_pVehicle->m_pDriver == pPlayer)
            {
                if (m_bVehHealth)
                {
                    ImGui::Text((TEXT_S("Menu.VehHealth") + ": %.f").c_str(), pPlayer->m_pVehicle->m_fHealth);
                }

                if (m_bVehSpeed)
                {
                    int speed = pPlayer->m_pVehicle->m_vecMoveSpeed.Magnitude() * 50.0f; // 02E3 - GET_CAR_SPEED
                    ImGui::Text(TEXT("Menu.VehSpeed"), speed);
                }
            }

            ImVec2 windowPos = ImGui::GetWindowPos();
            m_fPos = windowPos;

            ImGui::End();
        }
        ImGui::PopStyleColor();
    }
}