// Copyright 2020-2023, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
 * @file
 * @brief  Internal header for SDL XR system.
 * @author Jakob Bornecrantz <jakob@collabora.com>
 * @ingroup sdl_test
 */

#include "xrt/xrt_system.h"
#include "xrt/xrt_device.h"
#include "xrt/xrt_instance.h"
#include "xrt/xrt_tracking.h"
#include "xrt/xrt_compositor.h"

#include "util/u_pacing.h"
#include "util/u_logging.h"
#include "util/comp_base.h"
#include "util/comp_swapchain.h"

#include "SDL2/SDL.h"

#include "ogl/ogl_api.h"


#ifdef __cplusplus
extern "C" {
#endif

struct sdl_program;

/*!
 * Sub-class of @ref comp_swapchain, used to do OpenGL rendering.
 *
 * @ingroup sdl_test
 */
struct sdl_swapchain
{
	struct comp_swapchain base;

	//! Pointer back to main program.
	struct sdl_program *sp;

	//! Cached width and height.
	int w, h;

	//! Number of textures in base.base.base.image_count.
	GLuint textures[XRT_MAX_SWAPCHAIN_IMAGES];

	//! Same number of images as textures.
	GLuint memory[XRT_MAX_SWAPCHAIN_IMAGES];
};

/*!
 * Tracking frame state.
 *
 * @ingroup sdl_test
 */
struct sdl_comp_frame
{
	int64_t id;
	uint64_t predicted_display_time_ns;
	uint64_t desired_present_time_ns;
	uint64_t present_slop_ns;
};

/*!
 * Split out for convenience.
 *
 * This ultimately implements @ref xrt_compositor_native but does so by
 * extending @ref comp_base, similar to how @ref comp_compositor works.
 *
 * @extends comp_base
 * @ingroup sdl_test
 */
struct sdl_compositor
{
	//! Base native compositor.
	struct comp_base base;

	//! Pacing helper to drive us forward.
	struct u_pacing_compositor *upc;

	struct
	{
		//! Frame interval that we are using.
		uint64_t frame_interval_ns;
	} settings;

	// Kept here for convenience.
	struct xrt_system_compositor_info sys_info;


	//! @todo Insert your own required members here

	struct
	{
		struct sdl_comp_frame waited;
		struct sdl_comp_frame rendering;
	} frame;
};

struct sdl_program_plus;

/*!
 * C base class for the SDL program.
 *
 * @implements xrt_instance
 * @implements xrt_device
 * @implements xrt_system_devices
 * @extends sdl_compositor
 * @ingroup sdl_test
 */
struct sdl_program
{
	//! Base class for devices.
	struct xrt_device xdev_base;

	//! Instance base.
	struct xrt_instance xinst_base;

	//! System, implemented for now using helper code.
	struct u_system *usys;

	//! System devices base.
	struct xrt_system_devices xsysd_base;

	//! Space overseer, implemented for now using helper code.
	struct xrt_space_overseer *xso;

	//! SDL compositor struct.
	struct sdl_compositor c;

	//! Created system compositor.
	struct xrt_system_compositor *xsysc;

	//! Inputs exposed by the SDL device.
	struct xrt_input inputs[1];

	//! HMD parts exposed by the SDL device to become a HMD.
	struct xrt_hmd_parts hmd;

	//! Tracking origin that the device is located in.
	struct xrt_tracking_origin origin;

	//! The current log level.
	enum u_logging_level log_level;

	struct
	{
		struct
		{
			//! The pose of the head, only used for view space.
			struct xrt_pose pose;
		} head;

		struct
		{
			//! Pose of each individual eye.
			struct xrt_pose pose;

			//! Fov of each individual eye.
			struct xrt_fov fov;
		} left, right;
	} state;

	//! The main window.
	SDL_Window *win;

	//! Main OpenGL context.
	SDL_GLContext ctx;

	//! Protects the OpenGL context.
	struct os_mutex current_mutex;

	//! Pointer back to the C++ part of the program.
	struct sdl_program_plus *spp;
};


static inline struct sdl_program *
from_xinst(struct xrt_instance *xinst)
{
	return container_of(xinst, struct sdl_program, xinst_base);
}

static inline struct sdl_program *
from_xsysd(struct xrt_system_devices *xsysd)
{
	return container_of(xsysd, struct sdl_program, xsysd_base);
}

static inline struct sdl_program *
from_xdev(struct xrt_device *xdev)
{
	return container_of(xdev, struct sdl_program, xdev_base);
}

static inline struct sdl_program *
from_comp(struct xrt_compositor *xc)
{
	return container_of(xc, struct sdl_program, c.base.base);
}


/*!
 * Spew level logging.
 *
 * @relates sdl_program
 * @ingroup sdl_test
 */
#define ST_TRACE(sp, ...) U_LOG_IFL_T(sp->log_level, __VA_ARGS__);

/*!
 * Debug level logging.
 *
 * @relates sdl_program
 */
#define ST_DEBUG(sp, ...) U_LOG_IFL_D(sp->log_level, __VA_ARGS__);

/*!
 * Info level logging.
 *
 * @relates sdl_program
 * @ingroup sdl_test
 */
#define ST_INFO(sp, ...) U_LOG_IFL_I(sp->log_level, __VA_ARGS__);

/*!
 * Warn level logging.
 *
 * @relates sdl_program
 * @ingroup sdl_test
 */
#define ST_WARN(sp, ...) U_LOG_IFL_W(sp->log_level, __VA_ARGS__);

/*!
 * Error level logging.
 *
 * @relates sdl_program
 * @ingroup sdl_test
 */
#define ST_ERROR(sp, ...) U_LOG_IFL_E(sp->log_level, __VA_ARGS__);

/*!
 * Check for OpenGL errors, context needs to be current.
 *
 * @ingroup sdl_test
 */
#define CHECK_GL()                                                                                                     \
	do {                                                                                                           \
		GLint err = glGetError();                                                                              \
		if (err != 0) {                                                                                        \
			U_LOG_RAW("%s:%u: error: 0x%04x", __func__, __LINE__, err);                                    \
		}                                                                                                      \
	} while (false)

/*!
 * Makes the OpenGL context current in this thread, takes lock.
 *
 * @ingroup sdl_test
 */
static inline void
sdl_make_current(struct sdl_program *sp)
{
	os_mutex_lock(&sp->current_mutex);
	SDL_GL_MakeCurrent(sp->win, sp->ctx);
}

/*!
 * Unmakes the any OpenGL context current in this thread, releases the lock.
 *
 * @ingroup sdl_test
 */
static inline void
sdl_make_uncurrent(struct sdl_program *sp)
{
	SDL_GL_MakeCurrent(NULL, NULL);
	os_mutex_unlock(&sp->current_mutex);
}


/*
 *
 * sdl_device.c
 *
 */

/*!
 * Init the @ref xrt_device sub struct.
 *
 * In sdl_device.c
 *
 * @ingroup sdl_test
 */
void
sdl_device_init(struct sdl_program *sp);


/*
 *
 * sdl_swapchain.c
 *
 */

/*!
 * Implementation of @ref xrt_compositor::create_swapchain.
 *
 * @ingroup sdl_test
 */
xrt_result_t
sdl_swapchain_create(struct xrt_compositor *xc,
                     const struct xrt_swapchain_create_info *info,
                     struct xrt_swapchain **out_xsc);

/*!
 * Implementation of @ref xrt_compositor::import_swapchain.
 *
 * @ingroup sdl_test
 */
xrt_result_t
sdl_swapchain_import(struct xrt_compositor *xc,
                     const struct xrt_swapchain_create_info *info,
                     struct xrt_image_native *native_images,
                     uint32_t native_image_count,
                     struct xrt_swapchain **out_xsc);


/*
 *
 * sdl_compositor.c
 *
 */

/*!
 * Initializes the compositor part of the SDL program.
 *
 * @ingroup sdl_test
 */
void
sdl_compositor_init(struct sdl_program *sp);

/*!
 * Creates the system compositor that wraps the native compositor.
 *
 * @ingroup sdl_test
 */
xrt_result_t
sdl_compositor_create_system(struct sdl_program *sp, struct xrt_system_compositor **out_xsysc);


/*
 *
 * sdl_instance.c
 *
 */

/*!
 * Init the @ref xrt_system (and @ref u_system) struct.
 *
 * @ingroup sdl_test
 */
void
sdl_system_init(struct sdl_program *sp);

/*!
 * Init the @ref xrt_system_devices sub struct.
 *
 * @ingroup sdl_test
 */
void
sdl_system_devices_init(struct sdl_program *sp);

/*!
 * Init the @ref xrt_instance sub struct.
 *
 * @ingroup sdl_test
 */
void
sdl_instance_init(struct sdl_program *sp);


/*
 *
 * sdl_program.cpp
 *
 */

/*!
 * Create the SDL program.
 *
 * @ingroup sdl_test
 */
struct sdl_program *
sdl_program_plus_create(void);

/*!
 * Render a frame, called by the compositor when layers have been committed.
 *
 * @ingroup sdl_test
 */
void
sdl_program_plus_render(struct sdl_program_plus *spp);

/*!
 * Destroy the SDL program.
 *
 * @ingroup sdl_test
 */
void
sdl_program_plus_destroy(struct sdl_program_plus *spp);


#ifdef __cplusplus
}
#endif
