
#include "libretro.h"
#include "mednafen/mednafen-types.h"
#include <math.h>
#include <stdio.h>
#include <algorithm>
#include "mednafen/git.h"
#include "mednafen/psx/frontio.h"

//------------------------------------------------------------------------------
// Locals
//------------------------------------------------------------------------------

static retro_environment_t environ_cb; // cached during input_set_env
static retro_rumble_interface rumble; // acquired during input_init_env

static FrontIO* FIO; // cached in input_set_fio

#define MAX_CONTROLLERS 8

static unsigned players = 2;
static bool enable_analog_calibration = false;
static float mouse_sensitivity = 1.0f;
static int gun_cursor = FrontIO::SETTING_GUN_CROSSHAIR_CROSS;

// NegCon adjustment parameters
// > The NegCon 'twist' action is somewhat awkward when mapped
//   to a standard analog stick -> user should be able to tweak
//   response/deadzone for comfort
// > When response is linear, 'additional' deadzone (set here)
//   may be left at zero, since this is normally handled via in-game
//   options menus
// > When response is non-linear, deadzone should be set to match the
//   controller being used (otherwise precision may be lost)
// > negcon_linearity:
//   - 1: Response is linear - recommended when using racing wheel
//        peripherals, not recommended for standard gamepads
//   - 2: Response is quadratic - optimal setting for gamepads
//   - 3: Response is cubic - enables precise fine control, but
//        difficult to use...
#define NEGCON_RANGE 0x7FFF
static int negcon_deadzone = 0;
static int negcon_linearity = 1;

typedef union
{
	uint8_t u8[ 10 * sizeof( uint32_t ) ];
	uint32_t u32[ 10 ]; /* 1 + 8 + 1 */
	uint16_t gun_pos[ 2 ];
	uint16_t buttons;
}
INPUT_DATA;

// Controller state buffer (per player)
static INPUT_DATA input_data[ MAX_CONTROLLERS ] = {0};

struct analog_calibration
{
	float left;
	float right;
	float twist;
};

static struct analog_calibration analog_calibration[MAX_CONTROLLERS];

// Controller type (per player)
static uint32_t input_type[ MAX_CONTROLLERS ] = {0};


//------------------------------------------------------------------------------
// Supported Devices
//------------------------------------------------------------------------------

#define RETRO_DEVICE_PS_CONTROLLER         RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0)
#define RETRO_DEVICE_PS_DUALSHOCK          RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG, 1)
#define RETRO_DEVICE_PS_ANALOG             RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG, 0)
#define RETRO_DEVICE_PS_ANALOG_JOYSTICK    RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG, 2)
#define RETRO_DEVICE_PS_GUNCON             RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_LIGHTGUN, 0)
#define RETRO_DEVICE_PS_JUSTIFIER          RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_LIGHTGUN, 1)
#define RETRO_DEVICE_PS_MOUSE              RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 0)
#define RETRO_DEVICE_PS_NEGCON             RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG, 3)

enum { INPUT_DEVICE_TYPES_COUNT = 1 /*none*/ + 8 }; // <-- update me!

static const struct retro_controller_description input_device_types[ INPUT_DEVICE_TYPES_COUNT ] =
{
	{ "PlayStation Controller", RETRO_DEVICE_JOYPAD },
	{ "DualShock", RETRO_DEVICE_PS_DUALSHOCK },
	{ "Analog Controller", RETRO_DEVICE_PS_ANALOG },
	{ "Analog Joystick", RETRO_DEVICE_PS_ANALOG_JOYSTICK },
	{ "Guncon / G-Con 45", RETRO_DEVICE_PS_GUNCON },
	{ "Justifier", RETRO_DEVICE_PS_JUSTIFIER },
	{ "Mouse", RETRO_DEVICE_PS_MOUSE },
	{ "neGcon", RETRO_DEVICE_PS_NEGCON },
	{ NULL, 0 },
};



//------------------------------------------------------------------------------
// Mapping Helpers
//------------------------------------------------------------------------------

/* Controller (default) */
enum { INPUT_MAP_CONTROLLER_SIZE = 16 };
static const unsigned input_map_controller[ INPUT_MAP_CONTROLLER_SIZE ] =
{
	// libretro input				 at position	|| maps to PS1			on bit
	//-----------------------------------------------------------------------------
#ifdef MSB_FIRST
	RETRO_DEVICE_ID_JOYPAD_L2,		// L-trigger	-> L2
	RETRO_DEVICE_ID_JOYPAD_R2,		// R-trigger	-> R2
	RETRO_DEVICE_ID_JOYPAD_L,		// L-shoulder	-> L1
	RETRO_DEVICE_ID_JOYPAD_R,		// R-shoulder	-> R1
	RETRO_DEVICE_ID_JOYPAD_X,		// X(top)		-> Triangle
	RETRO_DEVICE_ID_JOYPAD_A,		// A(right)		-> Circle
	RETRO_DEVICE_ID_JOYPAD_B,		// B(down)		-> Cross
	RETRO_DEVICE_ID_JOYPAD_Y,		// Y(left)		-> Square
	RETRO_DEVICE_ID_JOYPAD_SELECT,	// Select		-> Select
	RETRO_DEVICE_ID_JOYPAD_L3,		// L-thumb		-> L3
	RETRO_DEVICE_ID_JOYPAD_R3,		// R-thumb		-> R3
	RETRO_DEVICE_ID_JOYPAD_START,	// Start		-> Start
	RETRO_DEVICE_ID_JOYPAD_UP,		// Pad-Up		-> Pad-Up
	RETRO_DEVICE_ID_JOYPAD_RIGHT,	// Pad-Right	-> Pad-Right
	RETRO_DEVICE_ID_JOYPAD_DOWN,	// Pad-Down		-> Pad-Down
	RETRO_DEVICE_ID_JOYPAD_LEFT,	// Pad-Left		-> Pad-Left
#else
	RETRO_DEVICE_ID_JOYPAD_SELECT,	// Select		-> Select				0
	RETRO_DEVICE_ID_JOYPAD_L3,		// L-thumb		-> L3					1
	RETRO_DEVICE_ID_JOYPAD_R3,		// R-thumb		-> R3					2
	RETRO_DEVICE_ID_JOYPAD_START,	// Start		-> Start				3
	RETRO_DEVICE_ID_JOYPAD_UP,		// Pad-Up		-> Pad-Up				4
	RETRO_DEVICE_ID_JOYPAD_RIGHT,	// Pad-Right	-> Pad-Right			5
	RETRO_DEVICE_ID_JOYPAD_DOWN,	// Pad-Down		-> Pad-Down				6
	RETRO_DEVICE_ID_JOYPAD_LEFT,	// Pad-Left		-> Pad-Left				7
	RETRO_DEVICE_ID_JOYPAD_L2,		// L-trigger	-> L2					8
	RETRO_DEVICE_ID_JOYPAD_R2,		// R-trigger	-> R2					9
	RETRO_DEVICE_ID_JOYPAD_L,		// L-shoulder	-> L1					10
	RETRO_DEVICE_ID_JOYPAD_R,		// R-shoulder	-> R1					11
	RETRO_DEVICE_ID_JOYPAD_X,		// X(top)		-> Triangle				12
	RETRO_DEVICE_ID_JOYPAD_A,		// A(right)		-> Circle				13
	RETRO_DEVICE_ID_JOYPAD_B,		// B(down)		-> Cross				14
	RETRO_DEVICE_ID_JOYPAD_Y,		// Y(left)		-> Square				15
#endif
};


//------------------------------------------------------------------------------
// Local Functions
//------------------------------------------------------------------------------

// Return the normalized distance between the origin and the current
// (x,y) analog stick position
static float analog_radius(int x, int y) {
  float fx = ((float)x) / 0x8000;
  float fy = ((float)y) / 0x8000;

  return sqrtf(fx * fx + fy * fy);
}
static float analog_deflection(int x) {
  float fx = ((float)x) / 0x8000;

  return fabsf(fx);
}

static void analog_scale(uint32_t *v, float s) {
  *v *= s;

  if (*v > 0x7fff) {
    *v = 0x7fff;
  }
}

static void SetInput(int port, const char *type, void *ptr)
{
   FIO->SetInput(port, type, ptr);
}

static uint16_t get_analog_button( retro_input_state_t input_state_cb,
								   int player_index,
								   int id )
{
	uint16_t button;

	// NOTE: Analog buttons were added Nov 2017. Not all front-ends support this
	// feature (or pre-date it) so we need to handle this in a graceful way.

	// First, try and get an analog value using the new libretro API constant
	button = input_state_cb( player_index,
							 RETRO_DEVICE_ANALOG,
							 RETRO_DEVICE_INDEX_ANALOG_BUTTON,
							 id );

	if ( button == 0 )
	{
		// If we got exactly zero, we're either not pressing the button, or the front-end
		// is not reporting analog values. We need to do a second check using the classic
		// digital API method, to at least get some response - better than nothing.

		// NOTE: If we're really just not holding the button, we're still going to get zero.

		button = input_state_cb( player_index,
								 RETRO_DEVICE_JOYPAD,
								 0,
								 id ) ? 0x7FFF : 0;
	}

	return button;
}


//------------------------------------------------------------------------------
// Global Functions
//------------------------------------------------------------------------------

void input_init_env( retro_environment_t _environ_cb )
{
	// Cache this
	environ_cb = _environ_cb;

	struct retro_input_descriptor desc[] =
	{
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "D-Pad Left" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "D-Pad Up" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "D-Pad Down" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B,     "Cross" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,     "Circle" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,     "Triangle" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y,     "Square" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "L1" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2,    "L2" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3,    "L3" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R,     "R1" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2,    "R2" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3,    "R3" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,    "Select" },
		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START,    "Start" },
		{ 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
		{ 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
		{ 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
		{ 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },


		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "D-Pad Left" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "D-Pad Up" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "D-Pad Down" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B,     "Cross" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,     "Circle" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,     "Triangle" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y,     "Square" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "L1" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2,    "L2" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3,    "L3" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R,     "R1" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2,    "R2" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3,    "R3" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,    "Select" },
		{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START,    "Start" },
		{ 1, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
		{ 1, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
		{ 1, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
		{ 1, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },

		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "D-Pad Left" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "D-Pad Up" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "D-Pad Down" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B,     "Cross" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,     "Circle" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,     "Triangle" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y,     "Square" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "L1" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2,    "L2" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3,    "L3" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R,     "R1" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2,    "R2" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3,    "R3" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,    "Select" },
		{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START,    "Start" },
		{ 2, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
		{ 2, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
		{ 2, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
		{ 2, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },

		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "D-Pad Left" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "D-Pad Up" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "D-Pad Down" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B,     "Cross" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,     "Circle" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,     "Triangle" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y,     "Square" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "L1" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2,    "L2" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3,    "L3" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R,     "R1" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2,    "R2" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3,    "R3" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,    "Select" },
		{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START,    "Start" },
		{ 3, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
		{ 3, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
		{ 3, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
		{ 3, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },

		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "D-Pad Left" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "D-Pad Up" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "D-Pad Down" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B,     "Cross" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,     "Circle" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,     "Triangle" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y,     "Square" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "L1" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2,    "L2" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3,    "L3" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R,     "R1" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2,    "R2" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3,    "R3" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,    "Select" },
		{ 4, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START,    "Start" },
		{ 4, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
		{ 4, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
		{ 4, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
		{ 4, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },

		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "D-Pad Left" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "D-Pad Up" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "D-Pad Down" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B,     "Cross" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,     "Circle" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,     "Triangle" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y,     "Square" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "L1" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2,    "L2" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3,    "L3" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R,     "R1" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2,    "R2" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3,    "R3" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,    "Select" },
		{ 5, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START,    "Start" },
		{ 5, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
		{ 5, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
		{ 5, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
		{ 5, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },

		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "D-Pad Left" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "D-Pad Up" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "D-Pad Down" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B,     "Cross" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,     "Circle" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,     "Triangle" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y,     "Square" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "L1" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2,    "L2" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3,    "L3" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R,     "R1" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2,    "R2" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3,    "R3" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,    "Select" },
		{ 6, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START,    "Start" },
		{ 6, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
		{ 6, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
		{ 6, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
		{ 6, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },

		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "D-Pad Left" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "D-Pad Up" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "D-Pad Down" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B,     "Cross" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,     "Circle" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,     "Triangle" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y,     "Square" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "L1" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2,    "L2" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3,    "L3" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R,     "R1" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2,    "R2" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3,    "R3" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,    "Select" },
		{ 7, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START,    "Start" },
		{ 7, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
		{ 7, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
		{ 7, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
		{ 7, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },

		{ 0 },
	};

	environ_cb( RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc );

	if ( environ_cb( RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble ) && log_cb )
		log_cb(RETRO_LOG_INFO, "Rumble interface supported!\n");
}

void input_set_env( retro_environment_t environ_cb )
{
	static const struct retro_controller_info ports[ MAX_CONTROLLERS + 1 ] =
	{
		{ input_device_types, INPUT_DEVICE_TYPES_COUNT },
		{ input_device_types, INPUT_DEVICE_TYPES_COUNT },
		{ input_device_types, INPUT_DEVICE_TYPES_COUNT },
		{ input_device_types, INPUT_DEVICE_TYPES_COUNT },
		{ input_device_types, INPUT_DEVICE_TYPES_COUNT },
		{ input_device_types, INPUT_DEVICE_TYPES_COUNT },
		{ input_device_types, INPUT_DEVICE_TYPES_COUNT },
		{ input_device_types, INPUT_DEVICE_TYPES_COUNT },
		{ 0 },
	};

	environ_cb( RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports );
}

void input_init()
{
	// Initialise to default and bind input buffers to PS1 emulation.
	for ( unsigned i = 0; i < MAX_CONTROLLERS; ++i )
	{
		input_type[ i ] = RETRO_DEVICE_JOYPAD;

		SetInput( i, "gamepad", (uint8*)&input_data[ i ] );
	}
}

void input_set_fio( FrontIO* fio )
{
	FIO = fio;
}

void input_init_calibration()
{
	for ( unsigned i = 0; i < MAX_CONTROLLERS; i++ )
	{
		analog_calibration[ i ].left = 0.7f;
		analog_calibration[ i ].right = 0.7f;
		analog_calibration[ i ].twist = 0.7f;
	}
}

void input_enable_calibration( bool enable )
{
	enable_analog_calibration = enable;
}

void input_set_player_count( unsigned _players )
{
	players = _players;
}

void input_set_mouse_sensitivity( int percent )
{
	if ( percent > 0 && percent <= 200 ) {
		mouse_sensitivity = (float)percent / 100.0f;
	}
}

void input_set_gun_cursor( int cursor )
{
	gun_cursor = cursor;
	if ( FIO ) {
		// todo -- support multiple guns.
		for ( int port = 0; port < 8; ++port ) {
			FIO->SetCrosshairsCursor( port, gun_cursor );
		}
	}
}

void input_set_negcon_deadzone( int deadzone )
{
	negcon_deadzone = deadzone;
}

void input_set_negcon_linearity( int linearity )
{
	negcon_linearity = linearity;
}

unsigned input_get_player_count()
{
	return players;
}

void input_update( retro_input_state_t input_state_cb )
{
	// For each player (logical controller)
	for ( unsigned iplayer = 0; iplayer < players; ++iplayer )
	{
		INPUT_DATA* p_input = &(input_data[ iplayer ]);

		switch ( input_type[ iplayer ] )
		{

		default:
			p_input->buttons = 0;
			break;

		case RETRO_DEVICE_JOYPAD:
		case RETRO_DEVICE_PS_CONTROLLER:
		case RETRO_DEVICE_PS_DUALSHOCK:
		case RETRO_DEVICE_PS_ANALOG:
		case RETRO_DEVICE_PS_ANALOG_JOYSTICK:

			// Use fixed lookup table to map RetroPad inputs to PlayStation input bitmap.
			p_input->buttons = 0;
			for ( unsigned i = 0; i < INPUT_MAP_CONTROLLER_SIZE; i++ )
			{
				p_input->buttons |=
					input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_controller[ i ] )
						? ( 1 << i ) : 0;
			}

			break;

		case RETRO_DEVICE_PS_GUNCON:
		case RETRO_DEVICE_PS_JUSTIFIER:

			{
				uint8_t shot_type;
				int gun_x, gun_y;
				int forced_reload;

				forced_reload = input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD );

				// off-screen?
				if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN ) || forced_reload )
				{
					shot_type = 0x8; // off-screen shot

					gun_x = -16384; // magic position to disable cross-hair drawing.
					gun_y = -16384;
				}
				else
				{
					shot_type = 0x1; // on-screen shot

					int gun_x_raw, gun_y_raw;
					gun_x_raw = input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X );
					gun_y_raw = input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y );

					// .. scale into screen space:
					// NOTE: the scaling here is semi-guesswork, need to re-write.
					// TODO: Test with PAL games.

					const int scale_x = 2800;
					const int scale_y = 240;

					gun_x = ( ( gun_x_raw + 0x7fff ) * scale_x ) / (0x7fff << 1);
					gun_y = ( ( gun_y_raw + 0x7fff ) * scale_y ) / (0x7fff << 1);
				}

				// position
				p_input->gun_pos[ 0 ] = gun_x;
				p_input->gun_pos[ 1 ] = gun_y;

				// buttons
				p_input->u8[ 4 ] = 0;

				// trigger
				if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER ) || forced_reload ) {
					p_input->u8[ 4 ] |= shot_type;
				}

				if ( input_type[ iplayer ] == RETRO_DEVICE_PS_JUSTIFIER )
				{
					// Justifier 'Aux'
					if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A ) ) {
						p_input->u8[ 4 ] |= 0x2;
					}

					// Justifier 'Start'
					if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_START ) ) {
						p_input->u8[ 4 ] |= 0x4;
					}
				}
				else
				{
					// Guncon 'A'
					if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A ) ) {
						p_input->u8[ 4 ] |= 0x2;
					}

					// Guncon 'B'
					if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_B ) ) {
						p_input->u8[ 4 ] |= 0x4;
					}
				}
			}

			break;

		case RETRO_DEVICE_PS_MOUSE:

			// Simple two-button mouse.
			{
				p_input->u32[ 2 ] = 0;
				if ( input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT ) ) {
					p_input->u32[ 2 ] |= ( 1 << 1 ); // Left
				}
				if ( input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_RIGHT ) ) {
					p_input->u32[ 2 ] |= ( 1 << 0 ); // Right
				}
			}

			// Relative input.
			{
				// mouse input
				int dx_raw, dy_raw;
				dx_raw = input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_X );
				dy_raw = input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_Y );

				p_input->u32[ 0 ] = (int)roundf( dx_raw * mouse_sensitivity );
				p_input->u32[ 1 ] = (int)roundf( dy_raw * mouse_sensitivity );
			}

			break;

		case RETRO_DEVICE_PS_NEGCON:

			// Analog Inputs
			{
				uint16_t button_ii, button_i, left_shoulder;

				button_ii = std::max(
					get_analog_button( input_state_cb, iplayer, RETRO_DEVICE_ID_JOYPAD_L2 ),
					get_analog_button( input_state_cb, iplayer, RETRO_DEVICE_ID_JOYPAD_Y )
				);

				button_i = std::max(
					get_analog_button( input_state_cb, iplayer, RETRO_DEVICE_ID_JOYPAD_R2 ),
					get_analog_button( input_state_cb, iplayer, RETRO_DEVICE_ID_JOYPAD_B )
				);

				left_shoulder = get_analog_button( input_state_cb, iplayer, RETRO_DEVICE_ID_JOYPAD_L );

				p_input->u32[ 3 ] = button_i; // Analog button I
				p_input->u32[ 4 ] = button_ii; // Analog button II
				p_input->u32[ 5 ] = left_shoulder; // Analog shoulder (left only!)
			}

			// Twist
			{
				int analog_left_x = input_state_cb( iplayer, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT,
					RETRO_DEVICE_ID_ANALOG_X);
				
				// Account for deadzone
				if (analog_left_x > negcon_deadzone){
					analog_left_x = analog_left_x - negcon_deadzone;
				} else if (analog_left_x < -negcon_deadzone){
					analog_left_x = analog_left_x + negcon_deadzone;
				} else {
					analog_left_x = 0;
				}
				
				// Convert to an 'amplitude' [-1.0,1.0]
				float analog_left_x_amplitude = (float)analog_left_x / (float)(NEGCON_RANGE - negcon_deadzone);
				
				// Handle 'analog self-calibration'...
				// NB: This seems pointless, since all it does is arbitrarily
				// reduce the precision of 'twist' input (making games rather
				// unplayable). Someone, however, must have thought it was a
				// good idea at some point, so we'll leave the basic functionality
				// in place...
				struct analog_calibration *calibration = &analog_calibration[ iplayer ];
				if ( enable_analog_calibration )
				{
					// Compute the current stick deflection
					float twist = fabsf(analog_left_x_amplitude);
					
					// We recalibrate when we find a new max value for the sticks
					if ( twist > analog_calibration->twist ) {
						analog_calibration->twist = twist;
						log_cb(RETRO_LOG_DEBUG, "Recalibrating twist, deflection: %f\n", twist);
					}
					
					// NOTE: This value was copied from the DualShock code below. Needs confirmation.
					static const float neGcon_analog_deflection = 1.35f;
					
					// Now compute the scaling factor to apply to convert the
					// emulator's controller coordinates to a native neGcon range.
					float twist_scaling = neGcon_analog_deflection / analog_calibration->twist;
					
					analog_left_x_amplitude = analog_left_x_amplitude * twist_scaling;
				}
				else
				{
					// Reset the calibration. Since we only increase the
					// calibration coordinates we can start with a reasonably
					// small value.
					analog_calibration->twist = 0.7;
				}
				
				// Safety check
				// (also fixes range when above 'analog self-calibration' twist_scaling
				// is applied)
				analog_left_x_amplitude = analog_left_x_amplitude < -1.0f ? -1.0f : analog_left_x_amplitude;
				analog_left_x_amplitude = analog_left_x_amplitude > 1.0f ? 1.0f : analog_left_x_amplitude;
				
				// Adjust response
				if (negcon_linearity == 2) {
					if (analog_left_x_amplitude < 0.0) {
						analog_left_x_amplitude = -(analog_left_x_amplitude * analog_left_x_amplitude);
					} else {
						analog_left_x_amplitude = analog_left_x_amplitude * analog_left_x_amplitude;
					}
				} else if (negcon_linearity == 3) {
					analog_left_x_amplitude = analog_left_x_amplitude * analog_left_x_amplitude * analog_left_x_amplitude;
				}
				
				// Convert back from an 'amplitude' [-1.0,1.0] to a 'range' [-0x7FFF,0x7FFF]
				analog_left_x = (int)(analog_left_x_amplitude * NEGCON_RANGE);
				
				uint32_t twist_left  = analog_left_x < 0 ? -analog_left_x : 0;
				uint32_t twist_right = analog_left_x > 0 ?  analog_left_x : 0;
				
				p_input->u32[ 1 ] = twist_right; // Twist Right
				p_input->u32[ 2 ] = twist_left; // Twist Left
			}

			// Digital Buttons
			{
				p_input->u8[ 0 ] = 0;

				if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP ) ) {
					p_input->u8[ 0 ] |= ( 1 << 4 ); // Pad-Up
				}
				if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT ) ) {
					p_input->u8[ 0 ] |= ( 1 << 5 ); // Pad-Right
				}
				if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN ) ) {
					p_input->u8[ 0 ] |= ( 1 << 6 ); // Pad-Down
				}
				if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT ) ) {
					p_input->u8[ 0 ] |= ( 1 << 7 ); // Pad-Left
				}
				if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START ) ) {
					p_input->u8[ 0 ] |= ( 1 << 3 ); // Start
				}

				p_input->u8[ 1 ] = 0;

				if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A ) ) {
					p_input->u8[ 1 ] |= ( 1 << 5 ); // neGcon A
				}
				if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X ) ) {
					p_input->u8[ 1 ] |= ( 1 << 4 ); // neGcon B
				}
				/*if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L ) ) {
					p_input->u8[ 1 ] |= ( 1 << 2 ); // neGcon L shoulder (digital - non-standard?)
				}*/
				if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R ) ) {
					p_input->u8[ 1 ] |= ( 1 << 3 ); // neGcon R shoulder (digital)
				}
				/*if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2 ) ) {
					p_input->u8[ 1 ] |= ( 1 << 0 ); // neGcon L2 (non-standard?)
				}*/
				/*if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2 ) ) {
					p_input->u8[ 1 ] |= ( 1 << 1 ); // neGcon R2 (non-standard?)
				}*/
			}

			break;

		}; // switch ( input_type[ iplayer ] )


		//
		// -- Dual Analog Sticks

		switch ( input_type[ iplayer ] )
		{

		case RETRO_DEVICE_PS_DUALSHOCK:
		case RETRO_DEVICE_PS_ANALOG:
		case RETRO_DEVICE_PS_ANALOG_JOYSTICK:

			{
				int analog_left_x = input_state_cb( iplayer, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT,
					RETRO_DEVICE_ID_ANALOG_X);

				int analog_left_y = input_state_cb( iplayer, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT,
					RETRO_DEVICE_ID_ANALOG_Y);

				int analog_right_x = input_state_cb( iplayer, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT,
					RETRO_DEVICE_ID_ANALOG_X);

				int analog_right_y = input_state_cb( iplayer, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT,
					RETRO_DEVICE_ID_ANALOG_Y);

      			struct analog_calibration *calibration = &analog_calibration[ iplayer ];

				uint32_t r_right = analog_right_x > 0 ?  analog_right_x : 0;
				uint32_t r_left  = analog_right_x < 0 ? -analog_right_x : 0;
				uint32_t r_down  = analog_right_y > 0 ?  analog_right_y : 0;
				uint32_t r_up    = analog_right_y < 0 ? -analog_right_y : 0;

				uint32_t l_right = analog_left_x > 0 ?  analog_left_x : 0;
				uint32_t l_left  = analog_left_x < 0 ? -analog_left_x : 0;
				uint32_t l_down  = analog_left_y > 0 ?  analog_left_y : 0;
				uint32_t l_up    = analog_left_y < 0 ? -analog_left_y : 0;

				if ( enable_analog_calibration )
				{
					// Compute the "radius" (distance from 0, 0) of the current
					// stick position, using the same normalized values
					float l = analog_radius(analog_left_x, analog_left_y);
					float r = analog_radius(analog_right_x, analog_right_y);

					// We recalibrate when we find a new max value for the sticks
					if ( l > analog_calibration->left ) {
						analog_calibration->left = l;
						log_cb(RETRO_LOG_DEBUG, "Recalibrating left stick, radius: %f\n", l);
					}

					if ( r > analog_calibration->right ) {
						analog_calibration->right = r;
						log_cb(RETRO_LOG_DEBUG, "Recalibrating right stick, radius: %f\n", r);
					}

					// This represents the maximal value the DualShock sticks can
					// reach, where 1.0 would be the maximum value along the X or
					// Y axis. XXX I need to measure this value more precisely,
					// it's a rough estimate at the moment.
					static const float dualshock_analog_radius = 1.35;

					// Now compute the scaling factor to apply to convert the
					// emulator's controller coordinates to a native DualShock's
					// ones
					float l_scaling = dualshock_analog_radius / analog_calibration->left;
					float r_scaling = dualshock_analog_radius / analog_calibration->right;

					analog_scale(&l_left, l_scaling);
					analog_scale(&l_right, l_scaling);
					analog_scale(&l_up, l_scaling);
					analog_scale(&l_down, l_scaling);

					analog_scale(&r_left, r_scaling);
					analog_scale(&r_right, r_scaling);
					analog_scale(&r_up, r_scaling);
					analog_scale(&r_down, r_scaling);
				}
				else
				{
					// Reset the calibration. Since we only increase the
					// calibration coordinates we can start with a reasonably
					// small value.
					analog_calibration->left = 0.7;
					analog_calibration->right = 0.7;
				}

				p_input->u32[1] = r_right;
				p_input->u32[2] = r_left;
				p_input->u32[3] = r_down;
				p_input->u32[4] = r_up;

				p_input->u32[5] = l_right;
				p_input->u32[6] = l_left;
				p_input->u32[7] = l_down;
				p_input->u32[8] = l_up;
      		}

			break;

		}; // switch ( input_type[ iplayer ] )


		//
		// -- Rumble

		if ( rumble.set_rumble_state )
		{
			switch ( input_type[ iplayer ] )
			{

			case RETRO_DEVICE_PS_DUALSHOCK:

				{
					// Appears to be correct.
					rumble.set_rumble_state( iplayer, RETRO_RUMBLE_WEAK, p_input->u8[9 * 4] * 0x101);
					rumble.set_rumble_state( iplayer, RETRO_RUMBLE_STRONG, p_input->u8[9 * 4 + 1] * 0x101);

					/*log_cb( RETRO_LOG_INFO, "Controller %u: Rumble: %d %d\n",
						iplayer,
						p_input->u8[9 * 4] * 0x101,
						p_input->u8[9 * 4 + 1] * 0x101
					);*/
				}

				break;

			}; // switch ( input_type[ iplayer ] )

		}; // can we rumble?

	}; // for each player
}

//------------------------------------------------------------------------------
// Libretro Interface
//------------------------------------------------------------------------------

void retro_set_controller_port_device( unsigned in_port, unsigned device )
{
	if ( in_port < MAX_CONTROLLERS )
	{
		// Store input type
		input_type[ in_port ] = device;

		switch ( device )
		{

		case RETRO_DEVICE_NONE:
			log_cb( RETRO_LOG_INFO, "Controller %u: Unplugged\n", (in_port+1) );
			SetInput( in_port, "none", (uint8*)&input_data[ in_port ] );
			break;

		case RETRO_DEVICE_JOYPAD:
		case RETRO_DEVICE_PS_CONTROLLER:
			log_cb( RETRO_LOG_INFO, "Controller %u: PlayStation Controller\n", (in_port+1) );
			SetInput( in_port, "gamepad", (uint8*)&input_data[ in_port ] );
			break;

		case RETRO_DEVICE_PS_DUALSHOCK:
			log_cb( RETRO_LOG_INFO, "Controller %u: DualShock\n", (in_port+1) );
			SetInput( in_port, "dualshock", (uint8*)&input_data[ in_port ] );
			break;

		case RETRO_DEVICE_PS_ANALOG:
			log_cb( RETRO_LOG_INFO, "Controller %u: Analog Controller\n", (in_port+1) );
			SetInput( in_port, "dualanalog", (uint8*)&input_data[ in_port ] );
			break;

		case RETRO_DEVICE_PS_ANALOG_JOYSTICK:
			log_cb( RETRO_LOG_INFO, "Controller %u: Analog Joystick\n", (in_port+1) );
			SetInput( in_port, "analogjoy", (uint8*)&input_data[ in_port ] );
			break;

		case RETRO_DEVICE_PS_GUNCON:
			log_cb( RETRO_LOG_INFO, "Controller %u: Guncon / G-Con 45\n", (in_port+1) );
			SetInput( in_port, "guncon", (uint8*)&input_data[ in_port ] );
			if ( FIO ) {
				FIO->SetCrosshairsCursor( in_port, gun_cursor );
			}
			break;

		case RETRO_DEVICE_PS_JUSTIFIER:
			log_cb( RETRO_LOG_INFO, "Controller %u: Justifier\n", (in_port+1) );
			SetInput( in_port, "justifier", (uint8*)&input_data[ in_port ] );
			if ( FIO ) {
				FIO->SetCrosshairsCursor( in_port, gun_cursor );
			}
			break;

		case RETRO_DEVICE_PS_MOUSE:
			log_cb( RETRO_LOG_INFO, "Controller %u: Mouse\n", (in_port+1) );
			SetInput( in_port, "mouse", (uint8*)&input_data[ in_port ] );
			break;

		case RETRO_DEVICE_PS_NEGCON:
			log_cb( RETRO_LOG_INFO, "Controller %u: neGcon\n", (in_port+1) );
			SetInput( in_port, "negcon", (uint8*)&input_data[ in_port ] );
			break;

		default:
			log_cb( RETRO_LOG_WARN, "Controller %u: Unsupported Device (%u)\n", (in_port+1), device );
			SetInput( in_port, "none", (uint8*)&input_data[ in_port ] );
			break;

		}; // switch ( device )

		// Clear rumble.
		if ( rumble.set_rumble_state )
		{
			rumble.set_rumble_state(in_port, RETRO_RUMBLE_STRONG, 0);
			rumble.set_rumble_state(in_port, RETRO_RUMBLE_WEAK, 0);
		}
		input_data[ in_port ].u32[ 9 ] = 0;

	}; // valid port?
}

//==============================================================================
