/*
Anytime I edit any C++ Code, in specifically Win32 code i get acute seizures cause of the naming
conventions. I hope that whoever came up with these fuckass names gets fucked by a femboy. idk.

Anyways this is under the AGPL3.0 License so do whatever you want under that license terms.

Quick description of the app:
    - A hyrax picture which bounces around your desktop, like bonzi buddy but not destructive, more of a fun thing which you can open
    when you get bored.
    - Run the exe youll find out.

This is not malware or a virus, its purely for fun and entertainment purpouses.

rattatwinko - 04/05/26
*/

#include <windows.h>
#include <windowsx.h>
#include <gdiplus.h>
#include <objidl.h>
#include <math.h>
#include <cstring>

#pragma comment(lib, "gdiplus.lib")

using namespace Gdiplus;

// consts
static constexpr double GRAVITY = 1200.0;
static constexpr double RESTITUTION = 0.6;
static constexpr double AIR_DRAG = 0.999;
static constexpr double GROUND_FRICTION = 0.8;
static constexpr double REST_THRESHOLD = 5.0;
static constexpr UINT   TIMER_MS = 10;
static constexpr int    IMAGE_RESOURCE = 111;
static constexpr int    RESIZE_BORDER = 12;  // make wider so easier grab

// globals
static ULONG_PTR  g_gdiplusToken = 0;
static Image* g_image = nullptr;
static int        g_winW = 640;
static int        g_winH = 480;
static LARGE_INTEGER g_freq = {};

// physics states
static double g_x = 200.0, g_y = 50.0;
static double g_vx = 0.0, g_vy = 0.0;
static double g_lastTime = 0.0;

// resize status
static bool   g_resizing = false;
static bool   g_dragging = false;
static int    g_dragOffsetX = 0;
static int    g_dragOffsetY = 0;
static POINT  g_lastMouse = {};
static double g_lastMouseTime = 0.0;

// helper definitions
static double Now() {
    LARGE_INTEGER t;
    QueryPerformanceCounter(&t);
    return static_cast<double>(t.QuadPart) / static_cast<double>(g_freq.QuadPart);
}

static Image* LoadImageFromResource(HINSTANCE hInst, int resId) {
    HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(resId), RT_RCDATA);
    if (!hRes) return nullptr;

    DWORD size = SizeofResource(hInst, hRes);
    if (!size) return nullptr;

    HGLOBAL hData = LoadResource(hInst, hRes);
    if (!hData) return nullptr;

    void* pData = LockResource(hData);
    if (!pData) return nullptr;

    HGLOBAL hBuf = GlobalAlloc(GMEM_MOVEABLE, size);
    if (!hBuf) return nullptr;

    void* pBuf = GlobalLock(hBuf);
    if (!pBuf) { GlobalFree(hBuf); return nullptr; }

    memcpy(pBuf, pData, size);
    GlobalUnlock(hBuf);

    IStream* stream = nullptr;
    if (FAILED(CreateStreamOnHGlobal(hBuf, TRUE, &stream)) || !stream) {
        GlobalFree(hBuf);
        return nullptr;
    }

    Image* img = Image::FromStream(stream);
    stream->Release();

    if (!img || img->GetLastStatus() != Ok) {
        delete img;
        return nullptr;
    }

    return img;
}

// physics "engine"
static void StepPhysics(double dt) {
    const int screenW = GetSystemMetrics(SM_CXSCREEN);
    const int screenH = GetSystemMetrics(SM_CYSCREEN);

    g_vy += GRAVITY * dt;
    g_vx *= AIR_DRAG;
    g_vy *= AIR_DRAG;

    g_x += g_vx * dt;
    g_y += g_vy * dt;

    // left/ right monitor borders
    if (g_x < 0.0) {
        g_x = 0.0;
        g_vx = -g_vx * RESTITUTION;
    }
    else if (g_x > screenW - g_winW) {
        g_x = static_cast<double>(screenW - g_winW);
        g_vx = -g_vx * RESTITUTION;
    }

    // top
    if (g_y < 0.0) {
        g_y = 0.0;
        g_vy = -g_vy * RESTITUTION;
    }

    // bottom
    if (g_y > screenH - g_winH) {
        g_y = static_cast<double>(screenH - g_winH);
        g_vy = -g_vy * RESTITUTION;
        g_vx *= GROUND_FRICTION;
        if (fabs(g_vy) < REST_THRESHOLD)
            g_vy = 0.0;
    }
}

// win api window proc
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {

    // init the window
    case WM_CREATE: {
        QueryPerformanceFrequency(&g_freq);

        GdiplusStartupInput si;
        GdiplusStartup(&g_gdiplusToken, &si, nullptr);

        HINSTANCE hInst = reinterpret_cast<CREATESTRUCT*>(lParam)->hInstance;
        g_image = LoadImageFromResource(hInst, IMAGE_RESOURCE);

        if (g_image) {
            g_winW = static_cast<int>(g_image->GetWidth());
            g_winH = static_cast<int>(g_image->GetHeight());
            SetWindowPos(hwnd, nullptr, 0, 0, g_winW, g_winH,
                SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
        }

        g_lastTime = Now();
        g_lastMouseTime = g_lastTime;

        SetTimer(hwnd, 1, TIMER_MS, nullptr);
        break;
    }

    // drag start
    case WM_LBUTTONDOWN: {
        g_dragging = true;
        g_dragOffsetX = GET_X_LPARAM(lParam);
        g_dragOffsetY = GET_Y_LPARAM(lParam);
        g_vx = g_vy = 0.0;

        GetCursorPos(&g_lastMouse);
        g_lastMouseTime = Now();

        SetCapture(hwnd);
        break;
    }

    // drag move
    case WM_MOUSEMOVE: {
        if (!g_dragging) break;

        POINT p;
        GetCursorPos(&p);

        const double now = Now();
        const double dt = now - g_lastMouseTime;

        if (dt > 0.0) {
            g_vx = (p.x - g_lastMouse.x) / dt;
            g_vy = (p.y - g_lastMouse.y) / dt;
        }

        g_lastMouse = p;
        g_lastMouseTime = now;

        g_x = p.x - g_dragOffsetX;
        g_y = p.y - g_dragOffsetY;

        SetWindowPos(hwnd, nullptr, static_cast<int>(g_x), static_cast<int>(g_y),
            0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
        break;
    }

    // drag end
    case WM_LBUTTONUP:
        g_dragging = false;
        ReleaseCapture();
        break;
        
    // while dragging nutz accross fohead disable OS from doing shit
    case WM_ENTERSIZEMOVE:
        g_resizing = true;
        break;

    case WM_EXITSIZEMOVE: {
        g_resizing = false;
        g_vx = g_vy = 0.0;

        // sync gx/y with resized cords
        RECT r;
        GetWindowRect(hwnd, &r);
        g_x = static_cast<double>(r.left);
        g_y = static_cast<double>(r.top);

        g_lastTime = Now();  // after everything is clean then redraw
        break;
    }

    // weird asf logic
    case WM_NCHITTEST: {
        POINT p = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
        RECT  r;
        GetWindowRect(hwnd, &r);

        const bool left = p.x < r.left + RESIZE_BORDER;
        const bool right = p.x >= r.right - RESIZE_BORDER;
        const bool top = p.y < r.top + RESIZE_BORDER;
        const bool bottom = p.y >= r.bottom - RESIZE_BORDER;

        if (top && left)  return HTTOPLEFT;
        if (top && right) return HTTOPRIGHT;
        if (bottom && left)  return HTBOTTOMLEFT;
        if (bottom && right) return HTBOTTOMRIGHT;
        if (left)            return HTLEFT;
        if (right)           return HTRIGHT;
        if (top)             return HTTOP;
        if (bottom)          return HTBOTTOM;
        return HTCLIENT;
    }

    // sync dimensions after resizing
    case WM_SIZE:
        g_winW = LOWORD(lParam);
        g_winH = HIWORD(lParam);
        InvalidateRect(hwnd, nullptr, FALSE);
        break;

    // cursor, bunch of this dont work
    case WM_SETCURSOR:
        if (LOWORD(lParam) == HTCLIENT) {
            SetCursor(LoadCursor(nullptr, g_dragging ? IDC_SIZEALL : IDC_HAND));
            return TRUE;
        }
        return DefWindowProcW(hwnd, msg, wParam, lParam);

    // phyisks tick
    case WM_TIMER: {
        const double now = Now();
        const double dt = now - g_lastTime;
        g_lastTime = now;

        if (!g_dragging && !g_resizing) {
            StepPhysics(dt);
            SetWindowPos(hwnd, nullptr, static_cast<int>(g_x), static_cast<int>(g_y),
                0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
        }
        break;
    }

    // pablo picasso
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        // db to avoid me floppin on the floor like a fish (cause of flashing lights)
        HDC     memDC = CreateCompatibleDC(hdc);
        HBITMAP memBmp = CreateCompatibleBitmap(hdc, g_winW, g_winH);
        HGDIOBJ old = SelectObject(memDC, memBmp);

        Graphics g(memDC);
        g.SetInterpolationMode(InterpolationModeHighQualityBicubic);

        if (g_image)
            g.DrawImage(g_image, 0, 0, g_winW, g_winH);

        BitBlt(hdc, 0, 0, g_winW, g_winH, memDC, 0, 0, SRCCOPY);

        SelectObject(memDC, old);
        DeleteObject(memBmp);
        DeleteDC(memDC);

        EndPaint(hwnd, &ps);
        break;
    }

    // escape destroy
    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE)
            DestroyWindow(hwnd);
        else if (wParam == VK_SPACE)
            DestroyWindow(hwnd);
        break;

    // consuela
    case WM_DESTROY:
        KillTimer(hwnd, 1);
        delete g_image;
        g_image = nullptr;
        GdiplusShutdown(g_gdiplusToken);
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProcW(hwnd, msg, wParam, lParam);
    }

    return 0;
}

// right around the entry point (reference to asshair guy)
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow) {
    WNDCLASSW wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"Hyrax";
    wc.hCursor = LoadCursor(nullptr, IDC_HAND);
    RegisterClassW(&wc);

    HWND hwnd = CreateWindowExW(
        WS_EX_TOPMOST,
        L"Hyrax", L"",
        WS_POPUP,
        static_cast<int>(g_x), static_cast<int>(g_y),
        g_winW, g_winH,
        nullptr, nullptr, hInstance, nullptr
    );

    if (!hwnd)
        return 1;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessageW(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return static_cast<int>(msg.wParam);
}