// Copyright 2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
 * @file
 * @brief  Remote debugging UI.
 * @author Jakob Bornecrantz <jakob@collabora.com>
 * @ingroup gui
 */

#include "xrt/xrt_config_drivers.h"

#include "util/u_misc.h"
#include "util/u_logging.h"

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

#include "gui_common.h"
#include "gui_imgui.h"

#include "remote/r_interface.h"


/*
 *
 * Structs and defines.
 *
 */

/*!
 * A GUI scene that lets the user select a user device.
 * @implements gui_scene
 */
struct gui_remote
{
	struct gui_scene base;

	struct r_remote_connection rc;

	struct r_remote_data reset;
	struct r_remote_data data;

	// Was the left trigger pushed last frame?
	bool left_trigger_was_pressed_last_frame;

	// Was the right trigger pushed last frame?
	bool right_trigger_was_pressed_last_frame;

	bool cheat_menu;

	char address[1024];
	int port;
};

const ImVec2 zero_dims = {0, 0};


/*
 *
 * Functions.
 *
 */

#ifdef XRT_BUILD_DRIVER_REMOTE
static void
handle_draggable_vec3_f32(const char *name, struct xrt_vec3 *v, const struct xrt_vec3 *reset)
{
	float min = -256.0f;
	float max = 256.0f;
	char tmp[256];

	snprintf(tmp, sizeof(tmp), "%s.reset", name);

	if (igArrowButton(tmp, ImGuiDir_Left)) {
		*v = *reset;
	}

	igSameLine(0, 3);
	igDragFloat3(name, (float *)v, 0.005f, min, max, "%+f", 1.0f);
}

static void
handle_draggable_quat(const char *name, struct xrt_quat *q, const struct xrt_quat *reset)
{
	float min = -1.0f;
	float max = 1.0f;

	char tmp[256];

	snprintf(tmp, sizeof(tmp), "%s.reset", name);

	if (igArrowButton(tmp, ImGuiDir_Left)) {
		*q = *reset;
	}

	igSameLine(0, 3);
	igDragFloat4(name, (float *)q, 0.005f, min, max, "%+f", 1.0f);

	// Avoid invalid
	if (q->x == 0.0f && q->y == 0.0f && q->z == 0.0f && q->w == 0.0f) {
		q->w = 1.0f;
	}

	// And make sure it's a unit rotation.
	math_quat_normalize(q);
}

static bool
handle_downable_button(const char *name)
{
	igButton(name, zero_dims);
	return igIsItemHovered(ImGuiHoveredFlags_RectOnly) && igIsMouseDown_Nil(ImGuiMouseButton_Left);
}

static void
handle_input(struct r_remote_controller_data *d, bool *trigger_was_pressed_last_frame)
{
	igText("Hover buttons and sliders to touch component.");
	bool touched = false;

	d->system_click = handle_downable_button("System");
	d->system_touch = igIsItemHovered(ImGuiHoveredFlags_RectOnly);
	igSameLine(0, 3);

	d->a_click = handle_downable_button("A");
	d->a_touch = igIsItemHovered(ImGuiHoveredFlags_RectOnly);
	igSameLine(0, 3);

	d->b_click = handle_downable_button("B");
	d->b_touch = igIsItemHovered(ImGuiHoveredFlags_RectOnly);
	igSameLine(0, 3);

	igCheckbox("Active", &d->active);

	// Squeeze
	igSliderFloat("Squeeze Value", &d->squeeze_value.x, 0, 1, "%.2f", 0);
	igSliderFloat("Squeeze Force", &d->squeeze_force.x, 0, 1, "%.2f", 0);

	// Trigger
	igText("Value > 0.0 causes touch, 0.7 > causes click");
	if (handle_downable_button("Press for 1.0")) {
		d->trigger_value.x = 1.0f;
		*trigger_was_pressed_last_frame = true;
	} else if (*trigger_was_pressed_last_frame) {
		d->trigger_value.x = 0.0f;
		*trigger_was_pressed_last_frame = false;
	}
	igSliderFloat("Trigger", &d->trigger_value.x, 0, 1, "%.2f", 0);
	touched |= igIsItemHovered(ImGuiHoveredFlags_RectOnly);
	d->trigger_click = d->trigger_value.x > 0.7;
	touched |= d->trigger_value.x > 0.0001;
	d->trigger_touch = touched;

	// Thumbstick
	touched = false;
	d->thumbstick_click = handle_downable_button("Thumbstick Click");
	touched |= igIsItemHovered(ImGuiHoveredFlags_RectOnly);
	igSliderFloat2("Thumbstick", *m_vec2_ptr_to_float_arr_ptr(&d->thumbstick), -1, 1, "%.2f", 0);
	touched |= igIsItemHovered(ImGuiHoveredFlags_RectOnly);
	d->thumbstick_touch = touched;

	// Trackpad
	touched = false;
	igSliderFloat2("Trackpad", *m_vec2_ptr_to_float_arr_ptr(&d->trackpad), -1, 1, "%.2f", 0);
	touched |= igIsItemHovered(ImGuiHoveredFlags_RectOnly);
	igSliderFloat("Trackpad Force", &d->trackpad_force.x, 0, 1, "%.2f", 0);
	touched |= igIsItemHovered(ImGuiHoveredFlags_RectOnly);
	touched |= d->trackpad_force.x >= 0.0001f;
	d->trackpad_touch = touched;
}

static void
render_cheat_menu(struct gui_remote *gr, struct gui_program *p)
{
	struct r_remote_data *d = &gr->data;

	if (igButton("Reset all", zero_dims)) {
		*d = gr->reset;
	}

	if (igButton("Interactive Throw #1", zero_dims)) {
#if 0
		d->left.pose.position.x = -1.0f;
		d->left.pose.position.y = 1.0f;
		d->left.pose.position.z = -3.0f;
#else
		d->left.pose.position.x = -0.200000f;
		d->left.pose.position.y = 1.300000f;
		d->left.pose.position.z = -0.500000f;
		d->left.pose.orientation.x = 0.000000f;
		d->left.pose.orientation.y = 0.000000f;
		d->left.pose.orientation.z = 0.000000f;
		d->left.pose.orientation.w = 1.000000f;
		d->left.linear_velocity.x = -0.770000f;
		d->left.linear_velocity.y = 3.255000f;
		d->left.linear_velocity.z = -2.620000f;
		d->left.angular_velocity.x = 0.000000f;
		d->left.angular_velocity.y = 0.000000f;
		d->left.angular_velocity.z = 0.000000f;
#endif
	}

	if (igButton("Interactive Throw #2", zero_dims)) {
#if 0
		d->left.pose.position.x = 1.0f;
		d->left.pose.position.y = 1.1f;
		d->left.pose.position.z = -4.0f;
#else
		d->left.pose.position.x = -0.200000f;
		d->left.pose.position.y = 1.300000f;
		d->left.pose.position.z = -0.500000f;
		d->left.pose.orientation.x = 0.858999f;
		d->left.pose.orientation.y = -0.163382f;
		d->left.pose.orientation.z = -0.000065f;
		d->left.pose.orientation.w = 0.485209f;
		d->left.linear_velocity.x = 0.000000f;
		d->left.linear_velocity.y = 0.000000f;
		d->left.linear_velocity.z = 0.000000f;
		d->left.angular_velocity.x = -10.625000f;
		d->left.angular_velocity.y = 0.000000f;
		d->left.angular_velocity.z = 0.000000f;
#endif
	}

	if (igButton("Interactive Throw #3", zero_dims)) {
#if 0
		d->left.pose.position.x = 0.0f;
		d->left.pose.position.y = 3.0f;
		d->left.pose.position.z = -5.0f;
#else
		d->left.pose.position.x = -0.200000f;
		d->left.pose.position.y = 1.300000f;
		d->left.pose.position.z = -0.500000f;
		d->left.pose.orientation.x = 0.862432f;
		d->left.pose.orientation.y = 0.000000f;
		d->left.pose.orientation.z = 0.000000f;
		d->left.pose.orientation.w = 0.506174f;
		d->left.linear_velocity.x = 0.000000f;
		d->left.linear_velocity.y = 0.000000f;
		d->left.linear_velocity.z = -1.830000f;
		d->left.angular_velocity.x = -16.900000f;
		d->left.angular_velocity.y = 0.000000f;
		d->left.angular_velocity.z = 0.000000f;
#endif
	}

	if (igButton("XR_EXT_hand_tracking Touch Index Fingertips", zero_dims)) {
		d->left.pose.position.x = -0.0250000f;
		d->left.pose.position.y = 1.300000f;
		d->left.pose.position.z = -0.500000f;
		d->left.pose.orientation.x = 0.000000f;
		d->left.pose.orientation.y = 0.000000f;
		d->left.pose.orientation.z = 0.000000f;
		d->left.pose.orientation.w = 1.000000f;
		d->left.linear_velocity.x = 0.000000f;
		d->left.linear_velocity.y = 0.000000f;
		d->left.linear_velocity.z = -1.830000f;
		d->left.angular_velocity.x = -16.900000f;
		d->left.angular_velocity.y = 0.000000f;
		d->left.angular_velocity.z = 0.000000f;

		d->left.hand_curl[0] = 0.0f;
		d->left.hand_curl[1] = 0.0f;
		d->left.hand_curl[2] = 0.0f;
		d->left.hand_curl[3] = 0.0f;
		d->left.hand_curl[4] = 0.0f;


		d->right.pose.position.x = 0.0250000f;
		d->right.pose.position.y = 1.300000f;
		d->right.pose.position.z = -0.500000f;
		d->right.pose.orientation.x = 0.000000f;
		d->right.pose.orientation.y = 0.000000f;
		d->right.pose.orientation.z = 0.000000f;
		d->right.pose.orientation.w = 1.000000f;
		d->right.linear_velocity.x = 0.000000f;
		d->right.linear_velocity.y = 0.000000f;
		d->right.linear_velocity.z = -1.830000f;
		d->right.angular_velocity.x = -16.900000f;
		d->right.angular_velocity.y = 0.000000f;
		d->right.angular_velocity.z = 0.000000f;

		d->right.hand_curl[0] = 0.0f;
		d->right.hand_curl[1] = 0.0f;
		d->right.hand_curl[2] = 0.0f;
		d->right.hand_curl[3] = 0.0f;
		d->right.hand_curl[4] = 0.0f;
	}

	if (igButton("Dump left", zero_dims)) {
		U_LOG_RAW(
		    "d->left.pose.position.x = %ff;\n"
		    "d->left.pose.position.y = %ff;\n"
		    "d->left.pose.position.z = %ff;\n"
		    "d->left.pose.orientation.x = %ff;\n"
		    "d->left.pose.orientation.y = %ff;\n"
		    "d->left.pose.orientation.z = %ff;\n"
		    "d->left.pose.orientation.w = %ff;\n"
		    "d->left.linear_velocity.x = %ff;\n"
		    "d->left.linear_velocity.y = %ff;\n"
		    "d->left.linear_velocity.z = %ff;\n"
		    "d->left.angular_velocity.x = %ff;\n"
		    "d->left.angular_velocity.y = %ff;\n"
		    "d->left.angular_velocity.z = %ff;\n",
		    d->left.pose.position.x, d->left.pose.position.y, d->left.pose.position.z,
		    d->left.pose.orientation.x, d->left.pose.orientation.y, d->left.pose.orientation.z,
		    d->left.pose.orientation.w, d->left.linear_velocity.x, d->left.linear_velocity.y,
		    d->left.linear_velocity.z, d->left.angular_velocity.x, d->left.angular_velocity.y,
		    d->left.angular_velocity.z);
	}
}

#define POSE(prefix)                                                                                                   \
	do {                                                                                                           \
		handle_draggable_vec3_f32(#prefix ".pose.position", &d->prefix.position, &r->prefix.position);         \
		handle_draggable_quat(#prefix ".pose.orientation", &d->prefix.orientation, &r->prefix.orientation);    \
	} while (false)

#define LIN_ANG(prefix)                                                                                                \
	do {                                                                                                           \
		handle_draggable_vec3_f32(#prefix ".linear_velocity", &d->prefix.linear_velocity,                      \
		                          &r->prefix.linear_velocity);                                                 \
		handle_draggable_vec3_f32(#prefix ".angular_velocity", &d->prefix.angular_velocity,                    \
		                          &r->prefix.angular_velocity);                                                \
	} while (false)

#define BUTTONS(prefix)                                                                                                \
	do {                                                                                                           \
		handle_input(&d->prefix, &gr->prefix##_trigger_was_pressed_last_frame);                                \
	} while (false)

#define CURL(prefix, name, index) igDragFloat(#prefix "." #name, &d->prefix.hand_curl[index], 0.01, 0.0, 1.0, "%f", 0);
#define HAND(prefix)                                                                                                   \
	do {                                                                                                           \
		igCheckbox("Hand tracking Active", &d->prefix.hand_tracking_active);                                   \
		CURL(prefix, little, 0);                                                                               \
		CURL(prefix, ring, 1);                                                                                 \
		CURL(prefix, middle, 2);                                                                               \
		CURL(prefix, index, 3);                                                                                \
		CURL(prefix, thumb, 4);                                                                                \
	} while (false)

static void
on_connected(struct gui_remote *gr, struct gui_program *p)
{
	const struct r_remote_data *r = &gr->reset;
	struct r_remote_data *d = &gr->data;

	igPushID_Ptr(&d->head); // Make all IDs unique.
	POSE(head.center);
	igPopID(); // Pop unique IDs

	igPushID_Ptr(&d->left); // Make all IDs unique.
	POSE(left.pose);
	LIN_ANG(left);
	BUTTONS(left);
	HAND(left);
	igPopID(); // Pop unique IDs

	igPushID_Ptr(&d->right); // Make all IDs unique.
	POSE(right.pose);
	LIN_ANG(right);
	BUTTONS(right);
	HAND(right);
	igPopID(); // Pop unique IDs

	igCheckbox("Predefined poses", &gr->cheat_menu);
	if (gr->cheat_menu) {
		render_cheat_menu(gr, p);
	}

	r_remote_connection_write_one(&gr->rc, &gr->data);
}

static void
on_not_connected(struct gui_remote *gr, struct gui_program *p)
{
	igInputText("Address", gr->address, sizeof(gr->address), 0, NULL, NULL);
	igInputInt("Port", &gr->port, 1, 1, 0);

	bool connect = igButton("Connect", zero_dims);

	igSameLine(0, 4.0f);

	if (igButton("Exit", zero_dims)) {
		gui_scene_delete_me(p, &gr->base);
		return;
	}

	if (!connect) {
		return;
	}

	r_remote_connection_init(&gr->rc, gr->address, gr->port);
	r_remote_connection_read_one(&gr->rc, &gr->reset);
	r_remote_connection_read_one(&gr->rc, &gr->data);
}
#endif


/*
 *
 * Scene functions.
 *
 */

static void
scene_render(struct gui_scene *scene, struct gui_program *p)
{
	struct gui_remote *gr = (struct gui_remote *)scene;
	(void)gr;

	igBegin("Remote control", NULL, 0);

#ifdef XRT_BUILD_DRIVER_REMOTE
#ifdef XRT_OS_WINDOWS
	bool socket_invalid = gr->rc.fd == INVALID_SOCKET;
#else
	bool socket_invalid = gr->rc.fd < 0;
#endif
	if (socket_invalid) {
		on_not_connected(gr, p);
	} else {
		on_connected(gr, p);
	}
#else
	igText("Not compiled with the remote driver");
	if (igButton("Exit", zero_dims)) {
		gui_scene_delete_me(p, &gr->base);
	}
#endif

	igEnd();
}

static void
scene_destroy(struct gui_scene *scene, struct gui_program *p)
{
	struct gui_remote *gr = (struct gui_remote *)scene;
	(void)gr;

	free(scene);
}


/*
 *
 * 'Exported' functions.
 *
 */

void
gui_scene_remote(struct gui_program *p, const char *address)
{
	struct gui_remote *gr = U_TYPED_CALLOC(struct gui_remote);

	gr->base.render = scene_render;
	gr->base.destroy = scene_destroy;
#ifdef XRT_OS_WINDOWS
	gr->rc.fd = INVALID_SOCKET;
#else
	gr->rc.fd = -1;
#endif

	// GUI input defaults.
	if (address != NULL) {
		snprintf(gr->address, sizeof(gr->address), "%s", address);
	} else {
		snprintf(gr->address, sizeof(gr->address), "localhost");
	}
	gr->port = 4242;

	gr->reset.header = R_HEADER_VALUE;
	gr->data.header = R_HEADER_VALUE;

	gui_scene_push_front(p, &gr->base);
}
