
///////////////////////////////////////////////////////////////////////////////
//
// File   : Asura_ControlMapping_Motion.cpp
// Author : Simon Watson
// Date   : 27-Aug-2008
//
//	A place to put all the motion sensing stuff taken out of Asura_Wii_Input.
//	Could really do with being split into a load of smaller classes
//	to stop the detect functions being so hideous
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header...
#include "Input/Asura_ControlMapping_Motion.h"

// Asura independent header-files...
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"

// Asura platform header-files...
#include "Input/Asura_Wii_Input.h"

// Tools header-files...


////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////

// Asura_Wii_Controller implementation
const u_int uASURA_WII_IN_OUT_NUM_SPIKES = 2;

static ASURA_WII_SPIKES s_aeInOutIDs[uASURA_WII_IN_OUT_NUM_SPIKES] =
{
	ASURA_WII_SPIKE_Z_POS,
	ASURA_WII_SPIKE_Z_NEG,
};

const u_int uASURA_WII_UP_DOWN_NUM_SPIKES = 2;

static ASURA_WII_SPIKES s_aeUpDownIDs[uASURA_WII_UP_DOWN_NUM_SPIKES] =
{
	ASURA_WII_SPIKE_Y_POS,
	ASURA_WII_SPIKE_Y_NEG,
};

const u_int uASURA_WII_LEFT_RIGHT_NUM_SPIKES = 2;

static ASURA_WII_SPIKES s_aeLeftRightIDs[uASURA_WII_LEFT_RIGHT_NUM_SPIKES] =
{
	ASURA_WII_SPIKE_X_POS,
	ASURA_WII_SPIKE_X_NEG,
};

const u_int uASURA_WII_CIRCLE_NUM_SPIKES = 4;

static ASURA_WII_SPIKES s_aeClockwiseCircleIDs[uASURA_WII_CIRCLE_NUM_SPIKES] =
{
	ASURA_WII_SPIKE_X_POS,
	ASURA_WII_SPIKE_Y_POS,
	ASURA_WII_SPIKE_X_NEG,
	ASURA_WII_SPIKE_Y_NEG,
};

static ASURA_WII_SPIKES s_aeAntiClockwiseCircleIDs[uASURA_WII_CIRCLE_NUM_SPIKES] =
{
	ASURA_WII_SPIKE_X_POS,
	ASURA_WII_SPIKE_Y_NEG,
	ASURA_WII_SPIKE_X_NEG,
	ASURA_WII_SPIKE_Y_POS,
};

////////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////

// Num wiimote samples to look back, in order to get a previous vector
DEBUGVAR u_int dbg_uNumFramesToLookBack	= 1;


DEBUGVAR float dbg_fAccelerationMultiplier	= 2.f;
DEBUGVAR float dbg_fAccelerationDeadZone	= 0.1f;

// Remote and Nunchuck Individual Flicks (in specific axes)
DEBUGVAR float dbg_fRemoteIndividualSpikeThreshold	= 1.0f;
DEBUGVAR float dbg_fNunchuckIndividualSpikeThreshold	= 1.0f;

// Remote and Nunchuck Flicks (in any axis)
DEBUGVAR float dbg_fRemoteFlickThresholds	= 2.0f;
DEBUGVAR float dbg_fNunchuckFlickThresholds	= 2.0f;
DEBUGVAR bool  dbg_bShowFlickDebugInfo		= false;

// Remote and Nunchuck Shakes
DEBUGVAR float dbg_fShakeThresholds			= 1.0f;
DEBUGVAR float dbg_fNunchuckShakeThresholds	= 1.0f;
DEBUGVAR u_int dbg_uMinimumShakes			= 4;	// just remote shakes
DEBUGVAR u_int dbg_uMinimumNunchuckShakes	= 4;	// just nunchuck shakes
DEBUGVAR u_int dbg_uMinimumBothShakes		= 2;	// both remote and nunchuck shakes together
DEBUGVAR bool  dbg_bShowShakeDebugInfo		= false;
DEBUGVAR float dbg_fTimerBetweenToggle		= 1.0f; // restricts toggling and flicks (no more than once every ToggleTimer seconds)
DEBUGVAR float dbg_fShakeTimer				= 0.5f;
DEBUGVAR float dbg_fNunchuckShakeTimer		= 0.5f;

// Remote In/Out
DEBUGVAR	float dbg_fRemoteInOutThreshold		= 0.2f;
DEBUGVAR	float dbg_fRemoteInOutTimeout		= 0.5f;
DEBUGVAR	u_int dbg_uMinimumRemoteInOutSpikes	= 3;
DEBUGVAR	bool  dbg_bShowRemoteInOutDebugInfo	= false;

// Nunchuck In/Out
DEBUGVAR	float dbg_fNunchuckInOutThreshold	  = 0.2f;
DEBUGVAR	float dbg_fNunchuckInOutTimeout		  = 0.5f;
DEBUGVAR	u_int dbg_uMinimumNunchuckInOutSpikes = 3;
DEBUGVAR	bool  dbg_bShowNunchuckInOutDebugInfo = false;

// Remote Up/Down
DEBUGVAR	float dbg_fRemoteUpDownThreshold	 = 0.4f;
DEBUGVAR	float dbg_fRemoteUpDownTimeout		 = 0.5f;
DEBUGVAR	u_int dbg_uMinimumRemoteUpDownSpikes = 4;
DEBUGVAR	bool  dbg_bShowRemoteUpDownDebugInfo = false;

// Nunchuck Up/Down
DEBUGVAR	float dbg_fNunchuckUpDownThreshold	   = 0.4f;
DEBUGVAR	float dbg_fNunchuckUpDownTimeout	   = 0.5f;
DEBUGVAR	u_int dbg_uMinimumNunchuckUpDownSpikes = 4;
DEBUGVAR	bool  dbg_bShowNunchuckUpDownDebugInfo = false;

// Remote Left/Right
DEBUGVAR	float dbg_fRemoteLeftRightThreshold		= 0.4f;
DEBUGVAR	float dbg_fRemoteLeftRightTimeout		= 0.5f;
DEBUGVAR	u_int dbg_uMinimumRemoteLeftRightSpikes	= 3;
DEBUGVAR	bool  dbg_bShowRemoteLeftRightDebugInfo	= false;

// Nunchuck Left/Right
DEBUGVAR	float dbg_fNunchuckLeftRightThreshold	  = 0.4f;
DEBUGVAR	float dbg_fNunchuckLeftRightTimeout		  = 0.5f;
DEBUGVAR	u_int dbg_uMinimumNunchuckLeftRightSpikes = 3;
DEBUGVAR	bool  dbg_bShowNunchuckLeftRightDebugInfo = false;

// Remote Circle (clockwise)
DEBUGVAR	float dbg_fRemoteClockwiseCircleThreshold	  = 0.2f;
DEBUGVAR	bool  dbg_bAllowRemoteClockwiseCircleSkips	  = false;
DEBUGVAR	float dbg_fRemoteClockwiseCircleTimeout		  = 0.5f;
DEBUGVAR	u_int dbg_uMinimumRemoteClockwiseCircleSpikes = 5;
DEBUGVAR	bool  dbg_bShowRemoteClockwiseCircleDebugInfo = false;

// Remote Circle (anti-clockwise)
DEBUGVAR	float dbg_fRemoteAntiClockwiseCircleThreshold	  = 0.2f;
DEBUGVAR	bool  dbg_bAllowRemoteAntiClockwiseCircleSkips	  = false;
DEBUGVAR	float dbg_fRemoteAntiClockwiseCircleTimeout		  = 0.5f;
DEBUGVAR	u_int dbg_uMinimumRemoteAntiClockwiseCircleSpikes = 5;
DEBUGVAR	bool  dbg_bShowRemoteAntiClockwiseCircleDebugInfo = false;

// Nunchuck Circle (clockwise)
DEBUGVAR	float dbg_fNunchuckClockwiseCircleThreshold	    = 0.2f;
DEBUGVAR	bool  dbg_bAllowNunchuckClockwiseCircleSkips	= false;
DEBUGVAR	float dbg_fNunchuckClockwiseCircleTimeout	    = 0.5f;
DEBUGVAR	u_int dbg_uMinimumNunchuckClockwiseCircleSpikes = 5;
DEBUGVAR	bool  dbg_bShowNunchuckClockwiseCircleDebugInfo = false;

// Nunchuck Circle (anti-clockwise)
DEBUGVAR	float dbg_fNunchuckAntiClockwiseCircleThreshold	    = 0.2f;
DEBUGVAR	bool  dbg_bAllowNunchuckAntiClockwiseCircleSkips	= false;
DEBUGVAR	float dbg_fNunchuckAntiClockwiseCircleTimeout	    = 0.5f;
DEBUGVAR	u_int dbg_uMinimumNunchuckAntiClockwiseCircleSpikes = 5;
DEBUGVAR	bool  dbg_bShowNunchuckAntiClockwiseCircleDebugInfo = false;


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ControlMapping_Motion::Asura_ControlMapping_Motion
// Author       : Simon Watson
// Date         : 28-Aug-2005
////////////////////////////////////////////////////////////////////////////////

Asura_ControlMapping_Motion::Asura_ControlMapping_Motion(	const int iController,							// Which controller are we bound to?
															const ASURA_WII_MOTION_ID eMotionId,			// What type of motion are we looking for?
															const float fScale ) 
:
	Asura_ControlMapping(fScale)

	, m_iController(iController)
	, m_eMotionId(eMotionId)

	, m_xIndividualFlickSpikeDetection(Asura_Maths::ZeroVector3)
	, m_xFlickSpikeDetection(Asura_Maths::ZeroVector3)
	, m_xInOutSpikeDetection(Asura_Maths::ZeroVector3)
	, m_xUpDownSpikeDetection(Asura_Maths::ZeroVector3)
	, m_xLeftRightSpikeDetection(Asura_Maths::ZeroVector3)
	, m_xClockwiseCircleSpikeDetection(Asura_Maths::ZeroVector3)
	, m_xAntiClockwiseCircleSpikeDetection(Asura_Maths::ZeroVector3)

	, m_uNumberOfSpikes(0)
	, m_uCurrentInOutSpike(0)
	, m_uNumInOutSpikes(0)
	, m_uCurrentUpDownSpike(0)
	, m_uNumUpDownSpikes(0)
	, m_uCurrentLeftRightSpike(0)
	, m_uNumLeftRightSpikes(0)
	, m_uCurrentClockwiseCircleSpike(0)
	, m_uNumClockwiseCircleSpikes(0)
	, m_uCurrentAntiClockwiseCircleSpike(0)
	, m_uNumAntiClockwiseCircleSpikes(0)

	, m_fShakeTimer(0.f)
	, m_fToggleTimer(0.f)
	, m_fLastInOutSpikeTime(0.f)
	, m_fLastUpDownSpikeTime(0.f)
	, m_fLastLeftRightSpikeTime(0.f)
	, m_fLastClockwiseCircleSpikeTime(0.f)
	, m_fLastAntiClockwiseCircleSpikeTime(0.f)

{
	// Nothing more to initialise...
}

/// End of function 'Asura_ControlMapping_Motion::Asura_ControlMapping_Motion'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ControlMapping_Motion::UpdateValue
// Author       : Simon Watson
// Date         : 28-Aug-2005
////////////////////////////////////////////////////////////////////////////////

void Asura_ControlMapping_Motion::UpdateValues(const bool bActive, float& fValue, float& fHUDValue)
{
	m_bActive = bActive;
	fHUDValue = 0.0f;	// We don't support this, but at least we can set it to something sensible

	fValue = 0.0f;

	if (!m_bActive) return;
	
	const Asura_Wii_Controller* pxController = static_cast<const Asura_Wii_Controller*>(Asura_Input::GetController(m_iController));
	if(!pxController || !pxController->IsConnected())
	{
		return;
	}

	const Asura_Vector_3 xCurrentGradient = GetCurrentAccSample() - GetPrevAccSample();

	// Splitting into two functions here means we only need half the member variables
	if ( GetMotionSource() == ASURA_WII_MOTION_SOURCE_REMOTE )
	{
		fValue = DetectRemoteSpikes(xCurrentGradient);
	}
	else
	{
		fValue = DetectNunchuckSpikes(xCurrentGradient);
	}
}

/// End of function 'Asura_ControlMapping_Motion::UpdateValue'. ///

// Detect spikes in acceleration
// These functions are pretty much identical except for the constants (which are set to identical values, but don't have to be).
// Should probably be tidied up into some sort of calculation object interface implemented by a factory method.
// I also realise we're detecting everything then checking if it's what we want, which is a bit backwards.
const float Asura_ControlMapping_Motion::DetectRemoteSpikes(const Asura_Vector_3& xCurrentGradient)
{
	bool abInOutSpikes[ASURA_WII_NUM_SPIKES];
	bool abUpDownSpikes[ASURA_WII_NUM_SPIKES];
	bool abLeftRightSpikes[ASURA_WII_NUM_SPIKES];
	bool abClockwiseCircleSpikes[ASURA_WII_NUM_SPIKES];
	bool abAntiClockwiseCircleSpikes[ASURA_WII_NUM_SPIKES];

	for(u_int uAxis = 0; uAxis < 3; ++uAxis)
	{
		const u_int uIndex = 2 * uAxis;		

		// Individual Remote Flicks
		const int iRemoteIndividualSpike = DetectSpike(m_xIndividualFlickSpikeDetection, xCurrentGradient, uAxis, dbg_fRemoteIndividualSpikeThreshold, dbg_fRemoteIndividualSpikeThreshold);

		if ( iRemoteIndividualSpike == 1 )
		{
			if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_FLICK_LEFT_X + uIndex) return fASURA_CONTROLLER_INPUT_MAX;
		}
		else if (iRemoteIndividualSpike == -1 )
		{
			if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_FLICK_RIGHT_X + uIndex) return fASURA_CONTROLLER_INPUT_MAX;
		}

		// Remote Flicks
		const int iRemoteFlick = DetectSpike(m_xFlickSpikeDetection, xCurrentGradient, uAxis, dbg_fRemoteFlickThresholds, dbg_fRemoteFlickThresholds);		
		if ( iRemoteFlick )
		{
			if (dbg_bShowFlickDebugInfo) { Asura_OutputToDebugger(("Remote Flick\n")); }
			if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_FLICK) return fASURA_CONTROLLER_INPUT_MAX;
		}

		// Remote Shakes
		if ((m_uNumberOfSpikes) && (m_fShakeTimer > 0.f)) // at least one spike was detected and in time
		{
			const int iRemoteSpike = DetectSpike(m_xShakeSpikeDetection, xCurrentGradient, uAxis, dbg_fShakeThresholds, dbg_fShakeThresholds);
			if ( iRemoteSpike ) // spike detected, increase spike counter and reset timer
			{ 
				m_uNumberOfSpikes++;
				m_fShakeTimer = dbg_fShakeTimer;
			}
			else // decrease timer
			{
				m_fShakeTimer -= Asura_Timers::GetFrameTime();
				if (m_fShakeTimer < 0.f) { m_fShakeTimer = 0.f; }
			}
		}
		// timer = 0 and at least one spike had been detected, meaning this has timed out, reset spike counter
		else if ((m_fShakeTimer == 0.f) && (m_uNumberOfSpikes))
		{
			m_uNumberOfSpikes = 0;
			if (dbg_bShowShakeDebugInfo) { Asura_OutputToDebugger(("Remote Timeout\n")); }
		}
		else // detecting first spike
		{
			const int iSpike = DetectSpike(m_xShakeSpikeDetection, xCurrentGradient, uAxis, dbg_fShakeThresholds, dbg_fShakeThresholds);
			if ( iSpike ) // spike detected, increase spike counter and reset timer
			{ 
				m_uNumberOfSpikes++;				
				m_fShakeTimer = dbg_fShakeTimer;
			}
		}

		// Remote In/Out Flicks
		const int iRemoteInOutSpike = DetectSpike(m_xInOutSpikeDetection, xCurrentGradient, uAxis, dbg_fRemoteInOutThreshold, dbg_fRemoteInOutThreshold);
		abInOutSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iRemoteInOutSpike ==  1 );
		abInOutSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iRemoteInOutSpike == -1 );

		// Remote Up/Down Flicks
		const int iRemoteUpDownSpike = DetectSpike(m_xUpDownSpikeDetection, xCurrentGradient, uAxis, dbg_fRemoteUpDownThreshold, dbg_fRemoteUpDownThreshold);
		abUpDownSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iRemoteUpDownSpike ==  1 );
		abUpDownSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iRemoteUpDownSpike == -1 );

		// Remote Left/Right Flicks
		const int iRemoteLeftRightSpike = DetectSpike(m_xLeftRightSpikeDetection, xCurrentGradient, uAxis, dbg_fRemoteLeftRightThreshold, dbg_fRemoteLeftRightThreshold);
		abLeftRightSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iRemoteLeftRightSpike ==  1 );
		abLeftRightSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iRemoteLeftRightSpike == -1 );

		// Remote Clockwise Circle Flicks
		const int iRemoteClockwiseCircleSpike = DetectSpike(m_xClockwiseCircleSpikeDetection, xCurrentGradient, uAxis, dbg_fRemoteClockwiseCircleThreshold, dbg_fRemoteClockwiseCircleThreshold);
		abClockwiseCircleSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iRemoteClockwiseCircleSpike ==  1 );
		abClockwiseCircleSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iRemoteClockwiseCircleSpike == -1 );

		// Remote Anti-Clockwise Circle Flicks
		const int iRemoteAntiClockwiseCircleSpike = DetectSpike(m_xAntiClockwiseCircleSpikeDetection, xCurrentGradient, uAxis, dbg_fRemoteAntiClockwiseCircleThreshold, dbg_fRemoteAntiClockwiseCircleThreshold);
		abAntiClockwiseCircleSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iRemoteAntiClockwiseCircleSpike ==  1 );
		abAntiClockwiseCircleSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iRemoteAntiClockwiseCircleSpike == -1 );
	}

	// both remote and nunchuck number of shakes detected
	if (m_fToggleTimer > 0.f) // not allowed to toggle, decrease timer
	{
		m_fToggleTimer -= Asura_Timers::GetFrameTime();
		if (m_fToggleTimer < 0.f) { m_fToggleTimer = 0.f; }
	}

	// remote shakes were detected
	if ((m_uNumberOfSpikes >= dbg_uMinimumShakes)
		&& (m_fToggleTimer == 0.f))

	{
		m_uNumberOfSpikes = 0;
		if (dbg_bShowShakeDebugInfo) { Asura_OutputToDebugger(("Remote Shake\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_SHAKE) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// remote in/out motion (already started)
	if ( m_uNumInOutSpikes )
	{
		const u_int uNextSpikeID = (m_uCurrentInOutSpike + 1) % uASURA_WII_IN_OUT_NUM_SPIKES;

		if(abInOutSpikes[s_aeInOutIDs[uNextSpikeID]])
		{
			m_uCurrentInOutSpike = uNextSpikeID;
			m_fLastInOutSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			++m_uNumInOutSpikes;			
		}
		else
		{			
			if(Asura_Timers::GetLocalUnscaledTime() > ( m_fLastInOutSpikeTime + dbg_fRemoteInOutTimeout) ) // timed out
			{
				m_uNumInOutSpikes = 0;
			}
		}
	}
	else
	{
		// remote in/out motion (starts here)
		for (u_int uInOutSpikeDetect = 0; uInOutSpikeDetect < uASURA_WII_IN_OUT_NUM_SPIKES; uInOutSpikeDetect++)
		{
			if (abInOutSpikes[s_aeInOutIDs[uInOutSpikeDetect]])
			{
				m_uCurrentInOutSpike	= uInOutSpikeDetect;
				m_uNumInOutSpikes		= 1;
				m_fLastInOutSpikeTime	= Asura_Timers::GetLocalUnscaledTime();
			}
		}		
	}

	if(m_uNumInOutSpikes >= dbg_uMinimumRemoteInOutSpikes)
	{		
		m_uNumInOutSpikes = 0;
		if (dbg_bShowRemoteInOutDebugInfo) { Asura_OutputToDebugger(("Remote In/Out\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_IN_OUT) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// remote up/down motion (already started)
	if ( m_uNumUpDownSpikes )
	{
		const u_int uNextSpikeID = (m_uCurrentUpDownSpike + 1) % uASURA_WII_UP_DOWN_NUM_SPIKES;

		if(abUpDownSpikes[s_aeUpDownIDs[uNextSpikeID]])
		{
			m_uCurrentUpDownSpike = uNextSpikeID;
			m_fLastUpDownSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			++m_uNumUpDownSpikes;			
		}
		else
		{			
			if(Asura_Timers::GetLocalUnscaledTime() > m_fLastUpDownSpikeTime + dbg_fRemoteUpDownTimeout) // timed out
			{
				m_uNumUpDownSpikes = 0;
			}
		}
	}
	else
	{
		// remote up/down motion (starts here)
		for (u_int uUpDownSpikeDetect = 0; uUpDownSpikeDetect < uASURA_WII_UP_DOWN_NUM_SPIKES; uUpDownSpikeDetect++)
		{
			if (abUpDownSpikes[s_aeUpDownIDs[uUpDownSpikeDetect]])
			{
				m_uCurrentUpDownSpike = uUpDownSpikeDetect;
				m_uNumUpDownSpikes		= 1;
				m_fLastUpDownSpikeTime	= Asura_Timers::GetLocalUnscaledTime();
			}
		}		
	}

	if(m_uNumUpDownSpikes >= dbg_uMinimumRemoteUpDownSpikes)
	{		
		m_uNumUpDownSpikes = 0;
		if (dbg_bShowRemoteUpDownDebugInfo) { Asura_OutputToDebugger(("Remote Up/Down\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_UP_DOWN) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// remote left/right motion (already started)
	if ( m_uNumLeftRightSpikes )
	{
		const u_int uNextSpikeID = (m_uCurrentLeftRightSpike + 1) % uASURA_WII_LEFT_RIGHT_NUM_SPIKES;

		if(abLeftRightSpikes[s_aeLeftRightIDs[uNextSpikeID]])
		{
			m_uCurrentLeftRightSpike  = uNextSpikeID;
			m_fLastLeftRightSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			++m_uNumLeftRightSpikes;			
		}
		else
		{			
			if(Asura_Timers::GetLocalUnscaledTime() > m_fLastLeftRightSpikeTime + dbg_fRemoteLeftRightTimeout) // timed out
			{
				m_uNumLeftRightSpikes = 0;
			}
		}
	}
	else
	{
		// remote left/right motion (starts here)
		for (u_int uLeftRightSpikeDetect = 0; uLeftRightSpikeDetect < uASURA_WII_LEFT_RIGHT_NUM_SPIKES; uLeftRightSpikeDetect++)
		{
			if (abLeftRightSpikes[s_aeLeftRightIDs[uLeftRightSpikeDetect]])
			{
				m_uCurrentLeftRightSpike  = uLeftRightSpikeDetect;
				m_uNumLeftRightSpikes	    = 1;
				m_fLastLeftRightSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			}
		}		
	}

	if(m_uNumLeftRightSpikes >= dbg_uMinimumRemoteLeftRightSpikes)
	{		
		m_uNumLeftRightSpikes = 0;
		if (dbg_bShowRemoteLeftRightDebugInfo) { Asura_OutputToDebugger(("Remote Left/Right\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_LEFT_RIGHT) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// remote clockwise circle motion (already started)
	if ( m_uNumClockwiseCircleSpikes )
	{
		const u_int uNextSpikeID	 = (m_uCurrentClockwiseCircleSpike + 1) % uASURA_WII_CIRCLE_NUM_SPIKES;
		const u_int uNextNextSpikeID = (m_uCurrentClockwiseCircleSpike + 2) % uASURA_WII_CIRCLE_NUM_SPIKES;

		if (dbg_bAllowRemoteClockwiseCircleSkips
			&& m_bClockwiseCircleAllowedToSkip
			&& (abClockwiseCircleSpikes[s_aeClockwiseCircleIDs[uNextNextSpikeID]])
			&& (m_uNumClockwiseCircleSpikes >= dbg_uMinimumRemoteClockwiseCircleSpikes))
		{
			m_uCurrentClockwiseCircleSpike  = uNextNextSpikeID;
			m_fLastClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			m_bClockwiseCircleAllowedToSkip = false;
			++m_uNumClockwiseCircleSpikes;
		}
		else 
			if(abClockwiseCircleSpikes[s_aeClockwiseCircleIDs[uNextSpikeID]]) // inputted flick is a valid one (current should be equal to NextSpikeID to be true)
			{
				if (dbg_bAllowRemoteClockwiseCircleSkips) { m_bClockwiseCircleAllowedToSkip  = true; }
				m_uCurrentClockwiseCircleSpike  = uNextSpikeID;
				m_fLastClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
				++m_uNumClockwiseCircleSpikes;			
			}
			else
			{			
				if(Asura_Timers::GetLocalUnscaledTime() > m_fLastClockwiseCircleSpikeTime + dbg_fRemoteClockwiseCircleTimeout) // timed out
				{
					m_uNumClockwiseCircleSpikes = 0;
				}
			}
	}
	else
	{
		// remote clockwise circle motion (starts here)
		for (u_int uClockwiseCircleSpikeDetect = 0; uClockwiseCircleSpikeDetect < uASURA_WII_CIRCLE_NUM_SPIKES; uClockwiseCircleSpikeDetect++)
		{
			if (abClockwiseCircleSpikes[s_aeClockwiseCircleIDs[uClockwiseCircleSpikeDetect]]) // if a spike is detected...
			{
				m_uCurrentClockwiseCircleSpike  = uClockwiseCircleSpikeDetect;	   // ...assign it to our first circle motion position
				m_uNumClockwiseCircleSpikes	  = 1;
				m_fLastClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			}
		}
	}

	if (m_uNumClockwiseCircleSpikes >= dbg_uMinimumRemoteClockwiseCircleSpikes) // inputted a circle. reset timers and values
	{		
		m_uNumClockwiseCircleSpikes = 0;
		if (dbg_bShowRemoteClockwiseCircleDebugInfo) { Asura_OutputToDebugger(("Remote Clockwise Circle\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_CLOCKWISE_CIRCLE) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// remote anti-clockwise circle motion (already started)
	if ( m_uNumAntiClockwiseCircleSpikes )
	{
		const u_int uNextSpikeID	 = (m_uCurrentAntiClockwiseCircleSpike + 1) % uASURA_WII_CIRCLE_NUM_SPIKES;
		const u_int uNextNextSpikeID = (m_uCurrentAntiClockwiseCircleSpike + 2) % uASURA_WII_CIRCLE_NUM_SPIKES;

		if (dbg_bAllowRemoteAntiClockwiseCircleSkips
			&& m_bAntiClockwiseCircleAllowedToSkip
			&& (abAntiClockwiseCircleSpikes[s_aeAntiClockwiseCircleIDs[uNextNextSpikeID]])
			&& (m_uNumAntiClockwiseCircleSpikes >= dbg_uMinimumRemoteAntiClockwiseCircleSpikes))
		{
			m_uCurrentAntiClockwiseCircleSpike  = uNextNextSpikeID;
			m_fLastAntiClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			m_bAntiClockwiseCircleAllowedToSkip = false;
			++m_uNumAntiClockwiseCircleSpikes;
		}
		else 
			if(abAntiClockwiseCircleSpikes[s_aeAntiClockwiseCircleIDs[uNextSpikeID]]) // inputted flick is a valid one (current should be equal to NextSpikeID to be true)
			{
				if (dbg_bAllowRemoteAntiClockwiseCircleSkips) { m_bAntiClockwiseCircleAllowedToSkip  = true; }
				m_uCurrentAntiClockwiseCircleSpike  = uNextSpikeID;
				m_fLastAntiClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
				++m_uNumAntiClockwiseCircleSpikes;			
			}
			else
			{			
				if(Asura_Timers::GetLocalUnscaledTime() > m_fLastAntiClockwiseCircleSpikeTime + dbg_fRemoteAntiClockwiseCircleTimeout) // timed out
				{
					m_uNumAntiClockwiseCircleSpikes = 0;
				}
			}
	}
	else
	{
		// remote anti-clockwise circle motion (starts here)
		for (u_int uAntiClockwiseCircleSpikeDetect = 0; uAntiClockwiseCircleSpikeDetect < uASURA_WII_CIRCLE_NUM_SPIKES; uAntiClockwiseCircleSpikeDetect++)
		{
			if (abAntiClockwiseCircleSpikes[s_aeAntiClockwiseCircleIDs[uAntiClockwiseCircleSpikeDetect]]) // if a spike is detected...
			{
				m_uCurrentAntiClockwiseCircleSpike  = uAntiClockwiseCircleSpikeDetect;	   // ...assign it to our first circle motion position
				m_uNumAntiClockwiseCircleSpikes	  = 1;
				m_fLastAntiClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			}
		}
	}

	if (m_uNumAntiClockwiseCircleSpikes >= dbg_uMinimumRemoteAntiClockwiseCircleSpikes) // inputted a circle. reset timers and values
	{		
		m_uNumAntiClockwiseCircleSpikes = 0;
		if (dbg_bShowRemoteAntiClockwiseCircleDebugInfo) { Asura_OutputToDebugger(("Remote AntiClockwise Circle\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_REMOTE_ANTI_CLOCKWISE_CIRCLE) return fASURA_CONTROLLER_INPUT_MAX;
	}

	return 0.0f;
}

const float Asura_ControlMapping_Motion::DetectNunchuckSpikes(const Asura_Vector_3& xCurrentGradient)
{
	bool abInOutSpikes[ASURA_WII_NUM_SPIKES];
	bool abUpDownSpikes[ASURA_WII_NUM_SPIKES];
	bool abLeftRightSpikes[ASURA_WII_NUM_SPIKES];
	bool abClockwiseCircleSpikes[ASURA_WII_NUM_SPIKES];
	bool abAntiClockwiseCircleSpikes[ASURA_WII_NUM_SPIKES];

	for(u_int uAxis = 0; uAxis < 3; ++uAxis)
	{
		const u_int uIndex = 2 * uAxis;		

		// Individual Nunchuck Flicks
		const int iNunchuckIndividualSpike = DetectSpike(m_xIndividualFlickSpikeDetection, xCurrentGradient, uAxis, dbg_fNunchuckIndividualSpikeThreshold, dbg_fNunchuckIndividualSpikeThreshold);

		if ( iNunchuckIndividualSpike == 1 )
		{
			if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_FLICK_LEFT_X + uIndex) return fASURA_CONTROLLER_INPUT_MAX;
		}
		else if (iNunchuckIndividualSpike == -1 )
		{
			if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_FLICK_RIGHT_X + uIndex) return fASURA_CONTROLLER_INPUT_MAX;
		}

		// Nunchuck Flicks
		const int iNunchuckFlick = DetectSpike(m_xFlickSpikeDetection, xCurrentGradient, uAxis, dbg_fNunchuckFlickThresholds, dbg_fNunchuckFlickThresholds);		
		if ( iNunchuckFlick )
		{
			if (dbg_bShowFlickDebugInfo) { Asura_OutputToDebugger(("Nunchuck Flick\n")); }
			if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_FLICK) return fASURA_CONTROLLER_INPUT_MAX;
		}

		// Nunchuck Shakes
		if ((m_uNumberOfSpikes) && (m_fShakeTimer > 0.f)) // at least one spike was detected and in time
		{
			const int iNunchuckSpike = DetectSpike(m_xShakeSpikeDetection, xCurrentGradient, uAxis, dbg_fShakeThresholds, dbg_fShakeThresholds);
			if ( iNunchuckSpike ) // spike detected, increase spike counter and reset timer
			{ 
				m_uNumberOfSpikes++;
				m_fShakeTimer = dbg_fShakeTimer;
			}
			else // decrease timer
			{
				m_fShakeTimer -= Asura_Timers::GetFrameTime();
				if (m_fShakeTimer < 0.f) { m_fShakeTimer = 0.f; }
			}
		}
		// timer = 0 and at least one spike had been detected, meaning this has timed out, reset spike counter
		else if ((m_fShakeTimer == 0.f) && (m_uNumberOfSpikes))
		{
			m_uNumberOfSpikes = 0;
			if (dbg_bShowShakeDebugInfo) { Asura_OutputToDebugger(("Nunchuck Timeout\n")); }
		}
		else // detecting first spike
		{
			const int iSpike = DetectSpike(m_xShakeSpikeDetection, xCurrentGradient, uAxis, dbg_fShakeThresholds, dbg_fShakeThresholds);
			if ( iSpike ) // spike detected, increase spike counter and reset timer
			{ 
				m_uNumberOfSpikes++;				
				m_fShakeTimer = dbg_fShakeTimer;
			}
		}

		// Nunchuck In/Out Flicks
		const int iNunchuckInOutSpike = DetectSpike(m_xInOutSpikeDetection, xCurrentGradient, uAxis, dbg_fNunchuckInOutThreshold, dbg_fNunchuckInOutThreshold);
		abInOutSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iNunchuckInOutSpike ==  1 );
		abInOutSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iNunchuckInOutSpike == -1 );

		// Nunchuck Up/Down Flicks
		const int iNunchuckUpDownSpike = DetectSpike(m_xUpDownSpikeDetection, xCurrentGradient, uAxis, dbg_fNunchuckUpDownThreshold, dbg_fNunchuckUpDownThreshold);
		abUpDownSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iNunchuckUpDownSpike ==  1 );
		abUpDownSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iNunchuckUpDownSpike == -1 );

		// Nunchuck Left/Right Flicks
		const int iNunchuckLeftRightSpike = DetectSpike(m_xLeftRightSpikeDetection, xCurrentGradient, uAxis, dbg_fNunchuckLeftRightThreshold, dbg_fNunchuckLeftRightThreshold);
		abLeftRightSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iNunchuckLeftRightSpike ==  1 );
		abLeftRightSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iNunchuckLeftRightSpike == -1 );

		// Nunchuck Clockwise Circle Flicks
		const int iNunchuckClockwiseCircleSpike = DetectSpike(m_xClockwiseCircleSpikeDetection, xCurrentGradient, uAxis, dbg_fNunchuckClockwiseCircleThreshold, dbg_fNunchuckClockwiseCircleThreshold);
		abClockwiseCircleSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iNunchuckClockwiseCircleSpike ==  1 );
		abClockwiseCircleSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iNunchuckClockwiseCircleSpike == -1 );

		// Nunchuck Anti-Clockwise Circle Flicks
		const int iNunchuckAntiClockwiseCircleSpike = DetectSpike(m_xAntiClockwiseCircleSpikeDetection, xCurrentGradient, uAxis, dbg_fNunchuckAntiClockwiseCircleThreshold, dbg_fNunchuckAntiClockwiseCircleThreshold);
		abAntiClockwiseCircleSpikes[ASURA_WII_SPIKE_X_POS + uIndex] = ( iNunchuckAntiClockwiseCircleSpike ==  1 );
		abAntiClockwiseCircleSpikes[ASURA_WII_SPIKE_X_NEG + uIndex] = ( iNunchuckAntiClockwiseCircleSpike == -1 );
	}

	// both nunchuck and nunchuck number of shakes detected
	if (m_fToggleTimer > 0.f) // not allowed to toggle, decrease timer
	{
		m_fToggleTimer -= Asura_Timers::GetFrameTime();
		if (m_fToggleTimer < 0.f) { m_fToggleTimer = 0.f; }
	}

	// nunchuck shakes were detected
	if ((m_uNumberOfSpikes >= dbg_uMinimumShakes)
		&& (m_fToggleTimer == 0.f))

	{
		m_uNumberOfSpikes = 0;
		if (dbg_bShowShakeDebugInfo) { Asura_OutputToDebugger(("Nunchuck Shake\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_SHAKE) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// nunchuck in/out motion (already started)
	if ( m_uNumInOutSpikes )
	{
		const u_int uNextSpikeID = (m_uCurrentInOutSpike + 1) % uASURA_WII_IN_OUT_NUM_SPIKES;

		if(abInOutSpikes[s_aeInOutIDs[uNextSpikeID]])
		{
			m_uCurrentInOutSpike = uNextSpikeID;
			m_fLastInOutSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			++m_uNumInOutSpikes;			
		}
		else
		{			
			if(Asura_Timers::GetLocalUnscaledTime() > m_fLastInOutSpikeTime + dbg_fNunchuckInOutTimeout) // timed out
			{
				m_uNumInOutSpikes = 0;
			}
		}
	}
	else
	{
		// nunchuck in/out motion (starts here)
		for (u_int uInOutSpikeDetect = 0; uInOutSpikeDetect < uASURA_WII_IN_OUT_NUM_SPIKES; uInOutSpikeDetect++)
		{
			if (abInOutSpikes[s_aeInOutIDs[uInOutSpikeDetect]])
			{
				m_uCurrentInOutSpike	= uInOutSpikeDetect;
				m_uNumInOutSpikes		= 1;
				m_fLastInOutSpikeTime	= Asura_Timers::GetLocalUnscaledTime();
			}
		}		
	}

	if(m_uNumInOutSpikes >= dbg_uMinimumNunchuckInOutSpikes)
	{		
		m_uNumInOutSpikes = 0;
		if (dbg_bShowNunchuckInOutDebugInfo) { Asura_OutputToDebugger(("Nunchuck In/Out\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_IN_OUT) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// nunchuck up/down motion (already started)
	if ( m_uNumUpDownSpikes )
	{
		const u_int uNextSpikeID = (m_uCurrentUpDownSpike + 1) % uASURA_WII_UP_DOWN_NUM_SPIKES;

		if(abUpDownSpikes[s_aeUpDownIDs[uNextSpikeID]])
		{
			m_uCurrentUpDownSpike = uNextSpikeID;
			m_fLastUpDownSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			++m_uNumUpDownSpikes;			
		}
		else
		{			
			if(Asura_Timers::GetLocalUnscaledTime() > m_fLastUpDownSpikeTime + dbg_fNunchuckUpDownTimeout) // timed out
			{
				m_uNumUpDownSpikes = 0;
			}
		}
	}
	else
	{
		// nunchuck up/down motion (starts here)
		for (u_int uUpDownSpikeDetect = 0; uUpDownSpikeDetect < uASURA_WII_UP_DOWN_NUM_SPIKES; uUpDownSpikeDetect++)
		{
			if (abUpDownSpikes[s_aeUpDownIDs[uUpDownSpikeDetect]])
			{
				m_uCurrentUpDownSpike = uUpDownSpikeDetect;
				m_uNumUpDownSpikes		= 1;
				m_fLastUpDownSpikeTime	= Asura_Timers::GetLocalUnscaledTime();
			}
		}		
	}

	if(m_uNumUpDownSpikes >= dbg_uMinimumNunchuckUpDownSpikes)
	{		
		m_uNumUpDownSpikes = 0;
		if (dbg_bShowNunchuckUpDownDebugInfo) { Asura_OutputToDebugger(("Nunchuck Up/Down\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_UP_DOWN) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// nunchuck left/right motion (already started)
	if ( m_uNumLeftRightSpikes )
	{
		const u_int uNextSpikeID = (m_uCurrentLeftRightSpike + 1) % uASURA_WII_LEFT_RIGHT_NUM_SPIKES;

		if(abLeftRightSpikes[s_aeLeftRightIDs[uNextSpikeID]])
		{
			m_uCurrentLeftRightSpike  = uNextSpikeID;
			m_fLastLeftRightSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			++m_uNumLeftRightSpikes;			
		}
		else
		{			
			if(Asura_Timers::GetLocalUnscaledTime() > m_fLastLeftRightSpikeTime + dbg_fNunchuckLeftRightTimeout) // timed out
			{
				m_uNumLeftRightSpikes = 0;
			}
		}
	}
	else
	{
		// nunchuck left/right motion (starts here)
		for (u_int uLeftRightSpikeDetect = 0; uLeftRightSpikeDetect < uASURA_WII_LEFT_RIGHT_NUM_SPIKES; uLeftRightSpikeDetect++)
		{
			if (abLeftRightSpikes[s_aeLeftRightIDs[uLeftRightSpikeDetect]])
			{
				m_uCurrentLeftRightSpike  = uLeftRightSpikeDetect;
				m_uNumLeftRightSpikes	    = 1;
				m_fLastLeftRightSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			}
		}		
	}

	if(m_uNumLeftRightSpikes >= dbg_uMinimumNunchuckLeftRightSpikes)
	{		
		m_uNumLeftRightSpikes = 0;
		if (dbg_bShowNunchuckLeftRightDebugInfo) { Asura_OutputToDebugger(("Nunchuck Left/Right\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_LEFT_RIGHT) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// nunchuck clockwise circle motion (already started)
	if ( m_uNumClockwiseCircleSpikes )
	{
		const u_int uNextSpikeID	 = (m_uCurrentClockwiseCircleSpike + 1) % uASURA_WII_CIRCLE_NUM_SPIKES;
		const u_int uNextNextSpikeID = (m_uCurrentClockwiseCircleSpike + 2) % uASURA_WII_CIRCLE_NUM_SPIKES;

		if (dbg_bAllowNunchuckClockwiseCircleSkips
			&& m_bClockwiseCircleAllowedToSkip
			&& (abClockwiseCircleSpikes[s_aeClockwiseCircleIDs[uNextNextSpikeID]])
			&& (m_uNumClockwiseCircleSpikes >= dbg_uMinimumNunchuckClockwiseCircleSpikes))
		{
			m_uCurrentClockwiseCircleSpike  = uNextNextSpikeID;
			m_fLastClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			m_bClockwiseCircleAllowedToSkip = false;
			++m_uNumClockwiseCircleSpikes;
		}
		else 
			if(abClockwiseCircleSpikes[s_aeClockwiseCircleIDs[uNextSpikeID]]) // inputted flick is a valid one (current should be equal to NextSpikeID to be true)
			{
				if (dbg_bAllowNunchuckClockwiseCircleSkips) { m_bClockwiseCircleAllowedToSkip  = true; }
				m_uCurrentClockwiseCircleSpike  = uNextSpikeID;
				m_fLastClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
				++m_uNumClockwiseCircleSpikes;			
			}
			else
			{			
				if(Asura_Timers::GetLocalUnscaledTime() > m_fLastClockwiseCircleSpikeTime + dbg_fNunchuckClockwiseCircleTimeout) // timed out
				{
					m_uNumClockwiseCircleSpikes = 0;
				}
			}
	}
	else
	{
		// nunchuck clockwise circle motion (starts here)
		for (u_int uClockwiseCircleSpikeDetect = 0; uClockwiseCircleSpikeDetect < uASURA_WII_CIRCLE_NUM_SPIKES; uClockwiseCircleSpikeDetect++)
		{
			if (abClockwiseCircleSpikes[s_aeClockwiseCircleIDs[uClockwiseCircleSpikeDetect]]) // if a spike is detected...
			{
				m_uCurrentClockwiseCircleSpike  = uClockwiseCircleSpikeDetect;	   // ...assign it to our first circle motion position
				m_uNumClockwiseCircleSpikes	  = 1;
				m_fLastClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			}
		}
	}

	if (m_uNumClockwiseCircleSpikes >= dbg_uMinimumNunchuckClockwiseCircleSpikes) // inputted a circle. reset timers and values
	{		
		m_uNumClockwiseCircleSpikes = 0;
		if (dbg_bShowNunchuckClockwiseCircleDebugInfo) { Asura_OutputToDebugger(("Nunchuck Clockwise Circle\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_CLOCKWISE_CIRCLE) return fASURA_CONTROLLER_INPUT_MAX;
	}

	// nunchuck anti-clockwise circle motion (already started)
	if ( m_uNumAntiClockwiseCircleSpikes )
	{
		const u_int uNextSpikeID	 = (m_uCurrentAntiClockwiseCircleSpike + 1) % uASURA_WII_CIRCLE_NUM_SPIKES;
		const u_int uNextNextSpikeID = (m_uCurrentAntiClockwiseCircleSpike + 2) % uASURA_WII_CIRCLE_NUM_SPIKES;

		if (dbg_bAllowNunchuckAntiClockwiseCircleSkips
			&& m_bAntiClockwiseCircleAllowedToSkip
			&& (abAntiClockwiseCircleSpikes[s_aeAntiClockwiseCircleIDs[uNextNextSpikeID]])
			&& (m_uNumAntiClockwiseCircleSpikes >= dbg_uMinimumNunchuckAntiClockwiseCircleSpikes))
		{
			m_uCurrentAntiClockwiseCircleSpike  = uNextNextSpikeID;
			m_fLastAntiClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			m_bAntiClockwiseCircleAllowedToSkip = false;
			++m_uNumAntiClockwiseCircleSpikes;
		}
		else 
			if(abAntiClockwiseCircleSpikes[s_aeAntiClockwiseCircleIDs[uNextSpikeID]]) // inputted flick is a valid one (current should be equal to NextSpikeID to be true)
			{
				if (dbg_bAllowNunchuckAntiClockwiseCircleSkips) { m_bAntiClockwiseCircleAllowedToSkip  = true; }
				m_uCurrentAntiClockwiseCircleSpike  = uNextSpikeID;
				m_fLastAntiClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
				++m_uNumAntiClockwiseCircleSpikes;			
			}
			else
			{			
				if(Asura_Timers::GetLocalUnscaledTime() > m_fLastAntiClockwiseCircleSpikeTime + dbg_fNunchuckAntiClockwiseCircleTimeout) // timed out
				{
					m_uNumAntiClockwiseCircleSpikes = 0;
				}
			}
	}
	else
	{
		// nunchuck anti-clockwise circle motion (starts here)
		for (u_int uAntiClockwiseCircleSpikeDetect = 0; uAntiClockwiseCircleSpikeDetect < uASURA_WII_CIRCLE_NUM_SPIKES; uAntiClockwiseCircleSpikeDetect++)
		{
			if (abAntiClockwiseCircleSpikes[s_aeAntiClockwiseCircleIDs[uAntiClockwiseCircleSpikeDetect]]) // if a spike is detected...
			{
				m_uCurrentAntiClockwiseCircleSpike  = uAntiClockwiseCircleSpikeDetect;	   // ...assign it to our first circle motion position
				m_uNumAntiClockwiseCircleSpikes	  = 1;
				m_fLastAntiClockwiseCircleSpikeTime = Asura_Timers::GetLocalUnscaledTime();
			}
		}
	}

	if (m_uNumAntiClockwiseCircleSpikes >= dbg_uMinimumNunchuckAntiClockwiseCircleSpikes) // inputted a circle. reset timers and values
	{		
		m_uNumAntiClockwiseCircleSpikes = 0;
		if (dbg_bShowNunchuckAntiClockwiseCircleDebugInfo) { Asura_OutputToDebugger(("Nunchuck AntiClockwise Circle\n")); }
		if ( m_eMotionId == ASURA_WII_MOTION_NUNCHUCK_ANTI_CLOCKWISE_CIRCLE) return fASURA_CONTROLLER_INPUT_MAX;
	}

	return 0.0f;
}


const int Asura_ControlMapping_Motion::DetectSpike(Asura_Vector_3& xSpikeDetection, const Asura_Vector_3& xGradient, 
									u_int uAxis, float fPosSpikeThreshold, float fNegSpikeThreshold)
{
	if(xSpikeDetection[uAxis] != 0.f)
	{
		if(xSpikeDetection[uAxis] * xGradient[uAxis] < 0.f)
		{
			// Hit a peak.
			if (Asura_Maths::Sign(xSpikeDetection[uAxis]) > 0.f)
			{
				xSpikeDetection[uAxis] = 0.f;				
				return 1;
			}
			else
			{
				xSpikeDetection[uAxis] = 0.f;				
				return -1;
			}
		}
	}
	else if (((xGradient[uAxis] > 0.f) && (Asura_Maths::Abs(xGradient[uAxis]) > fPosSpikeThreshold))  // positive gradient greater than positive threshold
		|| ((xGradient[uAxis] < 0.f) && (Asura_Maths::Abs(xGradient[uAxis]) > fNegSpikeThreshold))) // negative gradient greater than negative threshold
	{
		xSpikeDetection[uAxis] = Asura_Maths::Sign(xGradient[uAxis]);
	}
	return 0;
}

// Get the right acceleration for the motion we're looking at
const Asura_Vector_3 Asura_ControlMapping_Motion::GetCurrentAccSample(void)
{
	const Asura_Wii_Controller* pxController = static_cast<const Asura_Wii_Controller*>(Asura_Input::GetController(m_iController));

	if (!pxController) return Asura_Maths::ZeroVector3;

	if (GetMotionSource() == ASURA_WII_MOTION_SOURCE_REMOTE)
	{
		return pxController->GetRemoteAcceleration();
	}
	else
	{
		return pxController->GetNunchuckAcceleration();
	}
}

const Asura_Vector_3 Asura_ControlMapping_Motion::GetPrevAccSample(void)
{
	const Asura_Wii_Controller* pxController = static_cast<const Asura_Wii_Controller*>(Asura_Input::GetController(m_iController));

	if (!pxController) return Asura_Maths::ZeroVector3;

	if (GetMotionSource() == ASURA_WII_MOTION_SOURCE_REMOTE)
	{
		return pxController->GetRemoteAcceleration(dbg_uNumFramesToLookBack);
	}
	else
	{
		return pxController->GetNunchuckAcceleration(dbg_uNumFramesToLookBack);
	}
}

const ASURA_WII_MOTION_SOURCE Asura_ControlMapping_Motion::GetMotionSource() const
{
	switch ( m_eMotionId )
	{
	case ASURA_WII_MOTION_REMOTE_FLICK_LEFT_X:
	case ASURA_WII_MOTION_REMOTE_FLICK_RIGHT_X:
	case ASURA_WII_MOTION_REMOTE_FLICK_UP_Y:
	case ASURA_WII_MOTION_REMOTE_FLICK_DOWN_Y:
	case ASURA_WII_MOTION_REMOTE_FLICK_IN_Z:
	case ASURA_WII_MOTION_REMOTE_FLICK_OUT_Z:
	case ASURA_WII_MOTION_REMOTE_FLICK:
	case ASURA_WII_MOTION_REMOTE_SHAKE:
	case ASURA_WII_MOTION_REMOTE_IN_OUT:
	case ASURA_WII_MOTION_REMOTE_UP_DOWN:
	case ASURA_WII_MOTION_REMOTE_LEFT_RIGHT:
	case ASURA_WII_MOTION_REMOTE_CLOCKWISE_CIRCLE:
	case ASURA_WII_MOTION_REMOTE_ANTI_CLOCKWISE_CIRCLE:
		{
			return ASURA_WII_MOTION_SOURCE_REMOTE;
		}
	case ASURA_WII_MOTION_NUNCHUCK_FLICK_LEFT_X:
	case ASURA_WII_MOTION_NUNCHUCK_FLICK_RIGHT_X:
	case ASURA_WII_MOTION_NUNCHUCK_FLICK_UP_Y:
	case ASURA_WII_MOTION_NUNCHUCK_FLICK_DOWN_Y:
	case ASURA_WII_MOTION_NUNCHUCK_FLICK_IN_Z:
	case ASURA_WII_MOTION_NUNCHUCK_FLICK_OUT_Z:
	case ASURA_WII_MOTION_NUNCHUCK_FLICK:
	case ASURA_WII_MOTION_NUNCHUCK_SHAKE:
	case ASURA_WII_MOTION_NUNCHUCK_IN_OUT:
	case ASURA_WII_MOTION_NUNCHUCK_UP_DOWN:
	case ASURA_WII_MOTION_NUNCHUCK_LEFT_RIGHT:
	case ASURA_WII_MOTION_NUNCHUCK_CLOCKWISE_CIRCLE:
	case ASURA_WII_MOTION_NUNCHUCK_ANTI_CLOCKWISE_CIRCLE:
		{
			return ASURA_WII_MOTION_SOURCE_NUNCHUCK;
		}
	case ASURA_WII_NUM_MOTIONS:
	default:
		{
			Asura_Assert( false, "Motion type not recognised" );
			return ASURA_WII_MOTION_SOURCE_REMOTE;
		}
	}
}

// Clear the stored value.
void Asura_ControlMapping_Motion::ResetValue()
{
	m_bActive	= false;
}


/// End of file. ///
