#define RT_DONT_DEFINE_MAIN_LOOP_GLOBAL
#include "main_loop.h"
#include "runtime/runtime.h"
#include "runtime/config.h"

RT_CVAR_I(rt_MaxFrameLatency, "Maximum latency between update and rendering. Default: 2", 1);

RT_DLLEXPORT rt_main_loop g_main_loop;

/* ****************
 * Update Thread
 * ****************/

void UpdateThreadEntry(void *param) {
    rtLog("UT", "UpdateThread Entry");
    while (!g_main_loop.shutdown) {
        /* Wait until the render thread has catched up */
        rtWaitOnSemaphore(&g_main_loop.update_proceed);
        //rtLog("UT", "Processing %u", g_main_loop.u_frame_id);

        (g_main_loop.GameUpdate)(g_main_loop.u_frame_id);

        //rtLog("UT", "Finished   %u", g_main_loop.u_frame_id);
        g_main_loop.u_frame_id += 1;
        /* Signal the render thread that data is available */
        rtSignalSemaphore(&g_main_loop.render_proceed);
    }
}

/* ****************
 * Render Thread
 * ****************/

void RenderThreadEntry(void *param) {
    rtLog("RT", "RenderThread Entry");
    while (!g_main_loop.shutdown) {
        rtWaitOnSemaphore(&g_main_loop.render_proceed);
        //rtLog("RT", "Processing %u", g_main_loop.r_frame_id);

        (g_main_loop.GameRender)(g_main_loop.r_frame_id);

        //rtLog("RT", "Finished   %u", g_main_loop.r_frame_id);
        g_main_loop.r_frame_id += 1;
        /* Signal the update thread that we have finished and it can proceed */
        rtSignalSemaphore(&g_main_loop.update_proceed);
    }
}

RT_DLLEXPORT rt_result rtInitMainLoop(rt_main_loop_update_fn *update_cb,
                                      rt_main_loop_render_fn *render_cb) {
    RT_ASSERT(g_main_loop.render_thread == NULL && g_main_loop.update_thread == NULL,
              "InitMainLoop called multiple times.");
    RT_ASSERT(update_cb != NULL && render_cb != NULL,
              "Valid update and render functions must be provided.");

    g_main_loop.r_frame_id = 0;
    g_main_loop.u_frame_id = 0;
    g_main_loop.shutdown   = 0;
    g_main_loop.GameUpdate = update_cb;
    g_main_loop.GameRender = render_cb;

    int frame_latency = rt_MaxFrameLatency.i;
    if (frame_latency < 2) {
        rtLog("ML", "Forcing rt_MaxFrameLatency to minimum value 2.");
        frame_latency = 2;
    } else if (frame_latency > 3) {
        rtLog("ML", "Warning: rt_MaxFrameLatency is larger than 3 (%d)", frame_latency);
    }

    rt_create_semaphore_result update_proceed_create = rtCreateSemaphore(frame_latency, frame_latency);
    if (!update_proceed_create.ok) {
        rtReportError("ML", "Failed to create the update proceed semaphore.");
        return RT_UNKNOWN_ERROR;
    }
    g_main_loop.update_proceed = update_proceed_create.semaphore;

    rt_create_semaphore_result render_proceed_create = rtCreateSemaphore(0, frame_latency);
    if (!render_proceed_create.ok) {
        rtReportError("ML", "Failed to create the update proceed semaphore.");
        return RT_UNKNOWN_ERROR;
    }
    g_main_loop.render_proceed = render_proceed_create.semaphore;

    g_main_loop.update_thread = rtSpawnThread(UpdateThreadEntry, NULL, "UpdateThread");
    if (!g_main_loop.update_thread) {
        rtReportError("ML", "Failed to spawn the update thread.");
        return RT_UNKNOWN_ERROR;
    }

    g_main_loop.render_thread = rtSpawnThread(RenderThreadEntry, NULL, "RenderThread");
    if (!g_main_loop.render_thread) {
        rtReportError("ML", "Failed to spawn the render thread.");
        return RT_UNKNOWN_ERROR;
    }

    return RT_SUCCESS;
}

RT_DLLEXPORT void rtShutdownMainLoop(void) {
    g_main_loop.shutdown = 1;
    rtJoinThread(g_main_loop.update_thread);
    rtJoinThread(g_main_loop.render_thread);
    rtDestroySemaphore(&g_main_loop.update_proceed);
    rtDestroySemaphore(&g_main_loop.render_proceed);
}