#include "global.h"
#include <cassert>

#include "Sprite.h"
#include "RageTextureManager.h"
#include "XmlFile.h"
#include "RageLog.h"
#include "RageDisplay.h"
#include "RageTexture.h"
#include "RageUtil.h"
#include "ActorUtil.h"
#include "arch/Dialog/Dialog.h"
#include "Foreach.h"
#include "LuaBinding.h"
#include "LuaManager.h"

REGISTER_ACTOR_CLASS( Sprite );


Sprite::Sprite()
{
	m_pTexture = NULL;
	m_iCurState = 0;
	m_fSecsIntoState = 0.0f;
	m_bUsingCustomTexCoords = false;
	m_bSkipNextUpdate = true;
	m_EffectMode = EffectMode_Normal;
	
	m_fRememberedClipWidth = -1;
	m_fRememberedClipHeight = -1;

	m_fTexCoordVelocityX = 0;
	m_fTexCoordVelocityY = 0;
}


Sprite::~Sprite()
{
	UnloadTexture();
}

Sprite::Sprite( const Sprite &cpy ):
	Actor( cpy )
{
#define CPY(a) a = cpy.a
	CPY( m_States );
	CPY( m_iCurState );
	CPY( m_fSecsIntoState );
	CPY( m_bUsingCustomTexCoords );
	CPY( m_bSkipNextUpdate );
	CPY( m_EffectMode );
	memcpy( m_CustomTexCoords, cpy.m_CustomTexCoords, sizeof(m_CustomTexCoords) );
	CPY( m_fRememberedClipWidth );
	CPY( m_fRememberedClipHeight );
	CPY( m_fTexCoordVelocityX );
	CPY( m_fTexCoordVelocityY );
#undef CPY

	if( cpy.m_pTexture != NULL )
		m_pTexture = TEXTUREMAN->CopyTexture( cpy.m_pTexture );
	else
		m_pTexture = NULL;
}

void Sprite::InitState()
{
	Actor::InitState();
	m_iCurState = 0;
	m_fSecsIntoState = 0.0f;
	m_bSkipNextUpdate = true;
}

void Sprite::SetAllStateDelays(float fDelay)
{
	for(unsigned int i=0;i<m_States.size();i++)
	{
		m_States[i].fDelay = fDelay;
	}
}

RageTextureID Sprite::SongBGTexture( RageTextureID ID )
{
	ID.bMipMaps = true;

	/* Song backgrounds are, by definition, in the background, so there's no need to keep alpha. */
	ID.iAlphaBits = 0;

	/* By default, song graphics are volatile: they're removed after one use.  This
	 * is because some screens iteratively load and display lots of them (eg. ScreenSelectMusic,,
	 * ScreenEditMenu) one at a time, and we don't want to have hundreds of banners loaded at once. */
	ID.Policy = RageTextureID::TEX_VOLATILE;

	ID.bDither = true;

	return ID;
}

RageTextureID Sprite::SongBannerTexture( RageTextureID ID )
{
	// Older song banners often have HOT PINK color keys.
	/* Use color keying only if the graphic is a diagonal banner.
	 * The color key convention is causing small holes in moderm banners that
	 * use magenta, and it's not good to require graphic makers to know about
	 * archaic color key conventions. -Chris */
	// I disabled this anyways, it's extremely annoying -Colby
	ID.bHotPinkColorKey = false;

	/* Ignore the texture color depth preference and always use 32-bit textures
	 * if possible. Banners are loaded while moving the wheel, so we want it to
	 * be as fast as possible. */
	ID.iColorDepth = 32;

	/* If we don't support RGBA8 (and will probably fall back on RGBA4), we're
	 * probably on something very old and slow, so let's opt for banding
	 * instead of slowing things down further by dithering. */
	// ID.bDither = true;

	ID.Policy = RageTextureID::TEX_VOLATILE;

	return ID;
}

void Sprite::Load( RageTextureID ID )
{
	if( !ID.filename.empty() ) 
		LoadFromTexture( ID );

	LoadStatesFromTexture();
};

void Sprite::LoadFromNode( const XNode* pNode )
{
	/* Texture may refer to the ID of a render target; if it's already
	 * registered, use it without trying to resolve it. */
	RString sPath;
	pNode->GetAttrValue( "Texture", sPath );
	if( !sPath.empty() && !TEXTUREMAN->IsTextureRegistered( RageTextureID(sPath) ) )
		ActorUtil::GetAttrPath( pNode, "Texture", sPath );

	if( !sPath.empty() )
	{
		// Load the texture
		LoadFromTexture( sPath );

		LoadStatesFromTexture();

		// Read in frames and delays from the sprite file, 
		// overwriting the states that LoadFromTexture created.
		// If the .sprite file doesn't define any states, leave
		// frames and delays created during LoadFromTexture().
		vector<State> aStates;

		const XNode *pFrames = pNode->GetChild( "Frames" );
		if( pFrames != NULL )
		{
			/* All attributes are optional.  If Frame is omitted, use the previous state's
			 * frame (or 0 if the first).
			 * Frames = {
			 *  { Delay=1.0f; Frame=0; { x=0, y=0 }, { x=1, y=1 } };
			 * }
			 */
			int iFrameIndex = 0;
			for( int i=0; true; i++ )
			{
				const XNode *pFrame = pFrames->GetChild( ssprintf("%i", i+1) ); // +1 for Lua's arrays
				if( pFrame == NULL )
					break;

				State newState;
				if( !pFrame->GetAttrValue("Delay", newState.fDelay) )
					newState.fDelay = 0.1f;

				pFrame->GetAttrValue( "Frame", iFrameIndex );
				if( iFrameIndex >= m_pTexture->GetNumFrames() )
					RageException::Throw( "%s: State #%i is frame %d, but the texture \"%s\" only has %d frames",
						ActorUtil::GetWhere(pNode).c_str(), i, iFrameIndex, sPath.c_str(), m_pTexture->GetNumFrames() );
				newState.rect = *m_pTexture->GetTextureCoordRect( iFrameIndex );

				const XNode *pPoints[2] = { pFrame->GetChild( "1" ), pFrame->GetChild( "2" ) };
				if( pPoints[0] != NULL && pPoints[1] != NULL )
				{
					RectF r = newState.rect;

					float fX = 1.0f, fY = 1.0f;
					pPoints[0]->GetAttrValue( "x", fX );
					pPoints[0]->GetAttrValue( "y", fY );
					newState.rect.left = SCALE( fX, 0.0f, 1.0f, r.left, r.right );
					newState.rect.top = SCALE( fY, 0.0f, 1.0f, r.top, r.bottom );

					pPoints[1]->GetAttrValue( "x", fX );
					pPoints[1]->GetAttrValue( "y", fY );
					newState.rect.right = SCALE( fX, 0.0f, 1.0f, r.left, r.right );
					newState.rect.bottom = SCALE( fY, 0.0f, 1.0f, r.top, r.bottom );
				}

				aStates.push_back( newState );
			}
		}
		else for( int i=0; true; i++ )
		{
			// deprecated
			RString sFrameKey = ssprintf( "Frame%04d", i );
			RString sDelayKey = ssprintf( "Delay%04d", i );
			State newState;

			int iFrameIndex;
			if( !pNode->GetAttrValue(sFrameKey, iFrameIndex) )
				break;
			if( iFrameIndex >= m_pTexture->GetNumFrames() )
				RageException::Throw( "%s: %s is %d, but the texture \"%s\" only has %d frames",
					ActorUtil::GetWhere(pNode).c_str(), sFrameKey.c_str(), iFrameIndex, sPath.c_str(), m_pTexture->GetNumFrames() );

			newState.rect = *m_pTexture->GetTextureCoordRect( iFrameIndex );

			if( !pNode->GetAttrValue(sDelayKey, newState.fDelay) )
				break;

			aStates.push_back( newState );
		}

		if( !aStates.empty() )
		{
			m_States = aStates;
			Sprite::m_size.x = aStates[0].rect.GetWidth() / m_pTexture->GetSourceToTexCoordsRatioX();
			Sprite::m_size.y = aStates[0].rect.GetHeight() / m_pTexture->GetSourceToTexCoordsRatioY();
		}
	}

	Actor::LoadFromNode( pNode );
}

void Sprite::UnloadTexture()
{
	if( m_pTexture != NULL ) // If there was a previous bitmap...
	{
		TEXTUREMAN->UnloadTexture( m_pTexture ); // Unload it.
		m_pTexture = NULL;

		/* Make sure we're reset to frame 0, so if we're reused, we aren't left
		 * on a frame number that may be greater than the number of frames in
		 * the newly loaded image. */
		SetState( 0 );
	}
}

void Sprite::EnableAnimation( bool bEnable )
{ 
	bool bWasEnabled = m_bIsAnimating;
	Actor::EnableAnimation( bEnable ); 

	if( bEnable && !bWasEnabled )
	{
		/* When we start animating a movie, we need to discard the first update; send
		 * 0 instead of the passed time. This is for two reasons:
		 *
		 * First, and most fundamentally, the time we'll receive on the next update
		 * represents time that passed *before* the movie was started. For example,
		 * 1: 20ms passes; 2: EnableAnimation(true); 3: Update(.020). We don't want
		 * to send that 20ms to the texture; on the first update, the movie's time
		 * should be 0.
		 *
		 * Second, we don't receive Update() calls if we're in a BGAnimation that
		 * doesn't have focus. It looks like 1: EnableAnimation(false); 2: 30 seconds
		 * pass; 3: EnableAnimation(true); 4: Update(30). We must be sure not to send
		 * that long 30-second update to the movie.
		 *
		 * (detail: the timestamps here are actually coming from GetEffectDeltaTime())
		 */
		m_bSkipNextUpdate = true;
	}
}

void Sprite::SetTexture( RageTexture *pTexture )
{
	ASSERT( pTexture != NULL );

	if( m_pTexture != pTexture )
	{
		UnloadTexture();
		m_pTexture = pTexture;
	}

	ASSERT( m_pTexture->GetTextureWidth() >= 0 );
	ASSERT( m_pTexture->GetTextureHeight() >= 0 );

	// the size of the sprite is the size of the image before it was scaled
	Sprite::m_size.x = (float)m_pTexture->GetSourceFrameWidth();
	Sprite::m_size.y = (float)m_pTexture->GetSourceFrameHeight();

	// apply clipping (if any)
	if( m_fRememberedClipWidth != -1 && m_fRememberedClipHeight != -1 )
		ScaleToClipped( m_fRememberedClipWidth, m_fRememberedClipHeight );

	// Load default states if we haven't before.
	if( m_States.empty() )
		LoadStatesFromTexture();
}

void Sprite::LoadFromTexture( RageTextureID ID )
{
	// LOG->Trace( "Sprite::LoadFromTexture( %s )", ID.filename.c_str() );

	RageTexture *pTexture = NULL;
	if( m_pTexture && m_pTexture->GetID() == ID )
		pTexture = m_pTexture;
	else
		pTexture = TEXTUREMAN->LoadTexture( ID );

	SetTexture( pTexture );
}

void Sprite::LoadStatesFromTexture()
{
	// Assume the frames of this animation play in sequential order with 0.1 second delay.
	m_States.clear();

	if( m_pTexture == NULL )
	{
		State newState;
		newState.fDelay = 0.1f;
		newState.rect = RectF( 0, 0, 1, 1 );
		m_States.push_back( newState );
		return;
	}

	for( int i=0; i<m_pTexture->GetNumFrames(); ++i )
	{
		State newState;
		newState.fDelay = 0.1f;
		newState.rect = *m_pTexture->GetTextureCoordRect( i );
		m_States.push_back( newState );
	}
}

void Sprite::UpdateAnimationState()
{
	// Don't bother with state switching logic if there's only one state.  
	// We already know what's going to show.
	if( m_States.size() > 1 )
	{
		while( m_fSecsIntoState+0.0001f > m_States[m_iCurState].fDelay )	// it's time to switch frames
		{
			// increment frame and reset the counter
			m_fSecsIntoState -= m_States[m_iCurState].fDelay;		// leave the left over time for the next frame
			m_iCurState = (m_iCurState+1) % m_States.size();
		}
	}
}

/* We treat frame animation and movie animation slightly differently.
 *
 * Sprite animation is always tied directly to the effect timer. If you pause
 * animation, wait a while and restart it, sprite animations will snap back to
 * the effect timer. This allows sprites to animate to the beat in BGAnimations,
 * where they might be disabled for a while.
 *
 * Movies don't do this; if you pause a movie, wait a while and restart it,
 * it'll pick up where it left off. We may have a lot of movies loaded, so
 * it's too expensive to decode movies that aren't being displayed. Movies
 * also don't loop when the effect timer loops.
 *
 * (I'd like to handle sprite and movie animation as consistently as possible;
 * the above is just documentation of current practice.) -glenn */
// todo: see if "current" practice is just that. -aj
void Sprite::Update( float fDelta )
{
	Actor::Update( fDelta ); // do tweening

	const bool bSkipThisMovieUpdate = m_bSkipNextUpdate;
	m_bSkipNextUpdate = false;

	if( !m_bIsAnimating )
		return;

	if( !m_pTexture ) // no texture, nothing to animate
		return;

	float fTimePassed = GetEffectDeltaTime();
	m_fSecsIntoState += fTimePassed;

	if( m_fSecsIntoState < 0 )
		wrap( m_fSecsIntoState, GetAnimationLengthSeconds() );

	UpdateAnimationState();

	// If the texture is a movie, decode frames.
	if( !bSkipThisMovieUpdate )
		m_pTexture->DecodeSeconds( max(0, fTimePassed) );

	// update scrolling
	if( m_fTexCoordVelocityX != 0 || m_fTexCoordVelocityY != 0 )
	{
		float fTexCoords[8];
		Sprite::GetActiveTextureCoords( fTexCoords );
 
		// top left, bottom left, bottom right, top right
		fTexCoords[0] += fDelta*m_fTexCoordVelocityX;
		fTexCoords[1] += fDelta*m_fTexCoordVelocityY; 
		fTexCoords[2] += fDelta*m_fTexCoordVelocityX;
		fTexCoords[3] += fDelta*m_fTexCoordVelocityY;
		fTexCoords[4] += fDelta*m_fTexCoordVelocityX;
		fTexCoords[5] += fDelta*m_fTexCoordVelocityY;
		fTexCoords[6] += fDelta*m_fTexCoordVelocityX;
		fTexCoords[7] += fDelta*m_fTexCoordVelocityY;

		/* When wrapping, avoid gradual loss of precision and sending
		 * unreasonably large texture coordinates to the renderer by pushing
		 * texture coordinates back to 0. As long as we adjust all four
		 * coordinates by the same amount, this won't be visible. */
		if( m_bTextureWrapping )
		{
			const float fXAdjust = floorf( fTexCoords[0] );
			const float fYAdjust = floorf( fTexCoords[1] );
			fTexCoords[0] -= fXAdjust;
			fTexCoords[2] -= fXAdjust;
			fTexCoords[4] -= fXAdjust;
			fTexCoords[6] -= fXAdjust;
			fTexCoords[1] -= fYAdjust;
			fTexCoords[3] -= fYAdjust;
			fTexCoords[5] -= fYAdjust;
			fTexCoords[7] -= fYAdjust;
		}

		Sprite::SetCustomTextureCoords( fTexCoords );
	}
}

void TexCoordArrayFromRect( float fImageCoords[8], const RectF &rect )
{
	fImageCoords[0] = rect.left;	fImageCoords[1] = rect.top;	// top left
	fImageCoords[2] = rect.left;	fImageCoords[3] = rect.bottom;	// bottom left
	fImageCoords[4] = rect.right;	fImageCoords[5] = rect.bottom;	// bottom right
	fImageCoords[6] = rect.right;	fImageCoords[7] = rect.top;	// top right
}

void Sprite::DrawTexture( const TweenState *state )
{
	Actor::SetGlobalRenderStates(); // set Actor-specified render states

	RectF crop = state->crop;
	// bail if cropped all the way 
	if( crop.left + crop.right >= 1  || 
		crop.top + crop.bottom >= 1 ) 
		return; 

	// use m_temp_* variables to draw the object
	RectF quadVerticies;
	quadVerticies.left   = -m_size.x/2.0f;
	quadVerticies.right  = +m_size.x/2.0f;
	quadVerticies.top    = -m_size.y/2.0f;
	quadVerticies.bottom = +m_size.y/2.0f;

	/* Don't draw anything outside of the texture's image area.  Texels outside 
	 * of the image area aren't guaranteed to be initialized. */
	/* HACK: Clamp the crop values. It would be more accurate to clip the 
	 * vertices so that the diffuse value is adjusted. */
	CLAMP( crop.left, 0, 1 );
	CLAMP( crop.right, 0, 1 );
	CLAMP( crop.top, 0, 1 );
	CLAMP( crop.bottom, 0, 1 );

	RectF croppedQuadVerticies = quadVerticies; 
#define IF_CROP_POS(side,opp_side) \
	if(state->crop.side!=0) \
		croppedQuadVerticies.side = \
			SCALE( crop.side, 0.f, 1.f, quadVerticies.side, quadVerticies.opp_side )
	IF_CROP_POS( left, right ); 
	IF_CROP_POS( top, bottom ); 
	IF_CROP_POS( right, left ); 
	IF_CROP_POS( bottom, top ); 

	static RageSpriteVertex v[4];
	v[0].p = RageVector3( croppedQuadVerticies.left,	croppedQuadVerticies.top,	0 );	// top left
	v[1].p = RageVector3( croppedQuadVerticies.left,	croppedQuadVerticies.bottom,	0 );	// bottom left
	v[2].p = RageVector3( croppedQuadVerticies.right,	croppedQuadVerticies.bottom,	0 );	// bottom right
	v[3].p = RageVector3( croppedQuadVerticies.right,	croppedQuadVerticies.top,	0 );	// top right

	DISPLAY->ClearAllTextures();
	DISPLAY->SetTexture( TextureUnit_1, m_pTexture? m_pTexture->GetTexHandle():0 );

	// Must call this after setting the texture or else texture 
	// parameters have no effect.
	Actor::SetTextureRenderStates(); // set Actor-specified render states
	DISPLAY->SetEffectMode( m_EffectMode );

	if( m_pTexture )
	{
		float f[8];
		GetActiveTextureCoords( f );

		if( state->crop.left || state->crop.right || state->crop.top || state->crop.bottom )
		{
			RageVector2 texCoords[4] = {
				RageVector2( f[0], f[1] ),	// top left
				RageVector2( f[2], f[3] ),	// bottom left
				RageVector2( f[4], f[5] ),	// bottom right
				RageVector2( f[6], f[7] ) 	// top right
			};

			for( int i = 0; i < 4; ++i )
			{
				RageSpriteVertex *pVert = &v[i];

				float fTopX = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, texCoords[0].x, texCoords[3].x );
				float fBottomX = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, texCoords[1].x, texCoords[2].x );
				pVert->t.x = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, fTopX, fBottomX );

				float fLeftY = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, texCoords[0].y, texCoords[1].y );
				float fRightY = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, texCoords[3].y, texCoords[2].y );
				pVert->t.y = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, fLeftY, fRightY );
			}
		}
		else
		{
			v[0].t = RageVector2( f[0], f[1] );	// top left
			v[1].t = RageVector2( f[2], f[3] );	// bottom left
			v[2].t = RageVector2( f[4], f[5] );	// bottom right
			v[3].t = RageVector2( f[6], f[7] );	// top right
		}
	}
	else
	{
		// Just make sure we don't throw NaN/INF at the renderer:
		for( unsigned i = 0; i < 4; ++i )
			v[i].t.x = v[i].t.y = 0;
	}

	// Draw if we're not fully transparent
	if( state->diffuse[0].a > 0 || 
		state->diffuse[1].a > 0 ||
		state->diffuse[2].a > 0 ||
		state->diffuse[3].a > 0 )
	{
		DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Modulate );

		// render the shadow
		if( m_fShadowLengthX != 0  ||  m_fShadowLengthY != 0 )
		{
			DISPLAY->PushMatrix();
			DISPLAY->TranslateWorld( m_fShadowLengthX, m_fShadowLengthY, 0 );	// shift by 5 units
			RageColor c = m_ShadowColor;
			c.a *= state->diffuse[0].a;
			v[0].c = v[1].c = v[2].c = v[3].c = c;	// semi-transparent black
			DISPLAY->DrawQuad( v );
			DISPLAY->PopMatrix();
		}

		// render the diffuse pass
		v[0].c = state->diffuse[0]; // top left
		v[1].c = state->diffuse[2]; // bottom left
		v[2].c = state->diffuse[3]; // bottom right
		v[3].c = state->diffuse[1]; // top right
		DISPLAY->DrawQuad( v );
	}

	// render the glow pass
	if( state->glow.a > 0.0001f )
	{
		DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Glow );
		v[0].c = v[1].c = v[2].c = v[3].c = state->glow;
		DISPLAY->DrawQuad( v );
	}
	DISPLAY->SetEffectMode( EffectMode_Normal );
}

bool Sprite::EarlyAbortDraw() const
{
	return m_pTexture == NULL;
}

void Sprite::DrawPrimitives()
{
	if( m_pTempState->fade.top > 0 ||
		m_pTempState->fade.bottom > 0 ||
		m_pTempState->fade.left > 0 ||
		m_pTempState->fade.right > 0 )
	{
		// We're fading the edges.
		const RectF &FadeDist = m_pTempState->fade;

		// Actual size of the fade on each side:
		RectF FadeSize = FadeDist;

		// If the cropped size is less than the fade distance in either dimension, clamp.
		const float HorizRemaining = 1.0f - (m_pTempState->crop.left + m_pTempState->crop.right);
		if( FadeDist.left+FadeDist.right > 0 &&
			HorizRemaining < FadeDist.left+FadeDist.right )
		{
			const float LeftPercent = FadeDist.left/(FadeDist.left+FadeDist.right);
			FadeSize.left = LeftPercent * HorizRemaining;
			FadeSize.right = (1.0f-LeftPercent) * HorizRemaining;
		}

		const float VertRemaining = 1.0f - (m_pTempState->crop.top + m_pTempState->crop.bottom);
		if( FadeDist.top+FadeDist.bottom > 0 &&
			VertRemaining < FadeDist.top+FadeDist.bottom )
		{
			const float TopPercent = FadeDist.top/(FadeDist.top+FadeDist.bottom);
			FadeSize.top = TopPercent * VertRemaining;
			FadeSize.bottom = (1.0f-TopPercent) * VertRemaining;
		}

		// Alpha value of the un-faded side of each fade rect:
		const float RightAlpha  = SCALE( FadeSize.right,  FadeDist.right,  0, 1, 0 );
		const float LeftAlpha   = SCALE( FadeSize.left,   FadeDist.left,   0, 1, 0 );
		const float TopAlpha    = SCALE( FadeSize.top,    FadeDist.top,    0, 1, 0 );
		const float BottomAlpha = SCALE( FadeSize.bottom, FadeDist.bottom, 0, 1, 0 );

		// Draw the inside:
		TweenState ts = *m_pTempState;
		ts.crop.left += FadeDist.left;
		ts.crop.right += FadeDist.right;
		ts.crop.top += FadeDist.top;
		ts.crop.bottom += FadeDist.bottom;
		DrawTexture( &ts );

		if( FadeSize.left > 0.001f )
		{
			// Draw the left:
			ts.crop = m_pTempState->crop; // restore
			ts.crop.right = 1 - (ts.crop.left + FadeSize.left);
			ts.crop.top += FadeDist.top;		// lop off the corner if fading both x and y
			ts.crop.bottom += FadeDist.bottom;

			ts.diffuse[0] = m_pTempState->diffuse[0];	// top left -> top left
			ts.diffuse[2] = m_pTempState->diffuse[2];	// bottom left -> bottom left
			ts.diffuse[3] = m_pTempState->diffuse[2];	// bottom left -> bottom right
			ts.diffuse[1] = m_pTempState->diffuse[0];	// top left -> top right
			ts.diffuse[0].a = 0;				// top left
			ts.diffuse[2].a = 0;				// bottom left
			ts.diffuse[3].a *= LeftAlpha;			// bottom right
			ts.diffuse[1].a *= LeftAlpha;			// top right
			DrawTexture( &ts );
		}

		if( FadeSize.right > 0.001f )
		{
			// Draw the right:
			ts.crop = m_pTempState->crop; // restore
			ts.crop.left = 1 - (ts.crop.right + FadeSize.right);
			ts.crop.top += FadeDist.top;
			ts.crop.bottom += FadeDist.bottom;

			ts.diffuse[0] = m_pTempState->diffuse[1];	// top right -> top left
			ts.diffuse[2] = m_pTempState->diffuse[3];	// bottom right -> bottom left
			ts.diffuse[3] = m_pTempState->diffuse[3];	// bottom right -> bottom right
			ts.diffuse[1] = m_pTempState->diffuse[1];	// top right -> top right
			ts.diffuse[0].a *= RightAlpha;			// top left
			ts.diffuse[2].a *= RightAlpha;			// bottom left
			ts.diffuse[3].a = 0;				// bottom right
			ts.diffuse[1].a = 0;				// top right

			DrawTexture( &ts );
		}

		if( FadeSize.top > 0.001f )
		{
			// Draw the top:
			ts.crop = m_pTempState->crop; // restore
			ts.crop.bottom = 1 - (ts.crop.top + FadeSize.top);
			ts.crop.left += FadeDist.left;
			ts.crop.right += FadeDist.right;

			ts.diffuse[0] = m_pTempState->diffuse[0];	// top left -> top left
			ts.diffuse[2] = m_pTempState->diffuse[0];	// top left -> bottom left
			ts.diffuse[3] = m_pTempState->diffuse[1];	// top right -> bottom right
			ts.diffuse[1] = m_pTempState->diffuse[1];	// top right -> top right
			ts.diffuse[0].a = 0;				// top left
			ts.diffuse[2].a *= TopAlpha;			// bottom left
			ts.diffuse[3].a *= TopAlpha;			// bottom right
			ts.diffuse[1].a = 0;				// top right

			DrawTexture( &ts );
		}

		if( FadeSize.bottom > 0.001f )
		{
			// Draw the bottom:
			ts.crop = m_pTempState->crop; // restore
			ts.crop.top = 1 - (ts.crop.bottom + FadeSize.bottom);
			ts.crop.left += FadeDist.left;
			ts.crop.right += FadeDist.right;

			ts.diffuse[0] = m_pTempState->diffuse[2];	// bottom left -> top left
			ts.diffuse[2] = m_pTempState->diffuse[2];	// bottom left -> bottom left
			ts.diffuse[3] = m_pTempState->diffuse[3];	// bottom right -> bottom right
			ts.diffuse[1] = m_pTempState->diffuse[3];	// bottom right -> top right
			ts.diffuse[0].a *= BottomAlpha;			// top left
			ts.diffuse[2].a = 0;				// bottom left
			ts.diffuse[3].a = 0;				// bottom right
			ts.diffuse[1].a *= BottomAlpha;			// top right
			DrawTexture( &ts );
		}
	}
	else
	{
		DrawTexture( m_pTempState );
	}
}


int Sprite::GetNumStates() const
{
	return m_States.size(); 
}

void Sprite::SetState( int iNewState )
{
	/*
	 * This assert will likely trigger if the "missing" theme element graphic 
	 * is loaded in place of a multi-frame sprite. We want to know about these
	 * problems in debug builds, but they're not fatal.
	 */

	// Never warn about setting state 0.
	if( iNewState != 0 && (iNewState < 0  ||  iNewState >= (int)m_States.size()) )
	{
		// Don't warn about number of states in "_blank" or "_missing".
		if( !m_pTexture || (m_pTexture->GetID().filename.find("_blank") == string::npos &&
			m_pTexture->GetID().filename.find("_missing") == string::npos) )
		{
			RString sError;
			if( m_pTexture )
				sError = ssprintf("A Sprite '%s' (\"%s\") tried to set state to frame %d, but it has only %u frames.",
					/*
					 * Using the state directly tends to give you an error message like "tried to set frame 6 of 6"
					 * which is very confusing if you don't know that one is 0-indexed and the other is 1-indexed.
					 * - Colby
					 */
					m_pTexture->GetID().filename.c_str(), this->m_sName.c_str(), iNewState+1, unsigned(m_States.size()));
			else
				sError = ssprintf("A Sprite (\"%s\") tried to set state index %d, but no texture is loaded.", 
					this->m_sName.c_str(), iNewState );
			Dialog::OK( sError, "SPRITE_INVALID_FRAME" );
		}
	}

	CLAMP(iNewState, 0, (int)m_States.size()-1);
	m_iCurState = iNewState;
	m_fSecsIntoState = 0.0f;
}

float Sprite::GetAnimationLengthSeconds() const
{
	float fTotal = 0;
	FOREACH_CONST( State, m_States, s )
		fTotal += s->fDelay;
	return fTotal;
}

void Sprite::SetSecondsIntoAnimation( float fSeconds )
{
	SetState( 0 );	// rewind to the first state
	if( m_pTexture )
		m_pTexture->SetPosition( fSeconds );
	m_fSecsIntoState = fSeconds;
	UpdateAnimationState();
}

RString	Sprite::GetTexturePath() const
{
	if( m_pTexture==NULL )
		return RString();

	return m_pTexture->GetID().filename;
}

void Sprite::SetCustomTextureRect( const RectF &new_texcoord_frect ) 
{ 
	m_bUsingCustomTexCoords = true;
	m_bTextureWrapping = true;
	TexCoordArrayFromRect( m_CustomTexCoords, new_texcoord_frect );
}

void Sprite::SetCustomTextureCoords( float fTexCoords[8] ) // order: top left, bottom left, bottom right, top right
{ 
	m_bUsingCustomTexCoords = true;
	m_bTextureWrapping = true;
	for( int i=0; i<8; i++ )
		m_CustomTexCoords[i] = fTexCoords[i]; 
}

void Sprite::SetCustomImageRect( RectF rectImageCoords )
{
	// Convert to a rectangle in texture coordinate space.
	rectImageCoords.left	*= m_pTexture->GetImageWidth()	/ (float)m_pTexture->GetTextureWidth();
	rectImageCoords.right	*= m_pTexture->GetImageWidth()	/ (float)m_pTexture->GetTextureWidth();
	rectImageCoords.top	*= m_pTexture->GetImageHeight()	/ (float)m_pTexture->GetTextureHeight(); 
	rectImageCoords.bottom	*= m_pTexture->GetImageHeight()	/ (float)m_pTexture->GetTextureHeight(); 

	SetCustomTextureRect( rectImageCoords );
}

void Sprite::SetCustomImageCoords( float fImageCoords[8] )	// order: top left, bottom left, bottom right, top right
{
	// convert image coords to texture coords in place
	for( int i=0; i<8; i+=2 )
	{
		fImageCoords[i+0] *= m_pTexture->GetImageWidth()	/ (float)m_pTexture->GetTextureWidth(); 
		fImageCoords[i+1] *= m_pTexture->GetImageHeight()	/ (float)m_pTexture->GetTextureHeight(); 
	}

	SetCustomTextureCoords( fImageCoords );
}

const RectF *Sprite::GetCurrentTextureCoordRect() const
{
	return GetTextureCoordRectForState( m_iCurState );
}

const RectF *Sprite::GetTextureCoordRectForState( int iState ) const
{
	ASSERT_M( iState < (int) m_States.size(), ssprintf("%d, %d", int(iState), int(m_States.size())) );

	return &m_States[iState].rect;
}

/* If we're using custom coordinates, return them; otherwise return the
 * coordinates for the current state. */
void Sprite::GetActiveTextureCoords( float fTexCoordsOut[8] ) const
{
	if(m_bUsingCustomTexCoords) 
	{
		// GetCustomTextureCoords
		for( int i=0; i<8; i++ )
			fTexCoordsOut[i] = m_CustomTexCoords[i]; 
	}
	else
	{
		// GetCurrentTextureCoords
		const RectF *pTexCoordRect = GetCurrentTextureCoordRect();
		TexCoordArrayFromRect( fTexCoordsOut, *pTexCoordRect );
	}
}


void Sprite::StopUsingCustomCoords()
{
	m_bUsingCustomTexCoords = false;
}

void Sprite::SetTexCoordVelocity(float fVelX, float fVelY)
{
	m_fTexCoordVelocityX = fVelX;
	m_fTexCoordVelocityY = fVelY;
}

void Sprite::ScaleToClipped( float fWidth, float fHeight )
{
	m_fRememberedClipWidth = fWidth;
	m_fRememberedClipHeight = fHeight;

	if( !m_pTexture )
		return;

	float fScaleFudgePercent = 0.15f;	// scale up to this amount in one dimension to avoid clipping.

	// save the original X and Y.  We're going to restore them later.
	float fOriginalX = GetX();
	float fOriginalY = GetY();

	if( fWidth != -1 && fHeight != -1 )
	{
		// this is probably a background graphic or something not intended to be a CroppedSprite
		Sprite::StopUsingCustomCoords();

		// first find the correct zoom
		Sprite::ScaleToCover( RectF(0, 0, fWidth, fHeight) );
		// find which dimension is larger
		bool bXDimNeedsToBeCropped = GetZoomedWidth() > fWidth+0.01;
		
		if( bXDimNeedsToBeCropped ) // crop X
		{
			float fPercentageToCutOff = (this->GetZoomedWidth() - fWidth) / this->GetZoomedWidth();
			fPercentageToCutOff = max( fPercentageToCutOff-fScaleFudgePercent, 0 );
			float fPercentageToCutOffEachSide = fPercentageToCutOff / 2;

			// generate a rectangle with new texture coordinates
			RectF fCustomImageRect( 
				fPercentageToCutOffEachSide, 
				0, 
				1 - fPercentageToCutOffEachSide, 
				1 );
			SetCustomImageRect( fCustomImageRect );
		}
		else // crop Y
		{
			float fPercentageToCutOff = (this->GetZoomedHeight() - fHeight) / this->GetZoomedHeight();
			fPercentageToCutOff = max( fPercentageToCutOff-fScaleFudgePercent, 0 );
			float fPercentageToCutOffEachSide = fPercentageToCutOff / 2;

			// generate a rectangle with new texture coordinates
			RectF fCustomImageRect( 
				0, 
				fPercentageToCutOffEachSide,
				1, 
				1 - fPercentageToCutOffEachSide );
			SetCustomImageRect( fCustomImageRect );
		}
		m_size = RageVector2( fWidth, fHeight );
		SetZoom( 1 );
	}

	// restore original XY
	SetXY( fOriginalX, fOriginalY );
}


// magic hurr
// This code should either be removed or refactored in the future -aj
void Sprite::CropTo( float fWidth, float fHeight )
{
	m_fRememberedClipWidth = fWidth;
	m_fRememberedClipHeight = fHeight;

	if( !m_pTexture )
		return;

	// save the original X&Y.  We're going to restore them later.
	float fOriginalX = GetX();
	float fOriginalY = GetY();

	if( fWidth != -1 && fHeight != -1 )
	{
		// this is probably a background graphic or something not intended to be a CroppedSprite
		Sprite::StopUsingCustomCoords();

		// first find the correct zoom
		Sprite::ScaleToCover( RectF(0, 0, fWidth, fHeight) );
		// find which dimension is larger
		bool bXDimNeedsToBeCropped = GetZoomedWidth() > fWidth+0.01;
		
		if( bXDimNeedsToBeCropped )	// crop X
		{
			float fPercentageToCutOff = (this->GetZoomedWidth() - fWidth) / this->GetZoomedWidth();
			float fPercentageToCutOffEachSide = fPercentageToCutOff / 2;

			// generate a rectangle with new texture coordinates
			RectF fCustomImageRect( 
				fPercentageToCutOffEachSide, 
				0, 
				1 - fPercentageToCutOffEachSide, 
				1 );
			SetCustomImageRect( fCustomImageRect );
		}
		else		// crop Y
		{
			float fPercentageToCutOff = (this->GetZoomedHeight() - fHeight) / this->GetZoomedHeight();
			float fPercentageToCutOffEachSide = fPercentageToCutOff / 2;

			// generate a rectangle with new texture coordinates
			RectF fCustomImageRect( 
				0, 
				fPercentageToCutOffEachSide,
				1, 
				1 - fPercentageToCutOffEachSide );
			SetCustomImageRect( fCustomImageRect );
		}
		m_size = RageVector2( fWidth, fHeight );
		SetZoom( 1 );
	}

	// restore original XY
	SetXY( fOriginalX, fOriginalY );
}
// end magic

void Sprite::StretchTexCoords( float fX, float fY )
{
	float fTexCoords[8];
	GetActiveTextureCoords( fTexCoords );

	for( int j=0; j<8; j+=2 )
	{
		fTexCoords[j  ] += fX;
		fTexCoords[j+1] += fY;
	}

	SetCustomTextureCoords( fTexCoords );
}

void Sprite::AddImageCoords( float fX, float fY )
{
	float fTexCoords[8];
	GetActiveTextureCoords( fTexCoords );

	for( int j=0; j<8; j+=2 )
	{
		fTexCoords[j  ] += fX / (float)m_pTexture->GetTextureWidth();
		fTexCoords[j+1] += fY / (float)m_pTexture->GetTextureHeight();
	}

	SetCustomTextureCoords( fTexCoords );
}


// lua start
#include "LuaBinding.h"

/** @brief Allow Lua to have access to the Sprite. */ 
class LunaSprite: public Luna<Sprite>
{
public:
	static int Load( T* p, lua_State *L )
	{
		if( lua_isnil(L, 1) )
		{
			p->UnloadTexture();
		}
		else
		{
			RageTextureID ID( SArg(1) );
			p->Load( ID );
		}
		return 0;
	}
	static int LoadBackground( T* p, lua_State *L )
	{
		RageTextureID ID( SArg(1) );
		TEXTUREMAN->DisableOddDimensionWarning();
		p->Load( Sprite::SongBGTexture(ID) );
		TEXTUREMAN->EnableOddDimensionWarning();
		return 1;
	}
	static int LoadBanner( T* p, lua_State *L )
	{
		RageTextureID ID( SArg(1) );
		TEXTUREMAN->DisableOddDimensionWarning();
		p->Load( Sprite::SongBannerTexture(ID) );
		TEXTUREMAN->EnableOddDimensionWarning();
		return 1;
	}

	/* Commands that go in the tweening queue: 
	 * Commands that take effect immediately (ignoring the tweening queue): */
	static int customtexturerect( T* p, lua_State *L )	{ p->SetCustomTextureRect( RectF(FArg(1),FArg(2),FArg(3),FArg(4)) ); return 0; }
	static int SetCustomImageRect( T* p, lua_State *L )	{ p->SetCustomImageRect( RectF(FArg(1),FArg(2),FArg(3),FArg(4)) ); return 0; }
	static int texcoordvelocity( T* p, lua_State *L )	{ p->SetTexCoordVelocity( FArg(1),FArg(2) ); return 0; }
	static int scaletoclipped( T* p, lua_State *L )		{ p->ScaleToClipped( FArg(1),FArg(2) ); return 0; }
	static int CropTo( T* p, lua_State *L )		{ p->CropTo( FArg(1),FArg(2) ); return 0; }
	static int stretchtexcoords( T* p, lua_State *L )	{ p->StretchTexCoords( FArg(1),FArg(2) ); return 0; }
	static int addimagecoords( T* p, lua_State *L )		{ p->AddImageCoords( FArg(1),FArg(2) ); return 0; }
	static int setstate( T* p, lua_State *L )		{ p->SetState( IArg(1) ); return 0; }
	static int GetState( T* p, lua_State *L )		{ lua_pushnumber( L, p->GetState() ); return 1; }
	static int GetAnimationLengthSeconds( T* p, lua_State *L ) { lua_pushnumber( L, p->GetAnimationLengthSeconds() ); return 1; }
	static int SetSecondsIntoAnimation( T* p, lua_State *L )	{ p->SetSecondsIntoAnimation(FArg(0)); return 0; }
	static int SetTexture( T* p, lua_State *L )
	{
		RageTexture *pTexture = Luna<RageTexture>::check(L, 1);
		pTexture = TEXTUREMAN->CopyTexture( pTexture );
		p->SetTexture( pTexture );
		return 0;
	}
	static int GetTexture( T* p, lua_State *L )
	{
		RageTexture *pTexture = p->GetTexture();
		if( pTexture != NULL )
			pTexture->PushSelf(L);
		else
			lua_pushnil( L );
		return 1;
	}
	static int SetEffectMode( T* p, lua_State *L )
	{
		EffectMode em = Enum::Check<EffectMode>(L, 1);
		p->SetEffectMode( em );
		return 0;
	}
	static int GetNumStates( T* p, lua_State *L ) { lua_pushnumber( L, p->GetNumStates() ); return 1; }
	static int SetAllStateDelays( T* p, lua_State *L ) { p->SetAllStateDelays(FArg(1)); return 0; }

	LunaSprite()
	{
		ADD_METHOD( Load );
		ADD_METHOD( LoadBanner );
		ADD_METHOD( LoadBackground );
		ADD_METHOD( customtexturerect );
		ADD_METHOD( SetCustomImageRect );
		ADD_METHOD( texcoordvelocity );
		ADD_METHOD( scaletoclipped );
		ADD_METHOD( CropTo );
		ADD_METHOD( stretchtexcoords );
		ADD_METHOD( addimagecoords );
		ADD_METHOD( setstate );
		ADD_METHOD( GetState );
		ADD_METHOD( GetAnimationLengthSeconds );
		ADD_METHOD( SetSecondsIntoAnimation );
		ADD_METHOD( SetTexture );
		ADD_METHOD( GetTexture );
		ADD_METHOD( SetEffectMode );
		ADD_METHOD( GetNumStates );
		ADD_METHOD( SetAllStateDelays );
	}
};

LUA_REGISTER_DERIVED_CLASS( Sprite, Actor )
// lua end

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