#include "global.h"
#include "PaneDisplay.h"
#include "ThemeManager.h"
#include "GameState.h"
#include "Song.h"
#include "Steps.h"
#include "RageLog.h"
#include "ProfileManager.h"
#include "SongManager.h"
#include "Course.h"
#include "Style.h"

#define SHIFT_X(p)			THEME->GetMetricF(m_sName, ssprintf("ShiftP%iX", p+1))
#define SHIFT_Y(p)			THEME->GetMetricF(m_sName, ssprintf("ShiftP%iY", p+1))
#define NUM_ITEM_COLORS(s)		THEME->GetMetricI(m_sName, ssprintf("%sNumLevels",s))
#define ITEM_COLOR(s,n)			THEME->GetMetric (m_sName, ssprintf("%sLevel%i",s,n+1))

enum { NEED_NOTES=1, NEED_COURSE=2, NEED_PROFILE=4 };
struct Content_t
{
	const char *name;
	PaneTypes	type;
	int req;
};

static const Content_t g_Contents[NUM_PANE_CONTENTS] =
{
	{ "SongNumSteps",			PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "SongJumps",				PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "SongHolds",				PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "SongRolls",				PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "SongMines",				PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "SongHands",				PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "SongLifts",				PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "SongHiddens",			PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "DifficultyStream",			NUM_PANES,			NEED_NOTES }, // hide
	{ "DifficultyChaos",			NUM_PANES,			NEED_NOTES },
	{ "DifficultyFreeze",			NUM_PANES,			NEED_NOTES },
	{ "DifficultyAir",			NUM_PANES,			NEED_NOTES },
	{ "DifficultyVoltage",			NUM_PANES,			NEED_NOTES },
	{ "MachineHighScore",			PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "MachineNumPlays",			PANE_SONG_MACHINE_SCORES,	NEED_NOTES },
	{ "MachineRank",			PANE_SONG_MACHINE_SCORES,	NEED_NOTES },
	{ "MachineHighName",			PANE_SONG_DIFFICULTY,		NEED_NOTES },
	{ "ProfileHighScore",			PANE_SONG_DIFFICULTY,		NEED_NOTES|NEED_PROFILE },
	{ "ProfileNumPlays",			PANE_SONG_PROFILE_SCORES,	NEED_NOTES|NEED_PROFILE },
	{ "ProfileRank",			PANE_SONG_PROFILE_SCORES,	NEED_NOTES|NEED_PROFILE },
	{ "CourseMachineHighScore",		PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseMachineNumPlays",		NUM_PANES,			NEED_COURSE },
	{ "CourseMachineRank",			NUM_PANES,			NEED_COURSE },
	{ "CourseMachineHighName",		PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseProfileHighScore",		PANE_COURSE_MACHINE_SCORES,	NEED_COURSE|NEED_PROFILE },
	{ "CourseProfileNumPlays",		NUM_PANES,			NEED_COURSE|NEED_PROFILE },
	{ "CourseProfileRank",			NUM_PANES,			NEED_COURSE|NEED_PROFILE },
	{ "CourseNumSteps",			PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseJumps",			PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseHolds",			PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseRolls",			PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseMines",			PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseHands",			PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseLifts",			PANE_COURSE_MACHINE_SCORES,	NEED_COURSE },
	{ "CourseHiddens",			PANE_COURSE_MACHINE_SCORES,	NEED_COURSE }
};

static const PaneModes PaneMode[NUM_PANES] =
{
	PANEMODE_SONG,
	PANEMODE_SONG,
	PANEMODE_SONG,
//	PANEMODE_SONG,
	PANEMODE_BATTLE,
	PANEMODE_COURSE,
	PANEMODE_COURSE
//	PANEMODE_COURSE
};

static ProfileSlot PlayerMemCard( PlayerNumber pn )
{
	switch( pn )
	{
	case PLAYER_1: return PROFILE_SLOT_PLAYER_1;
	case PLAYER_2: return PROFILE_SLOT_PLAYER_2;
	default: ASSERT(0);	return PROFILE_SLOT_MACHINE;
	};
}

PaneDisplay::PaneDisplay()
{
	m_CurPane = PANE_INVALID;
	m_CurMode = PANEMODE_SONG;
}

void PaneDisplay::Load( PlayerNumber pn )
{
	m_PlayerNumber = pn;
	m_PreferredPaneForMode[PANEMODE_SONG] = PANE_SONG_DIFFICULTY;
	m_PreferredPaneForMode[PANEMODE_BATTLE] = PANE_BATTLE_DIFFICULTY;
	m_PreferredPaneForMode[PANEMODE_COURSE] = PANE_COURSE_MACHINE_SCORES;

	m_sprPaneUnder.Load( THEME->GetPathToG( ssprintf("PaneDisplay under p%i", pn+1)) );
	m_sprPaneUnder->SetName( "Under" );
	ON_COMMAND( m_sprPaneUnder );
	this->AddChild( m_sprPaneUnder );

	int p;
	for( p = 0; p < NUM_PANE_CONTENTS; ++p )
	{
		if( g_Contents[p].type == NUM_PANES )
			continue; /* skip, disabled */

		m_textContents[p].LoadFromFont( THEME->GetPathToF( "PaneDisplay text" ) );
		m_textContents[p].SetName( ssprintf("%sText", g_Contents[p].name) );
		SET_XY_AND_ON_COMMAND( m_textContents[p] );
		m_ContentsFrame.AddChild( &m_textContents[p] );

		m_Labels[p].Load( THEME->GetPathToG( ssprintf("PaneDisplay %s label", g_Contents[p].name)) );
		m_Labels[p]->SetName( ssprintf("%sLabel", g_Contents[p].name) );
		SET_XY_AND_ON_COMMAND( m_Labels[p] );
		m_ContentsFrame.AddChild( m_Labels[p] );
	}

	m_ContentsFrame.SetXY( SHIFT_X(m_PlayerNumber), SHIFT_Y(m_PlayerNumber) );
	this->AddChild( &m_ContentsFrame );

	m_sprPaneOver.Load( THEME->GetPathToG( ssprintf("PaneDisplay over p%i", pn+1)) );
	m_sprPaneOver->SetName( "Over" );
	ON_COMMAND( m_sprPaneOver );
	this->AddChild( m_sprPaneOver );

	for( unsigned i = 0; i < NUM_PANE_CONTENTS; ++i )
	{
		if( g_Contents[i].type == NUM_PANES )
			continue; /* skip, disabled */
		COMMAND( m_textContents[i], "LoseFocus"  );
		COMMAND( m_Labels[i], "LoseFocus"  );
		m_textContents[i].FinishTweening();
		m_Labels[i]->FinishTweening();
	}

	m_CurMode = GetMode();
	SetFocus( GetNext( m_PreferredPaneForMode[m_CurMode], 0 ) );
}

void PaneDisplay::Update( float fDeltaTime )
{
	ActorFrame::Update( fDeltaTime );
}

void PaneDisplay::SetContent( PaneContents c )
{
	CString str = "?";	// fill this in
	float val = 0;	// fill this in

	const Song *pSong = GAMESTATE->m_pCurSong;
	const Steps *pSteps = GAMESTATE->m_pCurSteps[m_PlayerNumber];
	const Course *pCourse = GAMESTATE->m_pCurCourse;
	const Trail *pTrail = GAMESTATE->m_pCurTrail[m_PlayerNumber];
	const Profile *pProfile = PROFILEMAN->GetProfile( m_PlayerNumber );
	bool bIsEdit = pSteps && pSteps->GetDifficulty() == DIFFICULTY_EDIT;

	if( (g_Contents[c].req&NEED_NOTES) && !pSteps )
		goto done;
	if( (g_Contents[c].req&NEED_COURSE) && !pTrail )
		goto done;
	if( (g_Contents[c].req&NEED_PROFILE) && !pProfile )
	{
		str = "N/A";
		goto done;
	}

	{
		RadarValues rv;

		if( g_Contents[c].req&NEED_NOTES )
			rv = pSteps->GetRadarValues();
		else if( g_Contents[c].req&NEED_COURSE )
			rv = pTrail->GetRadarValues();

		switch( c )
		{
		case SONG_DIFFICULTY_RADAR_STREAM:	val = rv[RADAR_STREAM]; break;
		case SONG_DIFFICULTY_RADAR_VOLTAGE:	val = rv[RADAR_VOLTAGE]; break;
		case SONG_DIFFICULTY_RADAR_AIR:		val = rv[RADAR_AIR]; break;
		case SONG_DIFFICULTY_RADAR_FREEZE:	val = rv[RADAR_FREEZE]; break;
		case SONG_DIFFICULTY_RADAR_CHAOS:	val = rv[RADAR_CHAOS]; break;
		case COURSE_NUM_STEPS:
		case SONG_NUM_STEPS:				val = rv[RADAR_NUM_TAPS_AND_HOLDS_AND_ROLLS]; break;
		case COURSE_JUMPS:
		case SONG_JUMPS:					val = rv[RADAR_NUM_JUMPS]; break;
		case COURSE_HOLDS:
		case SONG_HOLDS:					val = rv[RADAR_NUM_HOLDS]; break;
		case COURSE_ROLLS:
		case SONG_ROLLS:					val = rv[RADAR_NUM_ROLLS]; break;
		case COURSE_MINES:
		case SONG_MINES:					val = rv[RADAR_NUM_MINES]; break;
		case COURSE_HANDS:
		case SONG_HANDS:					val = rv[RADAR_NUM_HANDS]; break;
		case COURSE_LIFTS:
		case SONG_LIFTS:					val = rv[RADAR_NUM_LIFTS]; break;
		case COURSE_HIDDENS:
		case SONG_HIDDENS:					val = rv[RADAR_NUM_HIDDEN]; break;
		case SONG_PROFILE_HIGH_SCORE:
			val = 100.0f * PROFILEMAN->GetProfile(m_PlayerNumber)->GetStepsHighScoreList(pSong,pSteps).GetTopScore().fPercentDP;
			break;
		case SONG_PROFILE_NUM_PLAYS:
			val = (float) PROFILEMAN->GetProfile(m_PlayerNumber)->GetStepsNumTimesPlayed(pSong,pSteps);
			break;

		case SONG_MACHINE_HIGH_NAME: /* set val for color */
		case SONG_MACHINE_HIGH_SCORE:
			CHECKPOINT;
			if( bIsEdit )	
				goto done;	// no machine scores for edits
			val = 100.0f * PROFILEMAN->GetMachineProfile()->GetStepsHighScoreList(pSong,pSteps).GetTopScore().fPercentDP;
			break;

		case SONG_MACHINE_RANK:
			{
			const vector<Song*> best = SONGMAN->GetBestSongs( PROFILE_SLOT_MACHINE );
			val = (float) FindIndex( best.begin(), best.end(), pSong );
			val += 1;
			break;
			}

		case SONG_PROFILE_RANK:
			{
			const vector<Song*> best = SONGMAN->GetBestSongs( PlayerMemCard(m_PlayerNumber) );
			val = (float) FindIndex( best.begin(), best.end(), pSong );
			val += 1;
			break;
			}

		case COURSE_MACHINE_HIGH_NAME: /* set val for color */
		case COURSE_MACHINE_HIGH_SCORE:
			val = 100.0f * PROFILEMAN->GetMachineProfile()->GetCourseHighScoreList(pCourse,pTrail).GetTopScore().fPercentDP;
			break;

		case COURSE_MACHINE_NUM_PLAYS:
			val = (float) PROFILEMAN->GetMachineProfile()->GetCourseNumTimesPlayed( pCourse );
			break;

		case COURSE_MACHINE_RANK:
			{
			const vector<Course*> best = SONGMAN->GetBestCourses( PROFILE_SLOT_MACHINE );
			val = (float) FindIndex( best.begin(), best.end(), pCourse );
			val += 1;
			}
			break;

		case COURSE_PROFILE_HIGH_SCORE:
			val = 100.0f * PROFILEMAN->GetProfile(m_PlayerNumber)->GetCourseHighScoreList(pCourse,pTrail).GetTopScore().fPercentDP;
			break;
		case COURSE_PROFILE_NUM_PLAYS:
			val = (float) PROFILEMAN->GetProfile(m_PlayerNumber)->GetCourseNumTimesPlayed( pCourse );
			break;

		case COURSE_PROFILE_RANK:
			const vector<Course*> best = SONGMAN->GetBestCourses( PlayerMemCard(m_PlayerNumber) );
			val = (float) FindIndex( best.begin(), best.end(), pCourse );
			val += 1;
			break;
		};

		if( val == RADAR_VAL_UNKNOWN )
			goto done;

		/* Scale, round, clamp, etc. for floats: */
		switch( c )
		{
		case SONG_DIFFICULTY_RADAR_STREAM:
		case SONG_DIFFICULTY_RADAR_VOLTAGE:
		case SONG_DIFFICULTY_RADAR_AIR:
		case SONG_DIFFICULTY_RADAR_FREEZE:
		case SONG_DIFFICULTY_RADAR_CHAOS:
			val = roundf( SCALE( val, 0, 1, 0, 10 ) );
			val = clamp( val, 0, 10 );
			str = ssprintf( "%.0f", val );
			break;
		}

		switch( c )
		{
		case SONG_MACHINE_HIGH_NAME:
			str = PROFILEMAN->GetMachineProfile()->GetStepsHighScoreList(pSong,pSteps).GetTopScore().sName;
			break;
		case COURSE_MACHINE_HIGH_NAME:

			str = PROFILEMAN->GetMachineProfile()->GetCourseHighScoreList(pCourse,pTrail).GetTopScore().sName;
			break;

		case SONG_MACHINE_HIGH_SCORE:
		case COURSE_MACHINE_HIGH_SCORE:
		case SONG_PROFILE_HIGH_SCORE:
		case COURSE_PROFILE_HIGH_SCORE:
			str = ssprintf( "%.2f%%", val );
			break;
		case SONG_NUM_STEPS:
		case SONG_JUMPS:
		case SONG_HOLDS:
		case SONG_ROLLS:
		case SONG_MINES:
		case SONG_HANDS:
		case SONG_LIFTS:
		case SONG_HIDDENS:
		case COURSE_NUM_STEPS:
		case COURSE_JUMPS:
		case COURSE_HOLDS:
		case COURSE_ROLLS:
		case COURSE_MINES:
		case COURSE_HANDS:
		case COURSE_LIFTS:
		case COURSE_HIDDENS:
		case SONG_MACHINE_NUM_PLAYS:
		case COURSE_MACHINE_NUM_PLAYS:
		case SONG_PROFILE_NUM_PLAYS:
		case COURSE_PROFILE_NUM_PLAYS:
		case SONG_MACHINE_RANK:
		case COURSE_MACHINE_RANK:
		case SONG_PROFILE_RANK:
		case COURSE_PROFILE_RANK:
			str = ssprintf( "%.0f", val );
		}
	}


done:
	m_textContents[c].SetText( str );

	const int num = NUM_ITEM_COLORS( g_Contents[c].name );
	for( int p = 0; p < num; ++p )
	{
		const CString metric = ITEM_COLOR(g_Contents[c].name, p);
		CStringArray spec;
		split( metric, ";", spec );
		if( spec.size() < 2 )
			RageException::Throw( "Metric '%s' malformed", metric.c_str() );

		const float n = strtof( spec[0], NULL );
		if( val >= n )
			continue;
		spec.erase( spec.begin(), spec.begin()+1 );

		m_textContents[c].Command( join(";", spec) );
		break;
	}
}

void PaneDisplay::SetFromGameState()
{
	m_CurMode = GetMode();
	if( PaneMode[m_CurPane] != m_CurMode )
		SetFocus( GetNext( m_PreferredPaneForMode[m_CurMode], 0 ) );

	/* Don't update text that doesn't apply to the current mode.  It's still tweening off. */
	for( unsigned i = 0; i < NUM_PANE_CONTENTS; ++i )
	{
		if( g_Contents[i].type != m_CurPane )
			continue;
		SetContent( (PaneContents) i );
	}
}

PaneModes PaneDisplay::GetMode() const
{
	switch( GAMESTATE->m_SortOrder )
	{
	case SORT_ALL_COURSES:
	case SORT_NONSTOP_COURSES:
	case SORT_ONI_COURSES:
	case SORT_ENDLESS_COURSES:
		return PANEMODE_COURSE;
	case SORT_SORT_MENU:
	case SORT_MODE_MENU:
		return m_CurMode; // leave it
	default:
		return PANEMODE_SONG;
	}
}


void PaneDisplay::SetFocus( PaneTypes NewPane )
{
	if( m_CurPane == NewPane )
		return;

	for( unsigned i = 0; i < NUM_PANE_CONTENTS; ++i )
	{
		if( g_Contents[i].type == NUM_PANES )
			continue; /* skip, disabled */

		if( g_Contents[i].type == m_CurPane )
		{
			COMMAND( m_textContents[i], "LoseFocus"  );
			COMMAND( m_Labels[i], "LoseFocus"  );
		}
		else if( g_Contents[i].type == NewPane )
		{
			COMMAND( m_textContents[i], "GainFocus" );
			COMMAND( m_Labels[i], "GainFocus" );
		}
	}

	m_CurPane = NewPane;
	m_PreferredPaneForMode[m_CurMode] = NewPane;

	SetFromGameState();
}

bool PaneDisplay::PaneIsValid( PaneTypes p ) const
{
	if( PaneMode[p] != m_CurMode )
		return false;

	switch( p )
	{
	case PANE_SONG_PROFILE_SCORES:
	case PANE_COURSE_PROFILE_SCORES:
		if( !PROFILEMAN->IsUsingProfile( m_PlayerNumber ) )
			return false;
	}

	return true;
}

PaneTypes PaneDisplay::GetNext( PaneTypes current, int dir ) const
{
	if( dir == 0 )
	{
		/* Only move the selection if the current selection is invalid. */
		if( PaneIsValid( current ) )
			return current;

		dir = 1;
	}

	PaneTypes ret = current;
	while( 1 )
	{
		ret = (PaneTypes) (ret + dir);
		wrap( (int&) ret, NUM_PANES );

		if( PaneIsValid( ret ) )
			break;
	}

	LOG->Trace("pane %i", ret);

	return ret;
}

void PaneDisplay::Move( int dir )
{
	const PaneTypes NewPane = GetNext( m_CurPane, dir );
	SetFocus( NewPane );
}

/*
 * (c) 2003 Glenn Maynard
 * 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.
 */
