/*
 * MIT License
 * Author: Mark Allyn
 *
 * main.cpp — entry point for the Museum Vintage Radar Exhibit.
 *
 * Initialises GLFW/GLAD, builds the scope list, starts the background
 * threads, and runs the display loop until the window is closed.
 *
 * Thread map:
 *   Thread 1  — this function (GLFW event loop + rendering)
 *   Thread 2  — TrafficCop (polls Simulator and RPi receivers)
 *   Thread 3  — KnobPanel  (future hardware encoders, idles for now)
 *   Thread 4  — Simulator runs inside TrafficCop::run()
 */

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <cstdio>
#include <memory>

#include "settings.h"
#include "shared_render_state.h"
#include "target_buffer.h"
#include "left_panel.h"
#include "scope_manager.h"
#include "scope_intro.h"
#include "scope_marine_ppi.h"
#include "traffic_cop.h"
#include "knob_panel.h"

// Global pointer for GLFW callback (single-window app; safe as a global)
static ScopeManager* g_scopeMgr = nullptr;

static void keyCallback(GLFWwindow* window, int key, int scancode,
                        int action, int mods)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GLFW_TRUE);

    if (g_scopeMgr)
        g_scopeMgr->handleKey(key, scancode, action, mods);
}

static void errorCallback(int /*code*/, const char* msg) {
    fprintf(stderr, "GLFW error: %s\n", msg);
}

int main() {
    glfwSetErrorCallback(errorCallback);

    if (!glfwInit()) {
        fprintf(stderr, "GLFW init failed\n");
        return 1;
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE,   GLFW_FALSE);
    glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);

    GLFWwindow* window = glfwCreateWindow(
        WINDOW_WIDTH, WINDOW_HEIGHT,
        "Museum Vintage Radar Exhibit",
        nullptr, nullptr);
    if (!window) {
        fprintf(stderr, "GLFW window creation failed\n");
        glfwTerminate();
        return 1;
    }

    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);   // vsync on

    if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress))) {
        fprintf(stderr, "GLAD init failed\n");
        glfwDestroyWindow(window);
        glfwTerminate();
        return 1;
    }

    printf("OpenGL %s on %s\n",
           glGetString(GL_VERSION),
           glGetString(GL_RENDERER));

    glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    // ----------------------------------------------------------------
    // Shared state
    // ----------------------------------------------------------------
    SharedRenderState srs;
    TargetBuffer      tb;
    TextRenderer      tr;

    // Initialise text renderer — try primary font, then fallback
    bool fontOk = tr.init(FONT_PATH,     LEFT_PANEL_FONT_SIZE, "shaders/");
    if (!fontOk) {
        fprintf(stderr, "Primary font failed, trying fallback font\n");
        fontOk = tr.init(FONT_FALLBACK, LEFT_PANEL_FONT_SIZE, "shaders/");
    }
    if (!fontOk) {
        fprintf(stderr, "ERROR: cannot load any font; text will not render\n");
        // Non-fatal: continue without text rather than crashing the exhibit
    }

    // ----------------------------------------------------------------
    // Scopes
    // ----------------------------------------------------------------
    auto scopeMgr = std::make_unique<ScopeManager>();
    scopeMgr->addScope(std::make_unique<ScopeIntro>(srs, tb, tr));
    scopeMgr->addScope(std::make_unique<MarinePPIScope>(srs, tb, tr));

    g_scopeMgr = scopeMgr.get();
    glfwSetKeyCallback(window, keyCallback);

    if (!scopeMgr->init("shaders/")) {
        fprintf(stderr, "ERROR: scope initialisation failed\n");
        glfwDestroyWindow(window);
        glfwTerminate();
        return 1;
    }

    // ----------------------------------------------------------------
    // Background threads
    // ----------------------------------------------------------------
    TrafficCop trafficCop(tb, srs);
    KnobPanel  knobPanel(srs);
    trafficCop.start();
    knobPanel.start();

    // ----------------------------------------------------------------
    // Main display loop (Thread 1)
    // ----------------------------------------------------------------
    double prevTime = glfwGetTime();

    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        double now = glfwGetTime();
        float  dt  = static_cast<float>(now - prevTime);
        prevTime   = now;

        // Clamp dt so a paused/minimised window doesn't cause a huge jump
        if (dt > 0.05f) dt = 0.05f;

        glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        scopeMgr->render(dt,
                         static_cast<float>(WINDOW_WIDTH),
                         static_cast<float>(WINDOW_HEIGHT));

        glfwSwapBuffers(window);
    }

    // ----------------------------------------------------------------
    // Shutdown
    // ----------------------------------------------------------------
    trafficCop.stop();
    knobPanel.stop();

    g_scopeMgr = nullptr;
    scopeMgr.reset();   // destroys all GL resources while context is still current

    glfwDestroyWindow(window);
    glfwTerminate();
    return 0;
}
