#define SURVIVE_ENABLE_FULL_API
#include "stdbool.h"

#include <assert.h>
#include <survive_api.h>

#include "inttypes.h"
#include "os_generic.h"
#include "stdio.h"
#include "string.h"
#include "survive.h"

struct SurviveExternalObject {
	SurvivePose pose;
	SurviveVelocity velocity;
};

struct SurviveLighthouseData {
	int lighthouse;
	char serial_number[16];
};

struct SurviveSimpleObject {
	struct SurviveSimpleContext *actx;

	enum SurviveSimpleObject_type type;

	union {
		struct SurviveLighthouseData lh;
		struct SurviveObject *so;
		struct SurviveExternalObject seo;
	} data;

	char name[32];
	bool has_update;

	SurviveSimpleObject *next;
};

struct SurviveSimpleObjectList {
	size_t cnt;
	SurviveSimpleObject *head, *tail;
};

#define MAX_EVENT_SIZE 64
struct SurviveSimpleContext {
	SurviveContext *ctx;
	SurviveSimpleLogFn log_fn;
	void *user;

	bool running;
	og_thread_t thread;
	og_mutex_t poll_mutex;
	og_cv_t update_cv;

	size_t events_cnt;
	size_t event_next_write;
	struct SurviveSimpleEvent events[MAX_EVENT_SIZE];

	struct SurviveSimpleObjectList objects;
};

static enum SurviveSimpleObject_type to_simple_type(SurviveObjectType sot) {
	switch (sot) {
		case SURVIVE_OBJECT_TYPE_HMD:
			return SurviveSimpleObject_HMD;
		case SURVIVE_OBJECT_TYPE_OTHER:
		case SURVIVE_OBJECT_TYPE_CONTROLLER:
			return SurviveSimpleObject_OBJECT;
		default:
			return SurviveSimpleObject_UNKNOWN;
	}
}

static void unlock_and_notify_change(SurviveSimpleContext *actx) {
	OGBroadcastCond(actx->update_cv);
	OGUnlockMutex(actx->poll_mutex);
}

static void insert_into_event_buffer(SurviveSimpleContext *actx, const SurviveSimpleEvent *event) {
	bool buffer_full = actx->events_cnt == MAX_EVENT_SIZE;

	actx->events[actx->event_next_write] = *event;
	actx->event_next_write = (actx->event_next_write + 1) % MAX_EVENT_SIZE;
	if (!buffer_full)
		actx->events_cnt++;
	unlock_and_notify_change(actx);
}

static bool pop_from_event_buffer(SurviveSimpleContext *actx, SurviveSimpleEvent *event) {
	if (actx->events_cnt == 0)
		return false;

	size_t read_idx = (MAX_EVENT_SIZE + actx->event_next_write - actx->events_cnt) % MAX_EVENT_SIZE;
	*event = actx->events[read_idx];

	// Mark read events as invalid and assert if they are about to be passed on. This means there is a logic bug in the
	// circular buffer
	assert(event->event_type != -1);
	actx->events[read_idx].event_type = -1;

	actx->events_cnt--;
	return true;
}

static void SurviveSimpleObjectList_add(struct SurviveSimpleObjectList *list, SurviveSimpleObject *so) {
	list->cnt++;
	if (list->head == 0) {
		list->head = so;
	} else {
		assert(list->tail);
		list->tail->next = so;
	}

	list->tail = so;
}

static SurviveSimpleObject *find_or_create_external(SurviveSimpleContext *actx, const char *name) {
	for (struct SurviveSimpleObject *so = actx->objects.head; so; so = so->next) {
		if (strncmp(name, so->name, 32) == 0) {
			return so;
		}
	}

	SurviveSimpleObject *so = SV_CALLOC(sizeof(struct SurviveSimpleObject));
	so->type = SurviveSimpleObject_EXTERNAL;
	so->actx = actx;
	strncpy(so->name, name, sizeof(so->name) - 1);
	SurviveSimpleObjectList_add(&actx->objects, so);

	return so;
}

static void external_velocity_fn(SurviveContext *ctx, const char *name, const SurviveVelocity *velocity) {
	SurviveSimpleContext *actx = ctx->user_ptr;
	OGLockMutex(actx->poll_mutex);
	survive_default_external_velocity_process(ctx, name, velocity);

	SurviveSimpleObject *so = find_or_create_external(actx, name);
	so->has_update = true;
	so->data.seo.velocity = *velocity;
	unlock_and_notify_change(actx);
}

static void external_pose_fn(SurviveContext *ctx, const char *name, const SurvivePose *pose) {
	SurviveSimpleContext *actx = ctx->user_ptr;
	OGLockMutex(actx->poll_mutex);
	survive_default_external_pose_process(ctx, name, pose);

	SurviveSimpleObject *so = find_or_create_external(actx, name);
	so->has_update = true;
	so->data.seo.pose = *pose;
	unlock_and_notify_change(actx);
}
static void pose_fn(SurviveObject *so, survive_long_timecode timecode, const SurvivePose *pose) {
	SurviveSimpleContext *actx = so->ctx->user_ptr;
	OGLockMutex(actx->poll_mutex);
	survive_default_pose_process(so, timecode, pose);

	struct SurviveSimpleObject *sao = so->user_ptr;
	sao->has_update = true;
	unlock_and_notify_change(actx);
}

static inline SurviveSimpleObject *create_lighthouse(SurviveSimpleContext *actx, size_t i) {
	SurviveSimpleObject *obj = SV_CALLOC(sizeof(struct SurviveSimpleObject));
	obj->data.lh.lighthouse = i;
	obj->type = SurviveSimpleObject_LIGHTHOUSE;
	obj->actx = actx;

	SurviveContext *ctx = actx->ctx;
	obj->has_update = ctx->bsd[i].PositionSet;
	ctx->bsd[i].user_ptr = obj;
	snprintf(obj->name, 32, "LH%" PRIdPTR, i);
	snprintf(obj->data.lh.serial_number, 16, "LHB-%X", (unsigned)ctx->bsd[i].BaseStationID);
	SurviveSimpleObjectList_add(&actx->objects, obj);

	OGLockMutex(actx->poll_mutex);
	SurviveSimpleEvent event = {.event_type = SurviveSimpleEventType_DeviceAdded,
								.d = {.object_event = {
										  .time = survive_simple_run_time_since_epoch(actx),
										  .object = obj,
									  }}};
	insert_into_event_buffer(actx, &event);

	return obj;
}

static void lh_fn(SurviveContext *ctx, uint8_t lighthouse, const SurvivePose *lighthouse_pose) {
	SurviveSimpleContext *actx = ctx->user_ptr;
	OGLockMutex(actx->poll_mutex);
	survive_default_lighthouse_pose_process(ctx, lighthouse, lighthouse_pose);

	struct SurviveSimpleObject *sao = ctx->bsd[lighthouse].user_ptr;
	if (sao == 0)
		sao = create_lighthouse(actx, lighthouse);
	sao->has_update = true;

	unlock_and_notify_change(actx);
}

static void button_fn(SurviveObject *so, enum SurviveInputEvent eventType, enum SurviveButton buttonId,
					  const enum SurviveAxis *axisIds, const SurviveAxisVal_t *axisVals) {
	SurviveSimpleContext *actx = so->ctx->user_ptr;
	OGLockMutex(actx->poll_mutex);
	survive_default_button_process(so, eventType, buttonId, axisIds, axisVals);
	struct SurviveSimpleObject *sao = so->user_ptr;

	SurviveSimpleEvent event = {.event_type = SurviveSimpleEventType_ButtonEvent,
								.d = {.button_event = {
										  .time = survive_simple_run_time_since_epoch(actx),
										  .object = sao,
										  .event_type = eventType,
										  .button_id = buttonId,
									  }}};

	for (int i = 0; i < 16 && axisIds && axisIds[i] != 255; i++) {
		event.d.button_event.axis_count++;
		event.d.button_event.axis_ids[i] = axisIds[i];
		event.d.button_event.axis_val[i] = axisVals[i];
	}
	insert_into_event_buffer(actx, &event);
}

static int config_fn(struct SurviveObject *so, char *ct0conf, int len) {
	int res = survive_default_config_process(so, ct0conf, len);
	SurviveSimpleContext *actx = so->ctx->user_ptr;

	OGLockMutex(actx->poll_mutex);
	SurviveSimpleObject *sso = so->user_ptr;
	sso->type = to_simple_type(so->object_type);

	struct SurviveSimpleEvent event = {.event_type = SurviveSimpleEventType_ConfigEvent,
									   .d = {.config_event = {.time = survive_simple_run_time_since_epoch(actx),
															  .object = sso,
															  .cfg = survive_simple_json_config(sso)}}};

	insert_into_event_buffer(actx, &event);

	return res;
}

SURVIVE_EXPORT SurviveSimpleContext *survive_simple_init(int argc, char *const *argv) {
	return survive_simple_init_with_logger(argc, argv, 0);
}

SURVIVE_EXPORT void survive_simple_set_user(SurviveSimpleContext *actx, void *user) { actx->user = user; }
SURVIVE_EXPORT void *survive_simple_get_user(SurviveSimpleContext *actx) { return actx->user; }

static void simple_log_fn(SurviveContext *ctx, SurviveLogLevel logLevel, const char *msg) {
	SurviveSimpleContext *actx = ctx->user_ptr;
	if (actx == 0 || actx->log_fn == 0) {
		survive_default_log_process(ctx, logLevel, msg);
		return;
	}

	actx->log_fn(actx, logLevel, msg);
}

static void new_object_fn(SurviveObject *so) {
	SurviveSimpleContext *actx = so->ctx->user_ptr;

	SurviveSimpleObject *obj = SV_CALLOC(sizeof(struct SurviveSimpleObject));
	obj->data.so = so;
	obj->type = to_simple_type(so->object_type);
	obj->actx = actx;
	obj->data.so->user_ptr = (void *)obj;
	strncpy(obj->name, obj->data.so->codename, sizeof(obj->name));

	SurviveSimpleObjectList_add(&actx->objects, obj);

	OGLockMutex(actx->poll_mutex);
	survive_default_new_object_process(so);
	SurviveSimpleEvent event = {.event_type = SurviveSimpleEventType_DeviceAdded,
								.d = {.object_event = {
										  .time = survive_simple_run_time_since_epoch(actx),
										  .object = obj,
									  }}};
	insert_into_event_buffer(actx, &event);
}

SURVIVE_EXPORT SurviveSimpleContext *survive_simple_init_with_logger(int argc, char *const *argv,
																	 SurviveSimpleLogFn fn) {
	SurviveSimpleContext *actx = SV_CALLOC(sizeof(SurviveSimpleContext));
	actx->log_fn = fn;

	SurviveContext *ctx = survive_init_with_logger(argc, argv, actx, simple_log_fn);
	if (ctx == 0) {
		free(actx);
		return 0;
	}

	survive_install_new_object_fn(ctx, new_object_fn);

	if (ctx == 0) {
		free(actx);
		return 0;
	}
	ctx->user_ptr = actx;
	actx->ctx = ctx;
	actx->poll_mutex = OGCreateMutex();
	actx->update_cv = OGCreateConditionVariable();

	survive_startup(ctx);

	intptr_t i = 0;
	for (i = 0; i < ctx->activeLighthouses; i++) {
		create_lighthouse(actx, i);
	}

	survive_install_pose_fn(ctx, pose_fn);
	survive_install_external_pose_fn(ctx, external_pose_fn);
	survive_install_external_velocity_fn(ctx, external_velocity_fn);
	survive_install_button_fn(ctx, button_fn);
	survive_install_lighthouse_pose_fn(ctx, lh_fn);
	survive_install_config_fn(ctx, config_fn);
	return actx;
}

int survive_simple_stop_thread(SurviveSimpleContext *actx) {
	actx->running = false;
	intptr_t error = (intptr_t)OGJoinThread(actx->thread);
	actx->thread = 0;
	if (error != 0) {
		SurviveContext *ctx = actx->ctx;
		SV_INFO("Warning: Loop exited with error %" PRIdPTR, error);
	}
	return error;
}

void survive_simple_close(SurviveSimpleContext *actx) {
	if (actx->running) {
		survive_simple_stop_thread(actx);
	}

	survive_close(actx->ctx);

	for (struct SurviveSimpleObject *n = actx->objects.head; n;) {
		struct SurviveSimpleObject *freeMe = n;
		n = n->next;
		free(freeMe);
	}

	OGDeleteMutex(actx->poll_mutex);
	OGJoinThread(actx->thread);

	OGDeleteConditionVariable(actx->update_cv);
	actx->thread = 0;
	free(actx);
}

static inline void *__simple_thread(void *_actx) {
	SurviveSimpleContext *actx = _actx;
	intptr_t error = 0;
	while (actx->running && error == 0) {
		error = survive_poll(actx->ctx);
	}
	actx->running = false;
	return (void*)error; 
}
bool survive_simple_is_running(SurviveSimpleContext *actx) { return actx->running; }
void survive_simple_start_thread(SurviveSimpleContext *actx) {
	actx->running = true;
	actx->thread = OGCreateThread(__simple_thread, "survive simple api", actx);
}

const SurviveSimpleObject *survive_simple_get_next_object(SurviveSimpleContext *actx, const SurviveSimpleObject *curr) {
	return curr->next;
}

SurviveSimpleObject *survive_simple_get_object(SurviveSimpleContext *actx, const char *name) {
	for (struct SurviveSimpleObject *n = actx->objects.head; n; n = n->next) {
		if (strcmp(name, n->name) == 0) {
			return n;
		}
	}
	return 0;
}

const SurviveSimpleObject *survive_simple_get_first_object(SurviveSimpleContext *actx) { return actx->objects.head; }

size_t survive_simple_get_object_count(SurviveSimpleContext *actx) { return actx->objects.cnt; }

const SurviveSimpleObject *survive_simple_get_next_updated(SurviveSimpleContext *actx) {
	for (struct SurviveSimpleObject *n = actx->objects.head; n; n = n->next) {
		if (n->has_update) {
			n->has_update = false;
			return n;
		}
	}
	return 0;
}

FLT survive_simple_object_get_latest_velocity(const SurviveSimpleObject *sao, SurviveVelocity *velocity) {
	FLT timecode = 0;
	OGLockMutex(sao->actx->poll_mutex);

	switch (sao->type) {
	case SurviveSimpleObject_LIGHTHOUSE:
		if (velocity)
			*velocity = (SurviveVelocity){ 0 };
		timecode = survive_simple_run_time_since_epoch(sao->actx);
		break;
	case SurviveSimpleObject_HMD:
	case SurviveSimpleObject_OBJECT:
		if (velocity)
			*velocity = sao->data.so->velocity;
		timecode = SurviveSensorActivations_runtime(&sao->data.so->activations, sao->data.so->velocity_timecode) * 1e-6;
		break;
	case SurviveSimpleObject_EXTERNAL:
		if (velocity)
			*velocity = sao->data.seo.velocity;
		break;

	default: {
		SurviveContext *ctx = sao->actx->ctx;
		SV_GENERAL_ERROR("Invalid object type %d", sao->type);
	}
	}

	OGUnlockMutex(sao->actx->poll_mutex);
	return timecode;
}

void survive_simple_object_get_transform_to_imu(const SurviveSimpleObject *sao, SurvivePose *pose) {
    *pose = LinmathPose_Identity;
    const SurviveObject *so = survive_simple_get_survive_object(sao);
    if (so == 0)
        return;
    *pose = so->head2imu;
}

FLT survive_simple_object_get_latest_pose(const SurviveSimpleObject *sao, SurvivePose *pose) {
	FLT timecode = 0;
	OGLockMutex(sao->actx->poll_mutex);

	switch (sao->type) {
	case SurviveSimpleObject_LIGHTHOUSE: {
		if (pose)
			*pose = *survive_get_lighthouse_position(sao->actx->ctx, sao->data.lh.lighthouse);
		timecode = survive_simple_run_time_since_epoch(sao->actx);
		break;
	}
	case SurviveSimpleObject_HMD:
	case SurviveSimpleObject_OBJECT:
		if (pose)
			*pose = sao->data.so->OutPose;
		timecode = SurviveSensorActivations_runtime(&sao->data.so->activations, sao->data.so->OutPose_timecode) * 1e-6;
		break;
	case SurviveSimpleObject_EXTERNAL:
		if (pose) {
			ApplyPoseToPose(pose, survive_external_to_world(sao->actx->ctx), &sao->data.seo.pose);
		}
		break;

	default: {
		SurviveContext *ctx = sao->actx->ctx;
		SV_GENERAL_ERROR("Invalid object type %d", sao->type);
	}
	}

	OGUnlockMutex(sao->actx->poll_mutex);
	return timecode;
}

SURVIVE_EXPORT bool survive_simple_object_charging(const SurviveSimpleObject *sao) {
	switch (sao->type) {
	case SurviveSimpleObject_LIGHTHOUSE: {
		return true;
	}
	case SurviveSimpleObject_HMD:
	case SurviveSimpleObject_OBJECT:
		return sao->data.so->charging;
	case SurviveSimpleObject_EXTERNAL:
	default:
		return false;
	}
}

SURVIVE_EXPORT uint8_t survive_simple_object_charge_percet(const SurviveSimpleObject *sao) {
	switch (sao->type) {
	case SurviveSimpleObject_LIGHTHOUSE: {
		return 100;
	}
	case SurviveSimpleObject_HMD:
	case SurviveSimpleObject_OBJECT:
		return sao->data.so->charge;
	case SurviveSimpleObject_EXTERNAL:
	default:
		return 0;
	}
}

const char *survive_simple_object_name(const SurviveSimpleObject *sao) { return sao->name; }
const char *survive_simple_serial_number(const SurviveSimpleObject *sao) {
	switch (sao->type) {
	case SurviveSimpleObject_LIGHTHOUSE: {
		return sao->data.lh.serial_number;
	}
	case SurviveSimpleObject_HMD:
	case SurviveSimpleObject_OBJECT:
		return sao->data.so->serial_number;
	case SurviveSimpleObject_EXTERNAL:
	default:
		return survive_simple_object_name(sao);
	}
}
SURVIVE_EXPORT const char *survive_simple_json_config(const SurviveSimpleObject *sao) {
	switch (sao->type) {
	case SurviveSimpleObject_HMD:
	case SurviveSimpleObject_OBJECT:
		return sao->data.so->conf;
	case SurviveSimpleObject_EXTERNAL:
	case SurviveSimpleObject_LIGHTHOUSE:
	default:
		return 0;
	}
	return 0;
}

void survive_simple_lock(SurviveSimpleContext *actx) { OGLockMutex(actx->poll_mutex); }

void survive_simple_unlock(SurviveSimpleContext *actx) { OGUnlockMutex(actx->poll_mutex); }

SurviveContext *survive_simple_get_ctx(SurviveSimpleContext *actx) { return actx->ctx; }

SurviveObject *survive_simple_get_survive_object(const SurviveSimpleObject *sao) {
	switch (sao->type) {
	case SurviveSimpleObject_HMD:
	case SurviveSimpleObject_OBJECT:
		return sao->data.so;
	default:
		return NULL;
	}
}

BaseStationData *survive_simple_get_bsd(const SurviveSimpleObject *sao) {
	switch (sao->type) {
	case SurviveSimpleObject_LIGHTHOUSE:
		return &sao->actx->ctx->bsd[sao->data.lh.lighthouse];
	default:
		return NULL;
	}
}

const SurviveSimpleConfigEvent *survive_simple_get_config_event(const SurviveSimpleEvent *event) {
	if (event->event_type == SurviveSimpleEventType_ConfigEvent)
		return &event->d.config_event;
	return NULL;
}

const SurviveSimpleButtonEvent *survive_simple_get_button_event(const SurviveSimpleEvent *event) {
	if (event->event_type == SurviveSimpleEventType_ButtonEvent)
		return &event->d.button_event;
	return NULL;
}

const struct SurviveSimplePoseUpdatedEvent *survive_simple_get_pose_updated_event(const SurviveSimpleEvent *event) {
	if (event->event_type == SurviveSimpleEventType_PoseUpdateEvent) {
		return &event->d.pose_event;
	}
	return NULL;
}

bool survive_simple_wait_for_update(SurviveSimpleContext *actx) {
	OGLockMutex(actx->poll_mutex);
	OGWaitCondTimeout(actx->update_cv, actx->poll_mutex, 100);
	OGUnlockMutex(actx->poll_mutex);
	return survive_simple_is_running(actx);
}

enum SurviveSimpleEventType survive_simple_wait_for_event(SurviveSimpleContext *actx, SurviveSimpleEvent *event) {
	survive_simple_wait_for_update(actx);
	return survive_simple_next_event(actx, event);
}

enum SurviveSimpleEventType survive_simple_next_event(SurviveSimpleContext *actx, SurviveSimpleEvent *event) {
	event->event_type = SurviveSimpleEventType_None;

	OGLockMutex(actx->poll_mutex);
	pop_from_event_buffer(actx, event);
	OGUnlockMutex(actx->poll_mutex);

	if (event->event_type == SurviveSimpleEventType_None) {
		const SurviveSimpleObject *sso = survive_simple_get_next_updated(actx);
		if (sso) {
			event->event_type = SurviveSimpleEventType_PoseUpdateEvent;
			event->d.pose_event = (SurviveSimplePoseUpdatedEvent){
				.object = sso,
			};
			event->d.pose_event.time = survive_simple_object_get_latest_pose(sso, &event->d.pose_event.pose);
			survive_simple_object_get_latest_velocity(sso, &event->d.pose_event.velocity);
			return event->event_type;
		}
	}

	if (event->event_type == SurviveSimpleEventType_None && survive_simple_is_running(actx) == false) {
		return event->event_type = SurviveSimpleEventType_Shutdown;
	}

	return event->event_type;
}

enum SurviveSimpleObject_type survive_simple_object_get_type(const struct SurviveSimpleObject *sao) {
	return sao->type;
}
SurviveSimpleSubobject_type survive_simple_object_get_subtype(const struct SurviveSimpleObject *sao) {
	const SurviveObject *so = survive_simple_get_survive_object(sao);
	if (so == 0)
		return SURVIVE_OBJECT_SUBTYPE_GENERIC;

	return so->object_subtype;
}
int survive_simple_object_haptic(struct SurviveSimpleObject *sao, FLT frequency, FLT amplitude, FLT time_s) {
	SurviveObject *so = survive_simple_get_survive_object(sao);
	return survive_haptic(so, frequency, amplitude, time_s);
}
SurviveAxisVal_t survive_simple_object_get_input_axis(const struct SurviveSimpleObject *sao, enum SurviveAxis axis) {
	SurviveObject *so = survive_simple_get_survive_object(sao);
	if (so == 0 || axis > 16)
		return 0;

	return so->axis[axis];
}
SURVIVE_EXPORT int32_t survive_simple_object_get_button_mask(const struct SurviveSimpleObject *sao) {
    const SurviveObject *so = survive_simple_get_survive_object(sao);
    if (so == 0)
        return 0;
    return so->buttonmask;
}
SURVIVE_EXPORT int32_t survive_simple_object_get_touch_mask(const struct SurviveSimpleObject *sao)  {
    const SurviveObject *so = survive_simple_get_survive_object(sao);
    if (so == 0)
        return 0;
    return so->touchmask;
}

const struct SurviveSimpleObjectEvent *survive_simple_get_object_event(const SurviveSimpleEvent *event) {
	switch (event->event_type) {
	case SurviveSimpleEventType_DeviceAdded:
	case SurviveSimpleEventType_ButtonEvent:
	case SurviveSimpleEventType_ConfigEvent:
	case SurviveSimpleEventType_PoseUpdateEvent:
		return &event->d.object_event;
	case SurviveSimpleEventType_Shutdown:
		return NULL;
	}
	return NULL;
}
FLT survive_simple_run_time_since_epoch(const struct SurviveSimpleContext *actx) {
	if (actx->ctx == 0)
		return 0;
	return survive_run_time_since_epoch(actx->ctx);
}
FLT survive_simple_run_time(const struct SurviveSimpleContext *actx) {
	if (actx->ctx == 0)
		return 0;
	return survive_run_time(actx->ctx);
}
