//maccontrols.cpp
//mac-specific input code

#include <math.h>
#include <string.h>
#include <HID_Utilities_External.h>
#include <IOKit/hid/IOHIDUsageTables.h>

//needed for ForceFeedback.h
//-----------------------------------------------------------------
typedef OSStatus 		HRESULT;
typedef UInt32 			IOByteCount;
typedef unsigned int	io_service_t;
typedef unsigned int	io_object_t;
#define S_OK		    ((HRESULT)0x00000000L)
//-----------------------------------------------------------------
#include <ForceFeedback/ForceFeedback.h>
extern "C"{
#include "ImmrHIDUtilAddOn.h"
}
#include "controls.h"
#include "config.h"
#include "error.h"
#include "gameframe.h"
#include "gamesystem.h"
#include "gametime.h"

typedef struct{
	pRecDevice device;
	int numElements;
	pRecElement *elements;
} tHIDController;

tHIDController *gControllers;

int gInputHID=false;
int gNumControllers=0;

int gIShockIIFFB=false;
//iS2F_DeviceRef_t giShockList[iS2F_MAX_ISHOCK2_NUM];
int gIShockIIFFBBlock=0;

int gFFB=false;
FFDeviceObjectReference gFFDeviceRef;
FFEffectObjectReference	gGroundRumbleEffectRef = NULL;
FFEffectObjectReference	gJoltEffectRef = NULL;
FFEffectObjectReference	gFrictionEffectRef = NULL;
FFEffectObjectReference	gAlignEffectRef = NULL;
FFEffectObjectReference	gEngineEffectRef = NULL;

DWORD dwAxes[2] = {FFJOFS_X,FFJOFS_Y};
LONG lDirection[2] = {0,0};

extern int gAxisSteering;

//returns true if the key with the key code k is pressed, false otherwise
short IsPressed(unsigned short k )
{
	if(k<0||k>255)
		return false;
		
	KeyMapByteArray km;

	GetKeys( *((KeyMap*)&km));
	return ( ( km[k>>3] >> (k & 7) ) & 1);
}


//Initialize controls
void ControlInit()
{
	if(HIDBuildDeviceList(NULL,NULL))
	{
		gNumControllers=0;
		pRecDevice device=HIDGetFirstDevice();
		do{
			//if(!(device->usagePage==kHIDPage_Unknown||(device->usagePage==kHIDPage_GenericDesktop&&(device->usage==kHIDUsage_GD_Mouse||device->usage==kHIDUsage_GD_Keyboard))))
			if(device->usagePage==kHIDPage_GenericDesktop&&(device->usage==kHIDUsage_GD_Joystick||device->usage==kHIDUsage_GD_GamePad))//||kHIDUsage_GD_Mouse))
				gNumControllers++;
			device=HIDGetNextDevice(device);
		}while(device);
		
		gControllers=(tHIDController*)NewPtr(sizeof(tHIDController)*gNumControllers);
		
		gNumControllers=0;
		device=HIDGetFirstDevice();
		do{
//			if(!(device->usagePage==kHIDPage_Unknown||(device->usagePage==kHIDPage_GenericDesktop&&(device->usage==kHIDUsage_GD_Mouse||device->usage==kHIDUsage_GD_Keyboard))))
			if(device->usagePage==kHIDPage_GenericDesktop&&(device->usage==kHIDUsage_GD_Joystick||device->usage==kHIDUsage_GD_GamePad))//||kHIDUsage_GD_Mouse))
			{
				io_service_t hidDeviceObject=AllocateHIDObjectFromRecDevice(device);
				if(hidDeviceObject)
					if(FFIsForceFeedback(hidDeviceObject)==FF_OK)
						if(FFCreateDevice(hidDeviceObject,&gFFDeviceRef)==FF_OK)
							gFFB=true;	
				
				gControllers[gNumControllers].device=device;
				gControllers[gNumControllers].numElements=HIDCountDeviceElements(gControllers[gNumControllers].device,kHIDElementTypeInput);
				gControllers[gNumControllers].elements=(pRecElement*)NewPtr(sizeof(pRecElement)*gControllers[gNumControllers].numElements);
				gControllers[gNumControllers].elements[0]=HIDGetFirstDeviceElement(gControllers[gNumControllers].device,kHIDElementTypeInput);
				for(int j=1;j<gControllers[gNumControllers].numElements;j++)
					gControllers[gNumControllers].elements[j]=HIDGetNextDeviceElement(gControllers[gNumControllers].elements[j-1],kHIDElementTypeInput);
				gInputHID=true;
				gNumControllers++;
			}
			device=HIDGetNextDevice(device);
		}while(device);
	}
}


//Wrapper functions for the iShock II SDK.
void FFBJolt(float lMag,float rMag,float duration)
{
#ifndef __TARGET_TOOLAPP
/*	if(gIShockIIFFB&&gConfig->iShockFFB)
	{
		iS2F_JoltCmd_t joltCmd;
		joltCmd.motorCmd.leftMotorMagnitude=lMag*10;
		joltCmd.motorCmd.rightMotorMagnitude=rMag*10;
		joltCmd.duration=duration*1000;
		iS2F_SimpleJolt(giShockList[0],&joltCmd);
		gIShockIIFFBBlock=gFrameCount+duration*kFPS;
	}*/
	if(gFFB&&gAxisSteering&&gConfig->ffb)
	{
		float mag=(rMag+lMag)*0.5;
		float offset=rMag-lMag;
		
		HRESULT			hr;
		FFEFFECT		ffeffect;
		FFPERIODIC		ffperiodic;
		FFENVELOPE		ffenvelope;

		ffenvelope.dwSize = sizeof(ffenvelope);
	    ffenvelope.dwAttackLevel = 0;
	    ffenvelope.dwAttackTime = 0;             /* Microseconds         */
	    ffenvelope.dwFadeLevel = 0;
	    ffenvelope.dwFadeTime = duration*FF_SECONDS;               /* Microseconds         */
		
	    ffperiodic.dwMagnitude = FF_FFNOMINALMAX*gConfig->ffbIntensity*mag;
	    ffperiodic.lOffset = FF_FFNOMINALMAX*gConfig->ffbIntensity*offset;
	    ffperiodic.dwPhase = 0;
	    ffperiodic.dwPeriod = 0.1*FF_SECONDS; 					// 10 Hz
		
		ffeffect.dwSize = sizeof(ffeffect);			/* sizeof(FFEFFECT)     */
	    ffeffect.dwFlags = FFEFF_CARTESIAN;				/* FFEFF_*              */
	    ffeffect.dwDuration = duration*FF_SECONDS;					/* Microseconds         */
	    ffeffect.dwSamplePeriod = 1000;					/* Microseconds         */
	    ffeffect.dwGain = FF_FFNOMINALMAX;
	    ffeffect.dwTriggerButton = FFEB_NOTRIGGER;		/* or FFEB_NOTRIGGER    */
	    ffeffect.dwTriggerRepeatInterval = 0;			/* Microseconds         */
	    ffeffect.cAxes = 1;								/* Number of axes       */
	    ffeffect.rgdwAxes = dwAxes;						/* Array of axes        */
	    ffeffect.rglDirection = lDirection;				/* Array of directions  */
	    ffeffect.lpEnvelope = &ffenvelope;				/* Optional             */
	    ffeffect.cbTypeSpecificParams = sizeof(ffperiodic);     	/* Size of params       */
	    ffeffect.lpvTypeSpecificParams = &ffperiodic;    			/* Pointer to params    */
	    ffeffect.dwStartDelay = 0;            						/* Microseconds         */
		
		if(gJoltEffectRef==NULL)
		{
			FFDeviceCreateEffect(gFFDeviceRef, kFFEffectType_Square_ID, &ffeffect, &gJoltEffectRef);
			FFEffectDownload(gJoltEffectRef);
			FFEffectStart(gJoltEffectRef, 1, 0);
		}
		else
		{
			FFEffectSetParameters(gJoltEffectRef,&ffeffect,FFEP_GAIN);
			FFEffectStart(gJoltEffectRef, 1, 0);		
		}
	}
#endif
}


void FFBSetGoundRumble(float velo, float rumble)
{
	if(gFFB&&gConfig->ffb)
	{
		if(!gAxisSteering)
			rumble=0;
			
		velo/=60;
		if(velo>1)velo=1;
		
		HRESULT			hr;
		FFEFFECT		ffeffect;
		FFPERIODIC		ffperiodic;
		
	    ffperiodic.dwMagnitude = FF_FFNOMINALMAX*gConfig->ffbIntensity*rumble*velo*0.5;
	    ffperiodic.lOffset = 0;
	    ffperiodic.dwPhase = 0;
		if(velo)
			ffperiodic.dwPeriod = FF_SECONDS*0.8/velo; 					
		else
			ffperiodic.dwPeriod = 0; 					
		
		ffeffect.dwSize = sizeof(ffeffect);			/* sizeof(FFEFFECT)     */
	    ffeffect.dwFlags = FFEFF_CARTESIAN;				/* FFEFF_*              */
	    ffeffect.dwDuration = FF_INFINITE;					/* Microseconds         */
	    ffeffect.dwSamplePeriod = 1000;					/* Microseconds         */
	    ffeffect.dwGain = FF_FFNOMINALMAX;
	    ffeffect.dwTriggerButton = FFEB_NOTRIGGER;		/* or FFEB_NOTRIGGER    */
	    ffeffect.dwTriggerRepeatInterval = 0;			/* Microseconds         */
	    ffeffect.cAxes = 1;								/* Number of axes       */
	    ffeffect.rgdwAxes = dwAxes;						/* Array of axes        */
	    ffeffect.rglDirection = lDirection;				/* Array of directions  */
	    ffeffect.lpEnvelope = NULL;				/* Optional             */
	    ffeffect.cbTypeSpecificParams = sizeof(ffperiodic);     	/* Size of params       */
	    ffeffect.lpvTypeSpecificParams = &ffperiodic;    			/* Pointer to params    */
	    ffeffect.dwStartDelay = 0;            						/* Microseconds         */

		if(gGroundRumbleEffectRef==NULL)
		{
			FFDeviceCreateEffect(gFFDeviceRef, kFFEffectType_Sine_ID, &ffeffect, &gGroundRumbleEffectRef);
			FFEffectDownload(gGroundRumbleEffectRef);
			FFEffectStart(gGroundRumbleEffectRef, 1, 0);
		}
		else
		{
			FFEffectSetParameters(gGroundRumbleEffectRef,&ffeffect,FFEP_TYPESPECIFICPARAMS+FFEP_GAIN);
			FFEffectStatusFlag pFlags;  
			FFEffectGetEffectStatus(gGroundRumbleEffectRef,&pFlags);
			if(!pFlags&FFEGES_PLAYING)
				FFEffectStart(gGroundRumbleEffectRef, 1, 0);		
		}
	}
}

void FFBSetSteerResistance(float res,float alignment)
{
	if(gFFB&&gConfig->ffb)
	{
		if(!gAxisSteering)
		{
			res=0;
			alignment=0;
		}
		
		HRESULT			hr;
		FFEFFECT		ffeffect;
		FFCONDITION		ffcondition;
		
		ffcondition.lOffset=0;
		ffcondition.lPositiveCoefficient=FF_FFNOMINALMAX*gConfig->ffbIntensity*res;
		ffcondition.lNegativeCoefficient=FF_FFNOMINALMAX*gConfig->ffbIntensity*res;
		ffcondition.dwPositiveSaturation=FF_FFNOMINALMAX*gConfig->ffbIntensity*res;
		ffcondition.dwNegativeSaturation=FF_FFNOMINALMAX*gConfig->ffbIntensity*res;
		ffcondition.lDeadBand=0;
		
		ffeffect.dwSize = sizeof(ffeffect);				/* sizeof(FFEFFECT)     */
	    ffeffect.dwFlags = FFEFF_CARTESIAN;				/* FFEFF_*              */
	    ffeffect.dwDuration = FF_INFINITE;			/* Microseconds         */
	    ffeffect.dwSamplePeriod = 1000;					/* Microseconds         */
	    ffeffect.dwGain = FF_FFNOMINALMAX;
	    ffeffect.dwTriggerButton = FFEB_NOTRIGGER;		/* or FFEB_NOTRIGGER    */
	    ffeffect.dwTriggerRepeatInterval = 0;			/* Microseconds         */
	    ffeffect.cAxes = 1;								/* Number of axes       */
	    ffeffect.rgdwAxes = dwAxes;						/* Array of axes        */
	    ffeffect.rglDirection = lDirection;				/* Array of directions  */
	    ffeffect.lpEnvelope = NULL;				/* Optional             */
	    ffeffect.cbTypeSpecificParams = sizeof(ffcondition);     	/* Size of params       */
	    ffeffect.lpvTypeSpecificParams = &ffcondition;    			/* Pointer to params    */
	    ffeffect.dwStartDelay = 0;            						/* Microseconds         */

		if(gFrictionEffectRef==NULL)
		{
			FFDeviceCreateEffect(gFFDeviceRef, kFFEffectType_Friction_ID, &ffeffect, &gFrictionEffectRef);
			FFEffectDownload(gFrictionEffectRef);
			FFEffectStart(gFrictionEffectRef, 1, 0);
		}
		else
		{
			FFEffectSetParameters(gFrictionEffectRef,&ffeffect,FFEP_TYPESPECIFICPARAMS+FFEP_GAIN);
			FFEffectStatusFlag pFlags;  
			FFEffectGetEffectStatus(gFrictionEffectRef,&pFlags);
			if(!pFlags&FFEGES_PLAYING)
				FFEffectStart(gFrictionEffectRef, 1, 0);		
		}
		
		
		ffcondition.lOffset=0;
		ffcondition.lPositiveCoefficient=FF_FFNOMINALMAX*gConfig->ffbIntensity*alignment;
		ffcondition.lNegativeCoefficient=FF_FFNOMINALMAX*gConfig->ffbIntensity*alignment;
		ffcondition.dwPositiveSaturation=FF_FFNOMINALMAX*gConfig->ffbIntensity*alignment;
		ffcondition.dwNegativeSaturation=FF_FFNOMINALMAX*gConfig->ffbIntensity*alignment;
		ffcondition.lDeadBand=0;

		if(gAlignEffectRef==NULL)
		{
			FFDeviceCreateEffect(gFFDeviceRef, kFFEffectType_Spring_ID, &ffeffect, &gAlignEffectRef);
			FFEffectDownload(gAlignEffectRef);
			FFEffectStart(gAlignEffectRef, 1, 0);
		}
		else
		{
			FFEffectSetParameters(gAlignEffectRef,&ffeffect,FFEP_TYPESPECIFICPARAMS+FFEP_GAIN);
			FFEffectStatusFlag pFlags;  
			FFEffectGetEffectStatus(gAlignEffectRef,&pFlags);
			if(!pFlags&FFEGES_PLAYING)
				FFEffectStart(gAlignEffectRef, 1, 0);		
		}
	}
}


void FFBiShockDirect(float lMag,float rMag)
{
/*	if(gIShockIIFFB&&gConfig->iShockFFB&&gFrameCount>gIShockIIFFBBlock)
	{
		iS2F_MotorCmd_t directCmd;
		directCmd.leftMotorMagnitude=lMag*10;
		directCmd.rightMotorMagnitude=rMag*10;
		iS2F_SimpleDirectControl(giShockList[0],&directCmd);
	}*/
}

void FFBStop()
{
/*	if(gIShockIIFFB&&gConfig->iShockFFB)
	{
		iS2F_MotorCmd_t directCmd;
		directCmd.leftMotorMagnitude=0;
		directCmd.rightMotorMagnitude=0;
		iS2F_SimpleDirectControl(giShockList[0],&directCmd);
	}*/
	if(gFFB&&gConfig->ffb)
	{
		FFEffectStop(gGroundRumbleEffectRef);
		FFEffectStop(gJoltEffectRef);
		FFEffectStop(gFrictionEffectRef);
		FFEffectStop(gAlignEffectRef);
		FFEffectStop(gEngineEffectRef);
		
		gGroundRumbleEffectRef = NULL;	
		gJoltEffectRef = NULL;
		gFrictionEffectRef = NULL;
		gAlignEffectRef = NULL;
		gEngineEffectRef = NULL;
	}
}

//Dispose*/ Control structures
void ControlExit()
{
/*	if(gInputHID)
	{
		HIDReleaseDeviceList();
		TearDownHIDCFM();
	}
/*	if(gIShockIIFFB)
	{
		FFBStop();
		iS2F_Final();
	}*/
}

void CalibrateAxis(int id)
{
	while(GetInterfaceKey(kInterfaceKeyReturn)||GetInterfaceKey(kInterfaceKeyEnter));
	int controller=-1;
	for(int i=0;i<gNumControllers;i++)
		if(gControllers[i].device->vendorID==gConfig->axis[id].axisControllerID1&&gControllers[i].device->productID==gConfig->axis[id].axisControllerID2)
			controller=i;
							
	int element=gConfig->axis[id].axisElementID;
	
	if(controller<gNumControllers&&controller>=0)
	{
		gConfig->axis[id].max=gConfig->axis[id].mid+1;
		gConfig->axis[id].min=gConfig->axis[id].mid-1;	
		while(!(GetInterfaceKey(kInterfaceKeyReturn)||GetInterfaceKey(kInterfaceKeyEnter)))
		{
			SInt32 value=HIDGetElementValue(gControllers[controller].device,gControllers[controller].elements[element]);
			if(value<gConfig->axis[id].min)gConfig->axis[id].min=value;
			if(value>gConfig->axis[id].max)gConfig->axis[id].max=value;
		}
	}
}


#define kAxisMaxZone 0.9

//read an analogue axis
float GetAxisInput(int id)
{	
	if(gConfig->axis[id].axisControllerID1==-1&&gConfig->axis[id].axisControllerID2==-1)
	{
		//mouse pos
		tVector2 p=GetMousePos();
		if(gConfig->axis[id].axisElementID==1)
			return p.x;
		if(gConfig->axis[id].axisElementID==2)
			return p.y;			
	}
	if(gConfig->axis[id].axisControllerID1&&gInputHID){
		int controller=-1;
		for(int i=0;i<gNumControllers;i++)
			if(gControllers[i].device->vendorID==gConfig->axis[id].axisControllerID1&&gControllers[i].device->productID==gConfig->axis[id].axisControllerID2)
				controller=i;
								
		int element=gConfig->axis[id].axisElementID;
		
		if(controller<gNumControllers&&controller>=0)
			if(element<gControllers[controller].numElements&&element>=0)
			{
				SInt32 value=HIDGetElementValue(gControllers[controller].device,gControllers[controller].elements[element]);
				int min=gConfig->axis[id].mid-(gConfig->axis[id].mid-gConfig->axis[id].min)*kAxisMaxZone;
				int max=gConfig->axis[id].mid-(gConfig->axis[id].mid-gConfig->axis[id].max)*kAxisMaxZone;
				float ax;
				if(id>=kInputThrottleAxis)
				{
					if(value<(gConfig->axis[id].max-gConfig->axis[id].min)*gConfig->axis[id].deadzone*0.5)
						ax=0;
					else 
						ax=(value-(gConfig->axis[id].max-gConfig->axis[id].min)*gConfig->axis[id].deadzone*0.5)/((gConfig->axis[id].max-min)*(1-0.5*gConfig->axis[id].deadzone));
				}
				else
				{
					if(fabs(gConfig->axis[id].mid-value)<(gConfig->axis[id].max-gConfig->axis[id].min)*gConfig->axis[id].deadzone)
						ax=0;
					else if(value>gConfig->axis[id].mid)
						ax=(value-(gConfig->axis[id].mid+(gConfig->axis[id].max-gConfig->axis[id].min)*gConfig->axis[id].deadzone))/((gConfig->axis[id].mid-max)*(1-0.5*gConfig->axis[id].deadzone));
					else
						ax=(value-(gConfig->axis[id].mid-(gConfig->axis[id].max-gConfig->axis[id].min)*gConfig->axis[id].deadzone))/((min-gConfig->axis[id].mid)*(1-0.5*gConfig->axis[id].deadzone));
				}
				if(ax>1)ax=1;
				else if(ax<-1)ax=-1;
				return ax;
			}
	}
	return 0;
}

//read a digital input
int GetButtonInput(int id)
{
	if(gInputChatMode)
		return false;
	//is the key pressed?
	int key=gSystemSuspended?false:IsPressed(gConfig->keys[id].keyID);
	//is the HID button pressed?
	if(gConfig->axis[id].axisControllerID1==-1&&gConfig->axis[id].axisControllerID2==-1)
		if(Button())
			key=true;
	if(gConfig->keys[id].controllerID1&&gInputHID)
	{
		int controller=-1;
		for(int i=0;i<gNumControllers;i++)
			if(gControllers[i].device->vendorID==gConfig->keys[id].controllerID1&&gControllers[i].device->productID==gConfig->keys[id].controllerID2)
				controller=i;
								
		int element=gConfig->keys[id].elementID;
		
		if(controller<gNumControllers&&controller>=0)
			if(element<gControllers[controller].numElements&&element>=0)
				return key||HIDGetElementValue(gControllers[controller].device,gControllers[controller].elements[element]);
	}
	return key;
}

int gInterfaceKeys[kInterfaceNumKeys]={0x00,0x7e,0x7d,0x7b,0x7c,0x31,0x24,0x4c,0x35,0x07,0x33,0x37,0x3a,0x01,0x0f,0x0c,0xff};
int GetInterfaceKey(int id)
{
	if(id==kInterfaceMouseDown)
		return gSystemSuspended?false:Button();
	else
		return gSystemSuspended?false:IsPressed(gInterfaceKeys[id]);
}

static void GetElementName (pRecDevice pDevice,pRecElement pElement, char *strElementName)
{
	if(!HIDGetElementNameFromVendorProductUsage (pDevice->vendorID, pDevice->productID, pElement->usagePage, pElement->usage,strElementName))
	{
		// set name from vendor id/product id look up
		HIDGetElementNameFromVendorProductCookie (pDevice->vendorID, pDevice->productID, (long) pElement->cookie, strElementName);
		if (!*strElementName) { // if no name
			char buffer[1024];
			HIDGetUsageName (pElement->usagePage, pElement->usage,buffer);
			if (!*buffer) // if not usage
				sprintf (strElementName, "Unknown");
			else
				sprintf(strElementName,"%s %s",pDevice->product,buffer);
		}
	}
}


//used to user-configure input. if any key or HID button is pressed,
//this changes the according ID and Identifier string.
void GetInput(tKeyConfig *keyConfig)
{
	//FlushEvents(keyDownMask,0);
	int key=-1;
	do{
		for(int i=0;i<128;i++)
			if(IsPressed(i))
				if(i!=0x35&&i!=0x33)
					key=i;
				else
				{
					if(i==0x33)
					{
						keyConfig->controllerID1=0;
						keyConfig->controllerID2=0;
						keyConfig->elementID=0;
						strcpy(keyConfig->controllerIdentifier,"");
						keyConfig->keyID=-1;
						strcpy(keyConfig->identifier,"<none>");
					}
					FlushEvents(keyDownMask|mDownMask,0);
					FlushKeys();
					return;
				}
		if(gInputHID)
			for(int j=0;j<gNumControllers;j++)
				for(int i=0;i<gControllers[j].numElements;i++)
					if(gControllers[j].elements[i])
						if(gControllers[j].elements[i]->type==kIOHIDElementTypeInput_Button)
							if(HIDGetElementValue(gControllers[j].device,gControllers[j].elements[i]))
							{
								keyConfig->controllerID1=gControllers[j].device->vendorID;
								keyConfig->controllerID2=gControllers[j].device->productID;
								keyConfig->elementID=i;
								char elementName[256];
								GetElementName(gControllers[j].device,gControllers[j].elements[i],elementName);
//								sprintf(keyConfig->controllerIdentifier,"%s: %s",gControllers[j].device->product,elementName);
								strcpy(keyConfig->controllerIdentifier,elementName);
								return;
							}
		if(Button()&&!StillDown())
		{
			keyConfig->controllerID1=-1;
			keyConfig->controllerID2=-1;
			keyConfig->elementID=1;
			strcpy(keyConfig->controllerIdentifier,"Mouse Button");
			FlushEvents(keyDownMask|mDownMask,0);
			FlushKeys();
			return;
		}				
	}while(key==-1);
	
	int clear;
	do{
		clear=true;
		for(int i=0;i<128;i++)
			if(IsPressed(i))
				clear=false;
	}while(!clear);

	EventRecord evt;
	Str255 str;
	GetIndString(str,128,key+1);
	int len=str[0];
	BlockMoveData(str+1,str,255);
	str[len]='\0';
	
	if(str[0])
		strcpy(keyConfig->identifier,(char*)str);
	else
	{
		strcpy(keyConfig->identifier,"unknown key");	
		while(WaitNextEvent(keyUpMask,&evt,1,nil))
		{
			keyConfig->identifier[0]=evt.message&charCodeMask;
			if(keyConfig->identifier[0]>='a'&&keyConfig->identifier[0]<='z')
				keyConfig->identifier[0]+='A'-'a';
			keyConfig->identifier[1]='\0';
		}
	}
	
	keyConfig->keyID=key;
	
	FlushEvents(keyDownMask|mDownMask,0);
	FlushKeys();
}


//used to user-configure input. if any HID axis is moved,
//this changes the according ID and Identifier string.
void GetAxis(tAxisConfig *axis,bool allowVendor)
{
	tVector2 basep=GetMousePos();
	float **values=(float**)NewPtr(sizeof(float)*gNumControllers);
	for(int j=0;j<gNumControllers;j++)
	{
		values[j]=(float*)NewPtr(sizeof(float)*gControllers[j].numElements);
		for(int i=0;i<gControllers[j].numElements;i++)
			if(gControllers[j].elements[i]->type!=kIOHIDElementTypeInput_Button)
				values[j][i]=HIDGetElementValue(gControllers[j].device,gControllers[j].elements[i]);
	}

	int key=-1;
	do{
		long maxDiff=0;
		for(int i=0;i<128;i++)
			if(IsPressed(i))
				key=i;
		if(key==0x33)
		{
			axis->axisControllerID1=0;
			axis->axisControllerID2=0;
			axis->axisElementID=0;
			strcpy(axis->axisIdentifier,"<none>");
		}
		for(int j=0;j<gNumControllers;j++)
			for(int i=0;i<gControllers[j].numElements;i++)
				if(gControllers[j].elements[i]->usagePage!=kHIDPage_VendorDefinedStart||allowVendor)
				if(gControllers[j].elements[i]->type!=kIOHIDElementTypeInput_Button)
					if(abs(values[j][i]-HIDGetElementValue(gControllers[j].device,gControllers[j].elements[i]))>0)
					{
						maxDiff=abs(values[j][i]-HIDGetElementValue(gControllers[j].device,gControllers[j].elements[i]));
						axis->axisControllerID1=gControllers[j].device->vendorID;
						axis->axisControllerID2=gControllers[j].device->productID;
						axis->axisElementID=i;
						axis->min=gControllers[j].elements[i]->min;
						axis->max=gControllers[j].elements[i]->max;
						axis->mid=(axis->min+axis->max)/2;
						char elementName[256];
						GetElementName(gControllers[j].device,gControllers[j].elements[i],elementName);
						//sprintf(axis->axisIdentifier,"%s: %s",gControllers[j].device->product,elementName);
						strcpy(axis->axisIdentifier,elementName);
						
					}
		if(maxDiff)
		{
			for(int j=0;j<gNumControllers;j++)
				DisposePtr((Ptr)(values[j]));
			DisposePtr((Ptr)values);
			FlushEvents(keyDownMask|mDownMask,0);
			FlushKeys();
			return;
		}
		tVector2 p=GetMousePos();
		if(~(p-basep)>0.1)
		{
			axis->axisControllerID1=-1;
			axis->axisControllerID2=-1;
			axis->axisElementID=fabs(p.x-basep.x)>fabs(p.y-basep.y)?1:2;
			sprintf(axis->axisIdentifier,"Mouse %c Axis",'W'+axis->axisElementID);

			for(int j=0;j<gNumControllers;j++)
				DisposePtr((Ptr)(values[j]));
			DisposePtr((Ptr)values);
			FlushEvents(keyDownMask|mDownMask,0);
			FlushKeys();
			return;
		}
	}while(key!=0x24&&key!=0x4c&&key!=0x35);

	FlushEvents(keyDownMask|mDownMask,0);
	FlushKeys();
	for(int j=0;j<gNumControllers;j++)
		DisposePtr((Ptr)(values[j]));
	DisposePtr((Ptr)values);
}

tVector2 GetMousePos()
{
	Point p;
	GetMouse(&p);
	return Vector(-(2.0*p.h/gConfig->screenXSize)+1,(2.0*p.v/gConfig->screenYSize)-1);
}