#include "WickedEngine.h"
#include "voxel/VoxelRenderer.h"
#include "wiHelper.h"
#include <fstream>
#include <DbgHelp.h>
#pragma comment(lib, "dbghelp.lib")

// ── BVLE Voxels - Prototype Application ─────────────────────────
// Wicked Engine based voxel engine prototype for performance validation.

// ── Crash handler: writes stack trace + minidump on unhandled exception ──
static LONG WINAPI CrashHandler(EXCEPTION_POINTERS* ep) {
    std::ofstream crash("bvle_crash.log", std::ios::trunc);
    crash << "=== BVLE CRASH REPORT ===" << std::endl;

    DWORD code = ep->ExceptionRecord->ExceptionCode;
    PVOID addr = ep->ExceptionRecord->ExceptionAddress;
    crash << "Exception code: 0x" << std::hex << code << std::endl;
    crash << "Crash address: 0x" << addr << std::endl;

    if (code == EXCEPTION_ACCESS_VIOLATION && ep->ExceptionRecord->NumberParameters >= 2) {
        ULONG_PTR type = ep->ExceptionRecord->ExceptionInformation[0];
        ULONG_PTR target = ep->ExceptionRecord->ExceptionInformation[1];
        crash << (type == 0 ? "Reading" : "Writing") << " address: 0x" << std::hex << target << std::endl;
    }

    HANDLE process = GetCurrentProcess();
    HANDLE thread = GetCurrentThread();
    SymInitialize(process, NULL, TRUE);

    CONTEXT* ctx = ep->ContextRecord;
    STACKFRAME64 frame = {};
    frame.AddrPC.Offset = ctx->Rip;    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = ctx->Rbp; frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = ctx->Rsp; frame.AddrStack.Mode = AddrModeFlat;

    crash << "\nStack trace:" << std::endl;
    for (int i = 0; i < 32; i++) {
        if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, process, thread, &frame,
            ctx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
            break;
        char symbolBuf[sizeof(SYMBOL_INFO) + 256];
        SYMBOL_INFO* symbol = (SYMBOL_INFO*)symbolBuf;
        symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        symbol->MaxNameLen = 255;
        DWORD64 disp64 = 0;
        crash << "  [" << i << "] 0x" << std::hex << frame.AddrPC.Offset;
        if (SymFromAddr(process, frame.AddrPC.Offset, &disp64, symbol))
            crash << " " << symbol->Name << " +0x" << disp64;
        IMAGEHLP_LINE64 line = {}; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
        DWORD disp32 = 0;
        if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &disp32, &line))
            crash << " (" << line.FileName << ":" << std::dec << line.LineNumber << ")";
        crash << std::endl;
    }

    HANDLE dumpFile = CreateFileA("bvle_crash.dmp", GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (dumpFile != INVALID_HANDLE_VALUE) {
        MINIDUMP_EXCEPTION_INFORMATION mei;
        mei.ThreadId = GetCurrentThreadId();
        mei.ExceptionPointers = ep;
        mei.ClientPointers = FALSE;
        MiniDumpWriteDump(process, GetCurrentProcessId(), dumpFile,
            MiniDumpWithDataSegs, &mei, NULL, NULL);
        CloseHandle(dumpFile);
    }

    crash.close();
    SymCleanup(process);
    return EXCEPTION_EXECUTE_HANDLER;
}

static wi::Application application;
static voxel::VoxelRenderPath renderPath;

int APIENTRY wWinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine,
    _In_ int nCmdShow)
{
    SetUnhandledExceptionFilter(CrashHandler);

    // Win32 window setup
    static auto WndProc = [](HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -> LRESULT
    {
        switch (message)
        {
        case WM_SIZE:
        case WM_DPICHANGED:
            if (application.is_window_active)
                application.SetWindow(hWnd);
            break;
        case WM_CHAR:
            switch (wParam)
            {
            case VK_BACK:
                wi::gui::TextInputField::DeleteFromInput();
                break;
            case VK_RETURN:
                break;
            default:
                wi::gui::TextInputField::AddInput((const wchar_t)wParam);
                break;
            }
            break;
        case WM_INPUT:
            wi::input::rawinput::ParseMessage((void*)lParam);
            break;
        case WM_KILLFOCUS:
            application.is_window_active = false;
            break;
        case WM_SETFOCUS:
            application.is_window_active = true;
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    };

    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    // Parse arguments early so we can adjust window creation
    wi::arguments::Parse(lpCmdLine);
    bool isScreenshot = wi::arguments::HasArgument("screenshot");

    WNDCLASSEXW wcex = {};
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.hInstance = hInstance;
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszClassName = L"BVLEVoxels";
    RegisterClassExW(&wcex);

    // Screenshot mode: small minimized window to avoid interrupting user
    HWND hWnd = CreateWindowW(
        wcex.lpszClassName,
        isScreenshot ? L"BVLE Screenshot" : L"BVLE Voxels - Prototype",
        WS_OVERLAPPEDWINDOW,
        isScreenshot ? 0 : CW_USEDEFAULT,
        isScreenshot ? 0 : 0,
        isScreenshot ? 640 : 1920,
        isScreenshot ? 480 : 1080,
        nullptr, nullptr, hInstance, nullptr
    );
    // SW_SHOWNOACTIVATE: visible but doesn't steal focus (minimized windows don't render)
    ShowWindow(hWnd, isScreenshot ? SW_SHOWNOACTIVATE : SW_SHOWMAXIMIZED);

    // Initialize Wicked Engine
    application.SetWindow(hWnd);

    // Redirect Wicked Engine log to file
    wi::backlog::SetLogFile("bvle_backlog.txt");

    // Info display (disabled in screenshot mode)
    application.infoDisplay.active = !isScreenshot;
    application.infoDisplay.watermark = false;
    application.infoDisplay.resolution = !isScreenshot;
    application.infoDisplay.fpsinfo = !isScreenshot;
    application.infoDisplay.heap_allocation_counter = !isScreenshot;

    // Check for "debug" argument to enable face-color debug mode
    if (wi::arguments::HasArgument("debug")) {
        renderPath.debugMode = true;
    }
    // Check for "debugsmooth" argument to enable smooth blending debug scene
    if (wi::arguments::HasArgument("debugsmooth")) {
        renderPath.debugSmooth = true;
    }
    // Screenshot mode: auto-position camera, wait for AO convergence, capture, quit
    if (wi::arguments::HasArgument("screenshot")) {
        renderPath.screenshotMode = true;
    }

    // Activate our custom voxel render path
    application.ActivatePath(&renderPath);

    // Main loop
    MSG msg = { 0 };
    static int screenshotFrameCounter = 0;
    while (msg.message != WM_QUIT)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else {
            application.Run();

            // Screenshot mode: cycle through multiple camera views
            if (renderPath.screenshotMode) {
                struct CamView { float x, y, z, pitch, yaw; const char* name; };
                static const CamView views[] = {
                    { 223.f, 36.5f, 261.f, -0.20f, 0.7f, "closeup"   },  // close-up: slightly above grass, looking across
                    { 222.5f, 36.2f, 261.f, -0.10f, 0.5f,"blade"     },  // eye-level with grass blades
                    { 220.f, 39.f, 258.f, -0.35f, 0.7f,  "medium"    },  // medium shot of grass patch
                };
                static const int numViews = sizeof(views) / sizeof(views[0]);
                static int currentView = 0;
                static int convergenceFrames = 0;
                static bool waitingForRender = true;

                screenshotFrameCounter++;

                // Set camera for current view on first frame or after capture
                if (screenshotFrameCounter == 1 || convergenceFrames == 0) {
                    renderPath.setCamera(views[currentView].x, views[currentView].y,
                        views[currentView].z, views[currentView].pitch, views[currentView].yaw);
                }

                bool hasRendered = renderPath.renderer.getGpuMeshQuadCount() > 0;
                if (hasRendered) convergenceFrames++;

                // Wait 50 frames for AO convergence per view
                if (convergenceFrames >= 50) {
                    char filename[64];
                    snprintf(filename, sizeof(filename), "bvle_screenshot_%s.png",
                        views[currentView].name);
                    wi::helper::saveTextureToFile(renderPath.getVoxelRT(), filename);

                    currentView++;
                    if (currentView >= numViews) {
                        PostQuitMessage(0);
                    } else {
                        convergenceFrames = 0;
                        renderPath.setCamera(views[currentView].x, views[currentView].y,
                            views[currentView].z, views[currentView].pitch, views[currentView].yaw);
                        renderPath.resetAOHistory(); // invalidate temporal AO for new view
                    }
                }
            }
        }
    }

    wi::jobsystem::ShutDown();
    return (int)msg.wParam;
}
