#include "render.h"

ID3D11Device* renderer::g_pd3dDevice = nullptr;
ID3D11DeviceContext* renderer::g_pd3dDeviceContext = nullptr;
IDXGISwapChain* renderer::g_pSwapChain = nullptr;
bool renderer::g_SwapChainOccluded = false, renderer::g_OverlayVisible = false;
UINT renderer::g_ResizeWidth = 0, renderer::g_ResizeHeight = 0;
ID3D11RenderTargetView* renderer::g_mainRenderTargetView = nullptr;

void renderer::draw() {
    ImGui_ImplWin32_EnableDpiAwareness();

    WNDCLASSEX wc = {
        sizeof(WNDCLASSEX),
        CS_VREDRAW | CS_HREDRAW,
        WndProc,
        0,
        0,
        GetModuleHandle(nullptr),
        LoadIcon(nullptr, IDI_APPLICATION),
        LoadCursor(nullptr, IDC_ARROW),
        CreateSolidBrush(RGB(0, 0, 0)),
        nullptr,
        "ImGui Example",
        LoadIcon(nullptr, IDI_APPLICATION)
    };

    ::RegisterClassEx(&wc);
    auto hwnd = ::CreateWindowEx(WS_EX_TOPMOST, wc.lpszClassName, "Dear ImGui DirectX11 Example", WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), nullptr, nullptr, wc.hInstance, nullptr);
    rbx::globals::our_window = hwnd;

    SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 255, LWA_ALPHA);

    MARGINS margin = { -1 };
    DwmExtendFrameIntoClientArea(hwnd, &margin);

    if (!CreateDeviceD3D(hwnd))
    {
        CleanupDeviceD3D();
        ::UnregisterClass(wc.lpszClassName, wc.hInstance);
        return;
    }

    ::ShowWindow(hwnd, SW_SHOWDEFAULT);
    ::UpdateWindow(hwnd);

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;

    ImGui::StyleColorsDark();

    ImGui_ImplWin32_Init(hwnd);
    ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);

    ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);

    bool done = false;
    while (!done) {
        MSG msg;
        while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
            if (msg.message == WM_QUIT)
                done = true;
        }
        if (done)
            break;

        if (g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED)
        {
            ::Sleep(10);
            continue;
        }
        g_SwapChainOccluded = false;

        if (g_ResizeWidth != 0 && g_ResizeHeight != 0)
        {
            CleanupRenderTarget();
            g_pSwapChain->ResizeBuffers(0, g_ResizeWidth, g_ResizeHeight, DXGI_FORMAT_UNKNOWN, 0);
            g_ResizeWidth = g_ResizeHeight = 0;
            CreateRenderTarget();
        }

        if (GetAsyncKeyState(VK_INSERT) & 1)
            g_OverlayVisible = !g_OverlayVisible;

        if (GetAsyncKeyState(VK_END) & 1)
            exit(0);

        ImGui_ImplDX11_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();

        rbx::visuals::render();

        if (g_OverlayVisible) {
            ImGui::Begin("glacier_studios");

            if (ImGui::BeginTabBar("##tabbar")) {
                if (ImGui::BeginTabItem("main")) {
                    ImGui::Text("welcome!");
                    {
                        std::lock_guard<std::mutex> v(rbx::cache::mutex_v);
                        std::lock_guard<std::mutex> c(rbx::cache::mutex_c);
                        ImGui::Text("visual cache : %zu", rbx::cache::targets_v.size());
                        ImGui::Text("combat cache : %zu", rbx::cache::targets_c.size());
                    }
                    ImGui::EndTabItem();
                }

                if (ImGui::BeginTabItem("visuals")) {
                    ImGui::Checkbox("esp", &rbx::cheats::visuals::esp);
                    ImGui::Combo("style", &rbx::cheats::visuals::esp_style, "box\0corner");
                    ImGui::Checkbox("filled", &rbx::cheats::visuals::filled);
                    ImGui::Checkbox("skeleton", &rbx::cheats::visuals::skeleton);
                    ImGui::Separator();
                    ImGui::Checkbox("name", &rbx::cheats::visuals::name);
                    ImGui::Checkbox("health", &rbx::cheats::visuals::health);
                    ImGui::Checkbox("distance", &rbx::cheats::visuals::distance);
                    ImGui::Separator();
                    ImGui::Checkbox("tracers", &rbx::cheats::visuals::tracers);
                    ImGui::Checkbox("head dot", &rbx::cheats::visuals::head_dot);
                    ImGui::Checkbox("outlines", &rbx::cheats::visuals::outline);
                    ImGui::Separator();
                    ImGui::SliderFloat("min health", &rbx::cheats::visuals::min_health, 0.f, 100.f, "%.f");
                    ImGui::SliderFloat("max dist", &rbx::cheats::visuals::max_distance, 100.f, 5000.f, "%.f");

                    std::string label;
                    for (int i = 0; i < 5; i++) {
                        if (rbx::cheats::visuals::flags_vals[i]) {
                            if (!label.empty()) label += ",";
                            label += rbx::cheats::visuals::flags[i];
                        }
                    }

                    if (label.empty()) label = " ...";

                    if (ImGui::BeginCombo("flags", label.c_str())) {
                        for (int i = 0; i < 5; i++) {
                            bool enabled = rbx::cheats::visuals::flags_vals[i];
                            if (ImGui::Selectable(rbx::cheats::visuals::flags[i], enabled)) {
                                rbx::cheats::visuals::flags_vals[i] = !rbx::cheats::visuals::flags_vals[i];
                            }

                            if (rbx::cheats::visuals::flags_vals[i]) {
                                ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - 20);
                                ImGui::TextUnformatted("-");
                            }
                        }
                        ImGui::EndCombo();
                    }

                    ImGui::EndTabItem();
                }

                if (ImGui::BeginTabItem("combat")) {
                    ImGui::Checkbox("aimbot", &rbx::cheats::combat::aimbot);
                    ImGui::Combo("type", &rbx::cheats::combat::aimbot_type, "camera\0mouse");
                    ImGui::Checkbox("fov", &rbx::cheats::combat::fov);
                    ImGui::Checkbox("use fov", &rbx::cheats::combat::use_fov);
                    ImGui::Separator();
                    ImGui::SliderFloat("fov radius", &rbx::cheats::combat::fov_radius, 10.f, 1000.f, "%.f");
                    ImGui::SliderFloat("min health", &rbx::cheats::combat::min_health, 0.f, 100.f, "%.f");
                    ImGui::SliderFloat("max dist", &rbx::cheats::combat::max_distance, 100.f, 5000.f, "%.f");

                    std::string label;
                    for (int i = 0; i < 4; i++) {
                        if (rbx::cheats::combat::flags_vals[i]) {
                            if (!label.empty()) label += ",";
                            label += rbx::cheats::combat::flags[i];
                        }
                    }

                    if (label.empty()) label = " ...";

                    if (ImGui::BeginCombo("flags", label.c_str())) {
                        for (int i = 0; i < 4; i++) {
                            bool enabled = rbx::cheats::combat::flags_vals[i];
                            if (ImGui::Selectable(rbx::cheats::combat::flags[i], enabled)) {
                                rbx::cheats::combat::flags_vals[i] = !rbx::cheats::combat::flags_vals[i];
                            }

                            if (rbx::cheats::combat::flags_vals[i]) {
                                ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - 20);
                                ImGui::TextUnformatted("-");
                            }
                        }
                        ImGui::EndCombo();
                    }

                    ImGui::EndTabItem();
                }

                if (ImGui::BeginTabItem("player")) {
                    ImGui::Checkbox("speed", &rbx::cheats::player::speed);
                    if (rbx::cheats::player::speed) {
                        ImGui::Indent();
                        ImGui::SliderFloat("value##s", &rbx::cheats::player::speed_val, 0.f, 300.f, "%.2f");
                        ImGui::Unindent();
                    }

                    ImGui::Checkbox("jumppower", &rbx::cheats::player::jump);
                    if (rbx::cheats::player::jump) {
                        ImGui::Indent();
                        ImGui::SliderFloat("value##j", &rbx::cheats::player::jump_val, 0.f, 300.f, "%.2f");
                        ImGui::Unindent();
                    }

                    ImGui::Checkbox("flight", &rbx::cheats::player::fly);
                    if (rbx::cheats::player::fly) {
                        ImGui::Indent();
                        ImGui::SliderFloat("value##f", &rbx::cheats::player::fly_val, 0.f, 300.f, "%.2f");
                        ImGui::Unindent();
                    }

                    ImGui::EndTabItem();
                }

                if (ImGui::BeginTabItem("options")) {
                    ImGui::Checkbox("vsync", &rbx::globals::vsync);
                    ImGui::EndTabItem();
                }

                ImGui::EndTabBar();
            }

            ImGui::End();
        }

        auto a = WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW;
        auto b = WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT;
        SetWindowLong(hwnd, GWL_EXSTYLE, g_OverlayVisible ? a : b);

        ImGui::Render();
        const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
        g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
        g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
        ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

        HRESULT hr = g_pSwapChain->Present(rbx::globals::vsync ? 1 : 0, 0);
        g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
    }

    ImGui_ImplDX11_Shutdown();
    ImGui_ImplWin32_Shutdown();
    ImGui::DestroyContext();

    CleanupDeviceD3D();
    ::DestroyWindow(hwnd);
    ::UnregisterClass(wc.lpszClassName, wc.hInstance);
}

bool renderer::CreateDeviceD3D(HWND hWnd)
{
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 2;
    sd.BufferDesc.Width = 0;
    sd.BufferDesc.Height = 0;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    UINT createDeviceFlags = 0;
    D3D_FEATURE_LEVEL featureLevel;
    const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };
    HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
    if (res == DXGI_ERROR_UNSUPPORTED)
        res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
    if (res != S_OK)
        return false;

    CreateRenderTarget();
    return true;
}

void renderer::CleanupDeviceD3D()
{
    CleanupRenderTarget();
    if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = nullptr; }
    if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = nullptr; }
    if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; }
}

void renderer::CreateRenderTarget()
{
    ID3D11Texture2D* pBackBuffer;
    g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
    g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);
    pBackBuffer->Release();
}

void renderer::CleanupRenderTarget()
{
    if (g_mainRenderTargetView) { g_mainRenderTargetView->Release(); g_mainRenderTargetView = nullptr; }
}

LRESULT WINAPI renderer::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
        return true;

    switch (msg)
    {
    case WM_SIZE:
        if (wParam == SIZE_MINIMIZED)
            return 0;
        g_ResizeWidth = (UINT)LOWORD(lParam);
        g_ResizeHeight = (UINT)HIWORD(lParam);
        return 0;
    case WM_SYSCOMMAND:
        if ((wParam & 0xfff0) == SC_KEYMENU)
            return 0;
        break;
    case WM_DESTROY:
        ::PostQuitMessage(0);
        return 0;
    }
    return ::DefWindowProc(hWnd, msg, wParam, lParam);
}
