#include "engine.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void XrEngineInit(struct XrEngine* engine, void* system, const char* name, int version)
{
    if (engine->Initialized)
        return;
    memset(engine, 0, sizeof(engine));

#ifdef ANDROID
    PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
    xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR",
                          (PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
    if (xrInitializeLoaderKHR != NULL)
    {
        xrJava* java = (xrJava*)system;
        XrLoaderInitInfoAndroidKHR loader_info;
        memset(&loader_info, 0, sizeof(loader_info));
        loader_info.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
        loader_info.next = NULL;
        loader_info.applicationVM = java->vm;
        loader_info.applicationContext = java->activity;
        xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&loader_info);
    }
#endif

    int count = 0;
    const char* extensions[32];
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
    extensions[count++] = XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME;
#endif
#ifdef ANDROID
    if (engine->PlatformFlag[PLATFORM_EXTENSION_INSTANCE])
    {
        extensions[count++] = XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME;
    }
    if (engine->PlatformFlag[PLATFORM_EXTENSION_PASSTHROUGH])
    {
        extensions[count++] = XR_FB_PASSTHROUGH_EXTENSION_NAME;
    }
    if (engine->PlatformFlag[PLATFORM_EXTENSION_PERFORMANCE])
    {
        extensions[count++] = XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME;
        extensions[count++] = XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME;
    }
#endif

    // Create the OpenXR instance.
    XrApplicationInfo app_info;
    memset(&app_info, 0, sizeof(app_info));
    strcpy(app_info.applicationName, name);
    strcpy(app_info.engineName, name);
    app_info.applicationVersion = version;
    app_info.engineVersion = version;
    app_info.apiVersion = XR_CURRENT_API_VERSION;

    XrInstanceCreateInfo instance_info;
    memset(&instance_info, 0, sizeof(instance_info));
    instance_info.type = XR_TYPE_INSTANCE_CREATE_INFO;
    instance_info.next = NULL;
    instance_info.createFlags = 0;
    instance_info.applicationInfo = app_info;
    instance_info.enabledApiLayerCount = 0;
    instance_info.enabledApiLayerNames = NULL;
    instance_info.enabledExtensionCount = count;
    instance_info.enabledExtensionNames = extensions;

#ifdef ANDROID
    XrInstanceCreateInfoAndroidKHR instance_info_android = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR};
    if (engine->PlatformFlag[PLATFORM_EXTENSION_INSTANCE])
    {
        xrJava* java = (xrJava*)system;
        instance_info_android.applicationVM = java->vm;
        instance_info_android.applicationActivity = java->activity;
        instance_info.next = (XrBaseInStructure*)&instance_info_android;
    }
#endif

    XrResult result;
    OXR(result = xrCreateInstance(&instance_info, &engine->Instance));
    if (result != XR_SUCCESS)
    {
        ALOGE("Failed to create XR instance: %d", (int)result);
        exit(1);
    }

    XrInstanceProperties instance_properties;
    instance_properties.type = XR_TYPE_INSTANCE_PROPERTIES;
    instance_properties.next = NULL;
    OXR(xrGetInstanceProperties(engine->Instance, &instance_properties));
    ALOGV("Runtime %s: Version : %d.%d.%d", instance_properties.runtimeName,
          XR_VERSION_MAJOR(instance_properties.runtimeVersion),
          XR_VERSION_MINOR(instance_properties.runtimeVersion),
          XR_VERSION_PATCH(instance_properties.runtimeVersion));

    XrSystemGetInfo system_info;
    memset(&system_info, 0, sizeof(system_info));
    system_info.type = XR_TYPE_SYSTEM_GET_INFO;
    system_info.next = NULL;
    system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;

    OXR(result = xrGetSystem(engine->Instance, &system_info, &engine->SystemId));
    if (result != XR_SUCCESS)
    {
        ALOGE("Failed to get system");
        exit(1);
    }

    // Get the graphics requirements.
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
    PFN_xrGetOpenGLESGraphicsRequirementsKHR pfnGetOpenGLESGraphicsRequirementsKHR = NULL;
    OXR(xrGetInstanceProcAddr(engine->Instance, "xrGetOpenGLESGraphicsRequirementsKHR",
                              (PFN_xrVoidFunction*)(&pfnGetOpenGLESGraphicsRequirementsKHR)));

    XrGraphicsRequirementsOpenGLESKHR graphics_requirements = {};
    graphics_requirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR;
    OXR(pfnGetOpenGLESGraphicsRequirementsKHR(engine->Instance, engine->SystemId, &graphics_requirements));
#endif

#ifdef ANDROID
    engine->MainThreadId = gettid();
#endif
    engine->Initialized = true;
}

void XrEngineDestroy(struct XrEngine* engine)
{
    if (engine->Initialized)
    {
        xrDestroyInstance(engine->Instance);
        engine->Initialized = false;
    }
}

void XrEngineEnter(struct XrEngine* engine)
{
    if (engine->Session)
    {
        ALOGE("EnterXR called with existing session");
        return;
    }

    // Create the OpenXR Session.
    XrSessionCreateInfo session_info;
    memset(&session_info, 0, sizeof(session_info));
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
    XrGraphicsBindingOpenGLESAndroidKHR graphics_binding_gl = {};
    graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR;
    graphics_binding_gl.next = NULL;
    graphics_binding_gl.display = eglGetCurrentDisplay();
    graphics_binding_gl.config = NULL;
    graphics_binding_gl.context = eglGetCurrentContext();
    session_info.next = &graphics_binding_gl;
#endif
    session_info.type = XR_TYPE_SESSION_CREATE_INFO;
    session_info.createFlags = 0;
    session_info.systemId = engine->SystemId;

    XrResult result;
    OXR(result = xrCreateSession(engine->Instance, &session_info, &engine->Session));
    if (result != XR_SUCCESS)
    {
        ALOGE("Failed to create XR session: %d", (int)result);
        exit(1);
    }

    // Create a space to the first path
    XrReferenceSpaceCreateInfo space_info = {};
    space_info.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
    space_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
    space_info.poseInReferenceSpace.orientation.w = 1.0f;
    OXR(xrCreateReferenceSpace(engine->Session, &space_info, &engine->HeadSpace));
}

void XrEngineLeave(struct XrEngine* engine)
{
    if (engine->Session)
    {
        OXR(xrDestroySpace(engine->HeadSpace));
        // StageSpace is optional.
        if (engine->StageSpace != XR_NULL_HANDLE)
        {
            OXR(xrDestroySpace(engine->StageSpace));
        }
        OXR(xrDestroySpace(engine->FakeSpace));
        engine->CurrentSpace = XR_NULL_HANDLE;
        OXR(xrDestroySession(engine->Session));
        engine->Session = XR_NULL_HANDLE;
    }
}

void XrEngineWaitForFrame(struct XrEngine* engine)
{
    XrFrameWaitInfo wait_frame_info = {};
    wait_frame_info.type = XR_TYPE_FRAME_WAIT_INFO;
    wait_frame_info.next = NULL;

    XrFrameState frame_state = {};
    frame_state.type = XR_TYPE_FRAME_STATE;
    frame_state.next = NULL;

    OXR(xrWaitFrame(engine->Session, &wait_frame_info, &frame_state));
    engine->PredictedDisplayTime = frame_state.predictedDisplayTime;
}
