// win_input.c -- win32 mouse and joystick code
// 02/21/97 JCB Added extended DirectInput code to support external controllers.

// leave this as first line for PCH reasons...
//
// #include "../server/exe_headers.h"

#include <xtl.h>
#include "glw_win_dx8.h"

#include "../client/client.h"
#include "../qcommon/qcommon.h"
#ifdef _JK2MP
#include "../ui/keycodes.h"
#else
#include "../client/keycodes.h"
#endif

#include "win_local.h"
#include "win_input.h"

#define IN_MAX_CONTROLLERS 4

void IN_UIEmptyQueue();

struct inputstate_t
{
	struct controller_t
	{
		HANDLE handle;
		XINPUT_STATE state;
		XINPUT_FEEDBACK feedback;
	};
	controller_t controllers[IN_MAX_CONTROLLERS];
};

inputstate_t *in_state = NULL;

/*
=========================================================================

JOYSTICK

=========================================================================
*/
// Process all the insertions and removals, updating handles and such
void IN_ProcessChanges(DWORD dwInsert, DWORD dwRemove)
{
	for(int port = 0; port < IN_MAX_CONTROLLERS; ++port)
	{
		// Close removals.
		if( ((1 << port) & dwRemove) && in_state->controllers[port].handle )
		{
			XInputClose( in_state->controllers[port].handle );
			in_state->controllers[port].handle = 0;
			IN_PadUnplugged(port);
		}

		// Open insertions.
		if( (1 << port) & dwInsert )
		{
			in_state->controllers[port].handle = XInputOpen( XDEVICE_TYPE_GAMEPAD, port, XDEVICE_NO_SLOT, NULL );
			IN_PadPlugged(port);
		}
	}

	return;
}

/*********
IN_CheckForNoControllers()
If there are no controllers plugged in, the UI
is notified so it can display an appropriate
message.
*********/
void IN_CheckForNoControllers()
{
	extern bool noControllersConnected;
	if(!noControllersConnected)
	{
		extern bool wasPlugged[4];
		if( !wasPlugged[0] &&
			!wasPlugged[1] &&
			!wasPlugged[2] &&
			!wasPlugged[3] )
		{
			// Tell the UI that there are no controllers connected
		//	VM_Call( uivm, UI_CONTROLLER_UNPLUGGED, true, -1);
			noControllersConnected = true;
		}
	}
}

/*
=========================================================================

  RUMBLE SUPPORT

=========================================================================
*/

bool IN_RumbleAdjust(int controller, int left, int right)
{
	assert(controller >= 0 && controller < IN_MAX_CONTROLLERS);

	// Get a device handle for the controller.  This may fail.
	HANDLE handle = in_state->controllers[controller].handle;

	if (!handle) return false;
	
	XINPUT_FEEDBACK* fb = &in_state->controllers[controller].feedback;
	
	// If a prior rumble update is still pending, go away
	if (fb->Header.dwStatus == ERROR_IO_PENDING) return false;

	fb->Rumble.wLeftMotorSpeed = left;
	fb->Rumble.wRightMotorSpeed = right;
	
	return ERROR_IO_PENDING == XInputSetState(handle, fb);
}


/*
=========================================================================

=========================================================================
*/

/*
igBool IN_WindowClose(igWindow *window)
{
	SV_Shutdown ("Server quit\n");
	CL_Shutdown ();
	Com_Shutdown ();
	Sys_Quit ();
	return true;
}
*/

/*
===========
IN_Shutdown
===========
*/
void IN_Shutdown( void ) {
	IN_RumbleShutdown();

	delete in_state;
	in_state = NULL;
}


/*
===========
IN_Init
===========
*/
void IN_Init( void )
{
	in_state = new inputstate_t;

	// Initialize support for 4 gamepads
	XDEVICE_PREALLOC_TYPE xdpt[] = {
		{XDEVICE_TYPE_GAMEPAD, 4},
		{XDEVICE_TYPE_MEMORY_UNIT, 1},
		{XDEVICE_TYPE_VOICE_MICROPHONE, 1},
		{XDEVICE_TYPE_VOICE_HEADPHONE, 1}
	};

    // Initialize the peripherals. We can only ever
	// call XInitDevices once, no matter what.
	static bool bInputInitialized = false;
	if (!bInputInitialized)
    	XInitDevices( sizeof(xdpt) / sizeof(XDEVICE_PREALLOC_TYPE), xdpt );
	bInputInitialized = true;

    // Zero all of our data, including handles
	memset(in_state->controllers, 0, sizeof(in_state->controllers));

	// Find out the status of all gamepad ports, then open them
	IN_ProcessChanges( XGetDevices( XDEVICE_TYPE_GAMEPAD ), 0 );

	IN_RumbleInit();
}

static inline float _joyAxisConvert(SHORT x)
{
	// Change scale
	float y = x / 32767.0;

	// Cheesy deadzone
	if(fabs(y) < 0.25f)
	{
		y = 0.0f;
	}

	return y;
}

// How many controls on the xbox gamepad?
#define IN_NUM_DIGITAL_BUTTONS 8
#define IN_NUM_ANALOG_BUTTONS 8
// Cutoff where the analog buttons are considered to be "pressed"
// This should be smarter.
#define IN_ANALOG_BUTTON_THRESHOLD 64

void IN_UpdateGamepad(int port)
{
	// Lookup table to convert the digital buttons to fakeAscii_t, in mask order
	const fakeAscii_t digitalXlat[IN_NUM_DIGITAL_BUTTONS] = {
		A_JOY5, // DPAD_UP
		A_JOY7, // DPAD_DOWN
		A_JOY8, // DPAD_LEFT
		A_JOY6, // DPAD_LEFT
		A_JOY4, // Start
		A_JOY1, // Back
		A_JOY2, // Left stick
		A_JOY3  // Right stick
	};

	// Lookup table to convet the analog buttons to fakeAscii_t, in DX order
	const fakeAscii_t analogXlat[IN_NUM_ANALOG_BUTTONS] = {
		A_JOY15, // A
		A_JOY14, // B
		A_JOY16, // X
		A_JOY13, // Y
		A_JOY10, // Black
		A_JOY9,  // White
		A_JOY11, // Left trigger
		A_JOY12  // Right trigger
	};

	// Get new state
	XINPUT_STATE newState;
	XInputGetState( in_state->controllers[port].handle, &newState );

	// Get old state
	XINPUT_STATE &oldState(in_state->controllers[port].state);

	int buttonIdx;
	bool oldPressed, newPressed;

	// Check all digital buttons first
	for (buttonIdx = 0; buttonIdx < IN_NUM_DIGITAL_BUTTONS; ++buttonIdx)
	{
		oldPressed = oldState.Gamepad.wButtons & (1 << buttonIdx);
		newPressed = newState.Gamepad.wButtons & (1 << buttonIdx);

		if (oldPressed != newPressed)
			IN_CommonJoyPress(port, digitalXlat[buttonIdx], newPressed);
	}

	// Now check all analog buttons
	for (buttonIdx = 0; buttonIdx < IN_NUM_ANALOG_BUTTONS; ++buttonIdx)
	{
		oldPressed = oldState.Gamepad.bAnalogButtons[buttonIdx] > IN_ANALOG_BUTTON_THRESHOLD;
		newPressed = newState.Gamepad.bAnalogButtons[buttonIdx] > IN_ANALOG_BUTTON_THRESHOLD;

		if (oldPressed != newPressed)
			IN_CommonJoyPress(port, analogXlat[buttonIdx], newPressed);
	}

	// Update joysticks
	_padInfo.joyInfo[0].x = _joyAxisConvert(newState.Gamepad.sThumbLX);
	_padInfo.joyInfo[0].y = _joyAxisConvert(newState.Gamepad.sThumbLY);
	_padInfo.joyInfo[1].x = _joyAxisConvert(newState.Gamepad.sThumbRX);
	_padInfo.joyInfo[1].y = _joyAxisConvert(newState.Gamepad.sThumbRY);
	_padInfo.joyInfo[0].valid = _padInfo.joyInfo[1].valid = true;
	_padInfo.padId = port;

	// Copy state back
	oldState = newState;

	// Update game
	IN_CommonUpdate();
}

/*
==================
IN_Frame

Called every frame, even if not generating commands
==================
*/
//extern int ignoreInputTime;
void IN_Frame (void)
{
	if (in_state)
	{
		// First, check for changes in device status (removed/inserted pads)
		DWORD dwInsert, dwRemove;
		if( XGetDeviceChanges( XDEVICE_TYPE_GAMEPAD, &dwInsert, &dwRemove ) )
		{
			IN_ProcessChanges(dwInsert, dwRemove);
		}
		else
		{
			IN_CheckForNoControllers();
		}

		// Generate callbacks for each controller that's plugged in
		for (int port = 0; port < IN_MAX_CONTROLLERS; ++port)
			if (in_state->controllers[port].handle)
				IN_UpdateGamepad(port);
		
		IN_UIEmptyQueue();
		IN_RumbleFrame();
	}
}
