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

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

using namespace Gdiplus;

Image* LoadImageFromResource(int resId) {
    HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(resId), RT_RCDATA);
    if (!hRes) return NULL;

    DWORD size = SizeofResource(NULL, hRes);
    if (size == 0) return NULL;

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

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

    HGLOBAL hBuffer = GlobalAlloc(GMEM_MOVEABLE, size);
    if (!hBuffer) return NULL;

    void* pBuffer = GlobalLock(hBuffer);
    if (!pBuffer) {
        GlobalFree(hBuffer);
        return NULL;
    }

    memcpy(pBuffer, pData, size);
    GlobalUnlock(hBuffer);

    IStream* stream = NULL;
    HRESULT hr = CreateStreamOnHGlobal(hBuffer, TRUE, &stream);
    if (FAILED(hr) || !stream) {
        GlobalFree(hBuffer);
        return NULL;
    }

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

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

    return img;
}


double x = 200.0, y = 50.0;
double vx = 0.0, vy = 0.0;

double gravity = 1200.0;
double restitution = 0.6;
double air_drag = 0.999;
double ground_friction = 0.8;

int win_w = 640;
int win_h = 480;

int dragging = 0;
int offset_x = 0, offset_y = 0;

POINT last_mouse;
double last_mouse_time = 0.0;
double last_time = 0.0;

LARGE_INTEGER freq;

ULONG_PTR gdiplusToken;
Image* img = NULL;


double now_time() {
    LARGE_INTEGER t;
    QueryPerformanceCounter(&t);
    return (double)t.QuadPart / (double)freq.QuadPart;
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {

    case WM_CREATE: {
        QueryPerformanceFrequency(&freq);

        GdiplusStartupInput gdiplusStartupInput;
        GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

        img = LoadImageFromResource(111);

        if (!img || img->GetLastStatus() != Ok) {
            // MessageBoxA(NULL, "Image failed to load", "Error", MB_OK);
        }
        else {
            win_w = 640;
            win_h = 480;
            SetWindowPos(hwnd, NULL, 0, 0, win_w, win_h, SWP_NOMOVE);
        }

        SetTimer(hwnd, 1, 10, NULL);

        last_time = now_time();
        last_mouse_time = last_time;
        break;
    }

    case WM_LBUTTONDOWN: {
        dragging = 1;
        offset_x = LOWORD(lParam);
        offset_y = HIWORD(lParam);

        vx = vy = 0;

        GetCursorPos(&last_mouse);
        last_mouse_time = now_time();

        SetCapture(hwnd);
        break;
    }
    
    case WM_SETCURSOR:
        if (dragging)
            SetCursor(LoadCursor(NULL, IDC_SIZEALL));
        else
            SetCursor(LoadCursor(NULL, IDC_HAND));
        return TRUE;

    case WM_MOUSEMOVE:
        if (dragging) {
            POINT p;
            GetCursorPos(&p);

            double now = now_time();
            double dt = now - last_mouse_time;

            if (dt > 0) {
                vx = (p.x - last_mouse.x) / dt;
                vy = (p.y - last_mouse.y) / dt;
            }

            last_mouse = p;
            last_mouse_time = now;

            x = p.x - offset_x;
            y = p.y - offset_y;

            SetWindowPos(hwnd, NULL, (int)x, (int)y, 0, 0,
                SWP_NOSIZE | SWP_NOZORDER);
        }
        break;

    case WM_LBUTTONUP:
        dragging = 0;
        ReleaseCapture();
        break;

    case WM_TIMER: {
        double now = now_time();
        double dt = now - last_time;
        last_time = now;

        int screen_w = GetSystemMetrics(SM_CXSCREEN);
        int screen_h = GetSystemMetrics(SM_CYSCREEN);

        if (!dragging) {
            vy += gravity * dt;

            vx *= air_drag;
            vy *= air_drag;

            x += vx * dt;
            y += vy * dt;

            if (x <= 0) {
                x = 0;
                vx = -vx * restitution;
            }
            else if (x >= screen_w - win_w) {
                x = screen_w - win_w;
                vx = -vx * restitution;
            }

            if (y >= screen_h - win_h) {
                y = screen_h - win_h;
                vy = -vy * restitution;
                vx *= ground_friction;

                if (fabs(vy) < 5)
                    vy = 0;
            }

            if (y <= 0) {
                y = 0;
                vy = -vy * restitution;
            }

            SetWindowPos(hwnd, NULL, (int)x, (int)y, 0, 0,
                SWP_NOSIZE | SWP_NOZORDER);
        }
        break;
    }

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

        Graphics g(hdc);

        if (img)
            g.DrawImage(img, 0, 0, win_w, win_h);

        EndPaint(hwnd, &ps);
        break;
    }

    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE)
            DestroyWindow(hwnd);
        break;

    case WM_DESTROY:
        if (img) delete img;
        GdiplusShutdown(gdiplusToken);
        PostQuitMessage(0);
        break;
    }

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


static int WINAPI wWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    PWSTR,
    int nCmdShow
) {

    if (IsDebuggerPresent())
        return 0;

    WNDCLASSW wc = { 0 };
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"Hyrax";
    wc.hCursor = LoadCursor(NULL, IDC_HAND);

    RegisterClassW(&wc);

    HWND hwnd = CreateWindowExW(
        WS_EX_TOPMOST,
        L"Hyrax",
        L"",
        WS_POPUP,
        (int)x, (int)y,
        win_w, win_h,
        NULL, NULL, hInstance, NULL
    );

    ShowWindow(hwnd, nCmdShow);

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

    return 0;
}