#include "global.h"
#include "Background.h"
#include "RageUtil.h"
#include "GameConstantsAndTypes.h"
#include "RageTimer.h"
#include "RageLog.h"
#include "RageTextureManager.h"
#include "GameState.h"
#include "PrefsManager.h"
#include "NoteTypes.h"
#include "Steps.h"
#include "DancingCharacters.h"
#include "BeginnerHelper.h"
#include "StatsManager.h"
#include "ScreenDimensions.h"
#include "ThemeMetric.h"
#include "PlayerState.h"
#include "ActorUtil.h"
#include <float.h>
#include "XmlFile.h"
#include "XmlFileUtil.h"
#include "BackgroundUtil.h"
#include "Song.h"
#include "AutoActor.h"

static ThemeMetric<float> LEFT_EDGE				("Background","LeftEdge");
static ThemeMetric<float> TOP_EDGE				("Background","TopEdge");
static ThemeMetric<float> RIGHT_EDGE				("Background","RightEdge");
static ThemeMetric<float> BOTTOM_EDGE				("Background","BottomEdge");
static ThemeMetric<float> CLAMP_OUTPUT_PERCENT			("Background","ClampOutputPercent");
static ThemeMetric<bool> SHOW_DANCING_CHARACTERS		("Background","ShowDancingCharacters");
static ThemeMetric<bool> USE_STATIC_BG		("Background","UseStaticBackground");


static Preference<bool>	g_bShowDanger( "ShowDanger", true );
static Preference<float> g_fBGBrightness( "BGBrightness", 0.7f );
static Preference<RandomBackgroundMode> g_RandomBackgroundMode( "RandomBackgroundMode",	BGMODE_RANDOMMOVIES );
static Preference<int> g_iNumBackgrounds( "NumBackgrounds", 8 );
static Preference<bool> g_bSongBackgrounds( "SongBackgrounds", true );

// Width of the region separating the left and right brightness areas:
static float g_fBackgroundCenterWidth = 40;

class BrightnessOverlay: public ActorFrame
{
public:
	BrightnessOverlay();
	void Update( float fDeltaTime );

	void FadeToActualBrightness();
	void SetActualBrightness();
	void Set( float fBrightness );

private:
	Quad m_quadBGBrightness[NUM_PLAYERS];
	Quad m_quadBGBrightnessFade;
};

struct BackgroundTransition
{
	apActorCommands cmdLeaves;
	apActorCommands cmdRoot;
};


class BackgroundImpl : public ActorFrame
{
public:
	BackgroundImpl();
	~BackgroundImpl();
	void Init();

	virtual void LoadFromSong( const Song *pSong );
	virtual void Unload();

	virtual void Update( float fDeltaTime );
	virtual void DrawPrimitives();

	void FadeToActualBrightness() { m_Brightness.FadeToActualBrightness(); }
	void SetBrightness( float fBrightness ) { m_Brightness.Set(fBrightness); } /* overrides pref and Cover */

	DancingCharacters* GetDancingCharacters() { return m_pDancingCharacters; };

	void GetLoadedBackgroundChanges( vector<BackgroundChange> *pBackgroundChangesOut[NUM_BackgroundLayer] );

protected:
	bool m_bInitted;
	DancingCharacters*	m_pDancingCharacters;
	const Song *m_pSong;
	map<RString,BackgroundTransition> m_mapNameToTransition;
	deque<BackgroundDef> m_RandomBGAnimations;	// random background to choose from.  These may or may not be loaded into m_BGAnimations.

	void LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change );
	bool IsDangerAllVisible();

	class Layer
	{
	public:
		Layer();
		void Unload();

		// return true if created and added to m_BGAnimations
		bool CreateBackground( const Song *pSong, const BackgroundDef &bd, Actor *pParent );
		// return def of the background that was created and added to m_BGAnimations. calls CreateBackground
		BackgroundDef CreateRandomBGA( const Song *pSong, const RString &sEffect, deque<BackgroundDef> &RandomBGAnimations, Actor *pParent );

		int FindBGSegmentForBeat( float fBeat ) const;
		void UpdateCurBGChange( const Song *pSong, float fLastMusicSeconds, float fCurrentTime, const map<RString,BackgroundTransition> &mapNameToTransition );

		map<BackgroundDef,Actor*> m_BGAnimations;
		vector<BackgroundChange> m_aBGChanges;
		int	  m_iCurBGChangeIndex;
		Actor *m_pCurrentBGA;
		Actor *m_pFadingBGA;
	};
	Layer m_Layer[NUM_BackgroundLayer];

	float m_fLastMusicSeconds;
	bool m_bDangerAllWasVisible;

	// cover up the edge of animations that might hang outside of the background rectangle
	Quad m_quadBorderLeft, m_quadBorderTop, m_quadBorderRight, m_quadBorderBottom;

	BrightnessOverlay m_Brightness;

	BackgroundDef m_StaticBackgroundDef;
};


static RageColor GetBrightnessColor( float fBrightnessPercent )
{
	RageColor cBrightness = RageColor( 0,0,0,1-fBrightnessPercent );
	RageColor cClamp = RageColor( 0.5f,0.5f,0.5f,CLAMP_OUTPUT_PERCENT );

	// blend the two colors above as if cBrightness is drawn, then cClamp drawn on top
	cBrightness.a *= (1-cClamp.a);	// premultiply alpha

	RageColor ret;
	ret.a = cBrightness.a + cClamp.a;
	ret.r = (cBrightness.r * cBrightness.a + cClamp.r * cClamp.a) / ret.a;
	ret.g = (cBrightness.g * cBrightness.a + cClamp.g * cClamp.a) / ret.a;
	ret.b = (cBrightness.b * cBrightness.a + cClamp.b * cClamp.a) / ret.a;
	return ret;
}

BackgroundImpl::BackgroundImpl()
{
	m_bInitted = false;
	m_pDancingCharacters = NULL;
	m_pSong = NULL;
}

BackgroundImpl::Layer::Layer()
{
	m_iCurBGChangeIndex = -1;
	m_pCurrentBGA = NULL;
	m_pFadingBGA = NULL;
}

void BackgroundImpl::Init()
{
	if( m_bInitted )
		return;
	m_bInitted = true;
	m_bDangerAllWasVisible = false;
	m_StaticBackgroundDef = BackgroundDef();

	if( !USE_STATIC_BG )
	{
		m_StaticBackgroundDef.m_sColor1 = "#00000000";
		m_StaticBackgroundDef.m_sColor2 = "#00000000";
	}

	// load transitions
	{
		ASSERT( m_mapNameToTransition.empty() );
		vector<RString> vsPaths, vsNames;
		BackgroundUtil::GetBackgroundTransitions( "", vsPaths, vsNames );
		for( unsigned i=0; i<vsPaths.size(); i++ )
		{
			const RString &sPath = vsPaths[i];
			const RString &sName = vsNames[i];

			XNode xml;
			XmlFileUtil::LoadFromFileShowErrors(xml, sPath);
			ASSERT( xml.GetName() == "BackgroundTransition" );
			BackgroundTransition &bgt = m_mapNameToTransition[sName];

			RString sCmdLeaves;
			bool bSuccess = xml.GetAttrValue( "LeavesCommand", sCmdLeaves );
			ASSERT( bSuccess );
			bgt.cmdLeaves = ActorUtil::ParseActorCommands( sCmdLeaves );

			RString sCmdRoot;
			bSuccess = xml.GetAttrValue( "RootCommand", sCmdRoot );
			ASSERT( bSuccess );
			bgt.cmdRoot = ActorUtil::ParseActorCommands( sCmdRoot );
		}
	}

	bool bOneOrMoreChars = false;
	bool bShowingBeginnerHelper = false;
	FOREACH_HumanPlayer( p )
	{
		bOneOrMoreChars = true;
		// Disable dancing characters if Beginner Helper will be showing.
		if( PREFSMAN->m_bShowBeginnerHelper && BeginnerHelper::CanUse() && 
			GAMESTATE->m_pCurSteps[p] && GAMESTATE->m_pCurSteps[p]->GetDifficulty() == Difficulty_Beginner )
			bShowingBeginnerHelper = true;
	}

	if( bOneOrMoreChars && !bShowingBeginnerHelper && SHOW_DANCING_CHARACTERS )
		m_pDancingCharacters = new DancingCharacters;

	RageColor c = GetBrightnessColor(0);

	m_quadBorderLeft.StretchTo( RectF(SCREEN_LEFT,SCREEN_TOP,LEFT_EDGE,SCREEN_BOTTOM) );
	m_quadBorderLeft.SetDiffuse( c );
	m_quadBorderTop.StretchTo( RectF(LEFT_EDGE,SCREEN_TOP,RIGHT_EDGE,TOP_EDGE) );
	m_quadBorderTop.SetDiffuse( c );
	m_quadBorderRight.StretchTo( RectF(RIGHT_EDGE,SCREEN_TOP,SCREEN_RIGHT,SCREEN_BOTTOM) );
	m_quadBorderRight.SetDiffuse( c );
	m_quadBorderBottom.StretchTo( RectF(LEFT_EDGE,BOTTOM_EDGE,RIGHT_EDGE,SCREEN_BOTTOM) );
	m_quadBorderBottom.SetDiffuse( c );

	this->AddChild( &m_quadBorderLeft );
	this->AddChild( &m_quadBorderTop );
	this->AddChild( &m_quadBorderRight );
	this->AddChild( &m_quadBorderBottom );

	this->AddChild( &m_Brightness );
}

BackgroundImpl::~BackgroundImpl()
{
	Unload();
	delete m_pDancingCharacters;
}

void BackgroundImpl::Unload()
{
	FOREACH_BackgroundLayer( i )
		m_Layer[i].Unload();
	m_pSong = NULL;
	m_fLastMusicSeconds	= -9999;
	m_RandomBGAnimations.clear();
}

void BackgroundImpl::Layer::Unload()
{
	FOREACHM( BackgroundDef, Actor*, m_BGAnimations, iter )
		delete iter->second;
	m_BGAnimations.clear();
	m_aBGChanges.clear();

	m_pCurrentBGA = NULL;
	m_pFadingBGA = NULL;
	m_iCurBGChangeIndex = -1;
}

bool BackgroundImpl::Layer::CreateBackground( const Song *pSong, const BackgroundDef &bd, Actor *pParent )
{
	ASSERT( m_BGAnimations.find(bd) == m_BGAnimations.end() );

	// Resolve the background names
	vector<RString> vsToResolve;
	vsToResolve.push_back( bd.m_sFile1 );
	vsToResolve.push_back( bd.m_sFile2 );

	vector<RString> vsResolved;
	vsResolved.resize( vsToResolve.size() );
	vector<LuaThreadVariable *> vsResolvedRef;
	vsResolvedRef.resize( vsToResolve.size() );

	for( unsigned i=0; i<vsToResolve.size(); i++ )
	{
		const RString &sToResolve = vsToResolve[i];

		if( sToResolve.empty() )
		{
			if( i == 0 )
				return false;
			else
				continue;
		}

		/* Look for vsFileToResolve[i] in:
			* song's dir
			* RandomMovies dir
			* BGAnimations dir.
		*/
		vector<RString> vsPaths, vsThrowAway;

		// Look for BGAnims in the song dir
		if( sToResolve == SONG_BACKGROUND_FILE )
			vsPaths.push_back( pSong->HasBackground() ? pSong->GetBackgroundPath() : THEME->GetPathG("Common","fallback background") );
		if( vsPaths.empty() )	BackgroundUtil::GetSongBGAnimations(	pSong, sToResolve, vsPaths, vsThrowAway );
		if( vsPaths.empty() )	BackgroundUtil::GetSongMovies(		pSong, sToResolve, vsPaths, vsThrowAway );
		if( vsPaths.empty() )	BackgroundUtil::GetSongBitmaps(		pSong, sToResolve, vsPaths, vsThrowAway );
		if( vsPaths.empty() )	BackgroundUtil::GetGlobalBGAnimations(	pSong, sToResolve, vsPaths, vsThrowAway );
		if( vsPaths.empty() )	BackgroundUtil::GetGlobalRandomMovies(	pSong, sToResolve, vsPaths, vsThrowAway );

		RString &sResolved = vsResolved[i];

		if( !vsPaths.empty() )
		{
			sResolved = vsPaths[0];
		}
		else
		{
			// If the main background file is missing, we failed.
			if( i == 0 )
				return false;
			else
				sResolved = "../"+ThemeManager::GetBlankGraphicPath();
		}

		ASSERT( !sResolved.empty() );

		vsResolvedRef[i] = new LuaThreadVariable( ssprintf("File%d",i+1), sResolved );
	}

	RString sEffect = bd.m_sEffect;
	if( sEffect.empty() )
	{
		FileType ft = ActorUtil::GetFileType(vsResolved[0]);
		switch( ft )
		{
		default:
			LOG->Warn( "CreateBackground() Unknown file type '%s'", vsResolved[0].c_str() );
			// fall through
		case FT_Bitmap:
		case FT_Movie:
			sEffect = SBE_StretchNormal;
			break;
		case FT_Directory:
		case FT_Lua:
		case FT_Model:
			sEffect = SBE_UpperLeft;
			break;
		}
	}
	ASSERT( !sEffect.empty() );

	// Set Lua color globals
	LuaThreadVariable sColor1( "Color1", bd.m_sColor1.empty() ? RString("#FFFFFFFF") : bd.m_sColor1 );
	LuaThreadVariable sColor2( "Color2", bd.m_sColor2.empty() ? RString("#FFFFFFFF") : bd.m_sColor2 );

	// Resolve the effect file.
	RString sEffectFile;
	for( int i=0; i<2; i++ )
	{
		vector<RString> vsPaths, vsThrowAway;
		BackgroundUtil::GetBackgroundEffects( sEffect, vsPaths, vsThrowAway );
		if( vsPaths.empty() )
		{
			LOG->Warn( "BackgroundEffect '%s' is missing.",sEffect.c_str() );
			sEffect = SBE_Centered;
		}
		else if( vsPaths.size() > 1 )
		{
			LOG->Warn( "BackgroundEffect '%s' has more than one match.",sEffect.c_str() );
			sEffect = SBE_Centered;
		}
		else
		{
			sEffectFile = vsPaths[0];
			break;
		}
	}
	ASSERT( !sEffectFile.empty() );

	Actor *pActor = ActorUtil::MakeActor( sEffectFile );

	ASSERT( pActor );
	m_BGAnimations[bd] = pActor;

	for( unsigned i=0; i<vsResolvedRef.size(); i++ )
		delete vsResolvedRef[i];

	return true;
}

BackgroundDef BackgroundImpl::Layer::CreateRandomBGA( const Song *pSong, const RString &sEffect, deque<BackgroundDef> &RandomBGAnimations, Actor *pParent )
{
	if( g_RandomBackgroundMode == BGMODE_OFF )
		return BackgroundDef();

	// Set to not show any BGChanges, whether scripted or random
	if( GAMESTATE->m_SongOptions.GetCurrent().m_bStaticBackground )
		return BackgroundDef();

	if( RandomBGAnimations.empty() )
		return BackgroundDef();

	/* XXX: every time we fully loop, shuffle, so we don't play the same sequence
	 * over and over; and nudge the shuffle so the next one won't be a repeat */
	BackgroundDef bd = RandomBGAnimations.front();
	RandomBGAnimations.push_back( RandomBGAnimations.front() );
	RandomBGAnimations.pop_front();

	if( !sEffect.empty() )
		bd.m_sEffect = sEffect;

	map<BackgroundDef,Actor*>::const_iterator iter = m_BGAnimations.find( bd );

	// create the background if it's not already created
	if( iter == m_BGAnimations.end() )
	{
		bool bSuccess = CreateBackground( pSong, bd, pParent );
		ASSERT( bSuccess );	// we fed it valid files, so this shouldn't fail
	}
	return bd;
}

void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change )
{
	int iStartRow = BeatToNoteRow(fFirstBeat);
	int iEndRow = BeatToNoteRow(fEndBeat);

	const TimingData &timing = m_pSong->m_SongTiming;

	// change BG every time signature change or 4 measures
	const vector<TimingSegment *> &tSigs = timing.GetTimingSegments(SEGMENT_TIME_SIG);

	for (unsigned i = 0; i < tSigs.size(); i++)
	{
		TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[i]);
		int iSegmentEndRow = (i + 1 == tSigs.size()) ? iEndRow : tSigs[i+1]->GetRow();


		for(int j=max(ts->GetRow(),iStartRow);
			j<min(iEndRow,iSegmentEndRow);
			j+=4*ts->GetNoteRowsPerMeasure())
		{
			// Don't fade. It causes frame rate dip, especially on slower machines.
			BackgroundDef bd = m_Layer[0].CreateRandomBGA(m_pSong,
														  change.m_def.m_sEffect,
														  m_RandomBGAnimations, this);
			if( !bd.IsEmpty() )
			{
				BackgroundChange c = change;
				c.m_def = bd;
				c.m_fStartBeat = NoteRowToBeat(j);
				m_Layer[0].m_aBGChanges.push_back( c );
			}
		}
	}

	// change BG every BPM change that is at the beginning of a measure
	const vector<TimingSegment *> &bpms = timing.GetTimingSegments(SEGMENT_BPM);
	for( unsigned i=0; i<bpms.size(); i++ )
	{
		bool bAtBeginningOfMeasure = false;
		for (unsigned j=0; j<tSigs.size(); j++)
		{
			TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[j]);
			if ((bpms[i]->GetRow() - ts->GetRow()) % ts->GetNoteRowsPerMeasure() == 0)
			{
				bAtBeginningOfMeasure = true;
				break;
			}
		}

		if( !bAtBeginningOfMeasure )
			continue; // skip

		// start so that we don't create a BGChange right on top of fEndBeat
		bool bInRange = bpms[i]->GetRow() >= iStartRow && bpms[i]->GetRow() < iEndRow;
		if( !bInRange )
			continue; // skip

		BackgroundDef bd = m_Layer[0].CreateRandomBGA( m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this );
		if( !bd.IsEmpty() )
		{
			BackgroundChange c = change;
			c.m_def.m_sFile1 = bd.m_sFile1;
			c.m_def.m_sFile2 = bd.m_sFile2;
			c.m_fStartBeat = bpms[i]->GetBeat();
			m_Layer[0].m_aBGChanges.push_back( c );
		}
	}
}

void BackgroundImpl::LoadFromSong( const Song* pSong )
{
	Init();
	Unload();
	m_pSong = pSong;
	m_StaticBackgroundDef.m_sFile1 = SONG_BACKGROUND_FILE;

	if( g_fBGBrightness == 0.0f )
		return;

	// Choose a bunch of backgrounds that we'll use for the random file marker
	{
		vector<RString> vsThrowAway, vsNames;
		switch( g_RandomBackgroundMode )
		{
		default:
			ASSERT_M( 0, ssprintf("Invalid RandomBackgroundMode: %i", int(g_RandomBackgroundMode)) );
			break;
		case BGMODE_OFF:
			break;
		case BGMODE_ANIMATIONS:
			BackgroundUtil::GetGlobalBGAnimations( pSong, "", vsThrowAway, vsNames );
			break;
		case BGMODE_RANDOMMOVIES:
			BackgroundUtil::GetGlobalRandomMovies( pSong, "", vsThrowAway, vsNames, true, true );
			break;
		}

		// Pick the same random items every time the song is played.
		RandomGen rnd( GetHashForString(pSong->GetSongDir()) );
		random_shuffle( vsNames.begin(), vsNames.end(), rnd );
		int iSize = min( (int)g_iNumBackgrounds, (int)vsNames.size() );
		vsNames.resize( iSize );

		FOREACH_CONST( RString, vsNames, s )
		{
			BackgroundDef bd;
			bd.m_sFile1 = *s;
			m_RandomBGAnimations.push_back( bd );
		}
	}

	/* Song backgrounds (even just background stills) can get very big; never keep them
	 * in memory. */
	RageTextureID::TexPolicy OldPolicy = TEXTUREMAN->GetDefaultTexturePolicy();
	TEXTUREMAN->SetDefaultTexturePolicy( RageTextureID::TEX_VOLATILE );

	TEXTUREMAN->DisableOddDimensionWarning();

	// Set to not show any BGChanges, whether scripted or random if m_bStaticBackground is on
	if( !g_bSongBackgrounds || GAMESTATE->m_SongOptions.GetCurrent().m_bStaticBackground )
	{
		// Backgrounds are disabled; just display the song background.
		BackgroundChange change;
		change.m_def = m_StaticBackgroundDef;
		change.m_fStartBeat = 0;
		m_Layer[0].m_aBGChanges.push_back( change );
	}
	// If m_bRandomBGOnly is on, then we want to ignore the scripted BG in favour of randomly loaded BGs
	else if( pSong->HasBGChanges() && !GAMESTATE->m_SongOptions.GetCurrent().m_bRandomBGOnly )
	{
		FOREACH_BackgroundLayer( i )
		{
			Layer &layer = m_Layer[i];

			// Load all song-specified backgrounds
			FOREACH_CONST( BackgroundChange, pSong->GetBackgroundChanges(i), bgc )
			{
				BackgroundChange change = *bgc;
				BackgroundDef &bd = change.m_def;
				
				bool bIsAlreadyLoaded = layer.m_BGAnimations.find(bd) != layer.m_BGAnimations.end();

				if( bd.m_sFile1 != RANDOM_BACKGROUND_FILE  &&  !bIsAlreadyLoaded )
				{
					if( layer.CreateBackground( m_pSong, bd, this ) )
					{
						;	// do nothing.  Create was successful.
					}
					else
					{
						if( i == BACKGROUND_LAYER_1 )
						{
							// The background was not found. Try to use a random one instead.
							// Don't use the BackgroundDef's effect, because it may be an
							// effect that requires 2 files, and random BGA will only supply one file
							bd = layer.CreateRandomBGA( pSong, "", m_RandomBGAnimations, this );
							if( bd.IsEmpty() )
								bd = m_StaticBackgroundDef;
						}
					}
				}
			
				if( !bd.IsEmpty() )
					layer.m_aBGChanges.push_back( change );
			}
		}
	}
	else	// pSong doesn't have an animation plan
	{
		Layer &layer = m_Layer[0];
		float firstBeat = pSong->GetFirstBeat();
		float lastBeat = pSong->GetLastBeat();

		LoadFromRandom( firstBeat, lastBeat, BackgroundChange() );

		// end showing the static song background
		BackgroundChange change;
		change.m_def = m_StaticBackgroundDef;
		change.m_fStartBeat = lastBeat;
		layer.m_aBGChanges.push_back( change );
	}

	// sort segments
	FOREACH_BackgroundLayer( i )
	{
		Layer &layer = m_Layer[i];
		BackgroundUtil::SortBackgroundChangesArray( layer.m_aBGChanges );
	}

	Layer &mainlayer = m_Layer[0];

	/* If the first BGAnimation isn't negative, add a lead-in image showing
	 * the song background. */
	if( mainlayer.m_aBGChanges.empty() || mainlayer.m_aBGChanges.front().m_fStartBeat >= 0 )
	{
		BackgroundChange change;
		change.m_def = m_StaticBackgroundDef;
		change.m_fStartBeat = -10000;
		mainlayer.m_aBGChanges.insert( mainlayer.m_aBGChanges.begin(), change );
	}

	// If any BGChanges use the background image, load it.
	bool bStaticBackgroundUsed = false;
	FOREACH_BackgroundLayer( i )
	{
		Layer &layer = m_Layer[i];
		FOREACH_CONST( BackgroundChange, layer.m_aBGChanges, bgc )
		{
			const BackgroundDef &bd = bgc->m_def;
			if( bd == m_StaticBackgroundDef )
			{
				bStaticBackgroundUsed = true;
				break;
			}
		}
		if( bStaticBackgroundUsed )
			break;
	}

	if( bStaticBackgroundUsed )
	{
		bool bIsAlreadyLoaded = mainlayer.m_BGAnimations.find(m_StaticBackgroundDef) != mainlayer.m_BGAnimations.end();
		if( !bIsAlreadyLoaded )
		{
			bool bSuccess = mainlayer.CreateBackground( m_pSong, m_StaticBackgroundDef, this );
			ASSERT( bSuccess );
		}
	}

	// Look for the random file marker, and replace the segment with LoadFromRandom.
	for( unsigned i=0; i<mainlayer.m_aBGChanges.size(); i++ )
	{
		const BackgroundChange change = mainlayer.m_aBGChanges[i];
		if( change.m_def.m_sFile1 != RANDOM_BACKGROUND_FILE )
			continue;

		float fStartBeat = change.m_fStartBeat;
		float fEndBeat = pSong->GetLastBeat();
		if( i+1 < mainlayer.m_aBGChanges.size() )
			fEndBeat = mainlayer.m_aBGChanges[i+1].m_fStartBeat;

		mainlayer.m_aBGChanges.erase( mainlayer.m_aBGChanges.begin()+i );
		--i;

		LoadFromRandom( fStartBeat, fEndBeat, change );
	}

	// At this point, we shouldn't have any BGChanges to "".  "" is an invalid name.
	for( unsigned i=0; i<mainlayer.m_aBGChanges.size(); i++ )
		ASSERT( !mainlayer.m_aBGChanges[i].m_def.m_sFile1.empty() );

	// Re-sort.
	BackgroundUtil::SortBackgroundChangesArray( mainlayer.m_aBGChanges );

	TEXTUREMAN->EnableOddDimensionWarning();

	if( m_pDancingCharacters )
		m_pDancingCharacters->LoadNextSong();

	TEXTUREMAN->SetDefaultTexturePolicy( OldPolicy );
}

int BackgroundImpl::Layer::FindBGSegmentForBeat( float fBeat ) const
{
	if( m_aBGChanges.empty() )
		return -1;
	if( fBeat < m_aBGChanges[0].m_fStartBeat )
		return -1;

	// assumption: m_aBGChanges are sorted by m_fStartBeat
	int i;
	for( i=m_aBGChanges.size()-1; i>=0; i-- )
	{
		if( fBeat >= m_aBGChanges[i].m_fStartBeat )
			return i;
	}

	return i;
}

/* If the BG segment has changed, move focus to it. Send Update() calls. */
void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMusicSeconds, float fCurrentTime, const map<RString,BackgroundTransition> &mapNameToTransition )
{
	ASSERT( fCurrentTime != GameState::MUSIC_SECONDS_INVALID );

	if( m_aBGChanges.size() == 0 )
		return;

	float fBeat, fBPS, fThrowAway;
	bool bFreeze;
	int iThrowAway;
	pSong->m_SongTiming.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway );

	// Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it.
	const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;

	// Find the BGSegment we're in
	const int i = FindBGSegmentForBeat( fBeat );

	float fDeltaTime = fCurrentTime - fLastMusicSeconds;
	if( i != -1  &&  i != m_iCurBGChangeIndex )	// we're changing backgrounds
	{
		//LOG->Trace( "old bga %d -> new bga %d (%s), %f, %f", m_iCurBGChangeIndex, i, m_aBGChanges[i].GetTextDescription().c_str(), m_aBGChanges[i].m_fStartBeat, fBeat );

		BackgroundChange oldChange;
		if( m_iCurBGChangeIndex != -1 )
			oldChange = m_aBGChanges[m_iCurBGChangeIndex];

		m_iCurBGChangeIndex = i;

		const BackgroundChange& change = m_aBGChanges[i];

		m_pFadingBGA = m_pCurrentBGA;

		map<BackgroundDef,Actor*>::const_iterator iter = m_BGAnimations.find( change.m_def );
		if( iter == m_BGAnimations.end() )
		{
			XNode *pNode = change.m_def.CreateNode();
			RString xml = XmlFileUtil::GetXML( pNode );
			Trim( xml );
			LOG->Warn( "Tried to switch to a background that was never loaded:\n%s", xml.c_str() );
			SAFE_DELETE( pNode );
			return;
		}

		m_pCurrentBGA = iter->second;

		if( m_pFadingBGA == m_pCurrentBGA )
		{
			m_pFadingBGA = NULL;
			//LOG->Trace( "bg didn't actually change.  Ignoring." );
		}
		else
		{
			if( m_pFadingBGA )
			{
				m_pFadingBGA->PlayCommand( "LoseFocus" );
				
				if( !change.m_sTransition.empty() )
				{
					map<RString,BackgroundTransition>::const_iterator lIter = mapNameToTransition.find( change.m_sTransition );
					ASSERT( lIter != mapNameToTransition.end() );
					const BackgroundTransition &bt = lIter->second;
					m_pFadingBGA->RunCommandsOnLeaves( *bt.cmdLeaves );
					m_pFadingBGA->RunCommands( *bt.cmdRoot );
				}
			}
		}

		m_pCurrentBGA->SetUpdateRate( change.m_fRate );

		m_pCurrentBGA->InitState();
		m_pCurrentBGA->PlayCommand( "On" );
		m_pCurrentBGA->PlayCommand( "GainFocus" );

		/* How much time of this BGA have we skipped?  (This happens with SetSeconds.) */
		const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( change.m_fStartBeat );

		/* This is affected by the music rate. */
		fDeltaTime = fCurrentTime - fStartSecond;
	}

	if( m_pFadingBGA )
	{
		if( m_pFadingBGA->GetTweenTimeLeft() == 0 )
			m_pFadingBGA = NULL;
	}

	/* This is unaffected by the music rate. */
	float fDeltaTimeNoMusicRate = max( fDeltaTime / fRate, 0 );

	if( m_pCurrentBGA )
		m_pCurrentBGA->Update( fDeltaTimeNoMusicRate );
	if( m_pFadingBGA )
		m_pFadingBGA->Update( fDeltaTimeNoMusicRate );
}

void BackgroundImpl::Update( float fDeltaTime )
{
	ActorFrame::Update( fDeltaTime );

	{
		bool bVisible = IsDangerAllVisible();
		if( m_bDangerAllWasVisible != bVisible )
			MESSAGEMAN->Broadcast( bVisible? "ShowDangerAll":"HideDangerAll" );
		m_bDangerAllWasVisible = bVisible;
	}

	if( m_pDancingCharacters )
		m_pDancingCharacters->Update( fDeltaTime );

	FOREACH_BackgroundLayer( i )
	{
		Layer &layer = m_Layer[i];
		layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_Position.m_fMusicSeconds, m_mapNameToTransition );
	}
	m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
}

void BackgroundImpl::DrawPrimitives()
{
	if( g_fBGBrightness == 0.0f )
		return;

	if( IsDangerAllVisible() )
	{
		// Since this only shows when DANGER is visible, it will flash red on it's own accord :)
		if( m_pDancingCharacters )
			m_pDancingCharacters->m_bDrawDangerLight = true;
	}

	{
		if( m_pDancingCharacters )
			m_pDancingCharacters->m_bDrawDangerLight = false;

		FOREACH_BackgroundLayer( i )
		{
			Layer &layer = m_Layer[i];
			if( layer.m_pCurrentBGA )
				layer.m_pCurrentBGA->Draw();
			if( layer.m_pFadingBGA )
				layer.m_pFadingBGA->Draw();
		}
	}

	if( m_pDancingCharacters )
		m_pDancingCharacters->Draw();

	ActorFrame::DrawPrimitives();
}

void BackgroundImpl::GetLoadedBackgroundChanges( vector<BackgroundChange> *pBackgroundChangesOut[NUM_BackgroundLayer] )
{
	FOREACH_BackgroundLayer( i )
		*pBackgroundChangesOut[i] = m_Layer[i].m_aBGChanges;
}

bool BackgroundImpl::IsDangerAllVisible()
{
	// The players are never in danger in FAIL_OFF.
	FOREACH_PlayerNumber( p )
		if( GAMESTATE->GetPlayerFailType(GAMESTATE->m_pPlayerState[p]) == PlayerOptions::FAIL_OFF )
			return false;
	if( !g_bShowDanger )
		return false;

	/* Don't show it if everyone is already failing: it's already too late and it's
	 * annoying for it to show for the entire duration of a song. */
	if( STATSMAN->m_CurStageStats.AllFailed() )
		return false;

	return GAMESTATE->AllAreInDangerOrWorse();
}

BrightnessOverlay::BrightnessOverlay()
{
	float fQuadWidth = (RIGHT_EDGE-LEFT_EDGE)/2;
	fQuadWidth -= g_fBackgroundCenterWidth/2;

	m_quadBGBrightness[0].StretchTo( RectF(LEFT_EDGE,TOP_EDGE,LEFT_EDGE+fQuadWidth,BOTTOM_EDGE) );
	m_quadBGBrightnessFade.StretchTo( RectF(LEFT_EDGE+fQuadWidth,TOP_EDGE,RIGHT_EDGE-fQuadWidth,BOTTOM_EDGE) );
	m_quadBGBrightness[1].StretchTo( RectF(RIGHT_EDGE-fQuadWidth,TOP_EDGE,RIGHT_EDGE,BOTTOM_EDGE) );

	m_quadBGBrightness[0].SetName( "BrightnessOverlay" );
	ActorUtil::LoadAllCommands( m_quadBGBrightness[0], "Background" );
	this->AddChild( &m_quadBGBrightness[0] );

	m_quadBGBrightness[1].SetName( "BrightnessOverlay" );
	ActorUtil::LoadAllCommands( m_quadBGBrightness[1], "Background" );
	this->AddChild( &m_quadBGBrightness[1] );

	m_quadBGBrightnessFade.SetName( "BrightnessOverlay" );
	ActorUtil::LoadAllCommands( m_quadBGBrightnessFade, "Background" );
	this->AddChild( &m_quadBGBrightnessFade );

	SetActualBrightness();
}

void BrightnessOverlay::Update( float fDeltaTime )
{
	ActorFrame::Update( fDeltaTime );
	/* If we're actually playing, then we're past fades, etc; update the
	 * background brightness to follow Cover. */
	if( !GAMESTATE->m_bGameplayLeadIn )
		SetActualBrightness();
}

void BrightnessOverlay::SetActualBrightness()
{
	float fLeftBrightness = 1-GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fCover;
	float fRightBrightness = 1-GAMESTATE->m_pPlayerState[PLAYER_2]->m_PlayerOptions.GetCurrent().m_fCover;

	float fBaseBGBrightness = g_fBGBrightness;

	// HACK: Always show training in full brightness
	if( GAMESTATE->m_pCurSong && GAMESTATE->m_pCurSong->IsTutorial() )
		fBaseBGBrightness = 1.0f;

	fLeftBrightness *= fBaseBGBrightness;
	fRightBrightness *= fBaseBGBrightness;

	if( !GAMESTATE->IsHumanPlayer(PLAYER_1) )
		fLeftBrightness = fRightBrightness;
	if( !GAMESTATE->IsHumanPlayer(PLAYER_2) )
		fRightBrightness = fLeftBrightness;

	RageColor LeftColor = GetBrightnessColor(fLeftBrightness);
	RageColor RightColor = GetBrightnessColor(fRightBrightness);

	m_quadBGBrightness[PLAYER_1].SetDiffuse( LeftColor );
	m_quadBGBrightness[PLAYER_2].SetDiffuse( RightColor );
	m_quadBGBrightnessFade.SetDiffuseLeftEdge( LeftColor );
	m_quadBGBrightnessFade.SetDiffuseRightEdge( RightColor );
}

void BrightnessOverlay::Set( float fBrightness )
{
	RageColor c = GetBrightnessColor(fBrightness);

	FOREACH_PlayerNumber(pn)
		m_quadBGBrightness[pn].SetDiffuse( c );
	m_quadBGBrightnessFade.SetDiffuse( c );
}

void BrightnessOverlay::FadeToActualBrightness()
{
	this->PlayCommand( "Fade" );
	SetActualBrightness();
}

Background::Background()				{ m_pImpl = new BackgroundImpl; this->AddChild(m_pImpl); }
Background::~Background()				{ SAFE_DELETE( m_pImpl ); }
void Background::Init()					{ m_pImpl->Init(); }
void Background::LoadFromSong( const Song *pSong )	{ m_pImpl->LoadFromSong(pSong); }
void Background::Unload()				{ m_pImpl->Unload(); }
void Background::FadeToActualBrightness()		{ m_pImpl->FadeToActualBrightness(); }
void Background::SetBrightness( float fBrightness )	{ m_pImpl->SetBrightness(fBrightness); }
DancingCharacters* Background::GetDancingCharacters()	{ return m_pImpl->GetDancingCharacters(); }
void Background::GetLoadedBackgroundChanges( vector<BackgroundChange> *pBackgroundChangesOut[NUM_BackgroundLayer] ) { m_pImpl->GetLoadedBackgroundChanges(pBackgroundChangesOut); }

/*
 * (c) 2001-2004 Chris Danford, Ben Nordstrom
 * 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.
 */
