/*****************************************************************************
 * $Id: mod_base.cc,v 1.3 2006/11/25 17:27:23 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Module classes.
 * @remarks	Some #includes are intentionally redundant in order for the
 *          to be noticed by makedepend.  Eg, mod_class.hh includes
 *          mod_base_class.hh but makedepend won't notice further levels.
 *          
 *          See mod_base_init.cc about the distinctions between
 *          construction, initialization, and starting.
 *//*
 * 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_CC 1
#include "mod_base_common.hh"
#include "mod_base_combat.hh"
#include "mod_base_joystick.hh"
#include "mod_base_craft.hh"
#include "mod_base_event.hh"
#include "mod_config.hh"
#include "eng_dyna.hh"
#include "gfx_file.hh"

/*****************************************************************************
 * Start module.
 * Construction and starting has to be done at different times.
 *****************************************************************************/
void
Module::Start( void )
{
	// Order dependent.
	SelfChecks();
	gEng.mView.SetGeom( mDefaultWindowWidth, mDefaultWindowHeight );

	// Were any cmd-line args unrecognized?
ASSERT( mCmdargBase.mArgc > 0  &&  mCmdargBase.mArgv0 != "" );
	for ( int i = 1; i < mCmdargBase.mArgc; ++i )
	{
		if ( ! IfCmdargDone( i ) )
			*gStreamOut << "unrecognized cmd-line arg: " << mCmdargBase.mArgv[i] << endl;
	}
}
void
Module::End( void )
{
	StartStopListeners( false );
#if DEBUG
	EndDebug();
#endif
}

/*****************************************************************************
 * Self-checks.  Causes program exit if any fail.
 *****************************************************************************/
void
Module::SelfChecks( void )
{
	// Not a good check as module-specific typesigs can't be checked at this level.
	ASSERT( TYPESIG_AFTER_MODULE_BASE < TYPESIG_MAX ); // catch if typesig value is too big
}

/*****************************************************************************
 * Module animation routine.
 * Called periodically by a timer (by the engine's Animate()).
 * @return True if redisplay is necessary.
 *****************************************************************************/
int
Module::Animate( void )
{
DEBUG_CODE( ++mAnimating; )  // to catch disrupting AnimateList(), ++/-- compatible with derived class
	int redisplay = 0;

	// If keyboard/joystick input demands redisplaying.
	if ( gEng.mView.IfRedisplayInput() ) { ++redisplay; gEng.mView.ResetRedisplayInput(); }

	// Animate dynamic objects.
	redisplay |= AnimateList<Craft>( mCraftDlist );
	redisplay |= AnimateList<DynaModel>( mDynaModelTargetDlist );
	redisplay |= AnimateList<Tracer>( mTracerDlist );

	// Lastly, animate the eye.
	redisplay |= gEng.mEye.Animate();

DEBUG_CODE( --mAnimating; )
	return redisplay;
}

/*****************************************************************************
 * Every timer tick.
 * Called periodically by a timer (by the engine's Tick()).
 *****************************************************************************/
void
Module::Tick( void )
{
	// The amount of time since the last timer interrupt.
	const int milsec = gEng.GetMilsecFrame();

	// Call InputQueue::Tick() periodically (to feed the input queue).
	// Then poll the InputQueue for events.
	static int milsecInputQueueCountdown = INPUT_QUEUE_POLL_TIMER_MILSEC;
	milsecInputQueueCountdown -= milsec;
	if ( milsecInputQueueCountdown <= 0 )
	{
		RESET_COUNTDOWN( milsecInputQueueCountdown, INPUT_QUEUE_POLL_TIMER_MILSEC );
		mInputQueue.Tick( milsec );

		// Don't process input events while rendering.
		// Eg, moving the joystick while an enemy view is being
		// rendered would affect an enemy object, not the player object.
		if ( ! gEng.mView.IfBusy() )
		{
			PollInput();
		}
	}
}

/*****************************************************************************
 * Poll all input event queues.
 *****************************************************************************/
void
Module::PollInput( void )
{
	PollKeyboard();
	gJoy.PollJoystick();

	// Begin dropping events if an event queue is growing too quickly.
	// The joystick is more prone to causing this problem than the keyboard.
	while ( mInputQueue.KeyEvents() > 10 )
	{
		KeyEvent event;
		mInputQueue.GetKeyEvent( &event ); // drop oldest keyboard event
	}
	while ( mInputQueue.JoyEvents() > 10 )
	{
		JoyEvent event;
		mInputQueue.GetJoyEvent( &event ); // drop oldest joystick event
	}
}

/*****************************************************************************
 * Return player's matrix (read-only).
 *****************************************************************************/
const Matrix&
Module::GetPlayerMatrix( void )
{
	// Player can be flying in first-person or chase view.
	if ( gEng.GetCurrent() == &gEng.mEye )
		return gEng.mEye.mCm;
	else
		return GetPlayerCraft()->mCm;
}

/*****************************************************************************
 * Functions to manage change response/sensitivity of flight controls.
 * Two response values are used: one for 1st-person flight mode, another for
 * for chase plane mode.
 *****************************************************************************/
Percent
Module::GetControlResponse( void )
{
	if ( gEng.GetCurrent() == &gEng.mEye )
		return mControlResponseEye;
	else
		return mControlResponseChase;
}
// dir(ection) should be +1 or -1 (nothing else).
void
Module::IncControlResponse( int dir )
{
	const int inc = 10 * dir;  // 10%
	Percent* r = (gEng.GetCurrent()==&gEng.mEye) ? &mControlResponseEye : &mControlResponseChase;

	// Increment value.
	*r += inc;
	*r = Range( int(*r), int(CONTROL_RESPONSE_PERCENT_MIN), int(CONTROL_RESPONSE_PERCENT_MAX) );

	// Change appropriate Dyna object.
	if ( gEng.GetCurrent() == &gEng.mEye )
	{
		SetControlResponse( gEng.mEye, *r );
	}
	else
	{
		SetControlResponse( *mPlayer1Craft, *r );
		SetControlResponse( *mPlayer2Craft, *r );
	}
}
void
Module::SetControlResponse( Dyna& dyna, const Percent percent )
{
	if ( &dyna == &gEng.mEye )
		mControlResponseEye = percent;
	else
		mControlResponseChase = percent;

    // Dyna's notion of control response is a float multiplier.
	// Convert Percent to float.
	dyna.SetResponse( Max(fp(0.1), fp(percent)/50.0) );  // min 0.1 (10%)

	switch ( percent/10 )
	{
		case 1: default:
			gJoy.ScaleJoystickResponse( 1.00 );
		break;

		case 2:
			gJoy.ScaleJoystickResponse( 1.33 );
		break;

		case 3:
			gJoy.ScaleJoystickResponse( 1.66 );
		break;

		case 4:
			gJoy.ScaleJoystickResponse( 2.33 );
		break;

		case 5:  // 50% means default [2006/10]
			gJoy.DefaultJoystickResponse();
			//gJoy.ScaleJoystickResponse( 2.66 );
		break;

		case 6:
			gJoy.ScaleJoystickResponse( 3.33 );
		break;

		case 7:
			gJoy.ScaleJoystickResponse( 3.66 );
		break;

		case 8:
			gJoy.ScaleJoystickResponse( 4.33 );
		break;

		case 9:
			gJoy.ScaleJoystickResponse( 4.66 );
		break;

		case 10:
			gJoy.ScaleJoystickResponse( 5.33 );
		break;
	}
}

/*****************************************************************************
 * Pathname wrappers.
 * Module base class doesn't know the executable name, so pass it.
 *****************************************************************************/
static string sInstallDir;
string
Module::GetInstallDir( const string& execName, char** argv )
{
	// Already computed the path?
	if ( sInstallDir.empty() )
	{
		// argv[0] may contain either an absolute or relative path.
		// Sudo-code:
		// if abspath($argv[0])
		//		$imgDir =         Remove($argv[0],$execName) + "../img/" + $filename
		//	 else
		//		$imgDir = cwd() + Remove($argv[0],$execName) + "../img/" + $filename
		if ( IfAbsPath( argv[0] ) )
		{
			sInstallDir = argv[0];
		}
		else
		{
			if ( mCwd.empty() )
			{
				*gStreamError << "ERROR: cwd failed" << endl;
				sInstallDir.erase();
				return sInstallDir;
			}
			sInstallDir  = mCwd;
			sInstallDir += DIRECTORY_CHAR_STR;
			sInstallDir += argv[0];
		}

		// Cut out executable name.
		sInstallDir.erase( sInstallDir.size() - execName.size() );
	}
	return sInstallDir;
}
