/*
**
**
**---------------------------------------------------------------------------
** Copyright 2005-2016 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------

#include <math.h>
#include "c_dispatch.h"
#include "vectors.h"
#include "m_joy.h"
#include "configfile.h"
#include "i_interface.h"
#include "d_eventbase.h"
#include "cmdlib.h"
#include "printf.h"

// MACROS ------------------------------------------------------------------

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

EXTERN_CVAR(Bool, joy_ps2raw)
EXTERN_CVAR(Bool, joy_dinput)
EXTERN_CVAR(Bool, joy_xinput)

extern const float JOYDEADZONE_DEFAULT = 0.1; // reduced from 0.25

extern const float JOYSENSITIVITY_DEFAULT = 1.0;

extern const float JOYTHRESH_DEFAULT = 0.05;
extern const float JOYTHRESH_TRIGGER = 0.05;
extern const float JOYTHRESH_STICK_X = 0.65;
extern const float JOYTHRESH_STICK_Y = 0.35;

extern const CubicBezier JOYCURVE[NUM_JOYCURVE] = {
	{{0.3, 0.0, 0.7, 0.4}}, // DEFAULT -> QUADRATIC

	{{0.0, 0.0, 1.0, 1.0}}, // LINEAR
	{{0.3, 0.0, 0.7, 0.4}}, // QUADRATIC
	{{0.5, 0.0, 0.7, 0.2}}, // CUBIC
};

// PUBLIC DATA DEFINITIONS -------------------------------------------------

CUSTOM_CVARD(Bool, use_joystick, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL, "enables input from the joystick if it is present")
{
#ifdef _WIN32
	joy_ps2raw->Callback();
	joy_dinput->Callback();
	joy_xinput->Callback();
#endif
}

// PRIVATE DATA DEFINITIONS ------------------------------------------------

// Bits 0 is X+, 1 is X-, 2 is Y+, and 3 is Y-.
static uint8_t JoyAngleButtons[8] = { 1, 1+4, 4, 2+4, 2, 2+8, 8, 1+8 };

// CODE --------------------------------------------------------------------

//==========================================================================
//
// IJoystickConfig - Virtual Destructor
//
//==========================================================================

IJoystickConfig::~IJoystickConfig()
{
}

//==========================================================================
//
// M_SetJoystickConfigSection
//
// Sets up the config for reading or writing this controller's axis config. 
//
//==========================================================================

static bool M_SetJoystickConfigSection(IJoystickConfig *joy, bool create, FConfigFile* GameConfig)
{
	FString id = "Joy:";
	id += joy->GetIdentifier();
	if (!GameConfig) return false;
	return GameConfig->SetSection(id.GetChars(), create);
}

//==========================================================================
//
// M_LoadJoystickConfig
//
//==========================================================================

bool M_LoadJoystickConfig(IJoystickConfig *joy)
{
	FConfigFile* GameConfig = sysCallbacks.GetConfig ? sysCallbacks.GetConfig() : nullptr;
	char key[32];
	const char *value;
	int axislen;
	int numaxes;

	joy->SetDefaultConfig();
	if (!M_SetJoystickConfigSection(joy, false, GameConfig))
	{
		return false;
	}

	assert(GameConfig);

	value = GameConfig->GetValueForKey("Enabled");
	if (value)
	{
		joy->SetEnabled((bool)atoi(value));
	}

	if(joy->AllowsEnabledInBackground())
	{
		value = GameConfig->GetValueForKey("EnabledInBackground");
		if (value)
		{
			joy->SetEnabledInBackground((bool)atoi(value));
		}
	}

	value = GameConfig->GetValueForKey("Sensitivity");
	if (value)
	{
		joy->SetSensitivity((float)atof(value));
	}

	numaxes = joy->GetNumAxes();
	for (int i = 0; i < numaxes; ++i)
	{
		axislen = mysnprintf(key, countof(key), "Axis%u", i);

		mysnprintf(key + axislen, countof(key) - axislen, "deadzone");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			joy->SetAxisDeadZone(i, (float)atof(value));
		}

		mysnprintf(key + axislen, countof(key) - axislen, "scale");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			joy->SetAxisScale(i, (float)atof(value));
		}

		mysnprintf(key + axislen, countof(key) - axislen, "threshold");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			joy->SetAxisDigitalThreshold(i, (float)atof(value));
		}

		mysnprintf(key + axislen, countof(key) - axislen, "curve");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			joy->SetAxisResponseCurve(i, (EJoyCurve)clamp(atoi(value), (int)JOYCURVE_CUSTOM, (int)NUM_JOYCURVE-1));
		}

		mysnprintf(key + axislen, countof(key) - axislen, "curve-x1");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			joy->SetAxisResponseCurvePoint(i, 0, (float)atof(value));
		}

		mysnprintf(key + axislen, countof(key) - axislen, "curve-y1");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			joy->SetAxisResponseCurvePoint(i, 1, (float)atof(value));
		}

		mysnprintf(key + axislen, countof(key) - axislen, "curve-x2");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			joy->SetAxisResponseCurvePoint(i, 2, (float)atof(value));
		}

		mysnprintf(key + axislen, countof(key) - axislen, "curve-y2");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			joy->SetAxisResponseCurvePoint(i, 3, (float)atof(value));
		}

		mysnprintf(key + axislen, countof(key) - axislen, "map");
		value = GameConfig->GetValueForKey(key);
		if (value)
		{
			EJoyAxis gameaxis = (EJoyAxis)atoi(value);
			if (gameaxis < JOYAXIS_None || gameaxis >= NUM_JOYAXIS)
			{
				gameaxis = JOYAXIS_None;
			}
			joy->SetAxisMap(i, gameaxis);
		}
	}
	return true;
}

//==========================================================================
//
// M_SaveJoystickConfig
//
// Only saves settings that are not at their defaults.
//
//==========================================================================

void M_SaveJoystickConfig(IJoystickConfig *joy)
{
	FConfigFile* GameConfig = sysCallbacks.GetConfig ? sysCallbacks.GetConfig() : nullptr;
	char key[32], value[32];
	int axislen, numaxes;

	if (GameConfig != NULL && M_SetJoystickConfigSection(joy, true, GameConfig))
	{
		GameConfig->ClearCurrentSection();
		if (!joy->GetEnabled())
		{
			GameConfig->SetValueForKey("Enabled", "0");
		}

		if (!joy->AllowsEnabledInBackground() && joy->GetEnabledInBackground())
		{
			GameConfig->SetValueForKey("EnabledInBackground", "1");
		}
		
		if (!joy->IsSensitivityDefault())
		{
			mysnprintf(value, countof(value), "%g", joy->GetSensitivity());
			GameConfig->SetValueForKey("Sensitivity", value);
		}
		numaxes = joy->GetNumAxes();
		for (int i = 0; i < numaxes; ++i)
		{
			axislen = mysnprintf(key, countof(key), "Axis%u", i);

			if (!joy->IsAxisDeadZoneDefault(i))
			{
				mysnprintf(key + axislen, countof(key) - axislen, "deadzone");
				mysnprintf(value, countof(value), "%g", joy->GetAxisDeadZone(i));
				GameConfig->SetValueForKey(key, value);
			}
			if (!joy->IsAxisScaleDefault(i))
			{
				mysnprintf(key + axislen, countof(key) - axislen, "scale");
				mysnprintf(value, countof(value), "%g", joy->GetAxisScale(i));
				GameConfig->SetValueForKey(key, value);
			}
			if (!joy->IsAxisDigitalThresholdDefault(i))
			{
				mysnprintf(key + axislen, countof(key) - axislen, "threshold");
				mysnprintf(value, countof(value), "%g", joy->GetAxisDigitalThreshold(i));
				GameConfig->SetValueForKey(key, value);
			}
			if (!joy->IsAxisResponseCurveDefault(i))
			{
				mysnprintf(key + axislen, countof(key) - axislen, "curve");
				mysnprintf(value, countof(value), "%d", joy->GetAxisResponseCurve(i));
				GameConfig->SetValueForKey(key, value);
			}
			if (joy->GetAxisResponseCurve(i) == JOYCURVE_CUSTOM)
			{
				mysnprintf(key + axislen, countof(key) - axislen, "curve-x1");
				mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 0));
				GameConfig->SetValueForKey(key, value);
				mysnprintf(key + axislen, countof(key) - axislen, "curve-y1");
				mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 1));
				GameConfig->SetValueForKey(key, value);
				mysnprintf(key + axislen, countof(key) - axislen, "curve-x2");
				mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 2));
				GameConfig->SetValueForKey(key, value);
				mysnprintf(key + axislen, countof(key) - axislen, "curve-y2");
				mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 3));
				GameConfig->SetValueForKey(key, value);
			}
			if (!joy->IsAxisMapDefault(i))
			{
				mysnprintf(key + axislen, countof(key) - axislen, "map");
				mysnprintf(value, countof(value), "%d", joy->GetAxisMap(i));
				GameConfig->SetValueForKey(key, value);
			}
		}
		// If the joystick is entirely at its defaults, delete this section
		// so that we don't write out a lone section header.
		if (GameConfig->SectionIsEmpty())
		{
			GameConfig->DeleteCurrentSection();
		}
	}
}

CCMD (gamepad)
{
	int COMMAND = 1, IDENTIFIER = 2, VALUE = 3;
	int argc = argv.argc()-1;

	TArray<IJoystickConfig *> sticks;
	I_GetJoysticks(sticks);

	auto usage = []()
	{
		Printf(
			"usage:"
			"\n\tgamepad list"
			"\n\tgamepad reset       pad"
			"\n\tgamepad enabled     pad      [0|1]"
			"\n\tgamepad background  pad      [0|1]"
			"\n\tgamepad sensitivity pad      [float]"
			"\n\tgamepad deadzone    pad.axis [float]"
			"\n\tgamepad scale       pad.axis [float]"
			"\n\tgamepad threshold   pad.axis [float]"
			"\n\tgamepad curve       pad.axis [-1|0|1|2|3]"
			"\n\tgamepad curve-x1    pad.axis [float]"
			"\n\tgamepad curve-y1    pad.axis [float]"
			"\n\tgamepad curve-x2    pad.axis [float]"
			"\n\tgamepad curve-y2    pad.axis [float]"
			"\n\tgamepad map         pad.axis [-1|0|1|2|3|4]"
			"\n"
		);
	};

	if (argc < COMMAND)
	{
		return usage();
	};

	FName command = argv[COMMAND];

	if (argc < IDENTIFIER)
	{
		if (command == "list")
		{
			for (int i = 0; i < sticks.SSize(); i++)
			{
				Printf("%d: '%s'\n", i, sticks[i]->GetName().GetChars());
				for (int j = 0; j < sticks[i]->GetNumAxes(); j++)
				{
					Printf("  %d.%d: '%s'\n", i, j, sticks[i]->GetAxisName(j));
				}
			}
			return;
		}
		return usage();
	}

	const char * id = argv[IDENTIFIER];
	const char * hasAxis = strchr(id, '.');

	int pad, axis;

	try {
		pad = (int)std::stod(id);

		if (pad < 0 || pad >= sticks.SSize())
		{
			return (void) Printf("Pad # out of range\n");
		}
	} catch (...) {
		return (void) Printf("Failed to parse pad #\n");
	}

	if (hasAxis)
	{
		try {
			axis = (int)std::stod(hasAxis+1);

			if (axis < 0 || axis >= sticks[pad]->GetNumAxes())
			{
				return (void) Printf("Axis # out of range\n");
			}
		} catch (...) {
			return (void) Printf("Failed to parse axis #\n");
		}
	}

	float value = 0;
	bool set = argc >= VALUE;

	if (set)
	{
		try {
			value = std::stod(argv[VALUE]);
		} catch (...) {
			return (void) Printf("Failed to parse args\n");
		}
	}

	if (command == "reset")
	{
		if (set) return usage();
		sticks[pad]->SetDefaultConfig();
		sticks[pad]->SetEnabled(true);
		sticks[pad]->SetEnabledInBackground(sticks[pad]->AllowsEnabledInBackground());
		sticks[pad]->SetSensitivity(1);
		return;
	}
	if (command == "enabled")
	{
		if (set) sticks[pad]->SetEnabled((int)value);
		return (void) Printf("%d\n", sticks[pad]->GetEnabled());
	}
	if (command == "background")
	{
		if (set) sticks[pad]->SetEnabledInBackground((int)value);
		return (void) Printf("%d\n", sticks[pad]->GetEnabledInBackground());
	}
	if (command == "sensitivity")
	{
		if (set) sticks[pad]->SetSensitivity(value);
		return (void) Printf("%g\n", sticks[pad]->GetSensitivity());
	}
	if (command == "deadzone")
	{
		if (set) sticks[pad]->SetAxisDeadZone(axis, value);
		return (void) Printf("%g\n", sticks[pad]->GetAxisDeadZone(axis));
	}
	if (command == "scale")
	{
		if (set) sticks[pad]->SetAxisScale(axis, value);
		return (void) Printf("%g\n", sticks[pad]->GetAxisScale(axis));
	}
	if (command == "threshold")
	{
		if (set) sticks[pad]->SetAxisDigitalThreshold(axis, value);
		return (void) Printf("%g\n", sticks[pad]->GetAxisDigitalThreshold(axis));
	}
	if (command == "curve")
	{
		if (set) sticks[pad]->SetAxisResponseCurve(axis, (EJoyCurve)value);
		return (void) Printf("%d\n", sticks[pad]->GetAxisResponseCurve(axis));
	}
	if (command == "curve-x1")
	{
		if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 0, value);
		return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 0));
	}
	if (command == "curve-y1")
	{
		if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 1, value);
		return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 1));
	}
	if (command == "curve-x2")
	{
		if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 2, value);
		return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 2));
	}
	if (command == "curve-y2")
	{
		if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 3, value);
		return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 3));
	}
	if (command == "map")
	{
		if (set) sticks[pad]->SetAxisMap(axis, (EJoyAxis)value);
		return (void) Printf("%d\n", sticks[pad]->GetAxisMap(axis));
	}

	return usage();
}

//===========================================================================
//
// Joy_RemoveDeadZone
//
//===========================================================================

double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons)
{
	uint8_t butt;

	// Cancel out deadzone.
	if (fabs(axisval) < deadzone)
	{
		axisval = 0;
		butt = 0;
	}
	// Make the dead zone the new 0.
	else if (axisval < 0)
	{
		axisval = (axisval + deadzone) / (1.0 - deadzone);
		butt = 2;	// button minus
	}
	else
	{
		axisval = (axisval - deadzone) / (1.0 - deadzone);
		butt = 1;	// button plus
	}
	if (buttons != NULL)
	{
		*buttons = butt;
	}
	return axisval;
}

//===========================================================================
//
// Joy_ApplyResponseCurveBezier
//
// Applies cubic bezier easing function
// Curve is defined by control points [(0,0) (x1,y1) (x2,y2) (1,1)]
// https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/cubic-bezier
//
//===========================================================================

double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input)
{
	// clamp + trivial cases
	if (input == 0) return 0;
	double sign = (input >= 0)? 1.0: -1.0;
	input = abs(input);
	input = (input > 1.0)? 1.0: input;
	if (input == 1.0) return sign*input;

	double t = input, T;
	float x1 = curve.x1, y1 = curve.y1, x2 = curve.x2, y2 = curve.y2;

	const int max_iter = 4;
	for (auto i = 0; i < max_iter; i++)
	{
		T = 1-t;

		double x = 3*T*T*t*x1 + 3*T*t*t*x2 + t*t*t;
		double dx = 3*T*T*x1 + 6*T*t*(x2-x1) + 3*t*t*(1-x2);

		// no div by 0
		if (abs(dx) < 0.00001) break;

		t = clamp(t - (x-input)/dx, 0.0, 1.0);
	}

	T = 1-t;
	t = 3*T*T*t*y1 + 3*T*t*t*y2 + t*t*t;

	return sign*t;
}

//===========================================================================
//
// Joy_XYAxesToButtons
//
// Given two axes, returns a button set for them. They should have already
// been sent through Joy_RemoveDeadZone. For axes that share the same
// physical stick, the angle the stick forms should determine whether or
// not the four component buttons are present. Going by deadzone alone gives
// you huge areas where you have to buttons pressed and thin strips where
// you only have one button pressed. For DirectInput gamepads, there is
// not much standard for how the right stick is presented, so we can only
// do this for the left stick for those, since X and Y axes are pretty
// standard. For XInput and Raw PS2 controllers, both sticks are processed
// through here.
//
//===========================================================================

int Joy_XYAxesToButtons(double x, double y)
{
	if (x == 0 && y == 0)
	{
		return 0;
	}
	double rad = atan2(y, x);
	if (rad < 0)
	{
		rad += 2*pi::pi();
	}
	// The circle is divided into eight segments for corresponding
	// button combinations. Each segment is pi/4 radians wide. We offset
	// by half this so that the segments are centered around the ideal lines
	// their buttons represent instead of being right on the lines.
	rad += pi::pi()/8;		// Offset
	rad *= 4/pi::pi();		// Convert range from [0,2pi) to [0,8)
	return JoyAngleButtons[int(rad) & 7];
}

//===========================================================================
//
// Joy_GenerateButtonEvent
//
// Send either a button up or button down event for supplied key code
//
//===========================================================================

void Joy_GenerateButtonEvent(bool down, EKeyCodes which)
{
	event_t event = { 0,0,0,0,0,0,0 };
	event.type = down ? EV_KeyDown : EV_KeyUp;
	event.data1 = which;
	D_PostEvent(&event);
}

//===========================================================================
//
// Joy_GenerateButtonEvents
//
// Provided two bitmasks for a set of buttons, generates events to reflect
// any changes from the old to new set, where base is the key for bit 0,
// base+1 is the key for bit 1, etc.
//
//===========================================================================

void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base)
{
	int changed = oldbuttons ^ newbuttons;
	if (changed != 0)
	{
		int mask = 1;
		for (int j = 0; j < numbuttons; mask <<= 1, ++j)
		{
			if (changed & mask)
			{
				Joy_GenerateButtonEvent(newbuttons & mask, static_cast<EKeyCodes>(base + j));
			}
		}
	}
}

void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys)
{
	int changed = oldbuttons ^ newbuttons;
	if (changed != 0)
	{
		int mask = 1;
		for (int j = 0; j < numbuttons; mask <<= 1, ++j)
		{
			if (changed & mask)
			{
				Joy_GenerateButtonEvent(newbuttons & mask, static_cast<EKeyCodes>(keys[j]));
			}
		}
	}
}
