#include "common.h"
#include "gamepad.h"

#define LOOK_SENS_MULTIPLIER 3000.f

static unsigned long time;
static float         delta;
static cvar_t       *sensitivity;
static cvar_t       *m_yaw;
static cvar_t       *m_pitch;
static cvar_t       *gp_deadzone;
static cvar_t       *gp_sprint;
static cvar_t       *gp_run;
static cvar_t       *gp_accel;

static float dzStickLength[STICK_COUNT];
static float dzTrigger[TRIGGER_COUNT];

static BOOL buttons[BUTTON_COUNT][2];
static BOOL triggers[TRIGGER_COUNT][2];

static int  cmdRightMove;
static int  cmdForwardMove;
static BOOL cmdSprint;

static void gamepad_update_deadzone() {

	gp_deadzone->modified = FALSE;

	float v[STICK_COUNT + TRIGGER_COUNT];

	sscanf_s(gp_deadzone->string,"%f %f %f %f", &v[0], &v[1], &v[2], &v[3]);

	dzStickLength[STICK_LEFT]  = v[0];
	dzStickLength[STICK_RIGHT] = v[1];
	dzTrigger[TRIGGER_LEFT]    = v[2];
	dzTrigger[TRIGGER_RIGHT]   = v[3];

}

void gamepad_init() {

	GamepadInit();

	sensitivity = Cvar_Get("sensitivity", "5", CVAR_ARCHIVE);
	m_yaw       = Cvar_Get("m_yaw", "0.022", CVAR_ARCHIVE);
	m_pitch     = Cvar_Get("m_pitch", "0.022", CVAR_ARCHIVE);
	gp_deadzone = Cvar_Get("gp_deadzone", ".1 .1 .1 .1", CVAR_ARCHIVE);
	gp_sprint   = Cvar_Get("gp_sprint", "1.0", CVAR_ARCHIVE);
	gp_run      = Cvar_Get("gp_run", "0.7", CVAR_ARCHIVE);
	gp_accel    = Cvar_Get("gp_accel", "1.5", CVAR_ARCHIVE);

	gamepad_update_deadzone();

}

void gamepad_shutdown() {
	GamepadShutdown();
}

static float gamepad_get_length(GAMEPAD_DEVICE device, GAMEPAD_STICK stick) {

	float length = GamepadStickLength(device, stick);

	if (length < dzStickLength[stick]) {
		return .0f;
	} else if (dzStickLength[stick] < 1.f) {
		length -= dzStickLength[stick];
		length *= 1.f / (1.f - dzStickLength[stick]);
	}

	return length;

}

static void gamepad_get_xy(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, float *x, float *y) {

	float angle, length;

	angle  = GamepadStickAngle(device, stick);
	length = gamepad_get_length(device, stick);
	*x     = length * cosf(angle);
	*y     = length * sinf(angle);

}

static void gamepad_move(GAMEPAD_DEVICE device) {

	float x, y;
	int run = gp_run->value == .0f ? 255 : (int) (gp_run->value * 128.f);

	gamepad_get_xy(device, STICK_LEFT, &x, &y);

	cmdRightMove   = (int) (x * 128.f);
	cmdForwardMove = (int) (y * 128.f);

	if (abs(cmdRightMove) > run) {
		cmdRightMove = cmdRightMove > 0 ? 128 : -128;
	}

	if (abs(cmdForwardMove) > run) {
		cmdForwardMove = cmdForwardMove > 0 ? 128 : -128;
	}

	cmdSprint = gp_sprint->value > .0f && GamepadStickLength(device, STICK_LEFT) >= gp_sprint->value;

}

static inline float gamepad_look_axis(float value, cvar_t *m) {

	float multiplier = delta
		* sensitivity->value
		* m->value
		* LOOK_SENS_MULTIPLIER;

	if (gp_accel->value > .0f) {
		multiplier *= powf(value * gp_accel->value * (value < .0f ? -1.f : 1.f), 2.f);
	}

	return multiplier * value;

}

static void gamepad_look(GAMEPAD_DEVICE device) {

	float x, y;

	gamepad_get_xy(device, STICK_RIGHT, &x, &y);

	*game.mouseX = (*game.mouseX) - gamepad_look_axis(x, m_yaw);
	*game.mouseY = (*game.mouseY) - gamepad_look_axis(y, m_pitch);

}

static void gamepad_buttons(GAMEPAD_DEVICE device) {

	for (int i = BUTTON_DPAD_UP; i < BUTTON_COUNT; i++) {
		buttons[i][0] = GamepadButtonDown(device, i);
	}

	for (int i = TRIGGER_LEFT; i < TRIGGER_COUNT; i++) {
		triggers[i][0] = GamepadTriggerLength(device, i) > dzTrigger[i];
	}

}

void gamepad_frame() {

	unsigned long curTime = timeGetTime();
	delta = (float) (curTime - time) / 1000;
	time = curTime;

	if (gp_deadzone->modified) {
		gamepad_update_deadzone();
	}

	if (delta > 1.f || delta <= .0f) { // Hang or turnover.
		return;
	}

	GamepadUpdate();

	for (int i = GAMEPAD_0; i < GAMEPAD_COUNT; i++) {

		if (!GamepadIsConnected(i)) {
			continue;
		}

		gamepad_move(i);
		gamepad_look(i);
		gamepad_buttons(i);

	}

}

static signed char ClampChar(int i) {

	if (i < -128) {
		return -128;
	}

	if (i > 127) {
		return 127;
	}

	return (signed char) i;

}

void gamepad_cmd(usercmd_t *cmd) {

	cmd->rightmove   = ClampChar(cmd->rightmove + cmdRightMove);
	cmd->forwardmove = ClampChar(cmd->forwardmove + cmdForwardMove);

	if (cmdSprint) {
		cmd->buttons |= BUTTON_SPRINT;
	} else if ((cmdRightMove != 0 || cmdForwardMove != 0) // Do not open door slowly when standing still.
		&& abs(cmd->rightmove) < 64
		&& abs(cmd->forwardmove) < 64) {
		cmd->buttons |= BUTTON_WALKING;
		cmd->buttons &= ~BUTTON_SPRINT; // Do not waste stamina.
	}

	// Actually sprint when using a bind.
	if (cmd->buttons & BUTTON_SPRINT) {

		if (abs(cmd->rightmove) > 64) {
			cmd->rightmove = ClampChar(cmd->rightmove * 2);
		}

		if (abs(cmd->forwardmove) > 64) {
			cmd->forwardmove = ClampChar(cmd->forwardmove * 2);
		}

	}

}

void gamepad_keys() {

	for (int i = BUTTON_DPAD_UP; i < BUTTON_COUNT; i++) {

		if (buttons[i][0] != buttons[i][1]) {
			game.Sys_QueEvent(0, SE_KEY, K_JOY1 + i, buttons[i][0], 0, NULL);
			buttons[i][1] = buttons[i][0];
		}

	}

	for (int i = TRIGGER_LEFT; i < TRIGGER_COUNT; i++) {

		if (triggers[i][0] != triggers[i][1]) {
			game.Sys_QueEvent(0, SE_KEY, K_MOUSE2 - i, triggers[i][0], 0, NULL);
			triggers[i][1] = triggers[i][0];
		}

	}

}