/*****************************************************************************
 * $Id: game_pony_keyboard.cc,v 1.34 2006/08/26 21:47:51 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Keyboard.
 * @remarks	By design, key presses with counterparts to GUI objects
 *			don't affect the config file.  The idea is that the keyboard
 *			causes only temporary changes.  Only when the user makes an
 *			effort to manipulate the GUI should the configuration be saved.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define GAME_PONY_KEYBOARD_CC 1
#include "game_pony_common.hh"
#include "game_pony_keyboard.hh"
#include "game_joystick.hh"
#include "game_base_craft.hh"
#include "game_base_chase_plane.hh"
#include "game_base_combat.hh"
#include "game_pony_gui.hh"
#include "game_pony_craft.hh"
#include "gfx_stream.hh"

const fp stride = WORLD(40.0);  // for manual translation of object

static int sThrottlePercentTable[10] =
{
	0,		// 0
	20,		// 1
	30,		// 2
	40,		// 3
	50,		// 4
	60,		// 5
	70,		// 6
	80,		// 7
	90,		// 8
	100,	// 9
};

/*****************************************************************************
 * Shift/Ctrl magnify/minify maneuvering with cursor keys.
 * Control response is temporarily changed.
 *****************************************************************************/
#define CHANGE_CONTROL_RESPONSE											\
	Percent responseSaved = GetControlResponse();						\
	if (      keyEvent.mShift ) SetControlResponse( dyna, 100 );		\
	else if ( keyEvent.mCtrl  ) SetControlResponse( dyna, 10  );

#define RESTORE_CONTROL_RESPONSE										\
	SetControlResponse( dyna, responseSaved );

/*****************************************************************************
 * Poll keyboard queue and process all events until queue is empty.
 *****************************************************************************/
void
GamePony::PollKeyboard( void )
{
	for (;;)
	{
		KeyEvent keyEvent;
		if ( mInputQueue.GetKeyEvent( &keyEvent ) )
		{ 
			// timer() will display if redisplayInput is set.
			// Calling glutPostRedisplay() here out-of-sync with timer()
			// would result in turbulent rendering.
			// -- Merge not assign since either keyboard() or keyboard_special() can set this --
			if ( keyEvent.mSpecial )
			{
				if ( KeyboardSpecial( *gEng.GetCurrent(), keyEvent ) ) gEng.mView.PostRedisplayInput();
			}
			else
			{
				if ( Keyboard( *gEng.GetCurrent(), keyEvent ) ) gEng.mView.PostRedisplayInput();
			}
		}
		else
			break;
	}
}

/*****************************************************************************
 * @return True if the user has pressed any key.
 * Used to clear the splash screen.
 *****************************************************************************/
static bool sAnyKeyPressed = false;
bool
GamePony::IfAnyKeyPressed( void )
{
	return sAnyKeyPressed;
}

/*****************************************************************************
 * Helpers.
 *****************************************************************************/
static bool
IfImportantKey( int key )
{
	switch ( key )
	{
		case eGfxKey_ESC:
		case eGfxKey_CTRL_C:
      //case eGfxKey_CTRL_X:  // redefined as slowly yaw
		case eGfxKey_CTRL_P:
		case eGfxKey_HOME:
		case eGfxKey_END:
		{
			return true;
		}
		break;

		default:
		{
			return false;
		}
		break;
	}
    return false;  // shutup compiler
}

/*****************************************************************************
 * Keyboard handler (ASCII keys).
 * @return True if redisplay is needed.
 *****************************************************************************/
int
GamePony::Keyboard( Dyna& dyna, const KeyEvent& keyEvent )
{
	int key       = keyEvent.mKey;
	int keylc     = tolower( key );
//	bool modifier = keyEvent.mShift | keyEvent.mCtrl | keyEvent.mAlt;

	if ( ! GFX_IF_READY() )
		return false; // unexpected

CHECK_TYPESIG( &dyna, TYPESIG_DYNA );

	if ( ! sAnyKeyPressed ) gGui.SplashOff();
	sAnyKeyPressed = true;

	// Ignore most keys if paused.
	if ( gEng.mView.IfFreeze()  &&  ! IfImportantKey( key ) )
		return false;

	// Cases that affect the display should do ++redisplay.
	int redisplay = 0;
	switch ( key )
	{
		// q,w : [object] rotate Z axis (roll)
		case 'q': case 'w':
		{
			dyna.RotateInc( AXIS_Z, PLUS_MINUS_1(&dyna==&gEng.mEye ? key=='w' : key=='q') );
			++redisplay;
		}
		break;

		// a,s : [object] rotate X axis (pitch)
		case 'a': case 's':
		{
			dyna.RotateInc( AXIS_X, PLUS_MINUS_1(&dyna==&gEng.mEye ? key=='s' : key=='a') );
			++redisplay;
		}
		break;

		// z,x : [object] rotate Y axis (yaw)
		case 'z': case 'x':
		case 'Z': case 'X':
		case eGfxKey_CTRL_Z:
		case eGfxKey_CTRL_X:
		{
			CHANGE_CONTROL_RESPONSE;
			dyna.RotateInc( AXIS_Y, PLUS_MINUS_1(keylc == 'z' || key == eGfxKey_CTRL_Z) );
			RESTORE_CONTROL_RESPONSE;
			++redisplay;
		}
		break;

		// e,r (forward/backward): [object] translate Z
		case 'e': case 'r':
		{
			dyna.TranslateInc( AXIS_Z, PLUS_MINUS_1(key == 'e'), stride );
			++redisplay;
		}
		break;

		// d,f (left/right): [object] translate X
		case 'd': case 'f':
		{
			dyna.TranslateInc( AXIS_X, PLUS_MINUS_1(key == 'd') * PLUS_MINUS_1(&dyna==&gEng.mEye), stride );
			++redisplay;
		}
		break;

		// c,v (up/down): [object] translate Y
		case 'c': case 'v':
		{
			dyna.TranslateInc( AXIS_Y, PLUS_MINUS_1(key == 'v') * PLUS_MINUS_1(&dyna==&gEng.mEye), stride );
			++redisplay;
		}
		break;

		// o,p: [viewpoint] rotate eye roll
		case 'o': case 'p':
		{
			gEng.mEye.RotateInc( AXIS_Z, PLUS_MINUS_1(key == 'p') );
			++redisplay;
		}
		break;

		// l,';': [viewpoint] rotate eye pitch
		case 'l': case ';':
		{
			gEng.mEye.RotateInc( AXIS_X, PLUS_MINUS_1(key == ';') );
			++redisplay;
		}
		break;

		// '.','/': [viewpoint] rotate yaw
		case '.': case '/':
		{
			gEng.mEye.RotateInc( AXIS_Y, PLUS_MINUS_1(key == '.') );
			++redisplay;
		}
		break;

		// u,i: [viewpoint] translate Z
		case 'u': case 'i':
		{
			gEng.mEye.TranslateInc( AXIS_Z, PLUS_MINUS_1(key == 'u'), stride );
			++redisplay;
		}
		break;

		// j,k: [viewpoint] translate X
		case 'j': case 'k':
		{
			gEng.mEye.TranslateInc( AXIS_X, PLUS_MINUS_1(key == 'j'), stride );
			++redisplay;
		}
		break;

		// m,',: [viewpoint] translate Y
		case 'm': case ',':
		{
			gEng.mEye.TranslateInc( AXIS_Y, PLUS_MINUS_1(key == 'm'), stride );
			++redisplay;
		}
		break;

		// g : toggle landing gear
		case 'g':
		{
			CraftF14Tomcat* tomcat = GetCraftF14TomcatOrNULL();
            if ( tomcat != NULL )
            {
                tomcat->ToggleLandingGears();
            }
			++redisplay;
		}
		break;

		// 1..0 (speed)
		// Subtle dual-purpose:
		// It functions like a throttle when only keyboard input is available.
		// For a joystick, it will amplify/diminish its throttle/wheel response.
		case '0':
		{
			dyna.SetThrottle( sThrottlePercentTable[key-'0'] );
			dyna.SetAnimation( eAnimate_NONE ); // stop
			++redisplay;
		}
		break;

		case '1': case '2': case '3': case '4': case '5':
		case '6': case '7': case '8': case '9':
		{
			dyna.SetThrottle( sThrottlePercentTable[key-'0'] );
			if ( &dyna != &gEng.mEye || gEng.mEye.mEyeMode == Eye::eEyeMode_FLIGHT )
				dyna.SetAnimation( eAnimate_AUTO_TRANSLATE );
			dyna.Revive(); // let the user cheat after crashing
			++redisplay;
		}
		break;

		// 1..0 with SHIFT (rotation rate)
		// Affects keyboard and joystick response.
		case '!': // 1
		{
			SetControlResponse( dyna, 10 );
			gGui.UpdateControlResponse();
		}
		break;

		case '@': // 2
		{
			SetControlResponse( dyna, 20 );
			gGui.UpdateControlResponse();
		}
		break;

		case '#': // 3
		{
			SetControlResponse( dyna, 30 );
			gGui.UpdateControlResponse();
		}
		break;

		case '$': // 4
		{
			SetControlResponse( dyna, 40 );
			gGui.UpdateControlResponse();
		}
		break;

		case '%': // 5
		{
			SetControlResponse( dyna, 50 );
			gGui.UpdateControlResponse();
		}
		break;

		case '^': // 6
		{
			SetControlResponse( dyna, 60 );
			gGui.UpdateControlResponse();
		}
		break;

		case '&': // 7
		{
			SetControlResponse( dyna, 70 );
			gGui.UpdateControlResponse();
		}
		break;

		case '*': // 8
		{
			SetControlResponse( dyna, 80 );
			gGui.UpdateControlResponse();
		}
		break;

		case '(': // 9
		{
			SetControlResponse( dyna, 90 );
			gGui.UpdateControlResponse();
		}
		break;

		case ')': // 0
		{
			SetControlResponse( dyna, 100 );
			gGui.UpdateControlResponse();
		}
		break;

		// space
		case ' ':
		{
			CombatFireGuns( *mPlayer, gEng.GetCurrent() != &gEng.mEye );
			++redisplay;
		}
		break;

		// Backspace|Delete: reset
		case eGfxKey_BS: case eGfxKey_DEL:
		{
			gEng.ResetScene();
			gEng.mEye.mEyeMode = Eye::eEyeMode_DEFAULT;
			++redisplay;
		}
		break;

		// Adjust chase plane distance.
		case eGfxKey_PLUS: case eGfxKey_MINUS:
		{
			dyna.mChasePlane->IncDistance( key==eGfxKey_PLUS ? -1 : 1 );
			++redisplay;
		}
		break;

		// Save view/state.
		case eGfxKey_CTRL_S:
		{
			if ( SaveState(true) )
			{
				*gStreamOut << "saved state" << endl;
			}
			else
			{
				*gStreamError << "ERROR: couldn't save state" << endl;
			}
		}
		break;

		// pause/freeze key -- also handled by special key handler
		case eGfxKey_CTRL_P:
		{
			gEng.mView.ToggleFreeze();
			GFX_POST_REDISPLAY(); // special case
			gGui.UpdatePause();
			//++redisplay;
		}
		break;

		// ESC: toggle menu screen
		case eGfxKey_ESC:
		{
			gGui.ToggleMenu();
			++redisplay;
		}
		break;

		// (CTRL-X is recommended)
		// CTRL-X or CTRL-C: quit
		// GLUT has no kind of exit event, just call the usual C exit().
		//case eGfxKey_CTRL_X: // redefined as slow yaw
		case eGfxKey_CTRL_C:
		{
			gEng.Exit(0);
		}
		break;

		case 0:
		{
			// NOP
		}
		break;

		// Print info.
		case '?':
		{
			*gStreamOut << "seed = 0x" << std::hex << gEng.GetSeed() << std::dec << endl;
			gGameBaseEvent.mPrintStats.Call();
#if DEBUG
			*gStreamOut << *gWorld.mLocus;
			*gStreamOut << "eye position = " << gEng.mEye.GetPosition() << endl;
			gEng.SetVerbose( true );
#else
			gEng.SetVerbose( ! gEng.IfVerbose() ); // toggle verbosity
#endif
			*gStreamOut << endl;

			if ( gEng.GetCurrent() == &gEng.mEye )
			{
				*gStreamOut << "Eye matrix: \n";
			}
			else
			{
				*gStreamOut << "Dyna matrix: \n";
			}
			PrintMatrix( gEng.GetCurrent()->mCm );

			++redisplay;
		}
		break;

		// default:
		default:
		{
			*gStreamOut << "unhandled key = 0x" << std::hex << key << std::dec << endl;
		}
		break;
	}

	return redisplay;
}

/*****************************************************************************
 * Keyboard handler (special keys).
 * @return True if redisplay is needed.
 *****************************************************************************/
int
GamePony::KeyboardSpecial( Dyna& dyna, const KeyEvent& keyEvent )
{
	int key       = keyEvent.mKey;
//	int keylc     = tolower( key );
	bool modifier = keyEvent.mShift | keyEvent.mCtrl | keyEvent.mAlt;

	if ( ! GFX_IF_READY() )
		return false; // unexpected

CHECK_TYPESIG( &dyna, TYPESIG_DYNA );

	if ( ! sAnyKeyPressed ) gGui.SplashOff();
	sAnyKeyPressed = true;

	// Ignore most keys if paused.
	if ( gEng.mView.IfFreeze()  &&  ! IfImportantKey( key ) )
		return false;

	// Cases that affect the display should do ++redisplay.
	int redisplay = 0;

	switch ( key )
	{
		// [viewpoint] translate Z
		// [object] rotate X (pitch)
		case eGfxKey_UP: case eGfxKey_DOWN:
		{
			if ( ! keyEvent.mAlt )
			{
				CHANGE_CONTROL_RESPONSE;
				if ( &dyna == &gEng.mEye )
				{
					if ( gEng.mEye.mEyeMode == Eye::eEyeMode_PAN )
						dyna.TranslateInc( AXIS_Z, PLUS_MINUS_1(key == eGfxKey_UP) );
					else // Eye::eEyeMode_FLIGHT
						dyna.RotateInc( AXIS_X, PLUS_MINUS_1(key == eGfxKey_UP) );
				}
				else
				{
					dyna.RotateInc( AXIS_X, PLUS_MINUS_1(key == eGfxKey_DOWN) );
				}
				RESTORE_CONTROL_RESPONSE;
			}
			else
			{
				// Chase plane controls.
				dyna.mChasePlane->IncRotate( AXIS_X, PLUS_MINUS_1(key == eGfxKey_UP) );
			}
			++redisplay;
		}
		break;

		// [viewpoint] rotate yaw
		// [object] rotate Z (roll)
		case eGfxKey_LEFT: case eGfxKey_RIGHT:
		{
			if ( ! keyEvent.mAlt )
			{
				CHANGE_CONTROL_RESPONSE;
				if ( &dyna == &gEng.mEye )
				{
					if ( gEng.mEye.mEyeMode == Eye::eEyeMode_PAN )
						dyna.RotateInc( AXIS_Y, PLUS_MINUS_1(key == eGfxKey_LEFT) );
					else // Eye::eEyeMode_FLIGHT
						dyna.RotateInc( AXIS_Z, PLUS_MINUS_1(key == eGfxKey_RIGHT) );
				}
				else
				{
					dyna.RotateInc( AXIS_Z, PLUS_MINUS_1(key == eGfxKey_LEFT) );
				}
				RESTORE_CONTROL_RESPONSE;
			}
			else
			{
				// Chase plane controls.
				dyna.mChasePlane->IncRotate( AXIS_Y, PLUS_MINUS_1(key == eGfxKey_RIGHT) );
			}
			++redisplay;
		}
		break;

		// F1: first-person flight mode
		case eGfxKey_F1:
		{
			gGui.PrintStatus( "FLIGHT MODE" );
			gEng.mEye.mEyeMode = Eye::eEyeMode_FLIGHT;
			gEng.SetCurrent( &gEng.mEye );
			gEng.GetCurrent()->ClaimInput();
//			dyna.SetThrottlePercent( sThrottlePercentTable[1] );
//			dyna.SetAnimation( eAnimate_AUTO_TRANSLATE );
			++redisplay;
			gGui.UpdateFlightView();
		}
		break;

		// F2: pan mode
		case eGfxKey_F2:
		{
			gGui.PrintStatus( "PAN MODE" );
			gEng.mEye.mEyeMode = Eye::eEyeMode_PAN;
			gEng.SetCurrent( &gEng.mEye );
			gEng.GetCurrent()->ClaimInput();
			dyna.SetAnimation( eAnimate_NONE ); // stop moving
			gGui.UpdateFlightView();
		}
		break;

		// F3: control player #1
		case eGfxKey_F3:
		{
			SetPlayer( 1 );
			gEng.SetCurrent( mPlayer1Craft );
			gEng.GetCurrent()->ClaimInput();
			gGui.PrintStatus( "CRAFT #1 SELECTED" );
			gGui.UpdateFlightView();
		}
		break;

		// F4: control player #2
		case eGfxKey_F4:
		{
			SetPlayer( 2 );
			gEng.SetCurrent( mPlayer2Craft );
			gEng.GetCurrent()->ClaimInput();
			gGui.PrintStatus( "CRAFT #2 SELECTED" );
			gGui.UpdateFlightView();
		}
		break;

		// switch joystick/keyboard control
		case eGfxKey_F5:
		{
			gJoy.Enable( ! gJoy.IfEnabled() );
			gGui.UpdateInputDevice();
			//++redisplay;
		}
		break;

#if 0 // TODO FIXME
		// Adjust F-14 Tomcat wings.  NOP for other craft.
		case eGfxKey_F11:
		{
			CraftF14Tomcat* tomcat = GetCraftF14TomcatOrNULL();
            if ( tomcat != NULL )
            {
				tomcat->ManualWingSweep( modifier ? -1 : 1 );
            }
   			++redisplay;
		}
		break;
#endif

		// chase plane view
		case eGfxKey_F12:
		{
			if ( modifier )
			{
				dyna.mChasePlane->Reset();
				++redisplay;
			}
			else
			{
				gEng.mEye.ToggleAnimation( eAnimate_CHASE_PLANE, eAnimate_NONE );
				if ( gEng.mEye.GetAnimation() == eAnimate_CHASE_PLANE )
					gGui.PrintStatus( "CHASE PLANE VIEW ON" );
				else
					gGui.PrintStatus( "CHASE PLANE VIEW OFF" );
				//++redisplay;
			}
		}
		break;

		// TODO reinstate this when terrain-follow is reinstated
		// Toggle auto-pilot.
		case eGfxKey_F6:
		{
			*gStreamOut << "F6 auto-pilot temporarily removed \n";
		}
		break;

#if DEBUG
		case eGfxKey_F8:
		{
			PrintMatrix( gEng.GetCurrent()->mCm );
		}
		break;
#endif

		// pause/freeze key -- also handled by regular key handler
		case eGfxKey_HOME: case eGfxKey_END:
		{
			gEng.mView.ToggleFreeze();
			gGui.UpdatePause();
			GFX_POST_REDISPLAY(); // special case
			//++redisplay;
		}
		break;

		default:
		{
			*gStreamOut << "unhandled special key = 0x" << std::hex << key << std::dec << endl;
		}
		break;
	}

	return redisplay;
}
