/*
**
**
**---------------------------------------------------------------------------
** 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 ------------------------------------------------------------

#define WIN32_LEAN_AND_MEAN
#define DIRECTINPUT_VERSION 0x800
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <dinput.h>
#ifndef __GNUC__
#include <wbemidl.h>
#endif
#include <oleauto.h>
#include <malloc.h>

#include "i_input.h"
#include "d_event.h"
#include "templates.h"
#include "gameconfigfile.h"
#include "cmdlib.h"
#include "v_text.h"
#include "m_argv.h"
#include "rawinput.h"

#define SAFE_RELEASE(x)		{ if (x != NULL) { x->Release(); x = NULL; } }

// WBEMIDL BITS -- because w32api doesn't have this, either -----------------

#ifdef __GNUC__
struct IWbemClassObject : public IUnknown
{
public:
	virtual HRESULT __stdcall GetQualifierSet() = 0;
	virtual HRESULT __stdcall Get(LPCWSTR wszName, long lFlags, VARIANT *pVal, long *pType, long *plFlavor) = 0;
	virtual HRESULT __stdcall Put() = 0;
	virtual HRESULT __stdcall Delete() = 0;
	virtual HRESULT __stdcall GetNames() = 0;
	virtual HRESULT __stdcall BeginEnumeration() = 0;
	virtual HRESULT __stdcall Next() = 0;
	virtual HRESULT __stdcall EndEnumeration() = 0;
	virtual HRESULT __stdcall GetPropertyQualifierSet() = 0;
	virtual HRESULT __stdcall Clone() = 0;
	virtual HRESULT __stdcall GetObjectText() = 0;
	virtual HRESULT __stdcall SpawnDerivedClass() = 0;
	virtual HRESULT __stdcall SpawnInstance() = 0;
	virtual HRESULT __stdcall CompareTo() = 0;
	virtual HRESULT __stdcall GetPropertyOrigin() = 0;
	virtual HRESULT __stdcall InheritsFrom() = 0;
	virtual HRESULT __stdcall GetMethod() = 0;
	virtual HRESULT __stdcall PutMethod() = 0;
	virtual HRESULT __stdcall DeleteMethod() = 0;
	virtual HRESULT __stdcall BeginMethodEnumeration() = 0;
	virtual HRESULT __stdcall NextMethod() = 0;
	virtual HRESULT __stdcall EndMethodEnumeration() = 0;
	virtual HRESULT __stdcall GetMethodQualifierSet() = 0;
	virtual HRESULT __stdcall GetMethodOrigin() = 0;
};

struct IEnumWbemClassObject : public IUnknown
{
public:
	virtual HRESULT __stdcall Reset() = 0;
	virtual HRESULT __stdcall Next(long lTimeout, ULONG uCount,
		IWbemClassObject **apObjects, ULONG *puReturned) = 0;
	virtual HRESULT __stdcall NextAsync() = 0;
	virtual HRESULT __stdcall Clone() = 0;
	virtual HRESULT __stdcall Skip(long lTimeout, ULONG nCount) = 0;
};

struct IWbemServices : public IUnknown
{
public:
	virtual HRESULT __stdcall OpenNamespace() = 0;
	virtual HRESULT __stdcall CancelAsyncCall() = 0;
	virtual HRESULT __stdcall QueryObjectSink() = 0;
	virtual HRESULT __stdcall GetObject() = 0;
	virtual HRESULT __stdcall GetObjectAsync() = 0;
	virtual HRESULT __stdcall PutClass() = 0;
	virtual HRESULT __stdcall PutClassAsync() = 0;
	virtual HRESULT __stdcall DeleteClass() = 0;
	virtual HRESULT __stdcall DeleteClassAsync() = 0;
	virtual HRESULT __stdcall CreateClassEnum() = 0;
	virtual HRESULT __stdcall CreateClassEnumAsync() = 0;
	virtual HRESULT __stdcall PutInstance() = 0;
	virtual HRESULT __stdcall PutInstanceAsync() = 0;
	virtual HRESULT __stdcall DeleteInstance() = 0;
	virtual HRESULT __stdcall DeleteInstanceAsync() = 0;
	virtual HRESULT __stdcall CreateInstanceEnum(
		const BSTR strFilter, long lFlags, void *pCtx, IEnumWbemClassObject **ppEnum) = 0;
	virtual HRESULT __stdcall CreateInstanceEnumAsync() = 0;
	virtual HRESULT __stdcall ExecQuery() = 0;
	virtual HRESULT __stdcall ExecQueryAsync() = 0;
	virtual HRESULT __stdcall ExecNotificationQuery() = 0;
	virtual HRESULT __stdcall ExecNotificationQueryAsync() = 0;
	virtual HRESULT __stdcall ExecMethod() = 0;
	virtual HRESULT __stdcall ExecMethodAsync() = 0;
};

struct IWbemLocator : public IUnknown
{
public:
	virtual HRESULT __stdcall ConnectServer(
		const BSTR strNetworkResource,
		const BSTR strUser,
		const BSTR strPassword,
		const BSTR strLocale,
		long lSecurityFlags,
		const BSTR strAuthority,
		void *pCtx,
		IWbemServices **ppNamespace) = 0;
};
#endif

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

#define DEFAULT_DEADZONE			0.25f

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

class FDInputJoystick : public FInputDevice, IJoystickConfig
{
public:
	FDInputJoystick(const GUID *instance, FString &name);
	~FDInputJoystick();

	bool GetDevice();
	void ProcessInput();
	void AddAxes(float axes[NUM_JOYAXIS]);

	// IJoystickConfig interface
	FString GetName();
	float GetSensitivity();
	virtual void SetSensitivity(float scale);

	int GetNumAxes();
	float GetAxisDeadZone(int axis);
	EJoyAxis GetAxisMap(int axis);
	const char *GetAxisName(int axis);
	float GetAxisScale(int axis);

	void SetAxisDeadZone(int axis, float deadzone);
	void SetAxisMap(int axis, EJoyAxis gameaxis);
	void SetAxisScale(int axis, float scale);

	bool IsSensitivityDefault();
	bool IsAxisDeadZoneDefault(int axis);
	bool IsAxisMapDefault(int axis);
	bool IsAxisScaleDefault(int axis);

	void SetDefaultConfig();
	FString GetIdentifier();

protected:
	struct AxisInfo
	{
		FString Name;
		GUID Guid;
		DWORD Type;
		DWORD Ofs;
		LONG Min, Max;
		float Value;
		float DeadZone, DefaultDeadZone;
		float Multiplier, DefaultMultiplier;
		EJoyAxis GameAxis, DefaultGameAxis;
		uint8_t ButtonValue;
	};
	struct ButtonInfo
	{
		FString Name;
		GUID Guid;
		DWORD Type;
		DWORD Ofs;
		uint8_t Value;
	};

	LPDIRECTINPUTDEVICE8 Device;
	GUID Instance;
	FString Name;
	bool Marked;
	float Multiplier;
	int Warmup;
	TArray<AxisInfo> Axes;
	TArray<ButtonInfo> Buttons;
	TArray<ButtonInfo> POVs;

	DIOBJECTDATAFORMAT *Objects;
	DIDATAFORMAT DataFormat;

	static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef);
	void OrderAxes();
	bool ReorderAxisPair(const GUID &x, const GUID &y, int pos);
	HRESULT SetDataFormat();
	bool SetConfigSection(bool create);

	friend class FDInputJoystickManager;
};

class FDInputJoystickManager : public FJoystickCollection
{
public:
	FDInputJoystickManager();
	~FDInputJoystickManager();

	bool GetDevice();
	void ProcessInput();
	void AddAxes(float axes[NUM_JOYAXIS]);
	void GetDevices(TArray<IJoystickConfig *> &sticks);
	IJoystickConfig *Rescan();

protected:
	struct Enumerator
	{
		GUID Instance;
		FString Name;
	};
	struct EnumData
	{
		TArray<Enumerator> *All;
		bool GenericDevices;
	};
	TArray<FDInputJoystick *> Devices;

	FDInputJoystick *EnumDevices();

	static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef);
	static int NameSort(const void *a, const void *b);
	static bool IsXInputDevice(const GUID *guid);
	static bool IsXInputDeviceFast(const GUID *guid);
	static bool IsXInputDeviceSlow(const GUID *guid);
};

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

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

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

static void MapAxis(FIntCVar &var, int num);

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

extern LPDIRECTINPUT8 g_pdi;
extern HWND Window;

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

CUSTOM_CVAR(Bool, joy_dinput, true, CVAR_GLOBALCONFIG|CVAR_ARCHIVE|CVAR_NOINITCALL)
{
	I_StartupDirectInputJoystick();
	event_t ev = { EV_DeviceChange };
	D_PostEvent(&ev);
}

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

static const uint8_t POVButtons[9] = { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09, 0x00 };

//("dc12a687-737f-11cf-884d-00aa004b2e24")
static const IID IID_IWbemLocator =		{ 0xdc12a687, 0x737f, 0x11cf,
	{ 0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 } };

//("4590f811-1d3a-11d0-891f-00aa004b2e24")
static const CLSID CLSID_WbemLocator =	{ 0x4590f811, 0x1d3a, 0x11d0,
	{ 0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 } };

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

//===========================================================================
//
// FDInputJoystick - Constructor
//
//===========================================================================

FDInputJoystick::FDInputJoystick(const GUID *instance, FString &name)
{
	Device = NULL;
	DataFormat.rgodf = NULL;
	Instance = *instance;
	Name = name;
	Marked = false;
}

//===========================================================================
//
// FDInputJoystick - Destructor
//
//===========================================================================

FDInputJoystick::~FDInputJoystick()
{
	unsigned int i;

	if (Device != NULL)
	{
		M_SaveJoystickConfig(this);
		Device->Release();
		Device = NULL;
	}
	if (DataFormat.rgodf != NULL)
	{
		delete[] DataFormat.rgodf;
	}
	// Send key ups before destroying this.
	if (Axes.Size() == 1)
	{
		Joy_GenerateButtonEvents(Axes[0].ButtonValue, 0, 2, KEY_JOYAXIS1PLUS);
	}
	else if (Axes.Size() > 1)
	{
		Joy_GenerateButtonEvents(Axes[1].ButtonValue, 0, 4, KEY_JOYAXIS1PLUS);
		for (i = 2; i < Axes.Size(); ++i)
		{
			Joy_GenerateButtonEvents(Axes[i].ButtonValue, 0, 2, KEY_JOYAXIS1PLUS + i*2);
		}
	}
	for (i = 0; i < Buttons.Size(); ++i)
	{
		if (Buttons[i].Value)
		{
			event_t ev = { EV_KeyUp };
			ev.data1 = KEY_FIRSTJOYBUTTON + i;
		}
	}
	for (i = 0; i < POVs.Size(); ++i)
	{
		Joy_GenerateButtonEvents(POVs[i].Value, 0, 4, KEY_JOYPOV1_UP + i*4);
	}
}

//===========================================================================
//
// FDInputJoystick :: GetDevice
//
//===========================================================================

bool FDInputJoystick::GetDevice()
{
	HRESULT hr;

	if (g_pdi == NULL)
	{
		return false;
	}
	hr = g_pdi->CreateDevice(Instance, &Device, NULL);
	if (FAILED(hr) || Device == NULL)
	{
		return false;
	}
	hr = Device->EnumObjects(EnumObjectsCallback, this, DIDFT_ABSAXIS | DIDFT_BUTTON | DIDFT_POV);
	OrderAxes();
	hr = SetDataFormat();
	if (FAILED(hr))
	{
		Printf(TEXTCOLOR_ORANGE "Setting data format for %s failed.\n", Name.GetChars());
		return false;
	}
	hr = Device->SetCooperativeLevel(Window, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
	if (FAILED(hr))
	{
		Printf(TEXTCOLOR_ORANGE "Setting cooperative level for %s failed.\n", Name.GetChars());
		return false;
	}
	Device->Acquire();
	M_LoadJoystickConfig(this);
	Warmup = 4;
	return true;
}

//===========================================================================
//
// FDInputJoystick :: ProcessInput
//
// Send button events and record axes for later.
//
//===========================================================================

void FDInputJoystick::ProcessInput()
{
	HRESULT hr;
	uint8_t *state;
	unsigned i;
	event_t ev;

	if (Device == NULL)
	{
		return;
	}

	hr = Device->Poll();
	if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
	{
		hr = Device->Acquire();
	}
	if (FAILED(hr))
	{
		return;
	}

	state = (uint8_t *)alloca(DataFormat.dwDataSize);
	hr = Device->GetDeviceState(DataFormat.dwDataSize, state);
	if (FAILED(hr))
		return;

	// Some controllers send false values when they are first
	// initialized, so give some time for them to get past that
	// before we pay any attention to their state.
	if (Warmup > 0)
	{
		Warmup--;
		return;
	}

	// Convert axis values to floating point and save them for a later call
	// to AddAxes(). Axes that are past their dead zone will also be translated
	// into button presses.
	for (i = 0; i < Axes.Size(); ++i)
	{
		AxisInfo *info = &Axes[i];
		LONG value = *(LONG *)(state + info->Ofs);
		double axisval;
		uint8_t buttonstate = 0;

		// Scale to [-1.0, 1.0]
		axisval = (value - info->Min) * 2.0 / (info->Max - info->Min) - 1.0;
		// Cancel out dead zone
		axisval = Joy_RemoveDeadZone(axisval, info->DeadZone, &buttonstate);
		info->Value = float(axisval);
		if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1))
		{
			Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2);
		}
		else if (i == 1)
		{
			// Since we sorted the axes, we know that the first two are definitely X and Y.
			// They are probably a single stick, so use angular position to determine buttons.
			buttonstate = Joy_XYAxesToButtons(axisval, Axes[0].Value);
			Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS);
		}
		info->ButtonValue = buttonstate;
	}

	// Compare button states and generate events for buttons that have changed.
	memset(&ev, 0, sizeof(ev));
	for (i = 0; i < Buttons.Size(); ++i)
	{
		ButtonInfo *info = &Buttons[i];
		uint8_t newstate = *(uint8_t *)(state + info->Ofs) & 0x80;
		if (newstate != info->Value)
		{
			info->Value = newstate;
			ev.data1 = KEY_FIRSTJOYBUTTON + i;
			ev.type = (newstate != 0) ? EV_KeyDown : EV_KeyUp;
			D_PostEvent(&ev);
		}
	}

	// POV hats are treated as a set of four buttons, because if it's a
	// D-pad, that's exactly what it is.
	for (i = 0; i < POVs.Size(); ++i)
	{
		ButtonInfo *info = &POVs[i];
		DWORD povangle = *(DWORD *)(state + info->Ofs);
		int pov;

		// Smoosh POV angles down into octants. 8 is centered.
		pov = (LOWORD(povangle) == 0xFFFF) ? 8 : ((povangle + 2250) % 36000) / 4500;

		// Convert octant to one or two buttons needed to represent it.
		pov = POVButtons[pov];

		// Send events for POV "buttons" that have changed.
		Joy_GenerateButtonEvents(info->Value, pov, 4, KEY_JOYPOV1_UP + i*4);
		info->Value = pov;
	}
}

//===========================================================================
//
// FDInputJoystick :: AddAxes
//
// Add the values of each axis to the game axes.
//
//===========================================================================

void FDInputJoystick::AddAxes(float axes[NUM_JOYAXIS])
{
	for (unsigned i = 0; i < Axes.Size(); ++i)
	{
		// Add to the game axis.
		axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * Axes[i].Multiplier);
	}
}

//===========================================================================
//
// FDInputJoystick :: EnumObjectsCallback							STATIC
//
// Finds all axes, buttons, and hats on the controller.
//
//===========================================================================

BOOL CALLBACK FDInputJoystick::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef)
{
	FDInputJoystick *joy = (FDInputJoystick *)pvRef;

	if (lpddoi->guidType == GUID_Button)
	{
		ButtonInfo info;
		info.Name = lpddoi->tszName;
		info.Guid = lpddoi->guidType;
		info.Type = lpddoi->dwType;
		info.Ofs = 0;
		info.Value = 0;
		// We don't have the key labels necessary to support more than 128
		// joystick buttons. This is what DIJOYSTATE2 offers, so we
		// probably don't need to worry about any devices with more than
		// that.
		if (joy->Buttons.Size() < 128)
		{
			joy->Buttons.Push(info);
		}
	}
	else if (lpddoi->guidType == GUID_POV)
	{
		ButtonInfo info;
		info.Name = lpddoi->tszName;
		info.Guid = lpddoi->guidType;
		info.Type = lpddoi->dwType;
		info.Ofs = 0;
		info.Value = 0;
		// We don't have the key labels necessary to support more than 4
		// hats. I don't know any devices with more than 1, and the
		// standard DirectInput DIJOYSTATE does not support more than 4
		// hats either, so this is probably a non-issue.
		if (joy->POVs.Size() < 4)
		{
			joy->POVs.Push(info);
		}
	}
	else
	if (lpddoi->guidType == GUID_XAxis ||
		lpddoi->guidType == GUID_YAxis ||
		lpddoi->guidType == GUID_ZAxis ||
		lpddoi->guidType == GUID_RxAxis ||
		lpddoi->guidType == GUID_RyAxis ||
		lpddoi->guidType == GUID_RzAxis ||
		lpddoi->guidType == GUID_Slider)
	{
		DIPROPRANGE diprg;
		AxisInfo info;

		diprg.diph.dwSize = sizeof(DIPROPRANGE);
		diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
		diprg.diph.dwObj = lpddoi->dwType;
		diprg.diph.dwHow = DIPH_BYID;
		diprg.lMin = 0;
		diprg.lMax = 0;
		joy->Device->GetProperty(DIPROP_RANGE, &diprg.diph);

		info.Name = lpddoi->tszName;
		info.Guid = lpddoi->guidType;
		info.Type = lpddoi->dwType;
		info.Ofs = 0;
		info.Min = diprg.lMin;
		info.Max = diprg.lMax;
		info.GameAxis = JOYAXIS_None;
		info.Value = 0;
		info.ButtonValue = 0;
		joy->Axes.Push(info);
	}
	return DIENUM_CONTINUE;
}

//===========================================================================
//
// FDInputJoystick :: OrderAxes
//
// Try to put the axes in some sort of sane order. X and Y axes are pretty
// much standard. Unfortunately, the rest are entirely up to the
// manufacturers to decide how they want to assign them.
//
//===========================================================================

void FDInputJoystick::OrderAxes()
{
	// Make X,Y the first pair.
	if (!ReorderAxisPair(GUID_XAxis, GUID_YAxis, 0))
	{
		return;
	}
	// The second pair is either Rx,Ry or Rz,Z, depending on what we have
	if (!ReorderAxisPair(GUID_RxAxis, GUID_RyAxis, 2))
	{
		ReorderAxisPair(GUID_RzAxis, GUID_ZAxis, 2);
	}
}

bool FDInputJoystick::ReorderAxisPair(const GUID &xid, const GUID &yid, int pos)
{
	unsigned i;
	int x, y;

	// Find each axis.
	x = -1;
	y = -1;
	for (i = 0; i < Axes.Size(); ++i)
	{
		if (x < 0 && Axes[i].Guid == xid)
		{
			x = i;
		}
		else if (y < 0 && Axes[i].Guid == yid)
		{
			y = i;
		}
	}
	// If we don't have both X and Y axes, do nothing.
	if (x < 0 || y < 0)
	{
		return false;
	}
	if (x == pos + 1 && y == pos)
	{ // Xbox 360 Controllers return them in this order.
		swapvalues(Axes[pos], Axes[pos + 1]);
	}
	else if (x != pos || y != pos + 1)
	{
		AxisInfo xinfo = Axes[x], yinfo = Axes[y];
		Axes.Delete(x);
		if (x < y)
		{
			y--;
		}
		Axes.Delete(y);
		Axes.Insert(pos, xinfo);
		Axes.Insert(pos + 1, yinfo);
	}
	return true;
}

//===========================================================================
//
// FDInputJoystick :: SetDataFormat
//
// Using the objects we previously enumerated, construct a data format
// structure for DirectInput to use for this device. We could use the
// predefined c_dfDIJoystick2, except that we would have no way of knowing
// which axis mapped to a certain point in the structure if there is more
// than one of a particular type of axis. The dwOfs member of
// DIDEVICEOBJECTINSTANCE is practically useless, because it describes the
// offset of the object in the device's native data format, and not
// the offset in something we can actually use.
//
//===========================================================================

HRESULT FDInputJoystick::SetDataFormat()
{
	DIOBJECTDATAFORMAT *objects;
	DWORD numobjs;
	DWORD nextofs;
	unsigned i;

	objects = new DIOBJECTDATAFORMAT[Axes.Size() + POVs.Size() + Buttons.Size()];
	numobjs = nextofs = 0;

	// Add all axes
	for (i = 0; i < Axes.Size(); ++i)
	{
		objects[i].pguid = &Axes[i].Guid;
		objects[i].dwOfs = Axes[i].Ofs = nextofs;
		objects[i].dwType = Axes[i].Type;
		objects[i].dwFlags = 0;
		nextofs += sizeof(LONG);
	}
	numobjs = i;
	// Add all POVs
	for (i = 0; i < POVs.Size(); ++i)
	{
		objects[numobjs + i].pguid = &POVs[i].Guid;
		objects[numobjs + i].dwOfs = POVs[i].Ofs = nextofs;
		objects[numobjs + i].dwType = POVs[i].Type;
		objects[numobjs + i].dwFlags = 0;
		nextofs += sizeof(DWORD);
	}
	numobjs += i;
	// Add all buttons
	for (i = 0; i < Buttons.Size(); ++i)
	{
		objects[numobjs + i].pguid = &Buttons[i].Guid;
		objects[numobjs + i].dwOfs = Buttons[i].Ofs = nextofs;
		objects[numobjs + i].dwType = Buttons[i].Type;
		objects[numobjs + i].dwFlags = 0;
		nextofs += sizeof(uint8_t);
	}
	numobjs += i;

	// Set format
	DataFormat.dwSize = sizeof(DIDATAFORMAT);
	DataFormat.dwObjSize = sizeof(DIOBJECTDATAFORMAT);
	DataFormat.dwFlags = DIDF_ABSAXIS;
	DataFormat.dwDataSize = (nextofs + 3) & ~3;		// Round to the nearest multiple of 4.
	DataFormat.dwNumObjs = numobjs;
	DataFormat.rgodf = objects;
	return Device->SetDataFormat(&DataFormat);
}

//===========================================================================
//
// FDInputJoystick :: GetIdentifier
//
//===========================================================================

FString FDInputJoystick::GetIdentifier()
{
	char id[48];

	id[0] = 'D'; id[1] = 'I'; id[2] = ':';
	FormatGUID(id + 3, countof(id) - 3, Instance);
	return id;
}

//===========================================================================
//
// FDInputJoystick :: SetDefaultConfig
//
// Try for a reasonable default axis configuration.
//
//===========================================================================

void FDInputJoystick::SetDefaultConfig()
{
	unsigned i;

	Multiplier = 1;
	for (i = 0; i < Axes.Size(); ++i)
	{
		Axes[i].DeadZone = DEFAULT_DEADZONE;
		Axes[i].Multiplier = 1;
		Axes[i].GameAxis = JOYAXIS_None;
	}
	// Triggers on a 360 controller have a much smaller deadzone.
	if (Axes.Size() == 5 && Axes[4].Guid == GUID_ZAxis)
	{
		Axes[4].DeadZone = 30 / 256.f;
	}
	// Two axes? Horizontal is yaw and vertical is forward.
	if (Axes.Size() == 2)
	{
		Axes[0].GameAxis = JOYAXIS_Yaw;
		Axes[1].GameAxis = JOYAXIS_Forward;
	}
	// Three axes? First two are movement, third is yaw.
	else if (Axes.Size() >= 3)
	{
		Axes[0].GameAxis = JOYAXIS_Side;
		Axes[1].GameAxis = JOYAXIS_Forward;
		Axes[2].GameAxis = JOYAXIS_Yaw;
		// Four axes? First two are movement, last two are looking around.
		if (Axes.Size() >= 4)
		{
			Axes[3].GameAxis = JOYAXIS_Pitch;	Axes[3].Multiplier = 0.75f;
			// Five axes? Use the fifth one for moving up and down.
			if (Axes.Size() >= 5)
			{
				Axes[4].GameAxis = JOYAXIS_Up;
			}
		}
	}
	// If there is only one axis, then we make no assumptions about how
	// the user might want to use it.

	// Preserve defaults for config saving.
	for (i = 0; i < Axes.Size(); ++i)
	{
		Axes[i].DefaultDeadZone = Axes[i].DeadZone;
		Axes[i].DefaultMultiplier = Axes[i].Multiplier;
		Axes[i].DefaultGameAxis = Axes[i].GameAxis;
	}
}

//===========================================================================
//
// FDInputJoystick :: GetName
//
//===========================================================================

FString FDInputJoystick::GetName()
{
	return Name;
}

//===========================================================================
//
// FDInputJoystick :: GetSensitivity
//
//===========================================================================

float FDInputJoystick::GetSensitivity()
{
	return Multiplier;
}

//===========================================================================
//
// FDInputJoystick :: SetSensitivity
//
//===========================================================================

void FDInputJoystick::SetSensitivity(float scale)
{
	Multiplier = scale;
}

//===========================================================================
//
// FDInputJoystick :: IsSensitivityDefault
//
//===========================================================================

bool FDInputJoystick::IsSensitivityDefault()
{
	return Multiplier == 1;
}

//===========================================================================
//
// FDInputJoystick :: GetNumAxes
//
//===========================================================================

int FDInputJoystick::GetNumAxes()
{
	return (int)Axes.Size();
}

//===========================================================================
//
// FDInputJoystick :: GetAxisDeadZone
//
//===========================================================================

float FDInputJoystick::GetAxisDeadZone(int axis)
{
	if (unsigned(axis) >= Axes.Size())
	{
		return 0;
	}
	return Axes[axis].DeadZone;
}

//===========================================================================
//
// FDInputJoystick :: GetAxisMap
//
//===========================================================================

EJoyAxis FDInputJoystick::GetAxisMap(int axis)
{
	if (unsigned(axis) >= Axes.Size())
	{
		return JOYAXIS_None;
	}
	return Axes[axis].GameAxis;
}

//===========================================================================
//
// FDInputJoystick :: GetAxisName
//
//===========================================================================

const char *FDInputJoystick::GetAxisName(int axis)
{
	if (unsigned(axis) < Axes.Size())
	{
		return Axes[axis].Name;
	}
	return "Invalid";
}

//===========================================================================
//
// FDInputJoystick :: GetAxisScale
//
//===========================================================================

float FDInputJoystick::GetAxisScale(int axis)
{
	if (unsigned(axis) >= Axes.Size())
	{
		return 0;
	}
	return Axes[axis].Multiplier;
}

//===========================================================================
//
// FDInputJoystick :: SetAxisDeadZone
//
//===========================================================================

void FDInputJoystick::SetAxisDeadZone(int axis, float deadzone)
{
	if (unsigned(axis) < Axes.Size())
	{
		Axes[axis].DeadZone = clamp(deadzone, 0.f, 1.f);
	}
}

//===========================================================================
//
// FDInputJoystick :: SetAxisMap
//
//===========================================================================

void FDInputJoystick::SetAxisMap(int axis, EJoyAxis gameaxis)
{
	if (unsigned(axis) < Axes.Size())
	{
		Axes[axis].GameAxis = (unsigned(gameaxis) < NUM_JOYAXIS) ? gameaxis : JOYAXIS_None;
	}
}

//===========================================================================
//
// FDInputJoystick :: SetAxisScale
//
//===========================================================================

void FDInputJoystick::SetAxisScale(int axis, float scale)
{
	if (unsigned(axis) < Axes.Size())
	{
		Axes[axis].Multiplier = scale;
	}
}

//===========================================================================
//
// FDInputJoystick :: IsAxisDeadZoneDefault
//
//===========================================================================

bool FDInputJoystick::IsAxisDeadZoneDefault(int axis)
{
	if (unsigned(axis) < Axes.Size())
	{
		return Axes[axis].DeadZone == Axes[axis].DefaultDeadZone;
	}
	return true;
}

//===========================================================================
//
// FDInputJoystick :: IsAxisScaleDefault
//
//===========================================================================

bool FDInputJoystick::IsAxisScaleDefault(int axis)
{
	if (unsigned(axis) < Axes.Size())
	{
		return Axes[axis].Multiplier == Axes[axis].DefaultMultiplier;
	}
	return true;
}

//===========================================================================
//
// FDInputJoystick :: IsAxisMapDefault
//
//===========================================================================

bool FDInputJoystick::IsAxisMapDefault(int axis)
{
	if (unsigned(axis) < Axes.Size())
	{
		return Axes[axis].GameAxis == Axes[axis].DefaultGameAxis;
	}
	return true;
}

//===========================================================================
//
// FDInputJoystickManager - Constructor
//
//===========================================================================

FDInputJoystickManager::FDInputJoystickManager()
{
}

//===========================================================================
//
// FDInputJoystickManager - Destructor
//
//===========================================================================

FDInputJoystickManager::~FDInputJoystickManager()
{
	for (unsigned i = 0; i < Devices.Size(); ++i)
	{
		delete Devices[i];
	}
}

//===========================================================================
//
// FDInputJoystickManager :: GetDevice
//
//===========================================================================

bool FDInputJoystickManager::GetDevice()
{
	if (g_pdi == NULL)
	{
		return false;
	}
	EnumDevices();
	return true;
}

//===========================================================================
//
// FDInputJoystickManager :: ProcessInput
//
// Process input for every attached device.
//
//===========================================================================

void FDInputJoystickManager::ProcessInput()
{
	for (unsigned i = 0; i < Devices.Size(); ++i)
	{
		if (Devices[i] != NULL)
		{
			Devices[i]->ProcessInput();
		}
	}
}

//===========================================================================
//
// FDInputJoystickManager :: AddAxes
//
// Adds the state of all attached device axes to the passed array.
//
//===========================================================================

void FDInputJoystickManager :: AddAxes(float axes[NUM_JOYAXIS])
{
	for (unsigned i = 0; i < Devices.Size(); ++i)
	{
		Devices[i]->AddAxes(axes);
	}
}

//===========================================================================
//
// FDInputJoystickManager :: GetDevices
//
// Adds the IJoystick interfaces for each device we created to the sticks
// array.
//
//===========================================================================

void FDInputJoystickManager::GetDevices(TArray<IJoystickConfig *> &sticks)
{
	for (unsigned i = 0; i < Devices.Size(); ++i)
	{
		sticks.Push(Devices[i]);
	}
}

//===========================================================================
//
// FDInputJoystickManager :: EnumCallback							STATIC
//
// Adds each DirectInput game controller to a TArray.
//
//===========================================================================

BOOL CALLBACK FDInputJoystickManager::EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef)
{
	EnumData *data = (EnumData *)pvRef;

	// Special check for the Microsoft SideWinder Strategic Commander,
	// which for some odd reason reports itself as a generic device and
	// not a game controller.
	if (data->GenericDevices && lpddi->guidProduct.Data1 != MAKELONG(0x45e, 0x0033))
	{
		return DIENUM_CONTINUE;
	}

	// Do not add PS2 adapters if Raw PS2 Input was initialized.
	// Do not add XInput devices if XInput was initialized.
	if ((JoyDevices[INPUT_RawPS2] == NULL || !I_IsPS2Adapter(lpddi->guidProduct.Data1)) &&
		(JoyDevices[INPUT_XInput] == NULL || !IsXInputDevice(&lpddi->guidProduct)))
	{
		Enumerator thisone;

		thisone.Instance = lpddi->guidInstance;
		thisone.Name = lpddi->tszInstanceName;
		data->All->Push(thisone);
	}
	return DIENUM_CONTINUE;
}

//===========================================================================
//
// FDInputJoystickManager :: IsXInputDevice							STATIC
//
// Does the DirectInput product GUID correspond to an XInput controller?

// If the product's device ID contains contains "IG_"
// (ex. "VID_045E&PID_028E&IG_00"), then it is an XInput device.
// Unfortunately this information can not be found by just using DirectInput.
//
//===========================================================================

bool FDInputJoystickManager::IsXInputDevice(const GUID *guid)
{
	if (MyGetRawInputDeviceList == NULL || MyGetRawInputDeviceInfoA == NULL)
	{
		return IsXInputDeviceSlow(guid);
	}
	else
	{
		return IsXInputDeviceFast(guid);
	}
}

//===========================================================================
//
// FDInputJoystickManager :: IsXInputDeviceSlow						STATIC
//
// Pretty much copied straight from the article "XInput and DirectInput".
//
// Enum each PNP device using WMI and check each device ID. This is
// Microsoft's reference implementation, but it is damn slow. After
// a hardware change, connecting to the WMI server can take nearly three
// seconds, and creating the instance enumerator consistantly takes longer
// than 0.25 seconds.
//
// The XInput DLL can apparently be hacked fairly simply to work with
// Windows 2000, and since Raw Input wasn't introduced until XP, I think
// that's reason enough to keep this version around, despite it being
// so horrendously slow.
//
//===========================================================================

bool FDInputJoystickManager::IsXInputDeviceSlow(const GUID *guid)
{
	IWbemLocator *wbemlocator = NULL;
	IEnumWbemClassObject *enumdevices = NULL;
	IWbemClassObject *devices[20] = { 0 };
	IWbemServices *wbemservices = NULL;
	BSTR namespce = NULL;
	BSTR deviceid = NULL;
	BSTR classname = NULL;
	DWORD returned = 0;
	bool isxinput = false;
	UINT device = 0;
	VARIANT var;
	HRESULT hr;

	// Create WMI
	hr = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&wbemlocator);
	if (FAILED(hr) || wbemlocator == NULL)
		goto cleanup;

	if (NULL == (namespce = SysAllocString(OLESTR("\\\\.\\root\\cimv2")))) goto cleanup;
	if (NULL == (classname = SysAllocString(OLESTR("Win32_PNPEntity")))) goto cleanup;
	if (NULL == (deviceid = SysAllocString(OLESTR("DeviceID")))) goto cleanup;

	// Connect to WMI
	hr = wbemlocator->ConnectServer(namespce, NULL, NULL, 0, 0, NULL, NULL, &wbemservices);
	if (FAILED(hr) || wbemservices == NULL)
		goto cleanup;

	// Switch security level to IMPERSONATE.
	CoSetProxyBlanket(wbemservices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
		RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);

	hr = wbemservices->CreateInstanceEnum(classname, 0, NULL, &enumdevices);
	if (FAILED(hr) || enumdevices == NULL)
		goto cleanup;

	// Loop over all devices
	for (;;)
	{
		// Get 20 at a time.
		hr = enumdevices->Next(10000, countof(devices), devices, &returned);
		if (FAILED(hr))
			goto cleanup;
		if (returned == 0)
			break;

		for (device = 0; device < returned; device++)
		{
			// For each device, get its device ID.
			hr = devices[device]->Get(deviceid, 0L, &var, NULL, NULL);
			if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL)
			{
				// Check if the device ID contains "IG_". If it does, then it's an XInput device.
				// This information cannot be found from DirectInput.
				if (wcsstr(var.bstrVal, L"IG_"))
				{
					// If it does, then get the VID/PID from var.bstrVal.
					DWORD pid = 0, vid = 0;
					WCHAR *strvid = wcsstr(var.bstrVal, L"VID_");
					if (strvid && swscanf(strvid, L"VID_%4X", &vid) != 1)
						vid = 0;
					WCHAR *strpid = wcsstr(var.bstrVal, L"PID_");
					if (strpid && swscanf(strpid, L"PID_%4X", &pid) != 1)
						pid = 0;

					// Compare the VID/PID to the DInput device.
					DWORD vidpid = MAKELONG(vid, pid);
					if (vidpid == guid->Data1)
					{
						isxinput = true;
						goto cleanup;
					}
				}
			}
			SAFE_RELEASE(devices[device]);
		}
	}

cleanup:
	if (namespce)	SysFreeString(namespce);
	if (deviceid)	SysFreeString(deviceid);
	if (classname)	SysFreeString(classname);
	for (device = 0; device < countof(devices); ++device)
		SAFE_RELEASE(devices[device]);
	SAFE_RELEASE(enumdevices);
	SAFE_RELEASE(wbemlocator);
	SAFE_RELEASE(wbemservices);
	return isxinput;
}

//===========================================================================
//
// FDInputJoystickManager :: IsXInputDeviceFast						STATIC
//
// The theory of operation is the same as for IsXInputDeviceSlow, except that
// we use the Raw Input device list to find the device ID instead of WMI.
// This generally takes ~0.02 ms on my machine, though it occasionally spikes
// at over 1 ms. Either way, it is a huge order of magnitude faster than WMI.
// (around 10,000 times faster, in fact!)
//
//===========================================================================

bool FDInputJoystickManager::IsXInputDeviceFast(const GUID *guid)
{
	UINT nDevices, numDevices;
	RAWINPUTDEVICELIST *devices;
	UINT i;
	bool isxinput = false;

	if (MyGetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0)
	{
		return false;
	}
	if ((devices = (RAWINPUTDEVICELIST *)malloc(sizeof(RAWINPUTDEVICELIST) * nDevices)) == NULL)
	{
		return false;
	}
	if ((numDevices = MyGetRawInputDeviceList(devices, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT)-1)
	{
		free(devices);
		return false;
	}
	for (i = 0; i < numDevices; ++i)
	{
		// I am making the assumption here that all possible XInput devices
		// will report themselves as generic HID devices and not as keyboards
		// or mice.
		if (devices[i].dwType == RIM_TYPEHID)
		{
			RID_DEVICE_INFO rdi;
			UINT cbSize;

			cbSize = rdi.cbSize = sizeof(rdi);
			if ((INT)MyGetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) >= 0)
			{
				if(MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == (LONG)guid->Data1)
				{
					char name[256];
					UINT namelen = countof(name);
					UINT reslen;

					reslen = MyGetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, name, &namelen);
					if (reslen != (UINT)-1)
					{
						isxinput = (strstr(name, "IG_") != NULL);
						break;
					}
				}
			}
		}
	}
	free(devices);
	return isxinput;
}

//===========================================================================
//
// FDInputJoystickManager :: NameSort								STATIC
//
//===========================================================================

int FDInputJoystickManager::NameSort(const void *a, const void *b)
{
	const Enumerator *ea = (const Enumerator *)a;
	const Enumerator *eb = (const Enumerator *)b;
	int lex = ea->Name.Compare(eb->Name);
	if (lex == 0)
	{
		return memcmp(&ea->Instance, &eb->Instance, sizeof(GUID));
	}
	return lex;
}

//===========================================================================
//
// FDInputJoystickManager :: EnumDevices
//
// Find out what DirectInput game controllers are on the system and create
// FDInputJoystick objects for them. May return a pointer to the first new
// device found.
//
//===========================================================================

FDInputJoystick *FDInputJoystickManager::EnumDevices()
{
	FDInputJoystick *newone = NULL;
	TArray<Enumerator> controllers;
	EnumData data;
	unsigned i, j, k;

	// Find all game controllers
	data.All = &controllers;
	data.GenericDevices = false;
	g_pdi->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &data, DIEDFL_ALLDEVICES);
	// Again, just for the Strategic Commander
	data.GenericDevices = true;
	g_pdi->EnumDevices(DI8DEVCLASS_DEVICE, EnumCallback, &data, DIEDFL_ALLDEVICES);

	// Sort by name so that devices with duplicate names can have numbers appended.
	qsort(&controllers[0], controllers.Size(), sizeof(Enumerator), NameSort);
	for (i = 1; i < controllers.Size(); ++i)
	{
		// Does this device have the same name as the previous one? If so, how
		// many more have the same name?
		for (j = i; j < controllers.Size(); ++j)
		{
			if (controllers[j-1].Name.Compare(controllers[j].Name) != 0)
				break;
		}
		// j is one past the last duplicate name.
		if (j > i)
		{
			// Append numbers.
			for (k = i - 1; k < j; ++k)
			{
				controllers[k].Name.AppendFormat(" #%d", k - i + 2);
			}
		}
	}

	// Compare the new list of devices with the one we previously instantiated.
	// Every device we currently hold is marked 0. Then scan through the new
	// list and try to find it there, if it's found, it is marked 1. At the end
	// of this, devices marked 1 existed before and are left alone. Devices
	// marked 0 are no longer present and should be destroyed. If a device is
	// present in the new list that we have not yet instantiated, we 
	// instantiate it now.
	for (j = 0; j < Devices.Size(); ++j)
	{
		Devices[j]->Marked = false;
	}
	for (i = 0; i < controllers.Size(); ++i)
	{
		GUID *lookfor = &controllers[i].Instance;
		for (j = 0; j < Devices.Size(); ++j)
		{
			if (Devices[j]->Instance == *lookfor)
			{
				Devices[j]->Marked = true;
				break;
			}
		}
		if (j == Devices.Size())
		{ // Not found. Add it.
			FDInputJoystick *device = new FDInputJoystick(lookfor, controllers[i].Name);
			if (!device->GetDevice())
			{
				delete device;
			}
			else
			{
				device->Marked = true;
				Devices.Push(device);
				if (newone == NULL)
				{
					newone = device;
				}
			}
		}
	}
	// Remove detached devices and avoid holes in the list.
	for (i = j = 0; j < Devices.Size(); ++j)
	{
		if (!Devices[j]->Marked)
		{
			delete Devices[j];
		}
		else
		{
			if (i != j)
			{
				Devices[i] = Devices[j];
			}
			++i;
		}
	}
	Devices.Resize(i);
	return newone;
}

//===========================================================================
//
// FDInputJoystickManager :: Rescan
//
// Used by the joy_ps2raw and joy_xinput callbacks to rescan the
// DirectInput devices, since their presence or absence can affect the
// results of the scan.
//
//===========================================================================

IJoystickConfig *FDInputJoystickManager::Rescan()
{
	return EnumDevices();
}

//===========================================================================
//
// I_StartupDirectInputJoystick
//
//===========================================================================

void I_StartupDirectInputJoystick()
{
	if (!joy_dinput || !use_joystick || Args->CheckParm("-nojoy"))
	{
		if (JoyDevices[INPUT_DIJoy] != NULL)
		{
			delete JoyDevices[INPUT_DIJoy];
			JoyDevices[INPUT_DIJoy] = NULL;
			UpdateJoystickMenu(NULL);
		}
	}
	else
	{
		if (JoyDevices[INPUT_DIJoy] == NULL)
		{
			FJoystickCollection *joys = new FDInputJoystickManager;
			if (joys->GetDevice())
			{
				JoyDevices[INPUT_DIJoy] = joys;
			}
		}
	}
}
