/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Please read the file LICENSE.TXT for additional details.


	source:
		win32 joystick

	revision history:
		Jul/20/2000 - Timo Saarinen - initial revision
*/
#define INITGUID
#include <windows.h>

#define DIRECTINPUT_VERSION 0x700
#include <dinput.h>

#pragma comment( lib, "dinput" )

#include <prcore/prcore.hpp>
using namespace prcore;



//////////////////////////////////////////////////////
// local                                           //
////////////////////////////////////////////////////

namespace
{

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

	LPDIRECTINPUT				pDirectInput = 0;
	Array<LPDIRECTINPUTDEVICE2>	devices;	
	Array<String>				deviceNames;

	// absolute maximums
	const int MAX_BUTTONS	= 64;
	const int MAX_AXES		= 16;

	// range
	const float JOY_RANGE	= 8192.0f;

	// data format for device data (note: must be dword multiple in size)
	typedef struct
	{
		LONG	axis[ MAX_AXES ];
		BYTE	button[ MAX_BUTTONS ];
	} DeviceData;

	
	//
	//	object to make sure DirectInput objects are released (after the application terminates)
	//
	class Uniniter
	{
	public:
		~Uniniter()
		{
			if( pDirectInput )
			{
				for( int n=0; n < devices.GetSize(); n++ )
				{
					RELEASE( devices[n] );
				}
				RELEASE( pDirectInput );
				pDirectInput = 0;
			}
		}
	} uniniter;


	//
	//	DirectInput device enumeration callback
	//
	BOOL FAR PASCAL EnumJoystick( LPCDIDEVICEINSTANCE i, LPVOID v )	
	{
		switch ( i->dwDevType )
		{
			case DIDEVTYPEJOYSTICK_TRADITIONAL:		// A traditional joystick.
			case DIDEVTYPEJOYSTICK_FLIGHTSTICK:		// A joystick optimized for flight simulation.
			case DIDEVTYPEJOYSTICK_GAMEPAD:			// A device whose primary purpose is to provide button input.
			case DIDEVTYPEJOYSTICK_RUDDER:			// A device for yaw control.
			case DIDEVTYPEJOYSTICK_WHEEL:			// A steering wheel.
			case DIDEVTYPEJOYSTICK_HEADTRACKER:		// A device that tracks the movement of the luser's head
			case DIDEVTYPEJOYSTICK_UNKNOWN:
			default:
				// do nothing for now..
				break;
		}

		LPDIRECTINPUTDEVICE device = 0;
		if( pDirectInput->CreateDevice( i->guidInstance, &device, NULL ) == DI_OK )
		{
			LPDIRECTINPUTDEVICE2 device2;
			if( device->QueryInterface( IID_IDirectInputDevice2, (LPVOID *)&device2 ) == DI_OK )
			{
				// device is ok, add to the list
				devices.PushBack( device2 );
				deviceNames.PushBack( i->tszInstanceName );	// TODO: or i->tszProductName?
			}
		}

		if( device ) RELEASE( device );

		return( DIENUM_CONTINUE );
	}


	//
	//	load and initialize DirectInput
	//
	bool InitDirectInput()
	{
		if( pDirectInput ) return true; // already initialized?

		// create DirectInput COM object
		HINSTANCE module = LoadLibrary( "DINPUT.DLL" );
		if( !module ) return false;
	
		typedef HRESULT (WINAPI *DIRECTINPUTCREATE)(HINSTANCE, DWORD, LPDIRECTINPUTA *, LPUNKNOWN);

		DIRECTINPUTCREATE create = (DIRECTINPUTCREATE) GetProcAddress( module, "DirectInputCreateA" );
		if( !create ) return false;

		HRESULT result = create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, &pDirectInput, NULL );
		if( result != DI_OK )
		{
			RELEASE( pDirectInput );
			pDirectInput = 0;
			return false;
		}

		// enumerate attached joysticks
		result = pDirectInput->EnumDevices( DIDEVTYPE_JOYSTICK, EnumJoystick, 0, DIEDFL_ATTACHEDONLY );

		// ok
		return true;
	}


	//
	//	build appropriate data format desc		
	//
	void BuildDataFormat( DIDATAFORMAT& df, int numAxes, int numButtons )
	{
		df.dwSize		= sizeof( DIDATAFORMAT );
		df.dwObjSize	= sizeof( DIOBJECTDATAFORMAT );
		df.dwFlags		= DIDF_ABSAXIS;
		df.dwDataSize	= sizeof( DeviceData );
	
		int numObjs = numAxes + numButtons;
		DIOBJECTDATAFORMAT* pObjectData = new DIOBJECTDATAFORMAT[ numObjs ];

		for( int n=0; n < numAxes; n++ )
		{
			DIOBJECTDATAFORMAT& f = pObjectData[n];
			f.pguid		= NULL;
			f.dwOfs		= FIELD_OFFSET( DeviceData, axis ) + n*sizeof(LONG);
			f.dwType	= DIDFT_AXIS | DIDFT_ANYINSTANCE;
//			f.dwType	= DIDFT_AXIS | DIDFT_MAKEINSTANCE(n);	// TODO: or DIDFT_ABSAXIS
			f.dwFlags	= 0;
		}

		for( n=0; n < numButtons; n++ )
		{
			DIOBJECTDATAFORMAT& f = pObjectData[numAxes + n];
			f.pguid		= &GUID_Button;
			f.dwOfs		= FIELD_OFFSET( DeviceData, button ) + n;
			f.dwType	= DIDFT_BUTTON | DIDFT_ANYINSTANCE;
//			f.dwType	= DIDFT_BUTTON | DIDFT_MAKEINSTANCE(n);
			f.dwFlags	= 0;
		}

		df.dwNumObjs	= numObjs;
		df.rgodf		= pObjectData;
	}

	void ReleaseDataFormat( DIDATAFORMAT& df )
	{
		delete[] df.rgodf;
	}

} // namespace


//////////////////////////////////////////////////////
// JoystickRep                                     //
////////////////////////////////////////////////////

class prcore::JoystickRep
{
public:
	LPDIRECTINPUTDEVICE2	mpDevice;
	DIDEVCAPS				mCaps;
	int						mNumAxes;
	int						mNumButtons;
	int						mNumPOVs;		// # of Point-of-View controllers on the device
	DeviceData				mData;

public:
	JoystickRep( HWND hwnd, LPDIRECTINPUTDEVICE2 pDevice );
	~JoystickRep();

	void Read();
};


JoystickRep::JoystickRep( HWND hwnd, LPDIRECTINPUTDEVICE2 pDevice )
{
	mpDevice = pDevice;

	// get joystick caps
	mCaps.dwSize = sizeof( DIDEVCAPS );
	mpDevice->GetCapabilities( &mCaps );

	mNumAxes		= mCaps.dwAxes;
	mNumButtons		= mCaps.dwButtons;
	mNumPOVs		= mCaps.dwPOVs;
	
	// set data format
	DIDATAFORMAT df;
	BuildDataFormat( df, mNumAxes, mNumButtons );

	if( mpDevice->SetDataFormat( &df ) != DI_OK ) 
		PRCORE_EXCEPTION( "DirectInputDevice::SetDataFormat()", "failed." );

	ReleaseDataFormat( df );


//	if( mpDevice->SetDataFormat( &c_dfDIJoystick ) != DI_OK ) 
//		throw Exception("DirectInputDevice::SetDataFormat() failed",__FILE__);



	// set non-exclusive cooperative mode (TODO: force-feedback requires exclusive)
	mpDevice->SetCooperativeLevel( hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND );


	//
	//	set ranges
	//

	DIPROPRANGE diprg;
	diprg.diph.dwSize = sizeof( diprg );
	diprg.diph.dwHeaderSize = sizeof( diprg.diph );
	diprg.diph.dwHow = DIPH_BYOFFSET;
	diprg.lMin = -int(JOY_RANGE);
	diprg.lMax = +int(JOY_RANGE);

	diprg.diph.dwObj = DIJOFS_X;
	mpDevice->SetProperty( DIPROP_RANGE, &diprg.diph );
	
	diprg.diph.dwObj = DIJOFS_Y;
	mpDevice->SetProperty( DIPROP_RANGE, &diprg.diph );

	diprg.diph.dwObj = DIJOFS_Z;
	mpDevice->SetProperty( DIPROP_RANGE, &diprg.diph );

	diprg.diph.dwObj = DIJOFS_RX;
	mpDevice->SetProperty( DIPROP_RANGE, &diprg.diph );

	diprg.diph.dwObj = DIJOFS_RY;
	mpDevice->SetProperty( DIPROP_RANGE, &diprg.diph );

	diprg.diph.dwObj = DIJOFS_RZ;
	mpDevice->SetProperty( DIPROP_RANGE, &diprg.diph );

	// x/y/z axis dead zone 10%
	DIPROPDWORD dipdw;
	dipdw.diph.dwSize = sizeof( dipdw );
	dipdw.diph.dwHeaderSize = sizeof( dipdw.diph );
	dipdw.diph.dwObj = DIJOFS_X;
	dipdw.diph.dwHow = DIPH_BYOFFSET;
	dipdw.dwData = 1000;
	mpDevice->SetProperty( DIPROP_DEADZONE, &dipdw.diph );

	dipdw.diph.dwObj = DIJOFS_Y;
	mpDevice->SetProperty( DIPROP_DEADZONE, &dipdw.diph );

	dipdw.diph.dwObj = DIJOFS_Z;
	mpDevice->SetProperty( DIPROP_DEADZONE, &dipdw.diph );

	dipdw.diph.dwObj = DIJOFS_RX;
	mpDevice->SetProperty( DIPROP_DEADZONE, &dipdw.diph );

	dipdw.diph.dwObj = DIJOFS_RY;
	mpDevice->SetProperty( DIPROP_DEADZONE, &dipdw.diph );
	
	dipdw.diph.dwObj = DIJOFS_RZ;
	mpDevice->SetProperty( DIPROP_DEADZONE, &dipdw.diph );


	//
	//	and now can acquire the device..
	//

	if( mpDevice->Acquire() != DI_OK ) 
		PRCORE_EXCEPTION( "JoystickRep::JoystickRep()", "can't acquire joystick." );
}


JoystickRep::~JoystickRep()
{
}


void JoystickRep::Read()
{
	mpDevice->Poll();

	if( mpDevice->GetDeviceState( sizeof(mData), &mData ) == (DIERR_INPUTLOST | DIERR_NOTACQUIRED) )
	{
		mpDevice->Acquire();
		mpDevice->GetDeviceState( sizeof(mData), &mData );
	}
}


//////////////////////////////////////////////////////
// Joystick                                        //
////////////////////////////////////////////////////

Joystick::Joystick( WinHandle win, int index ) 
: mpRep(0)
{
	InitDirectInput();
	if( index >= devices.GetSize() ) 
		PRCORE_EXCEPTION( "Joystick::Joystick()", "index out of range." );

	mpRep = new JoystickRep( win.hwnd, devices[index] );
}


Joystick::~Joystick()
{
	if( mpRep )
	{
		delete mpRep;
		mpRep = 0;
	}
}


int Joystick::GetAxisCount() const
{
	return mpRep->mNumAxes;
}


int Joystick::GetButtonCount() const
{
	return mpRep->mNumButtons;
}


float Joystick::GetAxis(int index)
{
	if( index < mpRep->mNumAxes )
	{
		mpRep->Read();
		return (mpRep->mData.axis[ index ] / JOY_RANGE);
	} else return 0.f;
}


bool Joystick::GetButton(int index)
{
	if( index < mpRep->mNumButtons )
	{
		mpRep->Read();
		return (mpRep->mData.button[ index ] != 0);
	} else return false;
}


int	Joystick::GetJoystickCount()
{
	return devices.GetSize();
}


const char* Joystick::GetJoystickName(int index)
{
	if( (index < 0) || (index >= deviceNames.GetSize()) )
		PRCORE_EXCEPTION( "Joystick::GetJoystickName()", "index out of range." );

	return *deviceNames[index];
}
