#include "global.h"

#include "ActorUtil.h"
#include "BeginnerHelper.h"
#include "GameState.h"
#include "PrefsManager.h"
#include "RageLog.h"
#include "RageDisplay.h"
#include "Steps.h"
#include "Style.h"
#include "Model.h"
#include "ScreenDimensions.h"
#include "ThemeManager.h"

// "PLAYER_X" offsets are relative to the pad. ex: Setting this to 10, and the HELPER to 300, will put the dancer at 310
#define PLAYER_X( px )		THEME->GetMetricF("BeginnerHelper",ssprintf("Player%dX",px+1))
#define PLAYER_ANGLE		THEME->GetMetricF("BeginnerHelper","PlayerAngle")
#define DANCEPAD_ANGLE		THEME->GetMetricF("BeginnerHelper","DancePadAngle")

// "HELPER" offsets effect the pad/dancer as a whole. Their relative Y cooridinates are hard-coded for each other.
#define HELPER_X			THEME->GetMetricF("BeginnerHelper","HelperX")
#define HELPER_Y			THEME->GetMetricF("BeginnerHelper","HelperY")

#define ST_LEFT		0x01
#define ST_DOWN		0x02
#define ST_UP		0x04
#define ST_RIGHT	0x08
#define ST_JUMPLR	(ST_LEFT | ST_RIGHT)
#define ST_JUMPUD	(ST_UP | ST_DOWN)

enum Animation
{
	ANIM_DANCE_PAD,
	ANIM_DANCE_PADS,
	ANIM_UP,
	ANIM_DOWN,
	ANIM_LEFT,
	ANIM_RIGHT,
	ANIM_JUMPLR,
	NUM_ANIMATIONS
};

static const char *anims[NUM_ANIMATIONS] =
{
	"DancePad.txt",
	"DancePads.txt",
	"BeginnerHelper_step-up.bones.txt",
	"BeginnerHelper_step-down.bones.txt",
	"BeginnerHelper_step-left.bones.txt",
	"BeginnerHelper_step-right.bones.txt",
	"BeginnerHelper_step-jumplr.bones.txt"
};

static RString GetAnimPath( Animation a )
{
	return RString( "Characters/" ) + anims[a];
}

BeginnerHelper::BeginnerHelper()
{
	m_bShowBackground = true;
	m_bInitialized = false;
	m_iLastRowChecked = m_iLastRowFlashed = 0;
	FOREACH_PlayerNumber( pn )
		m_bPlayerEnabled[pn] = false;
	FOREACH_PlayerNumber( pn )
		m_pDancer[pn] = new Model;
	m_pDancePad = new Model;
}

BeginnerHelper::~BeginnerHelper()
{
	FOREACH_PlayerNumber( pn )
		delete m_pDancer[pn];
	delete m_pDancePad;
}

bool BeginnerHelper::Init( int iDancePadType )
{
	ASSERT( !m_bInitialized );
	if( !CanUse() )
		return false;

	// If no players were successfully added, bail.
	{
		bool bAnyLoaded = false;
		for( int pn=0; pn<NUM_PLAYERS; pn++ )
			if( m_bPlayerEnabled[pn] )
				bAnyLoaded = true;

		if( !bAnyLoaded )
			return false;
	}

	// Load the Background and flash. Flash only shows if the BG does.
	if( m_bShowBackground )
	{
		m_sBackground.Load( THEME->GetPathG("BeginnerHelper","background") );
		this->AddChild( m_sBackground );
		//m_sBackground.SetXY( 1, 1 );

		m_sFlash.Load( THEME->GetPathG("BeginnerHelper","flash") );
		m_sFlash.SetXY( 0, 0 );
		m_sFlash.SetDiffuseAlpha( 0 );
	}

	// Load StepCircle graphics
	for( int lsc=0; lsc<NUM_PLAYERS; lsc++ )
	{
		for( int lsce=0; lsce<4; lsce++ )
		{
			m_sStepCircle[lsc][lsce].Load( THEME->GetPathG("BeginnerHelper","stepcircle") );
			m_sStepCircle[lsc][lsce].SetZoom( 0 );	// Hide until needed.
			this->AddChild(&m_sStepCircle[lsc][lsce]);

			// Set StepCircle coordinates
			switch( lsce )
			{
			case 0: m_sStepCircle[lsc][lsce].SetXY((HELPER_X+PLAYER_X(lsc)-80),HELPER_Y);	break;	// Left
			case 1: m_sStepCircle[lsc][lsce].SetXY((HELPER_X+PLAYER_X(lsc)+80),HELPER_Y);	break;	// Right
			case 2: m_sStepCircle[lsc][lsce].SetXY((HELPER_X+PLAYER_X(lsc)),(HELPER_Y-60));	break;	// Up
			case 3: m_sStepCircle[lsc][lsce].SetXY((HELPER_X+PLAYER_X(lsc)),(HELPER_Y+60));	break;	// Down
			}
		}
	}

	SHOW_DANCE_PAD.Load( "BeginnerHelper","ShowDancePad" );
	// Load the DancePad
	if( SHOW_DANCE_PAD )
	{
		switch( iDancePadType )
		{
		case 0: break; // No pad
		case 1: m_pDancePad->LoadMilkshapeAscii(GetAnimPath(ANIM_DANCE_PAD)); break;
		case 2: m_pDancePad->LoadMilkshapeAscii(GetAnimPath(ANIM_DANCE_PADS)); break;
		}

		m_pDancePad->SetHorizAlign( align_left ); // xxx: hardcoded -aj
		m_pDancePad->SetRotationX( DANCEPAD_ANGLE );
		m_pDancePad->SetX( HELPER_X );
		m_pDancePad->SetY( HELPER_Y );
		// xxx: hardcoded -aj
		m_pDancePad->SetZoom( 23 );	// Pad should always be 3 units bigger in zoom than the dancer.
	}

	for( int pl=0; pl<NUM_PLAYERS; pl++ )	// Load players
	{
		// Skip if not enabled
		if( !m_bPlayerEnabled[pl] )
			continue;

		// Load character data
		const Character *Character = GAMESTATE->m_pCurCharacters[pl];
		ASSERT( Character != NULL );

		// Load textures
		m_pDancer[pl]->SetHorizAlign( align_left );
		m_pDancer[pl]->LoadMilkshapeAscii( Character->GetModelPath() );

		// Load needed animations
		m_pDancer[pl]->LoadMilkshapeAsciiBones( "Step-LEFT",	GetAnimPath(ANIM_LEFT) );
		m_pDancer[pl]->LoadMilkshapeAsciiBones( "Step-DOWN",	GetAnimPath(ANIM_DOWN) );
		m_pDancer[pl]->LoadMilkshapeAsciiBones( "Step-UP", 	GetAnimPath(ANIM_UP) );
		m_pDancer[pl]->LoadMilkshapeAsciiBones( "Step-RIGHT", 	GetAnimPath(ANIM_RIGHT) );
		m_pDancer[pl]->LoadMilkshapeAsciiBones( "Step-JUMPLR", 	GetAnimPath(ANIM_JUMPLR) );
		m_pDancer[pl]->LoadMilkshapeAsciiBones( "rest",		Character->GetRestAnimationPath() );
		m_pDancer[pl]->SetDefaultAnimation( "rest" );		// Stay bouncing after a step has finished animating
		m_pDancer[pl]->PlayAnimation( "rest" );
		m_pDancer[pl]->SetRotationX( PLAYER_ANGLE );
		m_pDancer[pl]->SetX( HELPER_X+PLAYER_X(pl) );
		m_pDancer[pl]->SetY( HELPER_Y+10 );
		m_pDancer[pl]->SetZoom( 20 );
		m_pDancer[pl]->SetCullMode( CULL_NONE );		// many of the models floating around have the vertex order flipped
	}

	m_bInitialized = true;
	return true;
}

void BeginnerHelper::ShowStepCircle( PlayerNumber pn, int CSTEP )
{
	int	isc=0;	// Save OR issues within array boundries.. it's worth the extra few bytes of memory.
	switch(CSTEP)
	{
	case ST_LEFT:	isc=0;	break;
	case ST_RIGHT:	isc=1; 	break;
	case ST_UP:	isc=2;	break;
	case ST_DOWN:	isc=3;	break;
	}
	
	m_sStepCircle[pn][isc].StopEffect();
	m_sStepCircle[pn][isc].SetZoom( 2 );
	m_sStepCircle[pn][isc].StopTweening();
	m_sStepCircle[pn][isc].BeginTweening( GAMESTATE->m_Position.m_fCurBPS/3, TWEEN_LINEAR );
	m_sStepCircle[pn][isc].SetZoom( 0 );
}

void BeginnerHelper::AddPlayer( PlayerNumber pn, const NoteData &ns )
{
	ASSERT( !m_bInitialized );
	ASSERT( pn >= 0 && pn < NUM_PLAYERS );
	ASSERT( GAMESTATE->IsHumanPlayer(pn) );

	if( !CanUse() )
		return;
	
	const Character *Character = GAMESTATE->m_pCurCharacters[pn];
	ASSERT( Character != NULL );
	if( !DoesFileExist(Character->GetModelPath()) )
		return;

	m_NoteData[pn].CopyAll( ns );
	m_bPlayerEnabled[pn] = true;
}

bool BeginnerHelper::CanUse()
{
	for (int i=0; i<NUM_ANIMATIONS; ++i )
		if( !DoesFileExist(GetAnimPath((Animation)i)) )
			return false;

	return GAMESTATE->GetCurrentStyle()->m_bCanUseBeginnerHelper;
}

void BeginnerHelper::DrawPrimitives()
{
	// If not initialized, don't bother with this
	if( !m_bInitialized )
		return;

	ActorFrame::DrawPrimitives();
	m_sFlash.Draw();

	bool DrawCelShaded = PREFSMAN->m_bCelShadeModels;
	// Draw Pad
	if( SHOW_DANCE_PAD )
	{
		if( DrawCelShaded )
			m_pDancePad->DrawCelShaded();
		else
		{
			DISPLAY->SetLighting( true );
			DISPLAY->SetLightDirectional( 
				0, 
				RageColor(0.5,0.5,0.5,1), 
				RageColor(1,1,1,1),
				RageColor(0,0,0,1),
				RageVector3(0, 0, 1) );

			m_pDancePad->Draw();
			DISPLAY->ClearZBuffer();	// So character doesn't step "into" the dance pad.
			DISPLAY->SetLightOff( 0 );
			DISPLAY->SetLighting( false );
		}
	}

	// Draw StepCircles
	for(int scd=0; scd<NUM_PLAYERS; scd++)
		for(int scde=0; scde<4; scde++)
			m_sStepCircle[scd][scde].Draw();

	// Draw Dancers
	if( DrawCelShaded )
	{
		FOREACH_PlayerNumber( pn )
			if( GAMESTATE->IsHumanPlayer(pn) )
				m_pDancer[pn]->DrawCelShaded();
	}
	else
	{
		DISPLAY->SetLighting( true );
		DISPLAY->SetLightDirectional( 
			0, 
			RageColor(0.5,0.5,0.5,1), 
			RageColor(1,1,1,1),
			RageColor(0,0,0,1),
			RageVector3(0, 0, 1) );

		FOREACH_PlayerNumber( pn )
			if( GAMESTATE->IsHumanPlayer(pn) )
				m_pDancer[pn]->Draw();

		DISPLAY->SetLightOff( 0 );
		DISPLAY->SetLighting( false );
	}
}

void BeginnerHelper::Step( PlayerNumber pn, int CSTEP )
{
	m_pDancer[pn]->StopTweening();
	m_pDancer[pn]->SetRotationY( 0 );	// Make sure we're not still inside of a JUMPUD tween.

	switch(CSTEP)
	{
	case ST_LEFT:
		ShowStepCircle( pn, ST_LEFT );
		m_pDancer[pn]->PlayAnimation( "Step-LEFT", 1.5f );
		break;
	case ST_RIGHT:
		ShowStepCircle( pn, ST_RIGHT );
		m_pDancer[pn]->PlayAnimation( "Step-RIGHT", 1.5f );
		break;
	case ST_UP:
		ShowStepCircle( pn, ST_UP );
		m_pDancer[pn]->PlayAnimation( "Step-UP", 1.5f );
		break;
	case ST_DOWN:
		ShowStepCircle( pn, ST_DOWN );
		m_pDancer[pn]->PlayAnimation( "Step-DOWN", 1.5f );
		break;
	case ST_JUMPLR:
		ShowStepCircle( pn, ST_LEFT );
		ShowStepCircle( pn, ST_RIGHT );
		m_pDancer[pn]->PlayAnimation( "Step-JUMPLR", 1.5f );
		break;
	case ST_JUMPUD:
		ShowStepCircle( pn, ST_UP );
		ShowStepCircle( pn, ST_DOWN );
		m_pDancer[pn]->StopTweening();
		m_pDancer[pn]->PlayAnimation( "Step-JUMPLR", 1.5f );
		m_pDancer[pn]->BeginTweening( GAMESTATE->m_Position.m_fCurBPS/8, TWEEN_LINEAR );
		m_pDancer[pn]->SetRotationY( 90 );
		m_pDancer[pn]->BeginTweening( 1/(GAMESTATE->m_Position.m_fCurBPS * 2) ); //sleep between jump-frames
		m_pDancer[pn]->BeginTweening( GAMESTATE->m_Position.m_fCurBPS /6, TWEEN_LINEAR );
		m_pDancer[pn]->SetRotationY( 0 );
		break;
	}

	m_sFlash.StopEffect();
	m_sFlash.StopTweening();
	m_sFlash.Sleep( GAMESTATE->m_Position.m_fCurBPS/16 );
	m_sFlash.SetDiffuseAlpha( 1 );
	m_sFlash.BeginTweening( 1/GAMESTATE->m_Position.m_fCurBPS * 0.5f );
	m_sFlash.SetDiffuseAlpha( 0 );
}

void BeginnerHelper::Update( float fDeltaTime )
{
	if( !m_bInitialized )
		return;

	// the row we want to check on this update
	int iCurRow = BeatToNoteRowNotRounded( GAMESTATE->m_Position.m_fSongBeat + 0.4f );
	FOREACH_EnabledPlayer( pn )
	{
		for( int iRow=m_iLastRowChecked; iRow<iCurRow; iRow++ )
		{
			// Check if there are any notes at all on this row.. If not, save scanning.
			if( !m_NoteData[pn].IsThereATapAtRow(iRow) )
				continue;

			// Find all steps on this row, in order to show the correct animations
			int iStep = 0;
			const int iNumTracks = m_NoteData[pn].GetNumTracks(); 
			for( int t=0; t<iNumTracks; t++ )
				if( m_NoteData[pn].GetTapNote(t,iRow).type == TapNote::tap )
					iStep |= 1 << t;

			// Assign new data
			this->Step( pn, iStep );
		}
	}

	// Make sure we don't accidentally scan a row 2x
	m_iLastRowChecked = iCurRow;

	// Update animations
	ActorFrame::Update( fDeltaTime );
	m_pDancePad->Update( fDeltaTime );
	m_sFlash.Update( fDeltaTime );

	float beat = fDeltaTime*GAMESTATE->m_Position.m_fCurBPS;
	// If this is not a human player, the dancer is not shown
	FOREACH_HumanPlayer( pu )
	{
		// Update dancer's animation and StepCircles
		m_pDancer[pu]->Update( beat );
		for( int scu=0; scu<NUM_PLAYERS; scu++ )
			for( int scue=0; scue<4; scue++ )
				m_sStepCircle[scu][scue].Update( beat );
	}
}

/*
 * (c) 2003 Kevin Slaughter, Thad Ward
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
