/*
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 <mmsystem.h>
#include <math.h>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cstdint>
#include "resource.h"

#pragma comment(lib, "gdiplus.lib")
#pragma comment(lib, "winmm.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
static constexpr int    DRAG_THRESHOLD = 5;     // pixels before its a drag not a click
static constexpr int    WAV_COUNT = 9;
static constexpr int    MAX_PEAKS = 64;         // max screams per sound
static constexpr double PEAK_HOLD = 0.10;       // seconds which we hold the awawa face
static constexpr double PEAK_THRESH = 0.65;     // fraction of max amplitude to count as a peak

// globals
static ULONG_PTR     g_gdiplusToken = 0;
static Image* g_image = nullptr;        // normal hyrax
static Image* g_imageScream = nullptr;  // awawa hyrax
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 / drag status
static bool   g_resizing = false;
static bool   g_dragging = false;
static bool   g_didDrag = false;
static int    g_dragOffsetX = 0;
static int    g_dragOffsetY = 0;
static POINT  g_lastMouse = {};
static POINT  g_clickOrigin = {};
static double g_lastMouseTime = 0.0;

// scream face stuff
static bool   g_screaming = false;
static double g_screamUntil = 0.0;      // timestamp to stop screaming
static double g_soundStart = 0.0;       // when playback began
static double g_peakTimes[MAX_PEAKS] = {};
static int    g_peakCount = 0;
static int    g_peakIdx = 0;            // next peak we havent fired yet

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

// load any image from any rc resource type
static Image* LoadImageFromResource(HINSTANCE hInst, int resId, LPCWSTR resType) {
    HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(resId), resType);
    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;
}

// walk RAW fucking PCM data and check for peaks
static void FindWavPeaks(const BYTE* buf, DWORD size) {
    g_peakCount = 0;
    g_peakIdx = 0;

    if (size < 44) return;
    if (memcmp(buf, "RIFF", 4) != 0 || memcmp(buf + 8, "WAVE", 4) != 0) return;

    WORD  channels = 0;
    DWORD sampleRate = 0;
    WORD  bitsPerSample = 0;
    const BYTE* pcm = nullptr;
    DWORD pcmSize = 0;

    // walk the chunks
    DWORD pos = 12;
    while (pos + 8 <= size) {
        DWORD chunkSize = *reinterpret_cast<const DWORD*>(buf + pos + 4);

        if (memcmp(buf + pos, "fmt ", 4) == 0 && chunkSize >= 16) {
            channels = *reinterpret_cast<const WORD*> (buf + pos + 10);
            sampleRate = *reinterpret_cast<const DWORD*>(buf + pos + 12);
            bitsPerSample = *reinterpret_cast<const WORD*> (buf + pos + 22);
        }
        else if (memcmp(buf + pos, "data", 4) == 0) {
            pcm = buf + pos + 8;
            pcmSize = chunkSize;
            break;
        }

        pos += 8 + chunkSize;
        if (chunkSize & 1) pos++;  // align word cause microsoft likes to fuck my ass
    }

    // only 16bit PCM cause everything else is just stoopid
    if (!pcm || !sampleRate || bitsPerSample != 16) return;

    DWORD totalSamples = pcmSize / (channels * sizeof(int16_t));

    // global maximum
    int maxAmp = 1;
    for (DWORD i = 0; i < pcmSize / sizeof(int16_t); i++) {
        int s = abs((int)*reinterpret_cast<const int16_t*>(pcm + i * sizeof(int16_t)));
        if (s > maxAmp) maxAmp = s;
    }

    int   threshold = static_cast<int>(maxAmp * PEAK_THRESH);
    DWORD windowSamples = sampleRate / 50;          // 20ms windows
    DWORD debounce = sampleRate / 8;                // 125ms min gap so we dont spam
    DWORD lastPeak = 0xFFFFFFFF;

    // stupid fucking logic. scan for loud moments
    for (DWORD s = 0; s + windowSamples <= totalSamples; s += windowSamples / 2) {
        int winMax = 0;
        for (DWORD j = 0; j < windowSamples * channels; j++) {
            DWORD byteOff = (s * channels + j) * sizeof(int16_t);
            if (byteOff + sizeof(int16_t) > pcmSize) break;
            int amp = abs((int)*reinterpret_cast<const int16_t*>(pcm + byteOff));
            if (amp > winMax) winMax = amp;
        }

        bool gapOk = (lastPeak == 0xFFFFFFFF) || (s - lastPeak >= debounce);
        if (winMax >= threshold && gapOk && g_peakCount < MAX_PEAKS) {
            g_peakTimes[g_peakCount++] = static_cast<double>(s) / sampleRate;
            lastPeak = s;
        }
    }
}

// play a random hyrax sound and prep the scream timeline
static void PlayRandomSound(HINSTANCE hInst) {
    int id = IDR_WAVE1 + (rand() % WAV_COUNT);

    // grab raw wave data and look for peaks ; possible cause of .rc WAVE type
    HRSRC   hRes = FindResource(hInst, MAKEINTRESOURCE(id), L"WAVE");
    HGLOBAL hData = hRes ? LoadResource(hInst, hRes) : nullptr;
    BYTE* pData = hData ? static_cast<BYTE*>(LockResource(hData)) : nullptr;
    DWORD   sz = hRes ? SizeofResource(hInst, hRes) : 0;

    if (pData && sz)
        FindWavPeaks(pData, sz);

    g_soundStart = Now();

    PlaySoundW(MAKEINTRESOURCEW(id), hInst, SND_RESOURCE | SND_ASYNC | SND_NODEFAULT);
}

// 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);
        srand(static_cast<unsigned>(time(nullptr)));

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

        HINSTANCE hInst = reinterpret_cast<CREATESTRUCT*>(lParam)->hInstance;

        g_image = LoadImageFromResource(hInst, IDR_RCDATA1, RT_RCDATA);
        g_imageScream = LoadImageFromResource(hInst, IDB_BITMAP1, L"JPG");

        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_didDrag = false;
        g_dragOffsetX = GET_X_LPARAM(lParam);
        g_dragOffsetY = GET_Y_LPARAM(lParam);
        g_vx = g_vy = 0.0;

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

        SetCapture(hwnd);
        break;
    }

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

        POINT p;
        GetCursorPos(&p);

        if (abs(p.x - g_clickOrigin.x) > DRAG_THRESHOLD ||
            abs(p.y - g_clickOrigin.y) > DRAG_THRESHOLD)
            g_didDrag = true;

        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 or click
    case WM_LBUTTONUP: {
        if (!g_didDrag) {
            HINSTANCE hInst = reinterpret_cast<HINSTANCE>(GetWindowLongPtrW(hwnd, GWLP_HINSTANCE));
            PlayRandomSound(hInst);
        }
        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 and scream face check
    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);
        }

        // extend scream hold
        if (g_peakCount > 0) {
            double elapsed = now - g_soundStart;
            while (g_peakIdx < g_peakCount && elapsed >= g_peakTimes[g_peakIdx]) {
                g_screamUntil = now + PEAK_HOLD;
                g_peakIdx++;
            }
        }

        // flip face state
        bool shouldScream = (g_imageScream && now < g_screamUntil);
        if (shouldScream != g_screaming) {
            g_screaming = shouldScream;
            InvalidateRect(hwnd, nullptr, FALSE);
        }

        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);

        // swap to scream face on peaks
        Image* frame = (g_screaming && g_imageScream) ? g_imageScream : g_image;
        if (frame)
            g.DrawImage(frame, 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;
        delete g_imageScream;
        g_image = g_imageScream = 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);
}