// Simple Win32 GUI launcher for WinFly CLI
// Provides fields for: number of flies, timeout (seconds), fly size (pixels), 
// and variation (percent). On Start it launches winfly.exe with the 
// appropriate command-line flags.
// v1.1 That One Guy

#include <windows.h>
#include <string>
#include <sstream>
#include <vector>
#include <commctrl.h>
// For font and control enumeration
#include <tchar.h>
// GDI+ for loading the fly image in the launcher
#include <gdiplus.h>
#include <objidl.h>
using namespace Gdiplus;

#define ID_TB_COUNT 1001
#define ID_TB_TIMEOUT 1002
#define ID_TB_SIZE 1003
#define ID_BTN_START 1004
#define ID_BTN_QUIT 1005
#define ID_TB_VARIATION 1006
#define ID_CHK_KEEPOPEN 1007

static HWND hCount = NULL;       // trackbar for count
static HWND hTimeout = NULL;     // trackbar for timeout
static HWND hSize = NULL;        // trackbar for size (1..5)
static HWND hVariation = NULL;   // trackbar for variation percent
static HWND hCountVal = NULL;    // static label showing numeric values
static HWND hTimeoutVal = NULL;
static HWND hSizeVal = NULL;
static HWND hVariationVal = NULL;
static HWND hKeepOpen = NULL;
static HFONT hGuiFont = NULL;
static ULONG_PTR g_gdiplusTokenGui = 0;
static Bitmap *g_flyBitmapGui = NULL;

// Height reserved at top for title + image so controls don't overlap
static const int GUI_HEADER_HEIGHT = 90;

// Helper to map trackbar position to size multiplier (0.5 to 3.0 in 0.1 increments)
// Trackbar stores tenths: pos 5 = 0.5, pos 10 = 1.0, pos 30 = 3.0
static float trackbarPosToSizeMultiplier(int pos) {
    return pos / 10.0f;
}

// Helper to map size multiplier to pixels (base size is 18px)
static int sizeMultiplierToPixels(float multiplier) {
    return (int)(18.0f * multiplier);
}

// Enum child windows to apply consistent font
static BOOL CALLBACK EnumSetFontProc(HWND hwnd, LPARAM lParam) {
    HFONT hf = (HFONT)lParam;
    SendMessageA(hwnd, WM_SETFONT, (WPARAM)hf, MAKELONG(TRUE,0));
    return TRUE;
}

void LaunchWinFly(const std::string &cmdline) {
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // Get the directory where winfly_gui.exe is located
    char exePath[MAX_PATH];
    GetModuleFileNameA(NULL, exePath, MAX_PATH);
    char *lastSlash = strrchr(exePath, '\\');
    if (lastSlash) *lastSlash = '\0';

    // Build full path to winfly.exe in the same directory
    std::string fullPath = std::string(exePath) + "\\winfly.exe";
    std::string fullCmd = fullPath + " " + cmdline.substr(cmdline.find(" "));

    // CreateProcess expects a mutable char buffer
    std::vector<char> buf(fullCmd.begin(), fullCmd.end());
    buf.push_back('\0');

    // Debug: show the command being executed (uncomment to debug)
    // MessageBoxA(NULL, fullCmd.c_str(), "Debug: Command Line", MB_OK);

    if (!CreateProcessA(NULL, buf.data(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        std::string errMsg = "Failed to launch: " + fullPath;
        MessageBoxA(NULL, errMsg.c_str(), "Error", MB_ICONERROR | MB_OK);
        return;
    }
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_CREATE: {
        // Ensure common controls are initialized for trackbars
        INITCOMMONCONTROLSEX icex;
        icex.dwSize = sizeof(icex);
        icex.dwICC = ICC_BAR_CLASSES;
        InitCommonControlsEx(&icex);

    // Layout constants
    const int lblX = 10; const int lblW = 160;
    const int sldX = 190; const int sldW = 440;
    const int rowY = GUI_HEADER_HEIGHT; const int rowH = 52;

        CreateWindowA("STATIC", "Number of flies:", WS_VISIBLE | WS_CHILD | SS_RIGHT, lblX, rowY, lblW, 20, hwnd, NULL, NULL, NULL);
        // value label above the slider (default 1 fly)
        hCountVal = CreateWindowA("STATIC", "1", WS_VISIBLE | WS_CHILD, sldX + sldW/2 - 20, rowY + 18, 40, 20, hwnd, NULL, NULL, NULL);
        hCount = CreateWindowExA(0, TRACKBAR_CLASSA, NULL, WS_VISIBLE | WS_CHILD | TBS_AUTOTICKS | TBS_ENABLESELRANGE,
            sldX, rowY, sldW, 24, hwnd, (HMENU)ID_TB_COUNT, NULL, NULL);
        SendMessageA(hCount, TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(1, 50));
        SendMessageA(hCount, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)1);

    CreateWindowA("STATIC", "Timeout (seconds):", WS_VISIBLE | WS_CHILD | SS_RIGHT, lblX, rowY + rowH, lblW, 20, hwnd, NULL, NULL, NULL);
    // value label above the slider (default 15 seconds)
    hTimeoutVal = CreateWindowA("STATIC", "15", WS_VISIBLE | WS_CHILD, sldX + sldW/2 - 30, rowY + rowH + 18, 80, 20, hwnd, NULL, NULL, NULL);
        hTimeout = CreateWindowExA(0, TRACKBAR_CLASSA, NULL, WS_VISIBLE | WS_CHILD | TBS_AUTOTICKS | TBS_ENABLESELRANGE,
            sldX, rowY + rowH, sldW, 24, hwnd, (HMENU)ID_TB_TIMEOUT, NULL, NULL);
        SendMessageA(hTimeout, TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(0, 600));
        SendMessageA(hTimeout, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)15);

    CreateWindowA("STATIC", "Fly size:", WS_VISIBLE | WS_CHILD | SS_RIGHT, lblX, rowY + rowH*2, lblW, 20, hwnd, NULL, NULL, NULL);
        // value label above the slider
    // initial label reflects default 2.0 (pos 20 = 2.0x = 36px)
    hSizeVal = CreateWindowA("STATIC", "2.0", WS_VISIBLE | WS_CHILD, sldX + sldW/2 - 10, rowY + rowH*2 + 18, 40, 20, hwnd, NULL, NULL, NULL);
        hSize = CreateWindowExA(0, TRACKBAR_CLASSA, NULL, WS_VISIBLE | WS_CHILD | TBS_AUTOTICKS | TBS_ENABLESELRANGE,
            sldX, rowY + rowH*2, sldW, 24, hwnd, (HMENU)ID_TB_SIZE, NULL, NULL);
    // Range 5 to 30 (representing 0.5 to 3.0)
    SendMessageA(hSize, TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(5, 30));
    // Default size multiplier is 2.0 (pos 20)
    SendMessageA(hSize, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)20);

        CreateWindowA("STATIC", "Size variation (%):", WS_VISIBLE | WS_CHILD | SS_RIGHT, lblX, rowY + rowH*3, lblW, 20, hwnd, NULL, NULL, NULL);
        // value label above the slider
        hVariationVal = CreateWindowA("STATIC", "0", WS_VISIBLE | WS_CHILD, sldX + sldW/2 - 20, rowY + rowH*3 + 18, 40, 20, hwnd, NULL, NULL, NULL);
        hVariation = CreateWindowExA(0, TRACKBAR_CLASSA, NULL, WS_VISIBLE | WS_CHILD | TBS_AUTOTICKS | TBS_ENABLESELRANGE,
            sldX, rowY + rowH*3, sldW, 24, hwnd, (HMENU)ID_TB_VARIATION, NULL, NULL);
        SendMessageA(hVariation, TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(0, 100));
        SendMessageA(hVariation, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)0);

        // Buttons aligned with sliders
        CreateWindowA("BUTTON", "Let's Fly", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_DEFPUSHBUTTON, sldX, rowY + rowH*4 + 6, 140, 34, hwnd, (HMENU)ID_BTN_START, NULL, NULL);
        CreateWindowA("BUTTON", "Quit", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, sldX + 160, rowY + rowH*4 + 6, 120, 34, hwnd, (HMENU)ID_BTN_QUIT, NULL, NULL);

        // Create a nicer Segoe UI font and apply to children
        hGuiFont = CreateFontA(-16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
            DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
            VARIABLE_PITCH | FF_SWISS, "Segoe UI");
        EnumChildWindows(hwnd, EnumSetFontProc, (LPARAM)hGuiFont);

        // Initialize GDI+ for the launcher and try to load embedded fly\fly.png resource
        GdiplusStartupInput gdiplusStartupInput;
        if (GdiplusStartup(&g_gdiplusTokenGui, &gdiplusStartupInput, NULL) == Ok) {
            HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);
            // Try to load PNG from RCDATA resource id 102 (use Unicode API)
            // Use numeric 10 for RT_RCDATA type with the wide MAKEINTRESOURCEW macro to ensure correct type.
            HRSRC hRes = FindResourceW(hInst, MAKEINTRESOURCEW(102), MAKEINTRESOURCEW(10));
            if (hRes) {
                HGLOBAL hResData = LoadResource(hInst, hRes);
                DWORD resSize = SizeofResource(hInst, hRes);
                void *pRes = LockResource(hResData);
                if (pRes && resSize > 0) {
                    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, resSize);
                    if (hMem) {
                        void *pMem = GlobalLock(hMem);
                        memcpy(pMem, pRes, resSize);
                        GlobalUnlock(hMem);
                        IStream *pStream = NULL;
                        if (CreateStreamOnHGlobal(hMem, TRUE, &pStream) == S_OK) {
                            Bitmap *bmp = Bitmap::FromStream(pStream);
                            pStream->Release();
                            if (bmp && bmp->GetLastStatus() == Ok) {
                                g_flyBitmapGui = bmp;
                            } else {
                                if (bmp) delete bmp;
                                g_flyBitmapGui = NULL;
                            }
                            // hMem will be freed when the stream is released (we passed TRUE)
                        } else {
                            // Failed to create stream; free allocated memory
                            GlobalFree(hMem);
                        }
                    }
                }
            }
            // Fallback: try to load from disk if resource wasn't found or failed
            if (!g_flyBitmapGui) {
                WCHAR wpath[MAX_PATH] = {0};
                MultiByteToWideChar(CP_ACP, 0, "fly\\fly.png", -1, wpath, MAX_PATH);
                Bitmap *bmp = Bitmap::FromFile(wpath);
                if (bmp && bmp->GetLastStatus() == Ok) {
                    g_flyBitmapGui = bmp;
                } else {
                    if (bmp) delete bmp;
                    g_flyBitmapGui = NULL;
                }
            }
        }
        // Force a layout/update pass
        SetWindowPos(hwnd, NULL, 0,0,0,0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_FRAMECHANGED);
        break; }
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        Graphics graphics(hdc);

        // Draw a larger bold "Fly" and the fly image side-by-side centered above the controls
        const WCHAR *title = L"Fly";
        FontFamily fontFamily(L"Segoe UI");
        Font titleFont(&fontFamily, 28.0f, FontStyleBold, UnitPixel);
        SolidBrush brush(Color(255,0,0,0));

        RECT client;
        GetClientRect(hwnd, &client);
        int cx = (client.right - client.left) / 2;

        int imgW = 0, imgH = 0;
        int drawImgW = 48, drawImgH = 48;
        if (g_flyBitmapGui) {
            imgW = g_flyBitmapGui->GetWidth();
            imgH = g_flyBitmapGui->GetHeight();
            // scale relative to original while keeping desired height
            if (imgH > 0) drawImgW = (int)((float)imgW * ((float)drawImgH / (float)imgH));
        } else {
            drawImgW = drawImgH = 48;
        }

        // Measure title width
        StringFormat fmt;
        RectF layoutRect(0,0,1000,100);
        RectF measured;
        graphics.MeasureString(title, -1, &titleFont, layoutRect, &measured);
        int titleW = (int)measured.Width;

        int spacing = 12;
        int totalW = drawImgW + spacing + titleW;
        int startX = cx - totalW/2;
    int imgX = startX;
    int imgY = (GUI_HEADER_HEIGHT - drawImgH) / 2; // center vertically in header area
    int titleX = imgX + drawImgW + spacing;
    int titleY = (int)((GUI_HEADER_HEIGHT - measured.Height) / 2);

        if (g_flyBitmapGui) {
            graphics.DrawImage(g_flyBitmapGui, (REAL)imgX, (REAL)imgY, (REAL)drawImgW, (REAL)drawImgH);
        } else {
            // draw a simple placeholder rectangle
            Pen pen(Color(255,0,0,0));
            graphics.DrawRectangle(&pen, (REAL)imgX, (REAL)imgY, (REAL)drawImgW, (REAL)drawImgH);
        }

        graphics.DrawString(title, -1, &titleFont, PointF((REAL)titleX, (REAL)titleY), &fmt, &brush);

        EndPaint(hwnd, &ps);
        break; }
    case WM_HSCROLL: {
        HWND src = (HWND)lParam;
        int pos = 0;
        char buf[32];
        if (src == hCount) {
            pos = (int)SendMessageA(hCount, TBM_GETPOS, 0, 0);
            _snprintf(buf, sizeof(buf), "%d", pos);
            SetWindowTextA(hCountVal, buf);
        } else if (src == hTimeout) {
            pos = (int)SendMessageA(hTimeout, TBM_GETPOS, 0, 0);
            if (pos == 0) {
                SetWindowTextA(hTimeoutVal, "No timeout");
            } else {
                _snprintf(buf, sizeof(buf), "%d", pos);
                SetWindowTextA(hTimeoutVal, buf);
            }
        } else if (src == hSize) {
            pos = (int)SendMessageA(hSize, TBM_GETPOS, 0, 0);
            float multiplier = trackbarPosToSizeMultiplier(pos);
            _snprintf(buf, sizeof(buf), "%.1f", multiplier);
            SetWindowTextA(hSizeVal, buf);
        } else if (src == hVariation) {
            pos = (int)SendMessageA(hVariation, TBM_GETPOS, 0, 0);
            _snprintf(buf, sizeof(buf), "%d", pos);
            SetWindowTextA(hVariationVal, buf);
        }
        break; }
    case WM_COMMAND:
        if (LOWORD(wParam) == ID_BTN_START) {
            int count = (int)SendMessageA(hCount, TBM_GETPOS, 0, 0);
            if (count <= 0) count = 5;
            int timeout = (int)SendMessageA(hTimeout, TBM_GETPOS, 0, 0);
            if (timeout < 0) timeout = 0;
            int sizePos = (int)SendMessageA(hSize, TBM_GETPOS, 0, 0);
            if (sizePos < 5) sizePos = 10; // default to 1.0
            float sizeMultiplier = trackbarPosToSizeMultiplier(sizePos);
            int sizePixels = sizeMultiplierToPixels(sizeMultiplier);
            int variation = (int)SendMessageA(hVariation, TBM_GETPOS, 0, 0);
            if (variation < 0) variation = 0;

            std::ostringstream ss;
            // Pass size as pixel value to match CLI expectations
            ss << "winfly.exe -n " << count << " -t " << timeout << " -s " << sizePixels;
            if (variation > 0) ss << " -v " << variation;
            std::string cmd = ss.str();
            LaunchWinFly(cmd);
            // Close the GUI after launching
            DestroyWindow(hwnd);
        } else if (LOWORD(wParam) == ID_BTN_QUIT) {
            PostQuitMessage(0);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmdLine, int nShowCmd) {
    // Register window class with icon resources embedded (id 101 in winfly_gui.rc)
    WNDCLASSEXA wc = {0};
    wc.cbSize = sizeof(WNDCLASSEXA);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = "WinFlyGuiClass";
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);

    // Try to load embedded icon resource (big and small). Resource id 101 matches winfly_gui.rc
    HICON hIconBig = (HICON)LoadImageA(hInstance, MAKEINTRESOURCEA(101), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);
    HICON hIconSmall = (HICON)LoadImageA(hInstance, MAKEINTRESOURCEA(101), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
    if (hIconBig) wc.hIcon = hIconBig;
    if (hIconSmall) wc.hIconSm = hIconSmall;

    if (!RegisterClassExA(&wc)) return -1;

    // Make window wider/taller so controls have room and don't clip
    // Increased height so header, sliders, and buttons all fit comfortably
    HWND hwnd = CreateWindowA(wc.lpszClassName, "WinFly Launcher", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,
        CW_USEDEFAULT, CW_USEDEFAULT, 680, 420, NULL, NULL, hInstance, NULL);
    if (!hwnd) return -1;

    // Ensure the window actually receives the icons too (set both big and small icons)
    if (hIconBig) SendMessageA(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIconBig);
    if (hIconSmall) SendMessageA(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSmall);

    ShowWindow(hwnd, nShowCmd);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // Cleanup GDI+ resources used by the GUI
    if (g_flyBitmapGui) {
        delete g_flyBitmapGui;
        g_flyBitmapGui = NULL;
    }
    if (g_gdiplusTokenGui) {
        GdiplusShutdown(g_gdiplusTokenGui);
        g_gdiplusTokenGui = 0;
    }
    return (int)msg.wParam;
}
