/*
 * Copyright 2013, Fredrik Hultin.
 * Copyright 2013, Jakob Bornecrantz.
 * Copyright 2016 Philipp Zabel
 * Copyright 2019-2022 Jan Schmidt
 * SPDX-License-Identifier: BSL-1.0
 *
 */

/*!
 * @file
 * @brief  Oculus Rift S headset tracking system
 *
 * The Rift S system provides the HID/USB polling thread
 * and dispatches incoming packets to the HMD and controller
 * implementations.
 *
 * Ported from OpenHMD
 *
 * @author Jan Schmidt <jan@centricular.com>
 * @ingroup drv_rift_s
 */

/* Oculus Rift S Driver - HID/USB Driver Implementation */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <assert.h>

#include "math/m_api.h"
#include "math/m_vec3.h"

#include "os/os_time.h"

#include "util/u_device.h"
#include "util/u_distortion_mesh.h"
#include "util/u_trace_marker.h"
#include "util/u_var.h"

#include "xrt/xrt_device.h"

#include "rift_s.h"
#include "rift_s_hmd.h"
#include "rift_s_controller.h"
#include "rift_s_camera.h"

static void *
rift_s_run_thread(void *ptr);
static void
rift_s_system_free(struct rift_s_system *sys);

static int
read_camera_calibration(struct os_hid_device *hid_hmd, struct rift_s_camera_calibration_block *calibration)
{
	char *json = NULL;
	int json_len = 0;

	int ret = rift_s_read_firmware_block(hid_hmd, RIFT_S_FIRMWARE_BLOCK_CAMERA_CALIB, &json, &json_len);
	if (ret < 0)
		return ret;

	ret = rift_s_parse_camera_calibration_block(json, calibration);
	free(json);

	return ret;
}

static int
read_hmd_fw_imu_calibration(struct os_hid_device *hid_hmd, struct rift_s_imu_calibration *imu_calibration)
{
	char *json = NULL;
	int json_len = 0;

	int ret = rift_s_read_firmware_block(hid_hmd, RIFT_S_FIRMWARE_BLOCK_IMU_CALIB, &json, &json_len);
	if (ret < 0)
		return ret;

	ret = rift_s_parse_imu_calibration(json, imu_calibration);
	free(json);

	return ret;
}

static int
read_hmd_proximity_threshold(struct os_hid_device *hid_hmd, int *proximity_threshold)
{
	char *json = NULL;
	int json_len = 0;

	int ret = rift_s_read_firmware_block(hid_hmd, RIFT_S_FIRMWARE_BLOCK_THRESHOLD, &json, &json_len);
	if (ret < 0)
		return ret;

	ret = rift_s_parse_proximity_threshold(json, proximity_threshold);
	free(json);

	return ret;
}

static int
read_hmd_config(struct os_hid_device *hid_hmd, struct rift_s_hmd_config *config)
{
	int ret;

	ret = rift_s_read_firmware_version(hid_hmd);
	if (ret < 0) {
		RIFT_S_ERROR("Failed to read Rift S firmware version");
		return ret;
	}

	ret = rift_s_read_panel_info(hid_hmd, &config->panel_info);
	if (ret < 0) {
		RIFT_S_ERROR("Failed to read Rift S device info");
		return ret;
	}

	ret = rift_s_read_imu_config_info(hid_hmd, &config->imu_config_info);
	if (ret < 0) {
		RIFT_S_ERROR("Failed to read IMU configuration block");
		return ret;
	}

	ret = read_hmd_fw_imu_calibration(hid_hmd, &config->imu_calibration);
	if (ret < 0) {
		RIFT_S_ERROR("Failed to read IMU configuration block");
		return ret;
	}

	/* Configure the proximity sensor threshold */
	ret = read_hmd_proximity_threshold(hid_hmd, &config->proximity_threshold);
	if (ret < 0) {
		RIFT_S_ERROR("Failed to read proximity sensor firmware block");
		return ret;
	}

	ret = read_camera_calibration(hid_hmd, &config->camera_calibration);
	if (ret < 0) {
		RIFT_S_ERROR("Failed to read HMD camera calibration block");
		return ret;
	}

	return 0;
}

struct rift_s_system *
rift_s_system_create(struct xrt_prober *xp,
                     const unsigned char *hmd_serial_no,
                     struct os_hid_device *hid_hmd,
                     struct os_hid_device *hid_status,
                     struct os_hid_device *hid_controllers)
{
	int ret;

	DRV_TRACE_MARKER();

	struct rift_s_system *sys = U_TYPED_CALLOC(struct rift_s_system);
	sys->base.type = XRT_TRACKING_TYPE_NONE;
	sys->base.initial_offset.orientation.w = 1.0f;

	/* Init refcount */
	sys->ref.count = 1;

	sys->handles[HMD_HID] = hid_hmd;
	sys->handles[STATUS_HID] = hid_status;
	sys->handles[CONTROLLER_HID] = hid_controllers;

	ret = os_mutex_init(&sys->dev_mutex);
	if (ret != 0) {
		RIFT_S_ERROR("Failed to init device mutex");
		goto cleanup;
	}
	//
	// Thread and other state.
	ret = os_thread_helper_init(&sys->oth);
	if (ret != 0) {
		RIFT_S_ERROR("Failed to init packet processing thread");
		goto cleanup;
	}

	if (read_hmd_config(hid_hmd, &sys->hmd_config) < 0) {
		RIFT_S_ERROR("Failed to read HMD configuration");
		goto cleanup;
	}

	sys->tracker = rift_s_tracker_create(&sys->base, &sys->xfctx, &sys->hmd_config);
	if (sys->tracker == NULL) {
		RIFT_S_ERROR("Failed to init tracking");
		goto cleanup;
	}

	rift_s_radio_state_init(&sys->radio_state);

	/* Create the HMD now. Controllers are created in the
	 * rift_s_system_get_controller() call later */
	struct rift_s_hmd *hmd = rift_s_hmd_create(sys, hmd_serial_no, &sys->hmd_config);
	if (hmd == NULL) {
		RIFT_S_ERROR("Failed to create Oculus Rift S device.");
		goto cleanup;
	}

	sys->hmd = hmd;

	// Start the packet reading thread
	ret = os_thread_helper_start(&sys->oth, rift_s_run_thread, sys);
	if (ret != 0) {
		RIFT_S_ERROR("Failed to start packet processing thread");
		goto cleanup;
	}

	/* Turn on the headset and display connection */
	if (rift_s_hmd_enable(sys->handles[HMD_HID], true) < 0) {
		RIFT_S_ERROR("Failed to enable Rift S");
		goto cleanup;
	}

	// Allow time for enumeration of available displays by host system, so the compositor can select among them.
	RIFT_S_INFO(
	    "Sleeping until the HMD display is powered up so, the available displays "
	    "can be enumerated by the host system.");

	// Two seconds seems to be needed for the display connection to stabilise
	os_nanosleep((uint64_t)U_TIME_1S_IN_NS * 2);

	// Start the camera input
	struct rift_s_camera *cam =
	    rift_s_camera_create(xp, &sys->xfctx, (const char *)hmd_serial_no, sys->handles[HMD_HID], sys->tracker,
	                         &sys->hmd_config.camera_calibration);
	if (cam == NULL) {
		RIFT_S_ERROR("Failed to open Rift S camera device");
		goto cleanup;
	}
	os_mutex_lock(&sys->dev_mutex);
	sys->cam = cam;
	os_mutex_unlock(&sys->dev_mutex);

	rift_s_tracker_start(sys->tracker);

	RIFT_S_DEBUG("Oculus Rift S driver ready");

	return sys;

cleanup:
	if (sys->hmd != NULL) {
		xrt_device_destroy((struct xrt_device **)&sys->hmd);
	}
	rift_s_system_reference(&sys, NULL);
	return NULL;
}

static void
rift_s_system_free(struct rift_s_system *sys)
{
	/* Stop the packet reading thread */
	os_thread_helper_destroy(&sys->oth);

	/* Stop all the frame processing (has to happen before the cameras
	 * and tracker are destroyed */
	xrt_frame_context_destroy_nodes(&sys->xfctx);

	rift_s_radio_state_clear(&sys->radio_state);

	if (sys->handles[HMD_HID]) {
		if (rift_s_hmd_enable(sys->handles[HMD_HID], false) < 0) {
			RIFT_S_WARN("Failed to disable Rift S");
		}
	}

	for (int i = 0; i < 3; i++) {
		if (sys->handles[i] != NULL)
			os_hid_destroy(sys->handles[i]);
	}

	/* Free the camera */
	if (sys->cam != NULL) {
		rift_s_camera_destroy(sys->cam);
	}

	if (sys->tracker != NULL) {
		rift_s_tracker_destroy(sys->tracker);
	}

	os_mutex_destroy(&sys->dev_mutex);

	free(sys);
}

/* Reference count handling for rift_s_system */
void
rift_s_system_reference(struct rift_s_system **dst, struct rift_s_system *src)
{
	struct rift_s_system *old_dst = *dst;

	if (old_dst == src) {
		return;
	}

	if (src) {
		xrt_reference_inc(&src->ref);
	}

	*dst = src;

	if (old_dst) {
		if (xrt_reference_dec_and_is_zero(&old_dst->ref)) {
			rift_s_system_free(old_dst);
		}
	}
}

struct os_hid_device *
rift_s_system_hid_handle(struct rift_s_system *sys)
{
	return sys->handles[HMD_HID];
}

rift_s_radio_state *
rift_s_system_radio(struct rift_s_system *sys)
{
	return &sys->radio_state;
}

struct rift_s_tracker *
rift_s_system_get_tracker(struct rift_s_system *sys)
{
	return sys->tracker;
}

struct xrt_device *
rift_s_system_get_hmd(struct rift_s_system *sys)
{
	return (struct xrt_device *)sys->hmd;
}

void
rift_s_system_remove_hmd(struct rift_s_system *sys)
{
	os_mutex_lock(&sys->dev_mutex);
	sys->hmd = NULL;
	os_mutex_unlock(&sys->dev_mutex);
}

struct xrt_device *
rift_s_system_get_controller(struct rift_s_system *sys, int index)
{
	assert(index >= 0 || index < MAX_TRACKED_DEVICES);
	assert(sys->controllers[index] == NULL); // Ensure only called once per controller

	os_mutex_lock(&sys->dev_mutex);
	if (index == 0) {
		sys->controllers[0] = rift_s_controller_create(sys, XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER);
	} else {
		sys->controllers[1] = rift_s_controller_create(sys, XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER);
	}
	os_mutex_unlock(&sys->dev_mutex);

	return (struct xrt_device *)sys->controllers[index];
}

void
rift_s_system_remove_controller(struct rift_s_system *sys, struct rift_s_controller *ctrl)
{
	os_mutex_lock(&sys->dev_mutex);

	for (int i = 0; i < MAX_TRACKED_DEVICES; i++) {
		if (sys->controllers[i] == ctrl) {
			sys->controllers[i] = NULL;
			break;
		}
	}

	os_mutex_unlock(&sys->dev_mutex);
}

struct xrt_device *
rift_s_system_get_hand_tracking_device(struct rift_s_system *sys)
{
	return rift_s_tracker_get_hand_tracking_device(sys->tracker);
}

/* Packet reading / handling */
static int
update_tracked_device_types(struct rift_s_system *sys)
{
	int res;
	rift_s_devices_list_t dev_list;
	struct os_hid_device *hid = sys->handles[HMD_HID];

	res = rift_s_read_devices_list(hid, &dev_list);
	if (res < 0)
		return res;

	for (int i = 0; i < dev_list.num_devices; i++) {
		rift_s_device_type_record_t *dev = dev_list.devices + i;
		int d;

		for (d = 0; d < sys->num_active_tracked_devices; d++) {
			if (sys->tracked_device[d].device_id == dev->device_id) {
				if (sys->tracked_device[d].device_type != dev->device_type) {
					sys->tracked_device[d].device_type = dev->device_type;
					RIFT_S_DEBUG("Tracked device 0x%16" PRIx64 " type %u now online",
					             dev->device_id, dev->device_type);
				}
				break;
			}
		}

		if (d == sys->num_active_tracked_devices) {
			RIFT_S_WARN("Got a device type record for an unknown device 0x%16" PRIx64 "\n", dev->device_id);
		}
	}

	return 0;
}

static void
handle_hmd_report(struct rift_s_system *sys, timepoint_ns local_ts, const unsigned char *buf, int size)
{
	rift_s_hmd_report_t report;

	if (!rift_s_parse_hmd_report(&report, buf, size)) {
		return;
	}

	os_mutex_lock(&sys->dev_mutex);
	if (sys->hmd != NULL) {
		rift_s_hmd_handle_report(sys->hmd, local_ts, &report);
	}
	os_mutex_unlock(&sys->dev_mutex);
}

static void
handle_controller_report(struct rift_s_system *sys, timepoint_ns local_ts, const unsigned char *buf, int size)
{
	rift_s_controller_report_t report;

	if (!rift_s_parse_controller_report(&report, buf, size)) {
		rift_s_hexdump_buffer("Invalid Controller Report", buf, size);
		return;
	}

	if (report.device_id == 0x00) {
		/* placeholder report. Ignore it */
		return;
	}

	int i;
	struct rift_s_tracked_device *td = NULL;

	for (i = 0; i < sys->num_active_tracked_devices; i++) {
		if (sys->tracked_device[i].device_id == report.device_id) {
			td = sys->tracked_device + i;
			break;
		}
	}

	if (td == NULL) {
		if (sys->num_active_tracked_devices == MAX_TRACKED_DEVICES) {
			RIFT_S_ERROR("Too many controllers. Can't add %08" PRIx64 "\n", report.device_id);
			return;
		}

		/* Add a new controller to the online list */
		td = sys->tracked_device + sys->num_active_tracked_devices;
		sys->num_active_tracked_devices++;

		memset(td, 0, sizeof(struct rift_s_tracked_device));
		td->device_id = report.device_id;

		update_tracked_device_types(sys);
	}

	struct rift_s_controller *ctrl = NULL;

	os_mutex_lock(&sys->dev_mutex);

	switch (td->device_type) {
	/* If we didn't already succeed in reading the type for this device, try again */
	case RIFT_S_DEVICE_TYPE_UNKNOWN: update_tracked_device_types(sys); break;
	case RIFT_S_DEVICE_LEFT_CONTROLLER: ctrl = sys->controllers[0]; break;
	case RIFT_S_DEVICE_RIGHT_CONTROLLER: ctrl = sys->controllers[1]; break;
	default: break; /* Ignore unknown device type */
	}

	if (ctrl != NULL) {
		rift_s_controller_update_configuration(ctrl, td->device_id);

		if (!rift_s_controller_handle_report(ctrl, local_ts, &report)) {
			rift_s_hexdump_buffer("Invalid Controller Report Content", buf, size);
		}
	}
	os_mutex_unlock(&sys->dev_mutex);
}

static bool
handle_packets(struct rift_s_system *sys)
{
	unsigned char buf[FEATURE_BUFFER_SIZE];
	bool ret = true;

	// Handle keep alive messages
	timepoint_ns now = os_monotonic_get_ns();

	if ((now - sys->last_keep_alive) / U_TIME_1MS_IN_NS >= KEEPALIVE_INTERVAL_MS) {
		// send keep alive message
		rift_s_send_keepalive(sys->handles[HMD_HID]);
		// Update the time of the last keep alive we have sent.
		sys->last_keep_alive = now;
	}

	/* Poll each of the 3 HID interfaces for messages and process them */
	for (int i = 0; i < 3; i++) {
		if (sys->handles[i] == NULL)
			continue;

		while (ret) {
			int size = os_hid_read(sys->handles[i], buf, FEATURE_BUFFER_SIZE, 0);
			if (size < 0) {
				RIFT_S_ERROR("error reading from HMD device");
				ret = false;
				break;
			} else if (size == 0) {
				break; // No more messages, return.
			}

			now = os_monotonic_get_ns();

			if (buf[0] == 0x65)
				handle_hmd_report(sys, now, buf, size);
			else if (buf[0] == 0x67)
				handle_controller_report(sys, now, buf, size);
			else if (buf[0] == 0x66) {
				/* System state packet. Enable the screen if the prox sensor is
				 * triggered. */
				bool prox_sensor = (buf[1] == 0) ? false : true;
				os_mutex_lock(&sys->dev_mutex);
				if (sys->hmd != NULL) {
					rift_s_hmd_set_proximity(sys->hmd, prox_sensor);
				}
				os_mutex_unlock(&sys->dev_mutex);
			} else {
				RIFT_S_WARN("Unknown Rift S report 0x%02x!", buf[0]);
			}
		}
	}

	return ret;
}


static void *
rift_s_run_thread(void *ptr)
{
	DRV_TRACE_MARKER();

	struct rift_s_system *sys = (struct rift_s_system *)ptr;

	os_thread_helper_lock(&sys->oth);
	while (os_thread_helper_is_running_locked(&sys->oth)) {
		os_thread_helper_unlock(&sys->oth);

		bool success = handle_packets(sys);

		if (success) {
			rift_s_radio_update(&sys->radio_state, sys->handles[HMD_HID]);

			os_mutex_lock(&sys->dev_mutex);
			if (sys->cam != NULL) {
				rift_s_camera_update(sys->cam, sys->handles[HMD_HID]);
			}
			os_mutex_unlock(&sys->dev_mutex);
		}

		os_thread_helper_lock(&sys->oth);

		if (!success) {
			break;
		}

		if (os_thread_helper_is_running_locked(&sys->oth)) {
			os_nanosleep(U_TIME_1MS_IN_NS / 2);
		}
	}
	os_thread_helper_unlock(&sys->oth);

	RIFT_S_DEBUG("Exiting packet reading thread");

	return NULL;
}
