#include "mungal4.h"
#pragma hdrstop

#include "l4ctrl.h"
#include "l4keybd.h"
#include "l4app.h"
#include "l4dinput.h"
#include "..\munga\appmgr.h"
#include "dxutils.h"

// #define LOCAL_TEST

#if defined(LOCAL_TEST)
#	define Test_Tell(n) Tell(n << std::flush);
#else
#	define Test_Tell(n)
#endif

static const char *initFileName  = "JOYSTICK.INI";
#define RIO_SPOOL_SIZE 12000

//###########################################################################
//########################### Button type table #############################
//###########################################################################
#define DARKBTN (char) 0
#define AUTOBTN (char) 1
#define MAPBTN  (char) 2

#define	DI_TRIGGER	0
#define DI_THUMB1	2
#define	DI_THUMB2	1
#define DI_THUMB3	3
#define DI_THROTTLE	10

static char
	buttonType[LBE4ControlsManager::ButtonCount] =
{
	AUTOBTN,	// ButtonAuxLowerLeft8=0x00
	AUTOBTN,	//	ButtonAuxLowerLeft7
	AUTOBTN,	// ButtonAuxLowerLeft6,
	AUTOBTN,	// ButtonAuxLowerLeft5,
	AUTOBTN,	// ButtonAuxLowerLeft4,
	AUTOBTN,	// ButtonAuxLowerLeft3,
	AUTOBTN,	// ButtonAuxLowerLeft2,
	AUTOBTN,	// ButtonAuxLowerLeft1,

	AUTOBTN,	// ButtonAuxLowerRight8=0x08
	AUTOBTN,	// ButtonAuxLowerRight7,
	AUTOBTN,	// ButtonAuxLowerRight6,
	AUTOBTN,	// ButtonAuxLowerRight5,
	AUTOBTN,	// ButtonAuxLowerRight4,
	AUTOBTN,	// ButtonAuxLowerRight3,
	AUTOBTN,	// ButtonAuxLowerRight2,
	AUTOBTN,	// ButtonAuxLowerRight1,

	AUTOBTN,	// ButtonSecondary1=0x10
	AUTOBTN,	// ButtonSecondary2,
	AUTOBTN,	// ButtonSecondary3,
	AUTOBTN,	// ButtonSecondary4,
	AUTOBTN,	// ButtonSecondary5,
	AUTOBTN,	// ButtonSecondary6,
	DARKBTN,	//
	DARKBTN,	//
	AUTOBTN,	// ButtonSecondary7=0x18
	AUTOBTN,	// ButtonSecondary8,
	AUTOBTN,	// ButtonSecondary9,
	AUTOBTN,	// ButtonSecondary10,
	AUTOBTN,	// ButtonSecondary11,
	AUTOBTN,	// ButtonSecondary12,
	DARKBTN,	//
	DARKBTN,	//

	AUTOBTN,	// ButtonAuxUpperCenter8=0x20
	AUTOBTN,	// ButtonAuxUpperCenter7,
	AUTOBTN,	// ButtonAuxUpperCenter6,
	AUTOBTN,	// ButtonAuxUpperCenter5,
	AUTOBTN,	// ButtonAuxUpperCenter4,
	AUTOBTN,	// ButtonAuxUpperCenter3,
	AUTOBTN,	// ButtonAuxUpperCenter2,
	AUTOBTN,	// ButtonAuxUpperCenter1,

	AUTOBTN,	// ButtonAuxUpperLeft8=0x28
	AUTOBTN,	// ButtonAuxUpperLeft7,
	AUTOBTN,	// ButtonAuxUpperLeft6,
	AUTOBTN,	// ButtonAuxUpperLeft5,
	AUTOBTN,	// ButtonAuxUpperLeft4,
	AUTOBTN,	// ButtonAuxUpperLeft3,
	AUTOBTN,	// ButtonAuxUpperLeft2,
	AUTOBTN,	// ButtonAuxUpperLeft1,

	AUTOBTN,	// ButtonAuxUpperRight8=0x30
	AUTOBTN,	// ButtonAuxUpperRight7,
	AUTOBTN,	// ButtonAuxUpperRight6,
	AUTOBTN,	// ButtonAuxUpperRight5,
	AUTOBTN,	// ButtonAuxUpperRight4,
	AUTOBTN,	// ButtonAuxUpperRight3,
	AUTOBTN,	// ButtonAuxUpperRight2,
	AUTOBTN,	// ButtonAuxUpperRight1,

	AUTOBTN, //	ButtonPanic=0x38,
	DARKBTN,	//
	DARKBTN,	//
	DARKBTN,	//
	DARKBTN,	//	ButtonDoor=0x3C,
	DARKBTN,	//
	DARKBTN,	//

	DARKBTN,	//	ButtonThrottle1=0x3F,
	MAPBTN,	// ButtonJoystickTrigger=0x40
	MAPBTN,	// ButtonJoystickHatDown=0x41
	MAPBTN,	// ButtonJoystickHatUp=0x42
	MAPBTN,	// ButtonJoystickHatRight=0x43
	MAPBTN,	// ButtonJoystickHatLeft=0x44
	MAPBTN,	// ButtonJoystickPinky=0x45
	MAPBTN,	// ButtonJoystickThumbLow=0x46
	MAPBTN	// ButtonJoystickThumbHigh=0x47
};

//###########################################################################
//############################ Joystick classes #############################
//###########################################################################
Joystick::Joystick()
{
	NotationFile
		init(initFileName);

		channel[0] = new FilterChannel(
			&init,
			FilterChannel::joystickXName,
			10,20,30			// bogus values in case file didn't previously exist
		);
		Check(channel[0]);

		channel[1] = new FilterChannel(
			&init,
			FilterChannel::joystickYName,
			10,20,30			// bogus values in case file didn't previously exist
		);
		Check(channel[1]);

	ButtonReleased = Joystick::NoButton;
	ButtonPressed = Joystick::NoButton;
	Check_Fpu();
}

Joystick::~Joystick()
{
	Check(this);

	NotationFile
		init(initFileName);

	//-------------------------------------------
	// Save DOS timestamp
	//-------------------------------------------
	time_t
		dos_time;
	time(&dos_time);
	init.SetEntry("operator", "time", ctime(&dos_time));

	for(int i=0; i<2; ++i)
	{
		Check(channel[i]);
		delete channel[i];
		channel[i] = NULL;
	}
	Check_Fpu();
}

void
	Joystick::BeginAlignment()
{
	Check(this);
	for(int i=0; i<2; ++i)
	{
		Check(channel[i]);
		channel[i]->BeginAlignment();
	}
	Check_Fpu();
}

void
	Joystick::EndAlignment()
{
	Check(this);
	//------------------------------------------------------------
	// If channel was never adjusted, synthesize reasonable values
	//------------------------------------------------------------
	NotationFile
		init(initFileName);
	EndAndSaveAlignment(&init);

	Check_Fpu();
}

void
	Joystick::EndAndSaveAlignment(NotationFile *init_file)
{
	Check(this);
	Check(init_file);
	for(int i=0; i<2; ++i)
	{
		Check(channel[i]);
		channel[i]->EndAlignment(init_file);
	}
	Check_Fpu();
}

void
	Joystick::Update()
{
	Fail("Joystick::Update should not be called!\n");
}

void
	Joystick::SetDeadBand(Scalar dead_band)
{
	Check(this);
	for(int i=0; i<2; ++i)
	{
		Check(channel[i]);
		channel[i]->SetDeadBand(dead_band);
	}
	Check_Fpu();
}

OrdinaryJoystick::OrdinaryJoystick()
{
	//STUBBED: JOYSTICK RB 1/14/07
	//PC_Joystick_Mask = PCJ_Standard_Mask;
	//ExtendedValue.x = (Scalar) 0;
	//ExtendedValue.y = (Scalar) 0;
	//Check_Fpu();
}

OrdinaryJoystick::~OrdinaryJoystick()
{
	Check_Fpu();
}

void OrdinaryJoystick::Update()
{
	//STUBBED: JOYSTICK RB 1/14/07
	//int	newButtons, changedButtons;

	//Check(this);
	////
	////---------------------------------------------------
	//// Update analog values
	////---------------------------------------------------
	////
	//PC_Joystick_Update();
	//Check(channel[0]);
	//Value.x = channel[0]->Update(PC_Joystick1_Raw_X);
	//Check(channel[1]);
	//Value.y = channel[1]->Update(PC_Joystick1_Raw_Y);
	////
	////---------------------------------------------------
	//// Handle button changes
	////---------------------------------------------------
	////
	//newButtons = (int) PC_Joystick_Buttons;
	//changedButtons = newButtons ^ oldButtons;

	//ButtonPressed = Joystick::NoButton;
	//ButtonReleased = Joystick::NoButton;

	//if (changedButtons)
	//{
	//	if (changedButtons & PCJ_Standard_Button1)
	//	{
	//		if (newButtons & PCJ_Standard_Button1)
	//		{
	//			ButtonPressed |= Joystick::Trigger;
	//		}
	//		else
	//		{
	//			ButtonReleased |= Joystick::Trigger;
	//		}
	//	}
	//	if (changedButtons & PCJ_Standard_Button2)
	//	{
	//		if (newButtons & PCJ_Standard_Button2)
	//		{
	//			ButtonPressed |= Joystick::Thumb3;
	//		}
	//		else
	//		{
	//			ButtonReleased |= Joystick::Thumb3;
	//		}
	//	}
	//}
	//oldButtons = newButtons;
	//Check_Fpu();
}

Logical
	Joystick::TestInstance() const
{
	return True;
}

FlightStickPro::FlightStickPro()
{
	//STUBBED: JOYSTICK RB 1/14/07
	//NotationFile
	//	init(initFileName);

	//throttle = new FilterChannel(
	//	&init,
	//	FilterChannel::throttleName,
	//	10,30			// bogus values in case file doesn't exist
	//);

	//Check(throttle);

	//PC_Joystick_Mask = PCJ_FSP_Mask;
	//ExtendedValue.x = (Scalar) 0;
	//Check_Fpu();
}

FlightStickPro::~FlightStickPro()
{
	Check(this);

	NotationFile
		init(initFileName);
	//-------------------------------------------
	// Save DOS timestamp
	//-------------------------------------------
	time_t
		dos_time;
	time(&dos_time);
	init.SetEntry("operator", "time", ctime(&dos_time));

	Check(throttle);
	delete throttle;
	throttle = NULL;

	Check_Fpu();
}

void FlightStickPro::Update()
{
	//STUBBED: JOYSTICK RB 1/14/07
	//int	newButtons;

	//Check(this);
	////
	////---------------------------------------------------
	//// Update analog values
	////---------------------------------------------------
	////
	//PC_Joystick_Update();
	//Check(channel[0]);
	//Value.x = channel[0]->Update(PC_Joystick1_Raw_X);
	//Check(channel[1]);
	//Value.y = channel[1]->Update(PC_Joystick1_Raw_Y);

	//// Note inverted direction (below) for throttle

	//Check(throttle);
	//ExtendedValue.y = 1.0 - throttle->Update(PC_Joystick2_Raw_Y);
	////
	////---------------------------------------------------
	//// Handle button changes
	////---------------------------------------------------
	////
	//newButtons = int (PC_Joystick_Buttons);

	//ButtonPressed = Joystick::NoButton;
	//ButtonReleased = Joystick::NoButton;

	//if (newButtons != oldButtons)
	//{
	//	switch(oldButtons)
	//	{
	//	case PCJ_FSP_Up:      ButtonReleased = Joystick::HatUp;    break;
	//	case PCJ_FSP_Down:    ButtonReleased = Joystick::HatDown;  break;
	//	case PCJ_FSP_Left:    ButtonReleased = Joystick::HatLeft;  break;
	//	case PCJ_FSP_Right:   ButtonReleased = Joystick::HatRight; break;
	//	case PCJ_FSP_Trigger: ButtonReleased = Joystick::Trigger;  break;
	//	case PCJ_FSP_LButton: ButtonReleased = Joystick::Thumb3;   break;
	//	case PCJ_FSP_RButton: ButtonReleased = Joystick::Thumb1;   break;
	//	case PCJ_FSP_CButton: ButtonReleased = Joystick::Thumb2;   break;
	//	}
	//	switch(newButtons)
	//	{
	//	case PCJ_FSP_Up:      ButtonPressed = Joystick::HatUp;    break;
	//	case PCJ_FSP_Down:    ButtonPressed = Joystick::HatDown;  break;
	//	case PCJ_FSP_Left:    ButtonPressed = Joystick::HatLeft;  break;
	//	case PCJ_FSP_Right:   ButtonPressed = Joystick::HatRight; break;
	//	case PCJ_FSP_Trigger: ButtonPressed = Joystick::Trigger;  break;
	//	case PCJ_FSP_LButton: ButtonPressed = Joystick::Thumb3;   break;
	//	case PCJ_FSP_RButton: ButtonPressed = Joystick::Thumb1;   break;
	//	case PCJ_FSP_CButton: ButtonPressed = Joystick::Thumb2;   break;
	//	}
	//	oldButtons = newButtons;
	//}
	//Check_Fpu();
}

void
	FlightStickPro::BeginAlignment()
{
	Check(this);
	Check(throttle);
	throttle->BeginAlignment();

	Joystick::BeginAlignment();
	Check_Fpu();
}

void
	FlightStickPro::EndAlignment()
{
	Check(this);

	NotationFile
		init(initFileName);

	Check(throttle);
	throttle->EndAlignment(&init);

	Joystick::EndAndSaveAlignment(&init);

	Check_Fpu();
}

BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext)
{
	ThrustMaster *joystick = (ThrustMaster*)pContext;
	if (FAILED(joystick->mDI->CreateDevice(pdidInstance->guidInstance, &joystick->mJoystick, NULL)))
		return DIENUM_CONTINUE;

	return DIENUM_STOP;
}

BOOL CALLBACK EnumAxesCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef)
{
	ThrustMaster *joystick = (ThrustMaster*)pvRef;
	DIPROPRANGE diprg;
	diprg.diph.dwSize = sizeof(DIPROPRANGE);
	diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
	diprg.diph.dwHow = DIPH_BYID;
	diprg.diph.dwObj = lpddoi->dwType;
	diprg.lMax = diprg.lMax = 0;

	if (lpddoi->guidType == GUID_XAxis)
	{
		if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
		{
			joystick->xAxisMin = diprg.lMin;
			joystick->xAxisMax = diprg.lMax;
		}
	}
	else if (lpddoi->guidType == GUID_YAxis)
	{
		if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
		{
			joystick->yAxisMin = diprg.lMin;
			joystick->yAxisMax = diprg.lMax;
		}
	}
	else if (lpddoi->guidType == GUID_ZAxis)
	{
		if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
		{
			joystick->zAxisMin = diprg.lMin;
			joystick->zAxisMax = diprg.lMax;
		}
	}
	else if (lpddoi->guidType == GUID_RxAxis)
	{
	}
	else if (lpddoi->guidType == GUID_RyAxis)
	{
	}
	else if (lpddoi->guidType == GUID_RzAxis)
	{
		if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
		{
			joystick->rzAxisMin = diprg.lMin;
			joystick->rzAxisMax = diprg.lMax;
		}
	}
	else if (lpddoi->guidType == GUID_Slider)
	{
		if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
		{
			joystick->zAxisMin = diprg.lMin;
			joystick->zAxisMax = diprg.lMax;
		}
	}

	return DIENUM_CONTINUE;
}

ThrustMaster::ThrustMaster(HINSTANCE hInstance, HWND hWnd)
{
	//STUBBED: JOYSTICK RB 1/14/07
	ExtendedValue.x = (Scalar) 0;
	ExtendedValue.y = (Scalar) 0;
	hatMax = 0;
	prevHatState = 0;

	mDI = NULL;
	mJoystick = NULL;

	if (SUCCEEDED(DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&mDI, NULL)))
	{
		mDI->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, this, DIEDFL_ATTACHEDONLY);
		if (mJoystick)
			if (SUCCEEDED(mJoystick->SetDataFormat(&c_dfDIJoystick2)))
				if (SUCCEEDED(mJoystick->SetCooperativeLevel(hWnd, DISCL_EXCLUSIVE | DISCL_BACKGROUND)))
					if (SUCCEEDED(mJoystick->EnumObjects(EnumAxesCallback, this, DIDFT_AXIS)))
						return;
	}

	if (mDI)
	{
		mDI->Release();
		mDI = NULL;
	}
	if (mJoystick)
	{
		mJoystick->Release();
		mJoystick = NULL;
	}
}

ThrustMaster::~ThrustMaster()
{
	Check_Fpu();
}

bool ThrustMaster::Poll()
{
	HRESULT hr;
	if (mJoystick)
	{
		V( mJoystick->Poll() );
		if (FAILED(hr))
		{
			V( mJoystick->Acquire() );
			while (hr == DIERR_INPUTLOST)
				V( mJoystick->Acquire() );
			return false;
		}

		V( mJoystick->GetDeviceState(sizeof(DIJOYSTATE2), &mJoystate) );
		if (FAILED(hr))
			return false;
	}

	return true;
}

void ThrustMaster::Update()
{
	if (!Poll())
		return;

	//
	//---------------------------------------------------
	// Update analog values
	//---------------------------------------------------
	//
	Value.x = ((Scalar)(mJoystate.lX - xAxisMin) / (Scalar)(xAxisMax - xAxisMin)) * 2.0f - 1.0f;
	Value.y = ((Scalar)(mJoystate.lY - yAxisMin) / (Scalar)(yAxisMax - yAxisMin)) * 2.0f - 1.0f;
	LONG z = (mJoystate.lZ == 0 ? mJoystate.rglSlider[0] : mJoystate.lZ);
	ExtendedValue.y = 1.0f - (Scalar)(z - zAxisMin) / (Scalar)(zAxisMax - zAxisMin);
	// full left rotation = 0, center rotation = 1/2 range, full right = full range
	LONG rot = (Scalar)(mJoystate.lRz - rzAxisMin);
	Scalar half = (Scalar)(rzAxisMax - rzAxisMin) / 2.0f;
	Rotation.x = (Scalar)0;
	Rotation.y = (Scalar)0;
	if (rot <= half)
		Rotation.x = ((half - rot) / half) * 470.0;
	else
		Rotation.y = ((rot - half) / half) * 470.0;

	//
	//---------------------------------------------------
	// Handle button changes
	//---------------------------------------------------
	//
	ButtonPressed = Joystick::NoButton;
	ButtonReleased = Joystick::NoButton;

	int newButtons = 0;
	if (mJoystate.rgbButtons[DI_TRIGGER])
		newButtons |= Joystick::Trigger;
	if (mJoystate.rgbButtons[DI_THUMB1])
		newButtons |= Joystick::Thumb1;
	if (mJoystate.rgbButtons[DI_THUMB2])
		newButtons |= Joystick::Thumb2;
	if (mJoystate.rgbButtons[DI_THUMB3])
		newButtons |= Joystick::Thumb3;
	if (mJoystate.rgbButtons[DI_THROTTLE])
		newButtons |= Joystick::Throttle;
	if (mJoystate.rgdwPOV[0] == 0)
		newButtons |= Joystick::HatUp;
	if (mJoystate.rgdwPOV[0] == 9000)
		newButtons |= Joystick::HatRight;
	if (mJoystate.rgdwPOV[0] == 18000)
		newButtons |= Joystick::HatDown;
	if (mJoystate.rgdwPOV[0] == 27000)
		newButtons |= Joystick::HatLeft;

	ButtonPressed = (oldButtons ^ newButtons) & newButtons;
	ButtonReleased = (oldButtons ^ newButtons) & oldButtons;
	oldButtons = newButtons;
}


//#########################################################################
//########################## LBE4ControlsManager ##########################
//#########################################################################

//---------------------------------------------------------------------------
// Class derivation
//---------------------------------------------------------------------------
Derivation* LBE4ControlsManager::GetClassDerivations()
{
	static Derivation classDerivations(ControlsManager::GetClassDerivations(), "L4ControlsManager");
	return &classDerivations;
}

//---------------------------------------------------------------------------
// Message support
//---------------------------------------------------------------------------
//const Receiver::HandlerEntry
//	LBE4ControlsManager::MessageHandlerEntries[]=
//{
//	MESSAGE_ENTRY(LBE4ControlsManager,BeginCalibration),
//	MESSAGE_ENTRY(LBE4ControlsManager,EndCalibration)
//};
//
//Receiver::MessageHandlerSet
//	LBE4ControlsManager::MessageHandlers(
//		ELEMENTS(LBE4ControlsManager::MessageHandlerEntries),
//		LBE4ControlsManager::MessageHandlerEntries,
//		ControlsManager::MessageHandlers
//	);
//---------------------------------------------------------------------------
// Shared data
//---------------------------------------------------------------------------
LBE4ControlsManager::SharedData
	LBE4ControlsManager::DefaultData(
		LBE4ControlsManager::GetClassDerivations(),
		LBE4ControlsManager::GetMessageHandlers()
	);
//
//############################################################################
// LBE4ControlsManager
//
// Creator method
//----------------------------------------------------------------------------
// Enter: virtualDataPtr
//############################################################################
//
LBE4ControlsManager::LBE4ControlsManager():
	ControlsManager(L4ControlsManagerClassID, DefaultData)
{
	Check_Pointer(this);
	//
	//-------------------------------------------------------------------------
	// configure
	//-------------------------------------------------------------------------
	//
	flags.mouseExists = 0;
	flags.keyboardExists = 0;
	flags.joystickExists = 0;
	flags.RIOExists = 0;

	virtualJoystickX = 0;
	virtualJoystickY = 0;

	lastJoystickUpdate = 0.0f;
	joystickType = LBE4ControlsManager::none;

	mousePointer = NULL;
	joystickPointer = NULL;
	rioPointer = NULL;
	lastRioRequest = 0L;
	primaryControlType = LBE4ControlsManager::None;

	char *controlString = getenv("L4CONTROLS");

	if (controlString != NULL)
	{
		//
		//-------------------------------------------------
		// parse environment variable, build configuration
		//-------------------------------------------------
		//
		char
			temp[128];
		char
			*dest;
		char
			c;
		int
			noMore(0);

		do
		{
			//
			// Get the next token
			// Can't use strtok(), because RIO::RIO uses it,
			// and it's not re-entrant.
			//

			dest = temp;
			if (noMore)
			{
				*dest = '\0';
			}
			else
			{
				do
				{
					switch(c=*controlString++)
					{
					case '\0':
					case '\n':
						noMore = 1;
						// deliberately falls through into next case

					case ',':
					case ';':
						c = '\0';
						// deliberately falls through into default case

					default:
						*dest++=c;
						break;
					}
				}
				while (c != '\0');
			}

			//Win32 Serial support: ADB 06/30/07
			if(strncmp(temp, "RIO", 3) == 0)
			{
				if(strncmp(temp, "RIO:COM", 7) == 0)
				{
					int l = strlen(temp+4) + 5;
					char* comm = new char[l];
					strcpy(comm, "\\\\.\\");
					strcpy(comm+4, temp+4);
					rioPointer = new RIO(comm);//temp + 4);
					Check(rioPointer);
					Register_Object(rioPointer);
					flags.RIOExists = 1;
					primaryControlType = LBE4ControlsManager::PrimaryRIO;
					delete comm;
				}
				else
				{
					rioPointer = new RIO("COM1");
					Check(rioPointer);
					Register_Object(rioPointer);
					flags.RIOExists = 1;
					primaryControlType = LBE4ControlsManager::PrimaryRIO;
				}
			}
			else if (strcmpi(temp, "KEYBOARD") == 0)
			{
				flags.keyboardExists = 1;
			}
			else if (strcmpi(temp, "MOUSE") == 0)
			{
				if (!flags.mouseExists)
				{
					flags.mouseExists = 1;
					mousePointer = new Mouse;
					Check(mousePointer);
					Register_Object(mousePointer);
					if (mousePointer->Errors)
					{
						Unregister_Object(mousePointer);
						delete mousePointer;
						mousePointer = NULL;
						flags.mouseExists = 0;
					} else 
					{
						primaryControlType = LBE4ControlsManager::PrimaryThrustMaster;
					}
				}
			}
			else if (strcmpi(temp, "JOYSTICK") == 0)
			{
				flags.joystickExists = 1;
				joystickType = LBE4ControlsManager::ordinary;
				joystickPointer = new OrdinaryJoystick();
				Register_Object(joystickPointer);
				primaryControlType = LBE4ControlsManager::PrimaryGenericJoystick;
			}
			else if (strcmpi(temp, "FLIGHTSTICKPRO") == 0)
			{
				flags.joystickExists = 1;
				joystickType = LBE4ControlsManager::flightstickPro;
				joystickPointer = new FlightStickPro();
				Register_Object(joystickPointer);
				primaryControlType = LBE4ControlsManager::PrimaryFlightStickPro;
			}                                           
			else if (strcmpi(temp, "THRUSTMASTER") == 0)
			{
				flags.joystickExists = 1;
				joystickType = LBE4ControlsManager::thrustMaster;
				joystickPointer = new ThrustMaster(ApplicationManager::GetCurrentManager()->GetHInstance(), ApplicationManager::GetCurrentManager()->GetHWnd());
				Register_Object(joystickPointer);
				primaryControlType = LBE4ControlsManager::PrimaryThrustMaster;
			}
			else if (strcmpi(temp, "DIJOYSTICK") == 0)
			{
				joystickPointer = new DIJoystick(ApplicationManager::GetCurrentManager()->GetHInstance(), ApplicationManager::GetCurrentManager()->GetHWnd());
				if (((DIJoystick*)joystickPointer)->IsValid())
				{
					flags.joystickExists = 1;
					joystickType = LBE4ControlsManager::directInputJoystick;
					Register_Object(joystickPointer);
					primaryControlType = LBE4ControlsManager::PrimaryThrustMaster;
				}
				else
				{
					delete joystickPointer;
					joystickPointer = NULL;
				}
			}
		}
		while (temp[0] != '\0');
	}

	//--------------------------------------------------
	// Clear all lamps  (and reset analog I/O)
	//--------------------------------------------------
	if (rioPointer != NULL)
	{
		Check(rioPointer);
		rioPointer->GeneralReset();
	}
	//--------------------------------------------------
	// Get mode mask for updating
	//--------------------------------------------------
	Check(application);
	Check(application->GetModeManager());
	ModeMask
		mode_mask = application->GetModeManager()->GetModeMask();
	//--------------------------------------------------
	// Initialize scalar group
	//--------------------------------------------------
	{
		int
			i;
		ControlsScalar
			initial_scalar = 0.0;

		for(i=0; i<ScalarCount; ++i)
		{
			scalarGroup[i].ForceUpdate(&initial_scalar, mode_mask);
		}
	}
	//--------------------------------------------------
	// Initialize joystick group
	//--------------------------------------------------
	{
		int
			i;
		ControlsJoystick
			initial_stick;

		initial_stick.x = 0.0;
		initial_stick.y = 0.0;

		for(i=0; i<JoystickCount; ++i)
		{
			joystickGroup[i].ForceUpdate(&initial_stick, mode_mask);
		}
	}
	//--------------------------------------------------
	// Initialize keyboard group
	//--------------------------------------------------
	{
		int
			i;
		ControlsKey
			initial_key = 0;

		for(i=0; i<KeyboardCount; ++i)
		{
			keyboardGroup[i].ForceUpdate(&initial_key, mode_mask);
		}
	}
	//--------------------------------------------------
	// Initialize button control groups
	//--------------------------------------------------
	{
		int
			i;
		ControlsButton
			initial_button_value = 0;

		for(i=0; i<ButtonCount; ++i)
		{
			buttonGroup[i].ForceUpdate(&initial_button_value, mode_mask);
			buttonActivateModeMask[i] = mode_mask;
		}
	}

	//--------------------------------------------------
	// Initialize control mode
	//--------------------------------------------------
	l4ControlsMode = normalMode;
	{
		int i;

		for(i=0; i<maxStringIDs; ++i)
		{
			temporaryStringID[i] = 0;
		}
	}


//	stringManager.Add("A90", KeyboardPilot, this, BeginCalibrationMessageID, 0);

	//
	//-----------------------------------------
	// Initialize the RIO stream spoofing stuff
	//-----------------------------------------
	//
	rioStream = NULL;
	streamStart = Now();
	playbackMode = False;
	streamEnabled = False;

	previousJoystickX  = (Scalar) 0;
	previousJoystickY  = (Scalar) 0;
	previousThrottle   = (Scalar) 0;
	previousPedalLeft  = (Scalar) 0;
	previousPedalRight = (Scalar) 0;
	//
	//-------------------------------------
	// See if we need to load in a RIO file
	//-------------------------------------
	//
	CString
		playback = L4Application::GetRIOPlaybackFileName();
	if (playback && strlen(playback))
	{
		if (!flags.RIOExists)
		{
			DEBUG_STREAM << "RIO must be enabled by setenv!\n" << std::flush;
			PostQuitMessage(1);
		}
		LoadPlayback(playback);
		playbackMode = True;
	}

	CString
		record = L4Application::GetRIORecordingFileName();
	if (record && strlen(record))
	{
		if (!flags.RIOExists)
		{
			DEBUG_STREAM << "RIO must be enabled by setenv!\n" << std::flush;
			PostQuitMessage(1);
		}

		RIOStreamEvent
			*events = new RIOStreamEvent[RIO_SPOOL_SIZE];
		Register_Pointer(events);

		Verify(rioStream == NULL);
		rioStream =
			new MemoryStream(events, RIO_SPOOL_SIZE * sizeof(RIOStreamEvent));
		Register_Object(rioStream);
	}
	Check_Fpu();
}

//
//############################################################################
// ~LBE4ControlsManager
//
// Destructor method
//############################################################################
//
LBE4ControlsManager::~LBE4ControlsManager()
{
	Check(this);

	//
	//----------------------------------------
	// If we are recording, save the recording
	//----------------------------------------
	//
	if (IsRecording())
	{
		CString
			record = L4Application::GetRIORecordingFileName();
		SaveRecording(record);
	}

	if (rioStream)
	{
		Check(rioStream);
		rioStream->Rewind();
		RIOStreamEvent
			*start = (RIOStreamEvent*)rioStream->GetPointer();

		Unregister_Object(rioStream);
		delete rioStream;

		Unregister_Pointer(start);
		delete[] start;

		rioStream = NULL;
		streamEnabled = False;
	}

	//--------------------------------------------------
	// Delete mouse
	//--------------------------------------------------
	if (mousePointer != NULL)
	{
		Check(mousePointer);
		Unregister_Object(mousePointer);
		delete mousePointer;
		mousePointer = NULL;
	}
	//--------------------------------------------------
	// Delete joystick
	//--------------------------------------------------
	if (joystickPointer != NULL)
	{
		Check(joystickPointer);
		Unregister_Object(joystickPointer);
		delete joystickPointer;
		joystickPointer = NULL;
	}
	//--------------------------------------------------
	// Delete string manager temporary groups
	//--------------------------------------------------
	ExitControlsMode();
	//--------------------------------------------------
	// Delete RIO AFTER buttons!
	// ...the 'autoManager' button class sends
	// lamp commands when deleted.
	//--------------------------------------------------
	if (rioPointer != NULL)
	{
		Check(rioPointer);
		Unregister_Object(rioPointer);
		delete rioPointer;
		rioPointer = NULL;
	}

	Check_Fpu();
}

//
//#############################################################################
// LocalShutdown
//#############################################################################
//
void
	LBE4ControlsManager::LocalShutdown()
{
	Check_Pointer(this);

	if (rioPointer != NULL)
	{
		Check(rioPointer);
		Unregister_Object(rioPointer);
		delete rioPointer;

		rioPointer = NULL;
	}

	ControlsManager::LocalShutdown();
	Check_Fpu();
}

//
//############################################################################
// Remove
//
// Remove all controlsInstance records bearing the given ID
//############################################################################
//
void
	LBE4ControlsManager::Remove(ModeMask mode_mask)
{
	int	i;

	Check(this);
	for(i=0; i<ScalarCount; ++i)
	{
		Check(&scalarGroup[i]);
		scalarGroup[i].Remove(mode_mask);
	}

	for(i=0; i<JoystickCount; ++i)
	{
		Check(&joystickGroup[i]);
		joystickGroup[i].Remove(mode_mask);
	}

	for(i=0; i<KeyboardCount; ++i)
	{
		Check(&keyboardGroup[i]);
		keyboardGroup[i].Remove(mode_mask);
	}

	for(i=0; i<ButtonCount; ++i)
	{
		Check(&buttonGroup[i]);
		buttonGroup[i].Remove(mode_mask);
	}

	Check(&mouseGroup[0]);
	mouseGroup[0].Remove(mode_mask);
	Check_Fpu();
}

//
//############################################################################
// SetLamp
//
// Sets a specific lamp to the designated state
//############################################################################
//

void
	LBE4ControlsManager::SetLamp(
		int lamp_number,
		int state
	)
{
	if (rioPointer != NULL)
	{
		Check(rioPointer);
      Verify(lamp_number >= 0);
		Verify(lamp_number < LBE4ControlsManager::LampCount);

		rioPointer->SetLamp(lamp_number, (RIO::LampState) state);
	}
	Check_Fpu();
}

//
//############################################################################
// Execute
//
// Execution method
//############################################################################
//
void
	LBE4ControlsManager::Execute()
{
	ControlsJoystick	virtual_joystick;
	ControlsKey			keyValue;
	ControlsMouse 		mouseValue;
	static Scalar		mouseWheel = 0.0;
	Logical				new_RIO_values(False);
	Logical				mouseMoved = false;
	Logical				mouseWheelMoved = false;
	static int			oldX = 0;
	static int			oldY = 0;
				
	Check(this);
	//--------------------------------------------------
	// Get mode mask for updating
	//--------------------------------------------------
	Check(application);
	Check(application->GetModeManager());
	ModeMask
		mode_mask = application->GetModeManager()->GetModeMask();
	//-------------------------------------------------------------------------
	// Process operator control mode
	//-------------------------------------------------------------------------
	// ManageControlsMode();

	//-------------------------------------------------------------------------
	// Process RIO
	//-------------------------------------------------------------------------
	if (flags.RIOExists)
	{
		Check(rioPointer);

		RIO::RIOEvent	rio_event;
		//
		//------------------------------------
		// Request analog update
		// (held down to 5 Hz unless reply
		// is received: reply allows another
		// request to be sent next invocation)
		//------------------------------------
		//
		if (!playbackMode)
		{
			//Scalar
			//	delta_t(Now() - lastRioRequest);
			//Scalar
			//	limit;
			//static Logical
			//	first_time = True;

			//ADB 10/01/07
			static DWORD last_check = GetTickCount();
			DWORD limit = 15000;

			if(application->GetApplicationState() == Application::RunningMission)
			{
				//Posible Bug: ADB 03/30/07
				//limit = 15.0;	// 0.2
				limit = 50;//reduced to improve responsiveness
			}
			//else
			//{
			//	//limit = 15.0;
			//	limit = 15000;
			//}

			//if (first_time)
			//{
			//	first_time = False;
			//}
			//else
			{
            DWORD this_check = GetTickCount();
            //if(this_check - last_check >= 1000)
               //DEBUG_STREAM << "No Request in " << (this_check - last_check) << "ms." << std::endl << std::flush;

				if((this_check - last_check) >= limit)
				//if (delta_t >= limit)	// 5 Hz minimum rate, in case of no reply
				{
					//-------------------------
					// Debugging aid
					//-------------------------
					//long time_in_ticks;

					//time_in_ticks = lastRioRequest;
					//if (time_in_ticks != 0L)
					//{
					//	DEBUG_STREAM << "LBE4ControlsManager::Execute, lost RIO analog request\n";

						//STUBBED: JOYSTICK RB 1/14/07
						/*extern Logical
							iThinkIRQIsOn;

						extern int
							pcspakCharactersSent;

						DEBUG_STREAM <<
							"iThinkIRQIsOn =" << iThinkIRQIsOn <<
							", actual state =" << PCSPAKIRQState() <<
							", characters sent =" << pcspakCharactersSent << "\n";*/
					//}

					rioPointer->RequestAnalogUpdate();
					//lastRioRequest = Now();
					last_check = this_check;
				}
				//-------------------------
				// Debugging aid
				//-------------------------
				//else if (delta_t < 0.0)
				//{
				//	DEBUG_STREAM <<
				//		"LBE4ControlsManager::Execute, delta_t went negative (" <<
				//		delta_t << ")\n";

				//	rioPointer->RequestAnalogUpdate();
				//	lastRioRequest = Now();
				//}
			}
		}

		//
		//------------------------------------
		// Process events
		//------------------------------------
		//
		while (GetNextRIOEvent(&rio_event))
		{
			ProcessRIOEvent(rio_event, &new_RIO_values);
		}
	}

	//
	//-------------------------------------------------------------------------
	// Read physical joystick inputs, filter the values
	//-------------------------------------------------------------------------
	//
	if (flags.joystickExists)
	{
		Scalar delta_t = (Scalar)Now() - lastJoystickUpdate;

		if (delta_t > .05)
		{
			Check(joystickPointer);
			joystickPointer->Update();
			joystickPointer->Value.x = -joystickPointer->Value.x;	// HACK!!!

			lastJoystickUpdate = Now();
		}
	}

	//
	//-------------------------------------------------------------------------
	// Read physical mouse inputs, generate virtual joystick values
	//-------------------------------------------------------------------------
	//
	if (flags.mouseExists)
	{
		Check(mousePointer);
		//OLD WAY: mousePointer->ReadCounters(&mouseValue.x, &mouseValue.y);
		//NEW WAY
		/*MSG msg;
		if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (msg.message == WM_MOUSEMOVE)
			{
				mouseMoved = true;

				GetMessage(&msg, NULL, 0, 0);
				int x = (msg.lParam & 0xFFFF);
				int y = (msg.lParam & 0xFFFF0000) >> 16;
				mouseValue.x = x - oldX;
				mouseValue.y = y - oldY;
				oldX = x;
				oldY = y;

				virtualJoystickX += mouseValue.x;
				virtualJoystickY += mouseValue.y;

				#define MAXEXTENT	400		// This is a guess. We'll have to try it and see.

				if (virtualJoystickX >  MAXEXTENT) { virtualJoystickX =  MAXEXTENT; }
				if (virtualJoystickX < -MAXEXTENT) { virtualJoystickX = -MAXEXTENT; }
				virtual_joystick.x = (Scalar) virtualJoystickX / (Scalar) MAXEXTENT;

				if (virtualJoystickY >  MAXEXTENT) { virtualJoystickY =  MAXEXTENT; }
				if (virtualJoystickY < -MAXEXTENT) { virtualJoystickY = -MAXEXTENT; }
				virtual_joystick.y = (Scalar) virtualJoystickY / (Scalar) MAXEXTENT;
			}*/
			//else if (msg.message == 0x020A /*WM_MOUSEWHEEL*/)
			/*{
				#define MAXEXTENT	1200.0f	// Just a guess

				mouseWheelMoved = true;
				GetMessage(&msg, NULL, 0, 0);
				//mouseWheel = (signed int)((msg.wParam & 0xFFFF0000) >> 16) * 1.0f;
				int delta = (mouseWheel * MAXEXTENT) + ((short)HIWORD(msg.wParam));
				
				if (delta > MAXEXTENT)
					delta = MAXEXTENT;
				if (delta < 0.0f)
					delta = 0.0f;

				mouseWheel = delta / MAXEXTENT;
			}
		}*/
	}

	//
	//-------------------------------------------------------------------------
	// Update the scalar mapping groups using the analog values
	//-------------------------------------------------------------------------
	//
	if (flags.RIOExists && new_RIO_values)
	{
		Check(rioPointer);

		Check(&scalarGroup[LBE4ControlsManager::ScalarThrottle]);
		scalarGroup[LBE4ControlsManager::ScalarThrottle].Update(
			&rioPointer->Throttle, mode_mask
		);

		Check(&scalarGroup[LBE4ControlsManager::ScalarLeftPedal]);
		scalarGroup[LBE4ControlsManager::ScalarLeftPedal].Update(
			&rioPointer->LeftPedal, mode_mask
		);

		Check(&scalarGroup[LBE4ControlsManager::ScalarRightPedal]);
		scalarGroup[LBE4ControlsManager::ScalarRightPedal].Update(
			&rioPointer->RightPedal, mode_mask
		);

		Check(&scalarGroup[LBE4ControlsManager::ScalarJoystickX]);
		scalarGroup[LBE4ControlsManager::ScalarJoystickX].Update(
			&rioPointer->JoystickX, mode_mask
		);

		Check(&scalarGroup[LBE4ControlsManager::ScalarJoystickY]);
		scalarGroup[LBE4ControlsManager::ScalarJoystickY].Update(
			&rioPointer->JoystickY, mode_mask
		);
	}
	else if (flags.joystickExists)
	{
		scalarGroup[LBE4ControlsManager::ScalarLeftPedal].Update(
			&joystickPointer->Rotation.x, mode_mask
		);

		scalarGroup[LBE4ControlsManager::ScalarRightPedal].Update(
			&joystickPointer->Rotation.y, mode_mask
		);

		Check(&scalarGroup[LBE4ControlsManager::ScalarThrottle]);
		scalarGroup[LBE4ControlsManager::ScalarThrottle].Update(
			&joystickPointer->ExtendedValue.y, mode_mask
		);

		Check(&scalarGroup[LBE4ControlsManager::ScalarJoystickX]);
		scalarGroup[LBE4ControlsManager::ScalarJoystickX].Update(
			&joystickPointer->Value.x, mode_mask
		);

		Check(&scalarGroup[LBE4ControlsManager::ScalarJoystickY]);
		scalarGroup[LBE4ControlsManager::ScalarJoystickY].Update(
			&joystickPointer->Value.y, mode_mask
		);
	}

	if (flags.mouseExists)
	{
		if (mouseMoved)
		{
			Check(&scalarGroup[LBE4ControlsManager::ScalarMouseX]);
			scalarGroup[LBE4ControlsManager::ScalarMouseX].Update(
				&virtual_joystick.x, mode_mask
			);
			Check(&scalarGroup[LBE4ControlsManager::ScalarMouseY]);
			scalarGroup[LBE4ControlsManager::ScalarMouseY].Update(
				&virtual_joystick.y, mode_mask
			);
		}
		else
		{
			Scalar clear = 0;
			scalarGroup[LBE4ControlsManager::ScalarMouseX].Update(&clear, mode_mask);
			scalarGroup[LBE4ControlsManager::ScalarMouseY].Update(&clear, mode_mask);
		}
		if (mouseWheelMoved)
		{
			scalarGroup[LBE4ControlsManager::ScalarThrottle].Update(
				&mouseWheel, mode_mask
			);
		}
	}

	//
	//-------------------------------------------------------------------------
	// Update the joystick mapping groups using the analog values
	//-------------------------------------------------------------------------
	//
	if (flags.RIOExists && new_RIO_values)
	{
		Check(rioPointer);

		ControlsJoystick
			stick;

		stick.x = rioPointer->JoystickX;
		stick.y = rioPointer->JoystickY;

		Check(&joystickGroup[LBE4ControlsManager::JoystickPhysical]);
		joystickGroup[LBE4ControlsManager::JoystickPhysical].Update(
			&stick, mode_mask
		);
	}
	else if (flags.joystickExists)
	{
		Check(&joystickGroup[LBE4ControlsManager::JoystickPhysical]);
		joystickGroup[LBE4ControlsManager::JoystickPhysical].Update(
			&joystickPointer->Value, mode_mask
		);
	}

	if (flags.mouseExists && mouseMoved)
	{
		Check(&joystickGroup[LBE4ControlsManager::JoystickVirtual]);
		/*joystickGroup[LBE4ControlsManager::JoystickVirtual].Update(
			&virtual_joystick, mode_mask
		);*/
		joystickGroup[LBE4ControlsManager::JoystickPhysical].Update(
			&virtual_joystick, mode_mask
		);
	}

	//
	//-------------------------------------------------------------------------
	// Update the PC keyboard mapping group
	//-------------------------------------------------------------------------
	//
	if (flags.keyboardExists)
	{
		//RB 1/20/07
		//keyValue = (ControlsKey) PC_Keyboard_Read();
		MSG msg;
		if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (msg.message == WM_SYSKEYUP || msg.message == WM_KEYUP || msg.message == WM_CHAR)
			{
				GetMessage(&msg, NULL, 0, 0);
				TranslateMessage(&msg);
				keyValue = (msg.wParam & VK_MASK);
				if (msg.message == WM_SYSKEYUP)
					keyValue |= ALT_BIT;
				//
				// BT port fix: for WM_KEYUP this wParam is a VIRTUAL-KEY code, not a
				// character, but every consumer (Application::KeyCommandMessageHandler
				// and friends) compares against typed CHARACTERS.  The navigation
				// block VK_PRIOR..VK_DELETE (0x21-0x2E: PgUp/PgDn/End/Home/arrows/
				// Insert/Delete) collides with the '!'..'.' hotkey characters --
				// releasing the UP ARROW (VK_UP==0x26=='&') silently killed the
				// mission via the '&' stop command.  Drop non-character keyups;
				// deliberately TYPED hotkeys still arrive via WM_CHAR.
				//
				if (msg.message == WM_KEYUP
					&& msg.wParam >= VK_PRIOR && msg.wParam <= VK_DELETE)
				{
					// swallowed: navigation key, not a character command
				}
				else
				{
					keyboardGroup[KeyboardPC].ForceUpdate(&keyValue, mode_mask);
					stringManager.Update(KeyboardPC, keyValue);
				}
			}
		}
	}

	//
	//-------------------------------------------------------------------------
	// Update the button mapping groups
	//-------------------------------------------------------------------------
	//

	//
	//--------------------------
	// Joystick buttons
	//--------------------------
	//
	if (flags.joystickExists)
	{
		int	i;
		ControlsButton	b;

		Check(joystickPointer);

		i = joystickPointer->ButtonReleased;
		if(i != Joystick::NoButton)
		{
			if (i & Joystick::Trigger)
			{
				b = - ButtonJoystickTrigger - 1;
				buttonGroup[ButtonJoystickTrigger].Update(&b, mode_mask);
			}
			if (i & Joystick::Thumb1)
			{
				b = - ButtonJoystickPinky - 1;
				buttonGroup[ButtonJoystickPinky].Update(&b, mode_mask);
			}
			if (i & Joystick::Thumb2)
			{
				b = - ButtonJoystickThumbLow - 1;
				buttonGroup[ButtonJoystickThumbLow].Update(&b, mode_mask);
			}
			if (i & Joystick::Thumb3)
			{
				b = - ButtonJoystickThumbHigh - 1;
				buttonGroup[ButtonJoystickThumbHigh].Update(&b, mode_mask);
			}
			if (i & Joystick::Throttle)
			{
				b = - ButtonThrottle1 - 1;
				buttonGroup[ButtonThrottle1].Update(&b, mode_mask);
			}
			if (i & Joystick::HatUp)
			{
				b = - ButtonJoystickHatUp - 1;
				buttonGroup[ButtonJoystickHatUp].Update(&b, mode_mask);
			}
			if (i & Joystick::HatDown)
			{
				b = - ButtonJoystickHatDown - 1;
				buttonGroup[ButtonJoystickHatDown].Update(&b, mode_mask);
			}
			if (i & Joystick::HatLeft)
			{
				b = - ButtonJoystickHatLeft - 1;
				buttonGroup[ButtonJoystickHatLeft].Update(&b, mode_mask);
			}
			if (i & Joystick::HatRight)
			{
				b = - ButtonJoystickHatRight - 1;
				buttonGroup[ButtonJoystickHatRight].Update(&b, mode_mask);
			}
		}

		i = joystickPointer->ButtonPressed;
		if(i != Joystick::NoButton)
		{
			if (i & Joystick::Trigger)
			{
				b = ButtonJoystickTrigger + 1;
				buttonGroup[ButtonJoystickTrigger].Update(&b, mode_mask);
			}
			if (i & Joystick::Thumb1)
			{
				b = ButtonJoystickPinky + 1;
				buttonGroup[ButtonJoystickPinky].Update(&b, mode_mask);
			}
			if (i & Joystick::Thumb2)
			{
				b = ButtonJoystickThumbLow + 1;
				buttonGroup[ButtonJoystickThumbLow].Update(&b, mode_mask);
			}
			if (i & Joystick::Thumb3)
			{
				b = ButtonJoystickThumbHigh + 1;
				buttonGroup[ButtonJoystickThumbHigh].Update(&b, mode_mask);
			}
			if (i & Joystick::Throttle)
			{
				b = ButtonThrottle1 - 1;
				buttonGroup[ButtonThrottle1].Update(&b, mode_mask);
			}
			if (i & Joystick::HatUp)
			{
				b = ButtonJoystickHatUp + 1;
				buttonGroup[ButtonJoystickHatUp].Update(&b, mode_mask);
			}
			if (i & Joystick::HatDown)
			{
				b = ButtonJoystickHatDown + 1;
				buttonGroup[ButtonJoystickHatDown].Update(&b, mode_mask);
			}
			if (i & Joystick::HatLeft)
			{
				b = ButtonJoystickHatLeft + 1;
				buttonGroup[ButtonJoystickHatLeft].Update(&b, mode_mask);
			}
			if (i & Joystick::HatRight)
			{
				b = ButtonJoystickHatRight + 1;
				buttonGroup[ButtonJoystickHatRight].Update(&b, mode_mask);
			}
		}
	}

	//
	//--------------------------
	// Mouse buttons
	//--------------------------
	//
	if (flags.mouseExists)
	{
		int i, j, k;
		ControlsButton b;

		k = mousePointer->ButtonCount;

		for(i=0, j=LBE4ControlsManager::ButtonMouseLeft; i<k; ++i,++j)
		{
			if (mousePointer->ButtonPressed(i))
			{
				b = (ControlsButton) (j+1);
				buttonGroup[j].Update(&b, mode_mask);
			}

			if (mousePointer->ButtonReleased(i))
			{
				b = (ControlsButton) (-j-1);
				buttonGroup[j].Update(&b, mode_mask);
			}
		}
		//
		//-------------------------------------------------------------------------
		// Update the mouse mapping group
		//-------------------------------------------------------------------------
		//
		mouseGroup[0].Update(&mouseValue, mode_mask);
	}

	// Tell("LBE4ControlsManager::Execute ends\n" << std::flush);
	Check_Fpu();
}

//
//############################################################################
// BeginAlignJoystick
//
// Start alignment process for joystick and analog inputs
//############################################################################
//
void
	LBE4ControlsManager::BeginAlignJoystick()
{
	Check(this);

	if (flags.joystickExists)
	{
		Check(joystickPointer);
		joystickPointer->BeginAlignment();
	}
	Check_Fpu();
}

//
//############################################################################
// EndAlignJoystick
//
// End alignment process for joystick and analog inputs
//############################################################################
//
void
	LBE4ControlsManager::EndAlignJoystick()
{
	Check(this);

	if (flags.RIOExists)
	{
		Check(rioPointer);
		rioPointer->ForceCenterJoystick();
	}

	if (flags.joystickExists)
	{
		Check(joystickPointer);
		joystickPointer->EndAlignment();
	}
	Check_Fpu();
}

void
	LBE4ControlsManager::CenterRIOJoystick()
{
	Check(this);

	if (flags.RIOExists)
	{
		Check(rioPointer);
		rioPointer->ForceCenterJoystick();
	}

	Check_Fpu();
}

void
	LBE4ControlsManager::SetJoystickDeadBand(Scalar dead_band)
{
	Check(this);

	if (flags.RIOExists)
	{
		Check(rioPointer);
		rioPointer->SetJoystickDeadBand(dead_band);
	}

	if (flags.joystickExists)
	{
		Check(joystickPointer);
		joystickPointer->SetDeadBand(dead_band);
	}

	Check_Fpu();
}

void
	LBE4ControlsManager::SetThrottleDeadBand(Scalar dead_band)
{
	Check(this);

	if (flags.RIOExists)
	{
		Check(rioPointer);
		rioPointer->SetThrottleDeadBand(dead_band);
	}
	Check_Fpu();
}

void
	LBE4ControlsManager::SetPedalsDeadBand(Scalar dead_band)
{
	Check(this);

	if (flags.RIOExists)
	{
		Check(rioPointer);
		rioPointer->SetPedalsDeadBand(dead_band);
	}
	Check_Fpu();
}

const char*
	LBE4ControlsManager::GetLampName(int lamp_number)
{
	const char
		*lamp_name[] =
			{
				"AuxLowerRight8",
				"AuxLowerRight7",
				"AuxLowerRight6",
				"AuxLowerRight5",
				"AuxLowerRight4",
				"AuxLowerRight3",
				"AuxLowerRight2",
				"AuxLowerRight1",
				"AuxLowerLeft8",
				"AuxLowerLeft7",
				"AuxLowerLeft6",
				"AuxLowerLeft5",
				"AuxLowerLeft4",
				"AuxLowerLeft3",
				"AuxLowerLeft2",
				"AuxLowerLeft1",
				"Secondary1",			//=0x10
				"Secondary2",
				"Secondary3",
				"Secondary4",
				"Secondary5",
				"Secondary6",
				"TeslaRelay3",
				"undefined_0x17",
				"Secondary7",			//=0x18
				"Secondary8",
				"Secondary9",
				"Secondary10",
				"Secondary11",
				"Secondary12",
				"TeslaRelay1",
				"TeslaRelay2",
				"AuxUpperCenter8",	//=0x20
				"AuxUpperCenter7",
				"AuxUpperCenter6",
				"AuxUpperCenter5",
				"AuxUpperCenter4",
				"AuxUpperCenter3",
				"AuxUpperCenter2",
				"AuxUpperCenter1",
				"AuxUpperLeft8",		//=0x28
				"AuxUpperLeft7",
				"AuxUpperLeft6",
				"AuxUpperLeft5",
				"AuxUpperLeft4",
				"AuxUpperLeft3",
				"AuxUpperLeft2",
				"AuxUpperLeft1",
				"AuxUpperRight8",		//=0x30
				"AuxUpperRight7",
				"AuxUpperRight6",
				"AuxUpperRight5",
				"AuxUpperRight4",
				"AuxUpperRight3",
				"AuxUpperRight2",
				"AuxUpperRight1",
				"PanicButton",			//=0x38
				"IcomAmpEnableRelay",//=0x39
				"IcomIncRelay",		//=0x3A
				"IcomPTTRelay",		//=0x03B
				"IcomDecRelay",		//=0x3C
				"undefined_0x3D",		//=0x3D unimplemented convenience lamp
				"FloorEntry"			//=0x3E
			};

	if (lamp_number >= LampCount)
	{
		return "overrange";
	}
	else if (lamp_number < 0)
	{
		return "underrange";
	}
	else
	{
		return lamp_name[lamp_number];
	}
}


#define ID_ENTRY(name,group)\
	{ #name , LBE4ControlsManager::##group, LBE4ControlsManager::##name }

struct {
	const char* controlName;
	Enumeration
		controlsGroup,
		controlsElement;
}
	Control_IDs[]=
{
	//-----------------------------------------------
	// Throttle
	//-----------------------------------------------
	ID_ENTRY(ScalarThrottle,ScalarGroup),
	ID_ENTRY(ButtonThrottle1,ButtonGroup),

	//-----------------------------------------------
	// Pedals
	//-----------------------------------------------
	ID_ENTRY(ScalarLeftPedal,ScalarGroup),
	ID_ENTRY(ScalarRightPedal,ScalarGroup),

	//-----------------------------------------------
	// Joystick
	//-----------------------------------------------
	ID_ENTRY(JoystickPhysical,JoystickGroup),
	ID_ENTRY(ButtonJoystickHatUp,ButtonGroup),
	ID_ENTRY(ButtonJoystickHatDown,ButtonGroup),
	ID_ENTRY(ButtonJoystickHatLeft,ButtonGroup),
	ID_ENTRY(ButtonJoystickHatRight,ButtonGroup),
	ID_ENTRY(ButtonJoystickTrigger,ButtonGroup),
	ID_ENTRY(ButtonJoystickThumbHigh,ButtonGroup),
	ID_ENTRY(ButtonJoystickThumbLow,ButtonGroup),
	ID_ENTRY(ButtonJoystickPinky,ButtonGroup),

	//-----------------------------------------------
	// Secondary buttons
	//-----------------------------------------------
	ID_ENTRY(ButtonSecondary1,ButtonGroup),
	ID_ENTRY(ButtonSecondary2,ButtonGroup),
	ID_ENTRY(ButtonSecondary3,ButtonGroup),
	ID_ENTRY(ButtonSecondary4,ButtonGroup),
	ID_ENTRY(ButtonSecondary5,ButtonGroup),
	ID_ENTRY(ButtonSecondary6,ButtonGroup),
	ID_ENTRY(ButtonSecondary7,ButtonGroup),
	ID_ENTRY(ButtonSecondary8,ButtonGroup),
	ID_ENTRY(ButtonSecondary9,ButtonGroup),
	ID_ENTRY(ButtonSecondary10,ButtonGroup),
	ID_ENTRY(ButtonSecondary11,ButtonGroup),
	ID_ENTRY(ButtonSecondary12,ButtonGroup),

	//-----------------------------------------------
	// Upper left auxiliary
	//-----------------------------------------------
	ID_ENTRY(ButtonAuxUpperLeft8,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperLeft7,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperLeft6,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperLeft5,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperLeft4,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperLeft3,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperLeft2,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperLeft1,ButtonGroup),

	//-----------------------------------------------
	// Center auxiliary
	//-----------------------------------------------
	ID_ENTRY(ButtonAuxUpperCenter8,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperCenter7,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperCenter6,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperCenter5,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperCenter4,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperCenter3,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperCenter2,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperCenter1,ButtonGroup),

	//-----------------------------------------------
	// Upper right auxiliary
	//-----------------------------------------------
	ID_ENTRY(ButtonAuxUpperRight8,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperRight7,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperRight6,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperRight5,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperRight4,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperRight3,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperRight2,ButtonGroup),
	ID_ENTRY(ButtonAuxUpperRight1,ButtonGroup),

	//-----------------------------------------------
	// Lower left auxiliary
	//-----------------------------------------------
	ID_ENTRY(ButtonAuxLowerLeft8,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerLeft7,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerLeft6,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerLeft5,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerLeft4,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerLeft3,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerLeft2,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerLeft1,ButtonGroup),

	//-----------------------------------------------
	// Lower right auxiliary
	//-----------------------------------------------
	ID_ENTRY(ButtonAuxLowerRight8,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerRight7,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerRight6,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerRight5,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerRight4,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerRight3,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerRight2,ButtonGroup),
	ID_ENTRY(ButtonAuxLowerRight1,ButtonGroup),

	//-----------------------------------------------
	// Miscellaneous buttons
	//-----------------------------------------------
	ID_ENTRY(ButtonPanic,ButtonGroup),
	ID_ENTRY(ButtonDoor,ButtonGroup)
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	LBE4ControlsManager::SetControlMappingID(
		const char *control_name,
		ControlsMapping *mapping
	)
{
	Check_Pointer(control_name);
	Check_Pointer(mapping);

	int i;
	for (i=0; i<ELEMENTS(Control_IDs); ++i)
	{
		if (!stricmp(control_name, Control_IDs[i].controlName))
		{
			mapping->controlsGroup = Control_IDs[i].controlsGroup;
			mapping->controlsElement = Control_IDs[i].controlsElement;
			break;
		}
	}
	Check_Fpu();
	return i < ELEMENTS(Control_IDs);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Lamp *
	LBE4ControlsManager::MakeLinkedLamp(
		int element,
		ModeMask mode_mask
	)
{
	Check(this);
	Check(application);

	if (application->GetGaugeRenderer() == NULL)
	{
   	return NULL;
	}
	else
	{
		Check(application->GetGaugeRenderer());

		//------------------------------------------
		// Create the lamp
		//------------------------------------------
		L4LampManager
			*lamp_manager = (L4LampManager *) application->
				GetGaugeRenderer()->GetLampManager();
		// DEV-COMPOSITE GUARD: when the gauge renderer is woken (BT_DEV_GAUGES) but its
		// reconstructed lamp manager isn't wired (GetLampManager returns NULL -- a recon
		// offset gap in BTL4GaugeRenderer), skip the linked cockpit lamp rather than
		// constructing an L4Lamp with a NULL manager (crashes in LampManager::AddLamp).
		// The lamp is a secondary cockpit indicator; inert on the pod (manager valid).
		if (lamp_manager == NULL)
			return NULL;
		Check(lamp_manager);

		Lamp
			*lamp = new L4Lamp(
				element,
				mode_mask,
				lamp_manager
			);
		Register_Object(lamp);
		//------------------------------------------
		// Add button mapping to lamp
		//------------------------------------------
		buttonGroup[element].Add(
			mode_mask,
			&((L4Lamp *)lamp)->automaticValue,
			lamp
		);

		Check_Fpu();
		return lamp;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LBE4ControlsManager::CreateStreamedMappings(
		Entity *player,
		const int *control_mappings
	)
{
	Check(this);
	Check_Pointer(control_mappings);

	int
		controls_count = *control_mappings;
	++control_mappings;

	ControlsMapping
		*mapping = (ControlsMapping*)control_mappings;
	Simulation
		*subsystem;
	void
		*attribute;

	for (int i=0; i<controls_count; ++i)
	{
		subsystem = player->GetSimulation(mapping->subsystemID);
		Verify(subsystem);

		//
		//---------------------------
		// Handle the direct mappings
		//---------------------------
		//
		int
			element = mapping->controlsElement;
		ModeMask
			mode_mask = mapping->modeMask;

		if (mapping->mappingType == ControlsMapping::DirectMapping)
		{
			attribute = subsystem->GetAttributePointer(mapping->attributeID);
			switch (mapping->controlsGroup)
			{
			case ScalarGroup:
				scalarGroup[element].Add(
					mode_mask,
					(ControlsScalar*)attribute,
					subsystem
				);
				break;
			case JoystickGroup:
				joystickGroup[element].Add(
					mode_mask,
					(ControlsJoystick*)attribute,
					subsystem
				);
				break;
			case ButtonGroup:
				//----------------------------------------------
				// Add button
				//----------------------------------------------
				buttonGroup[element].Add(
					mode_mask,
					(ControlsButton*)attribute,
					subsystem
				);
				//----------------------------------------------
				// Create associated lamp
				//----------------------------------------------
				if(buttonType[element] == AUTOBTN)
				{
#					if DEBUG_LEVEL > 0
						Lamp *
							lamp =
#					endif
								MakeLinkedLamp(element, mode_mask);

#					if DEBUG_LEVEL > 0
						if (lamp != NULL)
						{
							Check(lamp);
						}
#					endif
				}      
				break;
			default:
				Fail("Unknown mapping group!\n");
				break;
			}
		}

		//
		//--------------------------------------------------
		// Otherwise, it is an event mapping, so set that up
		//--------------------------------------------------
		//
		else if (mapping->mappingType == ControlsMapping::EventMapping)
		{
			Receiver::MessageID
				msg_id = mapping->messageID;

			switch (mapping->controlsGroup)
			{
			case ScalarGroup:
				scalarGroup[element].Add(mode_mask,subsystem,msg_id,subsystem);
				break;
			case JoystickGroup:
				joystickGroup[element].Add(mode_mask,subsystem,msg_id,subsystem);
				break;
			case ButtonGroup:
				//----------------------------------------------
				// Add button
				//----------------------------------------------
				buttonGroup[element].Add(
					mode_mask,
					subsystem,
					msg_id,
					subsystem
				);
				//----------------------------------------------
				// Create associated lamp
				//----------------------------------------------
				if(buttonType[element] == AUTOBTN)
				{
#					if DEBUG_LEVEL > 0
						Lamp *
							lamp =
#					endif
								MakeLinkedLamp(element, mode_mask);

#					if DEBUG_LEVEL > 0
						if (lamp != NULL)
						{
							Check(lamp);
						}
#					endif
				}
				break;
			default:
				Fail("Unknown mapping group!\n");
				break;
			}
		}
		else
		{
			Fail("Unknown mapping type!\n");
		}
		mapping = (ControlsMapping*)((char*)mapping + sizeof(ControlsMapping));
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LBE4ControlsManager::EnterControlsMode(
		L4ControlsMode new_mode,
		Scalar time_until_exit,
		Receiver::MessageID message_on_exit
	)
{
	Check(this);
	l4ControlsMode = new_mode;

	exitModeTime = ((Scalar) Now()) + time_until_exit;
	exitMessageID = message_on_exit;

	SetLamp(LampPanic, RIO::flashFast+RIO::state1Off+RIO::state2Bright);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LBE4ControlsManager::ExitControlsMode()
{
	Check(this);

	l4ControlsMode = normalMode;

	int
		i;

	for(i=0; i<maxStringIDs; ++i)
	{
		if (temporaryStringID[i] != 0)
		{
			stringManager.Remove(temporaryStringID[i]);
		}
	}

	SetLamp(LampPanic, RIO::solid+RIO::state1Off+RIO::state2Off);
	Verify(heapcheck() != _HEAPCORRUPT);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LBE4ControlsManager::ManageControlsMode()
{
	Check(this);

	if (l4ControlsMode != normalMode)
	{
		if (Now() >= exitModeTime)
		{
			Receiver::Message
				message(exitMessageID, sizeof(Receiver__Message));

			Dispatch(&message);
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	LBE4ControlsManager::TestInstance() const
{
	Verify(IsDerivedFrom(*GetClassDerivations()));
	return ControlsManager::TestInstance();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	LBE4ControlsManager::AnalogHasChanged()
{
	Check(this);

	Logical
		result = False;

	if (rioPointer != NULL)
	{
		Check(rioPointer);

		result  = (rioPointer->Throttle   != previousThrottle);
		result |= (rioPointer->LeftPedal  != previousPedalLeft);
		result |= (rioPointer->RightPedal != previousPedalRight);
		result |= (rioPointer->JoystickX  != previousJoystickX);
		result |= (rioPointer->JoystickY  != previousJoystickY);

		if (result)
		{
			previousThrottle   = rioPointer->Throttle;
			previousPedalLeft  = rioPointer->LeftPedal;
			previousPedalRight = rioPointer->RightPedal;
			previousJoystickX  = rioPointer->JoystickX;
			previousJoystickY  = rioPointer->JoystickY;
		}
	}

	Check_Fpu();
	return result;
}

Logical
	LBE4ControlsManager::GetNextRIOEvent(RIO::RIOEvent *rio_event)
{
	//
	//-------------------------------------------------
	// If we aren't in playback mode, read the real RIO
	//-------------------------------------------------
	//
	if (!playbackMode)
	{
		Logical
			result = rioPointer->GetNextEvent(rio_event);
		//----------------------------------------------------------------------
		// If the RIO stream exists, and a rio event was added,
		// store this event in the rioStream
		//----------------------------------------------------------------------
		if (result && streamEnabled)
		{
			Check(rioStream);
			if (rioStream->GetBytesRemaining() >= sizeof(RIOStreamEvent))
			{
				Check(rioStream);
				RIOStreamEvent
					*event = (RIOStreamEvent*)rioStream->GetPointer();
				Check_Pointer(event);

				event->timeStamp.ticks = Now().ticks - streamStart.ticks;

				if (rio_event->Type == RIO::AnalogEvent)
				{
					if (AnalogHasChanged())
					{
DEBUG_STREAM << "AnalogEvent at" << event->timeStamp.ticks << "\n";
DEBUG_STREAM << "Pointer=" << std::hex << event << std::dec << "\n";
						//----------------------------
						// Set the event type
						//----------------------------
						event->eventType = analog;
						//----------------------------
						// Get the analog state
						//----------------------------
						event->data.analog.throttle   = rioPointer->Throttle;
						event->data.analog.pedalLeft  = rioPointer->LeftPedal;
						event->data.analog.pedalRight = rioPointer->RightPedal;
						event->data.analog.joystickX  = rioPointer->JoystickX;
						event->data.analog.joystickY  = rioPointer->JoystickY;
						//----------------------------
						// Save the event
						//----------------------------
						rioStream->AdvancePointer(sizeof(RIOStreamEvent));
					}
				}
				else
				{
DEBUG_STREAM << "DigitalEvent at" << event->timeStamp.ticks << "\n";
DEBUG_STREAM << "Pointer=" << std::hex << event << std::dec << "\n";
					//----------------------------
					// Set the event type
					//----------------------------
					event->eventType = digital;
					//----------------------------
					// Save the event
					//----------------------------
					event->data.rioEvent = *rio_event;
					rioStream->AdvancePointer(sizeof(RIOStreamEvent));
				}
			}
		}
		return result;
	}

	//
	//----------------------------------------------------------------------
	// Otherwise, we are in playback mode, so get the next record out of the
	// memory block
	//----------------------------------------------------------------------
	//
	else if (streamEnabled)
	{
		Check(rioStream);
		if (rioStream->GetBytesRemaining() >= sizeof(RIOStreamEvent))
		{
			RIOStreamEvent
				*event = (RIOStreamEvent*)rioStream->GetPointer();

			Check_Pointer(event);
			Time
				now;

			now.ticks = Now().ticks - streamStart.ticks;

			if (event->timeStamp.ticks <= now.ticks)
			{
DEBUG_STREAM << "Event timestamp=" << event->timeStamp.ticks << "\n";
				switch(event->eventType)
				{
				case analog:
DEBUG_STREAM << "AnalogEvent\n";
					//----------------------------
					// Send event type ONLY
					//----------------------------
					rio_event->Type = RIO::AnalogEvent;
					//----------------------------
					// Restore the analog state
					//----------------------------
					rioPointer->Throttle   = event->data.analog.throttle;
					rioPointer->LeftPedal  = event->data.analog.pedalLeft;
					rioPointer->RightPedal = event->data.analog.pedalRight;
					rioPointer->JoystickX  = event->data.analog.joystickX;
					rioPointer->JoystickY  = event->data.analog.joystickY;
					break;

				case digital:
DEBUG_STREAM << "DigitalEvent\n";
					//----------------------------
					// Send the entire event
					//----------------------------
					*rio_event = event->data.rioEvent;
					break;
				}
				rioStream->AdvancePointer(sizeof(RIOStreamEvent));
				return True;
			}
		}
	}
	return False;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LBE4ControlsManager::ProcessRIOEvent(
		RIO::RIOEvent &rio_event,
		Logical *new_RIO_values
	)
{
	//--------------------------------------------------
	// Get mode mask for updating
	//--------------------------------------------------
	Check(application);
	Check(application->GetModeManager());
	ModeMask
		mode_mask = application->GetModeManager()->GetModeMask();
	ControlsButton temp;

	switch(rio_event.Type)
	{
	case RIO::ButtonPressedEvent:
		Verify(rio_event.Data.Unit >= 0);
		Verify(rio_event.Data.Unit < ButtonCount);
		temp = rio_event.Data.Unit + 1;

		Check(&buttonGroup[rio_event.Data.Unit]);
		buttonGroup[rio_event.Data.Unit].Update(&temp, mode_mask);
		//----------------------------------------
		// Save 'pressed' mode mask for 'release'
		//----------------------------------------
		buttonActivateModeMask[rio_event.Data.Unit] = mode_mask;
		break;

	case RIO::ButtonReleasedEvent:
		Verify(rio_event.Data.Unit >= 0);
		Verify(rio_event.Data.Unit < ButtonCount);
		temp = -rio_event.Data.Unit - 1;

		Check(&buttonGroup[rio_event.Data.Unit]);
		//----------------------------------------
		// Use 'pressed' mode mask for 'release'
		//----------------------------------------
		buttonGroup[rio_event.Data.Unit].Update(
			&temp,
			buttonActivateModeMask[rio_event.Data.Unit]
		);
		break;

	case RIO::KeyEvent:
		{
			Verify(rio_event.Data.Keyboard.Unit >= 0);
			Verify(rio_event.Data.Keyboard.Unit < KeyboardCount);
			//--------------------------------------
			// Convert key to ASCII
			//--------------------------------------
			ControlsKey
				new_key = rio_event.Data.Keyboard.Key;
			if (new_key <= 9)
			{
				new_key += '0';
			}
			else
			{
				new_key += 'A'-10;
			}
			Test_Tell(
				"LBE4ControlsManager::Execute, new_key=" << (int) new_key <<
				"\n"
			);

			Check(&keyboardGroup[rio_event.Data.Keyboard.Unit]);
			keyboardGroup[rio_event.Data.Keyboard.Unit].
				ForceUpdate(&new_key, mode_mask);

			Check(&stringManager);
			stringManager.Update(
				rio_event.Data.Keyboard.Unit,
				new_key
			);
		}
		break;

	case RIO::AnalogEvent:
		lastRioRequest = 0L;		// force another RIO update request
		*new_RIO_values = True;
		break;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LBE4ControlsManager::SaveRecording(const char* file_name)
{
	Check(this);
	Check_Pointer(file_name);
	Check(rioStream);

	FileStream
		output(file_name, True);
	size_t
		length = rioStream->GetBytesUsed();
	if (length)
	{
		rioStream->Rewind();

		output << length;
		output << *rioStream;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LBE4ControlsManager::LoadPlayback(const char* file_name)
{
	Check(this);
	Check_Pointer(file_name);

	FileStream
		input(file_name);
	Verify(input.IsFileOpened());

	//
	//-------------------------------
	// Figure out how big the file is
	//-------------------------------
	//
	size_t
		spool_size = 0;
	input >> spool_size;
	Verify(spool_size);
	Verify(spool_size <= RIO_SPOOL_SIZE * sizeof(RIOStreamEvent));

	RIOStreamEvent
		*events = new RIOStreamEvent[RIO_SPOOL_SIZE];
	Register_Pointer(events);

	Verify(rioStream == NULL);
	rioStream = new MemoryStream(events, spool_size);
	Register_Object(rioStream);

	input.ReadBytes(events, spool_size);
}

#if defined(TEST_CLASS)
#	include "l4ctrl.tcp"
#endif
