/*****************************************************************************
 * $Id: mod_base_joystick.cc,v 1.2 2006/11/25 17:27:56 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Module joystick functions.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define MOD_BASE_JOYSTICK_CC 1
#include "joystick.hh"
using namespace gfxInput;
#include "mod_base_common.hh"
#include "mod_base_joystick.hh"
#include "mod_base_combat.hh"
#include "mod_base_event.hh"
#include "gfx_stream.hh"

// Global.
ModuleJoystick gJoy;

/*****************************************************************************
 * Poll joystick axises and buttons.
 * @return False if no event was polled.
 * @remarks A quirk in controlling the rudder using a button and the left/right axis
 * is that the yaw action will be continued after the button is released.
 * This isn't a problem as the user can intuitively stop the yaw by
 * moving the axis to its center.
 *****************************************************************************/
bool
ModuleJoystick::PollJoystick( void )
{
	if ( ! mEnabled ) return false;

	static eJoyMove		joyMove				= eJoyMove_AT_CENTER; // movement at this time
	static int			directionPitch		= 0;
	static int			directionRoll		= 0;
	static int			directionYaw		= 0;
	static int			directionThrottle	= 0;
	static float		degreePitch			= 0;	// degree of current/previous axis movement
	static float		degreeRoll			= 0;	// degree of current/previous axis movement
	static float		degreeYaw			= 0;	// degree of current/previous axis movement
	static float		degreeThrottle		= 0;	// degree of current/previous axis movement
	const float			minDegree			= 0.01;	// to avoid tiny rotations
	static bool			buttonFire			= false;
	if ( ! mAutofire ) buttonFire = false; // to disable autofire
	static bool			buttonRudder		= false;
	DynaPtrConst dyna = IfJoyEye() ? &gEng.mEye : gEng.GetCurrent();
CHECK_TYPESIG( dyna, TYPESIG_DYNA );

	//     (-)
	//      |
	// (-)--o--(+)
	//      |
	//     (+)
	// Poll for the next joystick event(s).
	// Axis and button events can be reported simultaneously.
	// Joystick can be moved either within the center zone
	// or outward in 4 directions (plus 2 for a wheel on a 3rd axis).

	bool eventFlag = gModule.mInputQueue.GetJoyEvent( &mJoyEvent );

	// -------------------------------------------------------------------------
	// pitch (tailplanes):
	if ( eventFlag && mJoyEvent.mAxis == eJoyAxis_UPDOWN )
	{
		if ( mJoyEvent.mAxisVal < 0  &&  IsJoyBeyondCenter(mCalibration.mDown.mLo) )
		{
			joyMove			= eJoyMove_PITCH_DOWN;
			directionPitch	= -1;
			degreePitch		= mResponse[0] * JoystickPositionAbs( mCalibration.mDown.mHi );
		}
		else if ( IsJoyBeyondCenter(mCalibration.mUp.mLo) )
		{
			joyMove			= eJoyMove_PITCH_UP;
			directionPitch	= 1;
			degreePitch		= mResponse[0] * JoystickPositionAbs( mCalibration.mUp.mHi );
		}
		else // moved within deadzone
		{
			joyMove			= eJoyMove_AT_CENTER;
			directionPitch	= 0;
			degreePitch		= 0;
		}
	}
	// -------------------------------------------------------------------------
	// roll (ailerons):
	if ( eventFlag && mJoyEvent.mAxis == eJoyAxis_LEFTRIGHT )
	{
		if ( mJoyEvent.mAxisVal < 0  &&  IsJoyBeyondCenter(mCalibration.mLeft.mLo) )
		{
			if ( ! buttonRudder )
			{
				joyMove			= eJoyMove_ROLL_LEFT;
				directionRoll	= -1;
				degreeRoll		= mResponse[1] * JoystickPositionAbs( mCalibration.mLeft.mHi );
			}
			else
			{
				joyMove			= eJoyMove_YAW_LEFT;
				directionYaw	= 1;
				degreeYaw		= mResponse[1] * JoystickPositionAbs( mCalibration.mLeft.mHi );
			}
		}
		else if ( IsJoyBeyondCenter(mCalibration.mRight.mLo) )
		{
			if ( ! buttonRudder )
			{
				joyMove			= eJoyMove_ROLL_RIGHT;
				directionRoll	= 1;
				degreeRoll		= mResponse[1] * JoystickPositionAbs( mCalibration.mRight.mHi );
			}
			else
			{
				joyMove			= eJoyMove_YAW_RIGHT;
				directionYaw	= -1;
				degreeYaw		= mResponse[1] * JoystickPositionAbs( mCalibration.mRight.mHi );
			}
		}
		else // moved within deadzone
		{
			joyMove			= eJoyMove_AT_CENTER;
			directionRoll	= 0;		// clear both (shared case)
			directionYaw	= 0;
			degreeRoll		= 0;
			degreeYaw		= 0;
		}
	}
	// -------------------------------------------------------------------------
	// yaw (rudder) or throttle:    (see above comments about yaw behavior)
	if ( eventFlag && mJoyEvent.mAxis == eJoyAxis_THIRD && mAxis3 != eJoyAxis3_DISABLED )
	{
		if ( mJoyEvent.mAxisVal > 0  &&  IsJoyBeyondCenter(mCalibration.mSlow.mLo) )
		{
			if ( EX(mAxis3==eJoyAxis3_THROTTLE) )
			{
				joyMove				= eJoyMove_THROTTLE_SLOW;
				directionThrottle	= 1; // degreeThrottle multiplied by response[2] below
				degreeThrottle		= JoystickPositionAbs( mCalibration.mSlow.mHi );
			}
			else // eJoyAxis3_RUDDER
			{
				joyMove			= eJoyMove_YAW_LEFT;
				directionYaw	= -1;
				degreeYaw		= mResponse[2] * JoystickPositionAbs( mCalibration.mSlow.mHi );
			}
		}
		else if ( IsJoyBeyondCenter(mCalibration.mFast.mLo) )
		{
			if ( EX(mAxis3==eJoyAxis3_THROTTLE) )
			{
				joyMove				= eJoyMove_THROTTLE_FAST;
				directionThrottle	= -1; // degreeThrottle multiplied by response[2] below
				degreeThrottle		= JoystickPositionAbs( mCalibration.mFast.mHi );
			}
			else // eJoyAxis3_RUDDER
			{
				joyMove			= eJoyMove_YAW_RIGHT;
				directionYaw	= 1;
				degreeYaw		= mResponse[2] * JoystickPositionAbs( mCalibration.mFast.mHi );
			}
		}
		else // moved within deadzone
		{
			joyMove				= eJoyMove_AT_CENTER;
			directionThrottle	= 0;	// clear both throttle and yaw (shared case)
			directionYaw		= 0;
			degreeThrottle		= 0;
			degreeYaw			= 0;
		}
	}
	// -------------------------------------------------------------------------
	// button:
	bool buttonFireJustPressed = false;
	if ( eventFlag && mJoyEvent.mButton != eJoyButton_NONE )
	{
		switch ( mJoyEvent.mButton )
		{
			case eJoyButton_1:
			buttonFire = buttonFireJustPressed = mJoyEvent.mButtonVal;
			break;

			case eJoyButton_2: case eJoyButton_4:
			buttonRudder = mJoyEvent.mButtonVal; // (see above comments about yaw behavior)
			break;

			default: break;
		}
	}
	if ( buttonFire ) // also handles autofire
	{
		static int pollsButton = 0;
		// Limit rate of autofire by skipping a few polls but
		// fire immediately when button transitions to pressed.
		if ( mAutofire || buttonFireJustPressed || ((pollsButton++ & 1) == 0) )
		{
			gModuleBaseEvent.mJoystickButton.Broadcast( mJoyEvent.mButton );
		}
	}
	// -------------------------------------------------------------------------

	if ( IfJoystickCalibrationMode() )
	{
		RecordJoystickRaw( mJoyEvent.mAxisVal, joyMove );
		GFX_POST_REDISPLAY();
		return false;
	}

	// If there was no joystick axis event this time,
	// continue the previous axis event(s) if any.
	// No further actions if all joystick axises are at their deadzones/centers.
	if ( (directionPitch|directionRoll|directionYaw|directionThrottle) == 0 )
		return eventFlag; // return flag, not false, in case of button event with pending axis event

	if ( UX(IfPrintJoystick() && joyMove != eJoyMove_AT_CENTER) )
		PrintJoystickRaw( mJoyEvent.mAxisVal, joyMove );

	// Rotate object in up to 3 axises.
	// Adjust degree and direction of rotation.
	const float fixPitch = IfJoyEye() ? -1.0 :  1.0;
	const float fixRoll  = IfJoyEye() ?  1.0 : -1.0;
	const float fixYaw   = 1.0;
	if ( ABS(degreePitch) > minDegree ) dyna->Rotate( AXIS_X, fixPitch * degreePitch * directionPitch );
	if ( ABS(degreeRoll)  > minDegree ) dyna->Rotate( AXIS_Z, fixRoll  * degreeRoll  * directionRoll  );
	if ( ABS(degreeYaw)   > minDegree ) dyna->Rotate( AXIS_Y, fixYaw   * degreeYaw   * directionYaw   );

	// Translate object if throttle moved.
	if ( directionThrottle )
	{
		// Need event value in absolute range (forward movement only).
		// {-n,..,+n} --> {0,..,+n*2}
// ### TODO HOW TO CHANGE THROTTLE?????? #################
		float throttle = 1 - degreeThrottle*directionThrottle;
//		dyna->Throttle( throttle * mResponse[2] );
		dyna->SetThrottle( int(throttle * 100/2) );
	}

	// Redisplaying is necessary.
	gEng.mView.PostRedisplayInput();
	return eventFlag;
}

/*****************************************************************************
 * @return An absolute fraction of the joystick's position in one axis.
 * Close to 1 would be returned if the joystick is moved all the way.
 *****************************************************************************/
float
ModuleJoystick::JoystickPositionAbs( int high )
{
	if ( UX(high==0) ) { *gStreamError << "ERROR: joystick high=0, correcting" << endl; high = 1; }
	if ( ABS(mJoyEvent.mAxisVal) > ABS(high) ) return 1.0; // impose ceiling
	return ABS( float(mJoyEvent.mAxisVal) / float(high) );
}

/*****************************************************************************
 * Set/scale the software's responsiveness to joystick movement.
 *****************************************************************************/
void
ModuleJoystick::SetJoystickResponse( float r0, float r1, float r2 )
{
	mResponse[0] = mResponseSaved[0] = r0;
	mResponse[1] = mResponseSaved[1] = r1;
	mResponse[2] = mResponseSaved[2] = r2;
}
void
ModuleJoystick::DefaultJoystickResponse( void )
{
	// NOP if user specified joystick response (-jr).
	if ( ! gModule.mCmdargBase.mJoyResponse )
	{
		mResponse[0] = mResponseDefault;
		mResponse[1] = mResponseDefault;
		mResponse[2] = mResponseDefault;
	}
}
void
ModuleJoystick::ScaleJoystickResponse( float s0, float s1, float s2 )
{
	mResponse[0] = mResponseSaved[0] * s0;
	mResponse[1] = mResponseSaved[1] * s1;
	mResponse[2] = mResponseSaved[2] * s2;
}
void
ModuleJoystick::ScaleJoystickResponse( float s )
{
	ScaleJoystickResponse( s, s, s );  // same scale for all axises
}
void
ModuleJoystick::GetJoystickResponse( float* r0, float* r1, float* r2 )
{
	*r0 = mResponse[0];
	*r1 = mResponse[1];
	*r2 = mResponse[2];
}

/*****************************************************************************
 * Set joystick's axis for throttle or rudder control.
 *****************************************************************************/
bool
ModuleJoystick::SetJoystickAxis3( eJoyAxis3 a )
{
	mAxis3 = a;
	return true;
}
eJoyAxis3
ModuleJoystick::GetJoystickAxis3( void )
{
	return mAxis3;
}

/*****************************************************************************
 * Enable/disable autofire.
 *****************************************************************************/
void
ModuleJoystick::EnableJoystickAutofire( bool f )
{
	mAutofire = f;
}

/*****************************************************************************
 * Enable/disable printing joystick settings/values.
 *****************************************************************************/
void
ModuleJoystick::EnablePrintJoystick( bool f )
{
	mPrintJoystick = f;
}
bool
ModuleJoystick::IfPrintJoystick( void )
{
	return mPrintJoystick;
}

/*****************************************************************************
 * Print raw joystick axis value (the values that come from Joystick class).
 *****************************************************************************/
void
ModuleJoystick::PrintJoystickRaw( int value, eJoyMove joyMove )
{
	if ( value == 0 ) return;

	switch ( joyMove )
	{
		case eJoyMove_PITCH_UP: case eJoyMove_PITCH_DOWN:
			*gStreamOut << "joystick up/down    = " << value << endl;
		break;

		case eJoyMove_ROLL_LEFT: case eJoyMove_ROLL_RIGHT:
			*gStreamOut << "joystick left/right = " << value << endl;
		break;

		case eJoyMove_YAW_LEFT: case eJoyMove_YAW_RIGHT:
			*gStreamOut << "joystick yaw = " << value << endl;
		break;

		case eJoyMove_THROTTLE_SLOW:  case eJoyMove_THROTTLE_FAST:
			*gStreamOut << "joystick throttle = " << value << endl;
		break;

		default:
		break;
	}
}

/*****************************************************************************
 * Record raw joystick value.
 *****************************************************************************/
void
ModuleJoystick::RecordJoystickRaw( int value, eJoyMove joyMove )
{
	if ( value == 0 ) return;

	switch ( joyMove )
	{
		case eJoyMove_PITCH_UP:
			mJoyval.mUp = value;
		break;

		case eJoyMove_PITCH_DOWN:
			mJoyval.mDown = value;
		break;

		case eJoyMove_ROLL_LEFT:
			mJoyval.mLeft = value;
		break;

		case eJoyMove_ROLL_RIGHT:
			mJoyval.mRight = value;
		break;

		case eJoyMove_THROTTLE_SLOW:	// axis3
		case eJoyMove_YAW_LEFT:			// same thing
			mJoyval.mSlow = value;
		break;

		case eJoyMove_THROTTLE_FAST:	// axis3
		case eJoyMove_YAW_RIGHT:		// same thing
			mJoyval.mFast = value;
		break;

		default:
		break;
	}
}

/*****************************************************************************
 * When in calibration mode, joystick events are recorded in a Joyval object
 * but are otherwise ignored.
 *****************************************************************************/
bool
ModuleJoystick::IfJoystickCalibrationMode( void )
{
	return mCalibratingJoystick;
}
void
ModuleJoystick::SetJoystickCalibrationMode( bool f )
{
	mCalibratingJoystick = f;
}

/*****************************************************************************
 * Misc.
 *****************************************************************************/
bool
ModuleJoystick::IfJoyEye( void )
{
	return gEng.mEye.HaveInput();
}

bool
ModuleJoystick::IsJoyBeyondCenter( int lo )
{
	return ABS(mJoyEvent.mAxisVal) > ABS(lo);
}
