///////////////////////////////////////////////////////////////////////////////
/// @file de2D.cpp
///
/// @brief 2D Overlays and other 2D drawing
///
/// @author Assassin
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Mar 2002
/// @author Assassin
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include <cstring>
#include "de2D_priv.hpp"
#include "deMath.hpp"
#include "deCamera.hpp"
#include "deStack.hpp"

static deRect ZeroRect = {0,0,0,0};

long de2DCollection::s_NumCollections = 0;
IdeVertexBuffer* de2DCollection::s_CompositeFor2D = NULL;
IdeVertexBuffer* de2DCollection::s_BackdropQuad = NULL;
IdeRenderTexture*de2DCollection::s_BackdropTexture = NULL;

Ide2DCollection* Ide2DObject_Create2DCollection()
{
	return new de2DCollection();
}
Ide2DObject* Ide2DObject_Create2DObject(IdeVertexBuffer * VBuffer, 
										IdeRenderTexture * Texture, 
										deFloat Depth, deBoolean ExpireNow)
{
	return new de2DObject(VBuffer, Texture, Depth, ExpireNow);
}
Ide2DObject* Ide2DObject_Create2DOverlay(deRect *ScreenRect, deFloatRect *TexCoords,	
										 IdeRenderTexture * Texture,
										 deFloat Depth, deARGB color, deDouble FadePerSecond)
{
	return new de2DOverlay(ScreenRect, TexCoords, Texture, Depth, color, FadePerSecond);
}
Ide2DObject* Ide2DObject_Create2DCorona(deRect *SizeRect, deFloatRect *TexCoords, 
										IdeRenderTexture * Texture, IdeCamera * ScreenCamera, 
										deFloat Depth, deARGB color)
{
	return new de2DCorona(SizeRect, TexCoords, Texture, ScreenCamera, Depth, color);
}
Ide2DObject* Ide2DObject_Create2DLine(deVec3d Start, deVec3d End, 
									  deARGB color, deDouble FadePerSecond, 
									  deFloat Depth)
{
	return new de2DLine(Start, End, color, FadePerSecond, Depth);
}
Ide2DObject* Ide2DObject_Create2DLineList(deVec3d PointList[], long NumPoints, 
										  deARGB color, deDouble FadePerSecond, 
										  deFloat Depth)
{
	return new de2DLine(PointList, NumPoints, color, FadePerSecond, Depth);
}
Ide2DObject* Ide2DObject_Create2DLineRect(deRect & LineRect, deARGB color, 
										  deDouble FadePerSecond, deFloat Depth)
{
	return new de2DLine(LineRect, color, FadePerSecond, Depth);
}


de2DObject::de2DObject(IdeVertexBuffer * VBuffer, IdeRenderTexture * Texture, deFloat Depth, deBoolean ExpireNow)
{
	m_VBuffer = VBuffer;
	m_Texture = Texture;
	m_Expire = ExpireNow;
	m_Depth = Depth;

	if (m_Texture)
		m_Texture->Claim();
}

de2DObject::~de2DObject()
{
	if (m_VBuffer)
		m_VBuffer->Release();
	if (m_Texture)
		m_Texture->Release();
}

IdeVertexBuffer * de2DObject::GetVBuffer() const
{
	return m_VBuffer;
}

IdeRenderTexture* de2DObject::GetTexture() const
{
	return m_Texture;
}

deFloat de2DObject::GetDepth() const
{
	return m_Depth;
}

deBoolean de2DObject::SetDepth(deFloat Depth)
{
	deVertex *			Positions;
	IdeVertexBuffer *	VBuffer;
	int					i, len;
	
	VBuffer = GetVBuffer();
	if (!VBuffer->Lock())
		return DE_FALSE;
	Positions = (deVertex*)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_POSITION);
	if (!Positions)
		return DE_FALSE;
	len = VBuffer->GetVertexCount();
	
	for (i = 0; i < len; i++)
	{
		Positions[i].Z = Depth;
	}
	
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
	VBuffer->Unlock();

	return DE_TRUE;
}

deBoolean de2DObject::HasExpired() const
{
	return m_Expire;
}

void de2DObject::MakeExpire()
{
	m_Expire = DE_TRUE;
}

void de2DObject::SetVBuffer(IdeVertexBuffer * VBuffer)
{
	if (m_VBuffer)
		m_VBuffer->Release();
	m_VBuffer = VBuffer;
}

void de2DObject::SetTexture(IdeRenderTexture * Texture)
{
	m_Texture = Texture;
}

deBoolean de2DObject::Move(deVertex DeltaXY)
{
	deVertex *			Positions;
	IdeVertexBuffer *	VBuffer;
	int					i, len;

	VBuffer = GetVBuffer();
	if (!VBuffer->Lock())
		return DE_FALSE;
	Positions = (deVertex*)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_POSITION);
	if (!Positions)
		return DE_FALSE;
	len = VBuffer->GetVertexCount();
	DeltaXY.Z = 0;

	for (i = 0; i < len; i++)
	{
		Positions[i] = Positions[i] + DeltaXY;
	}

	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
	VBuffer->Unlock();
	
	return DE_TRUE;
}

deBoolean de2DObject::Update(deDouble DeltaTime)
{
	return UpdateObject(DeltaTime);
}

deBoolean de2DObject::UpdateObject(deDouble DeltaTime)
{
	DeltaTime = DeltaTime;
	return DE_TRUE;
}


//=======================================================

de2DOverlay::de2DOverlay(deRect* ScreenRect, deFloatRect* TexCoords,
						 IdeRenderTexture * Texture, deFloat Depth, 
						 deARGB color, deDouble FadePerSecond)
: de2DObject(NULL, Texture, Depth, DE_FALSE)
{
	IdeVertexBuffer *	VBuffer;
	deVertex *			Position;
	deARGB *			Color;
	deTexCoord *		Coords;
	const int			PointCount = 4;

	const deFloatRect	DefaultCoords[] = {{0,0},{1,0},{1,1},{0,1}};	

	m_Alpha = ((color >> 24) & 0x000000FF) / 255.0;
	m_FadePerSecond = FadePerSecond / 255.0;

#ifdef _DEBUG
	if (ScreenRect == NULL)
		throw;
#endif

	VBuffer = IdeVertexBuffer_CreateVBuffer();
	//VBuffer = IdeVertexBuffer_CreateSubVBuffer(de2DCollection::s_CompositeFor2D);
	if(!VBuffer)
		throw;

	//lock it
	VBuffer->Lock();

	if(!VBuffer->SetBufferSize(PointCount, deFALSE))
		throw;

	//allocate memory for the points
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE))
	{
		//something failed, destroy and exit
		VBuffer->Release();
		throw;
	}

	//a buffer for the color
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_DIFFUSE, deFALSE))
	{
		VBuffer->Release();
		throw;
	}

	//add in the UV coords for the texture
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0, deFALSE))
	{
		//something failed, destroy and exit
		VBuffer->Release();
		throw;
	}

	//grab the buffers now that we created and fill them
	Position = (deVertex *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_POSITION);
	Color = (deARGB *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);
	Coords = (deTexCoord *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0);

#ifdef _DEBUG
	if (Position == NULL)
		throw;
	if (Color == NULL)
		throw;
	if (Coords == NULL)
		throw;
#endif

	Position[0].x = (deFloat)ScreenRect->Left;
	Position[0].y = (deFloat)ScreenRect->Top;
	Position[0].z = Depth;
	Position[1].x = (deFloat)ScreenRect->Left;
	Position[1].y = (deFloat)ScreenRect->Bottom;
	Position[1].z = Depth;
	Position[2].x = (deFloat)ScreenRect->Right;
	Position[2].y = (deFloat)ScreenRect->Top;
	Position[2].z = Depth;
	Position[3].x = (deFloat)ScreenRect->Right;
	Position[3].y = (deFloat)ScreenRect->Bottom;
	Position[3].z = Depth;

	if (TexCoords)
	{
		Coords[0].u = TexCoords->Left;
		Coords[0].v = TexCoords->Top;
		Coords[1].u = TexCoords->Left;
		Coords[1].v = TexCoords->Bottom;
		Coords[2].u = TexCoords->Right;
		Coords[2].v = TexCoords->Top;
		Coords[3].u = TexCoords->Right;
		Coords[3].v = TexCoords->Bottom;
	}
	else
	{
		memcpy(Coords, &DefaultCoords, 4 * sizeof(deFloatRect));
	}

	Color[0] = Color[1] = Color[2] = Color[3] = color;

//	VBuffer->SetTriCount(2);

	//make sure we are doing a tri list
	VBuffer->SetRenderType(IdeVertexBuffer::RENDER_TRIANGLESTRIP);

	//make sure the buffers are dirtied
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0);

	VBuffer->Unlock();

	de2DObject::SetVBuffer(VBuffer);
}

de2DOverlay::~de2DOverlay()
{
}

deBoolean de2DOverlay::UpdateObject(deDouble DeltaTime)
{
	deARGB *			Color;
	unsigned char		AlphaMask, *AlphaPtr;
	IdeVertexBuffer *	VBuffer;
	int					i;

	if (m_FadePerSecond == 0.0)
		return DE_TRUE;

	m_Alpha -= m_FadePerSecond * DeltaTime;
	AlphaMask = (unsigned char)(m_Alpha*255);

	VBuffer = GetVBuffer();
	VBuffer->Lock();
	Color = (deARGB *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);

	for (i = 0; i < 4; i++)
	{
		AlphaPtr = (unsigned char*)(&(Color[i])) + 3;
		*AlphaPtr = AlphaMask;
	}

	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);
	VBuffer->Unlock();

	if (m_Alpha < 0)
		MakeExpire();

	return DE_TRUE;
}


//=======================================================

de2DCorona::de2DCorona(	deRect* SizeRect, deFloatRect* TexCoords,
						IdeRenderTexture * Texture, IdeCamera * ScreenCamera,
						deFloat Depth, deARGB color)
: de2DOverlay(SizeRect, TexCoords, Texture, Depth, color, 0.0)
{
	m_WorldPosition = deZeroVec;
	m_ScreenPosition = deMake_Vertex(deZeroVec);
	m_OnScreen = DE_FALSE;
	m_Camera = ScreenCamera;
}

de2DCorona::~de2DCorona()
{
}

void de2DCorona::SetPosition(deVec3d WorldPosition)
{
	m_WorldPosition = WorldPosition;
}

deBoolean de2DCorona::UpdateObject(deDouble DeltaTime)
{
	int x,y;
	deVertex v;

	if (!de2DOverlay::UpdateObject(DeltaTime))
		return DE_FALSE;

	if (!m_Camera)
	{
		m_OnScreen = DE_FALSE;
		return DE_FALSE;
	}

	m_Camera->WorldPosToScreenPos(m_WorldPosition, x, y);
	if (x == m_ScreenPosition.x && y == m_ScreenPosition.y)
		return m_OnScreen;
	if (x < 0 || y < 0)
	{
		m_OnScreen = DE_FALSE;
		return DE_FALSE;
	}
	v = deMake_Vertex((deFloat)x, (deFloat)y, 0) - m_ScreenPosition;
	m_ScreenPosition = m_ScreenPosition + v;
	Move(v);
	m_OnScreen = DE_TRUE;

	return m_OnScreen;
}

//=======================================================

de2DLine::de2DLine(	deVec3d PointList[], long NumPoints,
					deARGB color, deDouble FadePerSecond, deFloat Depth)
: de2DObject(NULL, NULL, Depth, DE_FALSE)
{
	IdeVertexBuffer *	VBuffer;
	deVertex *			Position;
	deARGB *			Color;
	int					i;

	m_Alpha = ((color >> 24) & 0x000000FF) / 255.0;
	m_FadePerSecond = FadePerSecond / 255.0;

	VBuffer = IdeVertexBuffer_CreateVBuffer();
	//VBuffer = IdeVertexBuffer_CreateSubVBuffer(de2DCollection::s_CompositeFor2D);
	if(!VBuffer)
		throw;

	//lock it
	VBuffer->Lock();

	if(!VBuffer->SetBufferSize(NumPoints, deFALSE))
		throw;

	//allocate memory for the points
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE))
	{
		//something failed, destroy and exit
		VBuffer->Release();
		throw;
	}

	//a buffer for the color
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_DIFFUSE, deFALSE))
	{
		VBuffer->Release();
		throw;
	}

	//grab the buffers now that we created and fill them
	Position = (deVertex *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_POSITION);
	Color = (deARGB *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);

	for (i = 0; i < NumPoints; i++)
	{
		Position[i] = deMake_Vertex(PointList[i]);
		Color[i] = color;
	}

//	VBuffer->SetTriCount(PointCount-1);

	//make sure we are doing a tri list
	VBuffer->SetRenderType(IdeVertexBuffer::RENDER_LINESTRIP);

	//make sure the buffers are dirtied
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);

	VBuffer->Unlock();

	de2DObject::SetVBuffer(VBuffer);
}

de2DLine::de2DLine(	deVec3d Start, deVec3d End, 
					deARGB color, deDouble FadePerSecond, deFloat Depth)
: de2DObject(NULL, NULL, Depth, DE_FALSE)
{
	IdeVertexBuffer *	VBuffer;
	deVertex *			Position;
	deARGB *			Color;
	int					PointCount;

	m_Alpha = ((color >> 24) & 0x000000FF) / 255.0;
	m_FadePerSecond = FadePerSecond / 255.0;

	PointCount = 2;

	VBuffer = IdeVertexBuffer_CreateVBuffer();
	//VBuffer = IdeVertexBuffer_CreateSubVBuffer(de2DCollection::s_CompositeFor2D);
	if(!VBuffer)
		throw;

	//lock it
	VBuffer->Lock();

	if(!VBuffer->SetBufferSize(PointCount, deFALSE))
		throw;

	//allocate memory for the points
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE))
	{
		//something failed, destroy and exit
		VBuffer->Release();
		throw;
	}

	//a buffer for the color
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_DIFFUSE, deFALSE))
	{
		VBuffer->Release();
		throw;
	}

	//grab the buffers now that we created and fill them
	Position = (deVertex *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_POSITION);
	Color = (deARGB *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);

	Position[0].x = (deFloat)(Start.x);
	Position[0].y = (deFloat)(Start.y);
	Position[0].z = Depth;
	Position[1].x = (deFloat)(End.x);
	Position[1].y = (deFloat)(End.y);
	Position[1].z = Depth;
	Color[0] = 	Color[1] = color;

//	VBuffer->SetTriCount(PointCount-1);

	//make sure we are doing a tri list
	VBuffer->SetRenderType(IdeVertexBuffer::RENDER_LINESTRIP);

	//make sure the buffers are dirtied
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);

	VBuffer->Unlock();

	de2DObject::SetVBuffer(VBuffer);
}

de2DLine::de2DLine(	deRect & LineRect, 
					deARGB color, deDouble FadePerSecond, deFloat Depth)
: de2DObject(NULL, NULL, Depth, DE_FALSE)
{
	IdeVertexBuffer *	VBuffer;
	deVertex *			Position;
	deARGB *			Color;
	int					PointCount;

	m_Alpha = ((color >> 24) & 0x000000FF) / 255.0;
	m_FadePerSecond = FadePerSecond / 255.0;

	PointCount = 5;

	VBuffer = IdeVertexBuffer_CreateVBuffer();
	if(!VBuffer)
		throw;

	//lock it
	VBuffer->Lock();

	if(!VBuffer->SetBufferSize(PointCount, deFALSE))
		throw;

	//allocate memory for the points
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE))
	{
		//something failed, destroy and exit
		VBuffer->Release();
		throw;
	}

	//a buffer for the color
	if(!VBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_DIFFUSE, deFALSE))
	{
		VBuffer->Release();
		throw;
	}

	//grab the buffers now that we created and fill them
	Position = (deVertex *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_POSITION);
	Color = (deARGB *)VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);

	Position[0].x = (deFloat)(LineRect.Left);
	Position[0].y = (deFloat)(LineRect.Top);
	Position[0].z = Depth;
	Position[1].x = (deFloat)(LineRect.Left);
	Position[1].y = (deFloat)(LineRect.Bottom);
	Position[1].z = Depth;
	Position[2].x = (deFloat)(LineRect.Right);
	Position[2].y = (deFloat)(LineRect.Bottom);
	Position[2].z = Depth;
	Position[3].x = (deFloat)(LineRect.Right);
	Position[3].y = (deFloat)(LineRect.Top);
	Position[3].z = Depth;
	Position[4].x = (deFloat)(LineRect.Left);
	Position[4].y = (deFloat)(LineRect.Top);
	Position[4].z = Depth;
	for (int i = 0; i < PointCount; i++)
	{
		Color[i] = color;
	}

	//make sure we are doing a tri list
	VBuffer->SetRenderType(IdeVertexBuffer::RENDER_LINESTRIP);

	//make sure the buffers are dirtied
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
	VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);

	VBuffer->Unlock();

	de2DObject::SetVBuffer(VBuffer);
}

de2DLine::~de2DLine()
{
}

deBoolean de2DLine::UpdateObject(deDouble DeltaTime)
{
	DeltaTime = DeltaTime;
	return DE_TRUE;
}

//=======================================================
// de2DCollection
//=======================================================

de2DCollection::de2DCollection()
{
	s_NumCollections++;

	if (!s_CompositeFor2D)
	{
		s_CompositeFor2D = IdeVertexBuffer_CreateVBuffer();
		s_CompositeFor2D->Lock();
		s_CompositeFor2D->SetBufferSize(100);
		s_CompositeFor2D->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deTRUE);
		s_CompositeFor2D->CreateBuffer(IdeVertexBuffer::BUFFER_DIFFUSE, deTRUE);
		s_CompositeFor2D->CreateBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0, deTRUE);
		s_CompositeFor2D->SetRenderType(IdeVertexBuffer::RENDER_POINTLIST);
		s_CompositeFor2D->SetDynamicStatus(deTRUE, deTRUE);
		s_CompositeFor2D->Unlock();
	}
	if (!s_BackdropQuad)
	{
		const deVertex PosArray[4] = {
			{0,0,10},
			{0,1,10},
			{1,0,10},
			{1,1,10},
		};
		const short IndexArray[6] = {0,1,2, 2,1,3};

		s_BackdropQuad = IdeVertexBuffer_CreateVBuffer();
		s_BackdropQuad->Lock();
		s_BackdropQuad->SetBufferSize(4, deFALSE);
		s_BackdropQuad->SetBufferSize(6, deTRUE);
		s_BackdropQuad->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE);
		s_BackdropQuad->CreateBuffer(IdeVertexBuffer::BUFFER_INDICES16, deFALSE);
		s_BackdropQuad->SetRenderType(IdeVertexBuffer::RENDER_TRIANGLELIST);
		s_BackdropQuad->SetDynamicStatus(deFALSE, deFALSE);

		deVertex * PosStream = (deVertex*)(s_BackdropQuad->GetBuffer(IdeVertexBuffer::BUFFER_POSITION));
		short * IndexStream = (short*)(s_BackdropQuad->GetBuffer(IdeVertexBuffer::BUFFER_INDICES16));
		memcpy(PosStream, PosArray, 4*sizeof(deVertex));
		memcpy(IndexStream, IndexArray, 6*sizeof(short));

		s_BackdropQuad->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
		s_BackdropQuad->DirtyBuffer(IdeVertexBuffer::BUFFER_INDICES16);

		s_BackdropQuad->Unlock();
	}
	if (!s_BackdropTexture)
	{
		s_BackdropTexture = IdeRenderState_CreateRenderTexture();
		s_BackdropTexture->Lock();
		s_BackdropTexture->SetColorArg(0, 1, IdeRenderTexture::TEXTUREARG_TFACTOR);
		s_BackdropTexture->SetColorOperation(0, IdeRenderTexture::TEXTUREOP_SELECTARG1);
		
		s_BackdropTexture->SetAlphaArg(0, 1, IdeRenderTexture::TEXTUREARG_TFACTOR);
		s_BackdropTexture->SetAlphaOperation(0, IdeRenderTexture::TEXTUREOP_SELECTARG1);
		
		s_BackdropTexture->SetColorOperation(1, IdeRenderTexture::TEXTUREOP_DISABLE);
		s_BackdropTexture->SetAlphaOperation(1, IdeRenderTexture::TEXTUREOP_DISABLE);
		
		s_BackdropTexture->SetBitmap(0, NULL);
		
		s_BackdropTexture->SetTextureMinFilter(0, IdeRenderTexture::TEXTUREFILTER_NONE);
		s_BackdropTexture->SetTextureMagFilter(0, IdeRenderTexture::TEXTUREFILTER_NONE);
		s_BackdropTexture->SetTextureMipFilter(0, IdeRenderTexture::TEXTUREFILTER_NONE);
		s_BackdropTexture->Unlock();
	}

	m_Scroll.x = m_Scroll.y = m_Scroll.z = 0;
	m_ClipRect.Left = m_ClipRect.Top = m_ClipRect.Right = m_ClipRect.Bottom = 0;
	m_pParent = NULL;
	m_Depth = 0;
	m_Enabled = deTRUE;
	m_BGColor = 0x00000000;
}

de2DCollection::~de2DCollection()
{
	DestroyAllObjects(deTRUE);

	s_NumCollections--;
	if (s_NumCollections <= 0 && s_CompositeFor2D)
	{
		s_CompositeFor2D->Release();
		s_CompositeFor2D = NULL;

		s_BackdropQuad->Release();
		s_BackdropQuad = NULL;
		
		s_BackdropTexture->Release();
		s_BackdropTexture = NULL;
	}
}

const deRect& de2DCollection::GetRect() const
{
	return m_ClipRect;
}

void de2DCollection::SetRect(deRect & rect)
{
	m_ClipRect = rect;
}

void de2DCollection::SetBGColor(deARGB color)
{
	m_BGColor = color;
}

deARGB de2DCollection::GetBGColor()
{
	return m_BGColor;
}

deFloat de2DCollection::GetDepth() const
{
	return m_Depth;
}

deBoolean de2DCollection::SetDepth(deFloat Depth)
{
	if (Depth <= 0)
		DEBUGRETURN(deFALSE, "SetDepth: only use positive depths");

	m_Depth = Depth;
	if (m_pParent)
	{
		m_pParent->RemoveSubCollection(this);
		m_pParent->AddSubCollection(this);
	}

	return deTRUE;
}

void de2DCollection::SetEnabled(deBoolean Enable)
{
	m_Enabled = Enable;
}

deBoolean de2DCollection::IsEnabled() const
{
	return m_Enabled;
}

void de2DCollection::GetScrollValue(deVec3d & scroll) const
{
	scroll = m_Scroll;
}

void de2DCollection::SetScrollValue(const deVec3d & scroll)
{
	m_Scroll = scroll;
}

deBoolean de2DCollection::AddItem(const de2DObjectUnion & NewItem)
{
	if (NewItem.Object == NULL)
		DEBUGRETURN(deFALSE, "AddItem: trying to add a NULL item");
	
	void *				ptr;
	de2DObjectUnion *	tempobj;
	deFloat				CurrDepth;
	deFloat				Depth;

	// cache the depth of the object-to-be-inserted (insertee)
	if (NewItem.IsObject)
	{
		Depth = NewItem.Object->GetDepth();
	}
	else
	{
		Depth = NewItem.Collection->GetDepth();
	}
	if (Depth <= 0)
		DEBUGRETURN(deFALSE, "AddItem: only use positive depths");
	
	// grab our first object, if there is one
	ptr = m_ChildList.GetNext(NULL, tempobj);
	if (!ptr)
	{	// if there are no objects in the list, just add the insertee
		if (m_ChildList.AddElement(NewItem))
		{
			return deTRUE;
		}
		else
			DEBUGRETURN(deFALSE, "AddItem: failed to add item to internal list");
	}

	// do a depth-based insertion in the list
	while (ptr)
	{
		if (tempobj->IsObject)
		{
			CurrDepth = tempobj->Object->GetDepth();
		}
		else
		{
			CurrDepth = tempobj->Collection->GetDepth();
		}
		// want to sort with closest things last
		// so if the insertee's depth is greater than the current object's, insertee goes in before current
		if (Depth >= CurrDepth)
		{
			if (m_ChildList.AddElementBefore(NewItem, ptr))
			{
				return deTRUE;
			}
			else
				DEBUGRETURN(deFALSE, "AddItem: failed to add item to internal list");
		}

		ptr = m_ChildList.GetNext(ptr, tempobj);
	}

	// the object must have a greater depth than all the others in the list
	if (!m_ChildList.AddElement(NewItem))
		DEBUGRETURN(deFALSE, "AddItem: failed to add item to internal list");

	return deTRUE;
}

deBoolean de2DCollection::AddObject(Ide2DObject * object)
{
	if (object == NULL)
	{
		DEBUGRETURN(deFALSE, "AddObject: trying to add a NULL item");
	}

	de2DObjectUnion		newobj;

	// fill in the object that will be inserted in the list
	newobj.Object = object;
	newobj.IsObject = deTRUE;

	// lay claim to the object
	object->Claim();
	if (AddItem(newobj))
		return deTRUE;

	// only get here if the object wasn't added
	object->Release();
	return deFALSE;
}

deBoolean de2DCollection::AddSubCollection(Ide2DCollection * collection)
{
	if (collection == NULL)
	{
		DEBUGRETURN(deFALSE, "AddSubCollection: trying to add a NULL item");
	}

	de2DObjectUnion		newobj;

	// fill in the object that will be inserted in the list
	newobj.Collection = collection;
	newobj.IsObject = deFALSE;

	// lay claim to the object
	collection->Claim();
	if (AddItem(newobj))
	{
		((de2DCollection*)collection)->SetParent(this);
		return deTRUE;
	}

	// only get here if the object wasn't added
	collection->Release();
	return deFALSE;
}

deBoolean de2DCollection::RemoveObject(Ide2DObject * object)
{
	void *				ptr;
	de2DObjectUnion *	tempobj;

	// just do a linear search in the list comparing pointers
	ptr = m_ChildList.GetNext(NULL, tempobj);
	while (ptr)
	{
		// can only do the compare if it's the right type
		if (tempobj->IsObject)
		{
			// if the pointer matches, remove it!
			if (tempobj->Object == object)
			{
				m_ChildList.RemoveElement(ptr);
				object->Release();
				return deTRUE;
			}
		}

		ptr = m_ChildList.GetNext(ptr, tempobj);
	}

	DEBUGMSG1("RemoveObject: could not find object 0x%.8x in internal list", object);
	return deFALSE;
}

deBoolean de2DCollection::RemoveSubCollection(Ide2DCollection * collection)
{
	void *				ptr;
	de2DObjectUnion *	tempobj;

	// just do a linear search in the list comparing pointers
	ptr = m_ChildList.GetNext(NULL, tempobj);
	while (ptr)
	{
		// can only do the compare if it's the right type
		if (!(tempobj->IsObject))
		{
			// if the pointer matches, remove it!
			if (tempobj->Collection == collection)
			{
				m_ChildList.RemoveElement(ptr);
				((de2DCollection*)collection)->SetParent(NULL);
				collection->Release();
				return deTRUE;
			}
		}

		ptr = m_ChildList.GetNext(ptr, tempobj);
	}

	DEBUGRETURN(deFALSE, "RemoveSubCollection: could not find object in internal list");
}

deBoolean de2DCollection::AdjustObjectDepth(Ide2DObject * object, deFloat Depth)
{
	if (!object)
		DEBUGRETURN(deFALSE, "AdjustObjectDepth: object param was NULL");
	if (Depth <= 0)
		DEBUGRETURN(deFALSE, "AdjustObjectDepth: only use positive depths");

	// don't want RemoveObject to destroy it
	object->Claim();
	
	if (!RemoveObject(object))
		return deFALSE;

	if (!(object->SetDepth(Depth)))
		return deFALSE;

	if (!AddObject(object))
		return deFALSE;

	// release the reference we got with our Claim call
	object->Release();

	return deTRUE;
}

void de2DCollection::SetParent(Ide2DCollection * ParentCollection)
{
	m_pParent = ParentCollection;
}

deBoolean de2DCollection::Set2DViewport(short FullWidth, short FullHeight, const deRect & SubRect, IdeDriver * pDriver)
{
	int Width, Height;
	Width = SubRect.Right - SubRect.Left;
	Height = SubRect.Bottom - SubRect.Top;

	if (Width == 0 && Height == 0)
	{
		Width = FullWidth;
		Height = FullHeight;
	}
	
	// set the "clipping rect"
	if(!pDriver->SetViewport(SubRect.Left, SubRect.Top, Width, Height, 0.0f, 1.0f))
	{
		DEBUGRETURN(deFALSE, "Set2DViewport: IdeDriver::SetViewport() failed");
	}

	//set the driver up for a 2d render
	if(!pDriver->SetOrthoClippingSubRect((deFloat)Width, (deFloat)Height, 0.1f, 1000.0f, deTRUE, SubRect))
	{
		DEBUGRETURN(deFALSE, "Set2DViewport: IdeDriver::SetOrthoClippingSubRect() failed");
	}

	return deTRUE;
}

deBoolean de2DCollection::FillBGColor(IdeDriver * pDriver, const deRect & Rect, const deVec3d & Scroll, deARGB BGColor, IdeRenderTexture* &PrevTexture)
{
	if (BGColor & 0xff000000)
	{
		deTransform BackdropTransform;
		BackdropTransform.ApplyScaling(Rect.Right-Rect.Left, Rect.Bottom-Rect.Top, 1);
		BackdropTransform.Translation = Scroll;
		if (!pDriver->SetWorldTransform(&BackdropTransform))
		{
			DEBUGRETURN(deFALSE, "Render: IdeDriver::SetWorldTransform() failed");
		}
		if (!pDriver->SetRenderState(s_BackdropTexture))
		{
			DEBUGRETURN(deFALSE, "Render: IdeDriver::SetRenderState(s_BackdropTexture) failed");
		}
		if (!pDriver->SetRenderState(IdeDriver::RENDER_TEXTUREFACTOR, BGColor))
		{
			DEBUGRETURN(deFALSE, "Render: IdeDriver::SetRenderState(CurrentBGColor) failed");
		}
		if (!pDriver->Render(s_BackdropQuad, 0))
		{
			DEBUGRETURN(deFALSE, "Render: IdeDriver::Render(s_BackdropQuad) failed");
		}
		PrevTexture = s_BackdropTexture;
	}
	return deTRUE;
}

deBoolean de2DCollection::Render(	IdeDriver * Driver, deDouble DeltaTime, 
									IdeRenderTexture *NullTex, IdeRender::deRenderStats * RenderStats,
									deBoolean /*DoOcclusion*/)
{
//#pragma todo (implement the DoOcclusion param) // perhaps not... not much stuff that is gonna need it

	void					*ptr;
	de2DObjectUnion			*tempobj;
	Ide2DObject				*temp2d;
	deRect					Rect, PrevRect, FullRect;
	deARGB					CurrentBGColor;
	deBoolean				RectSet;
	int						Counter;
	short					FullWidth, FullHeight;
	deTransform				ObjectTransform;
	IdeVertexBuffer			*VBuffer;
	IdeRenderTexture		*Texture;
	IdeRenderTexture		*PrevTexture;
	CollectionState_t		tempstate;
	deTList <de2DObjectUnion>			*CurrentCollectionList;
	deTStack < CollectionState_t >		CollectionStack(deTStack<CollectionState_t>::LIFO_STACK);
	deTList <CollectionState_t>			ToBeReleased;

	const int				NumStates = 11;
	RenderStateValue_t		States[NumStates];
	long					TempStateVal;
	
	States[0].State = IdeDriver::RENDER_ZENABLE;
	States[0].Value = deFALSE;								//disable the Z buffer
	States[1].State = IdeDriver::RENDER_FILLMODE;
	States[1].Value = IdeDriver::FILL_SOLID;				//solid render mode
	States[2].State = IdeDriver::RENDER_ALPHABLENDENABLE;
	States[2].Value = deTRUE;								//alpha blending desired
	States[3].State = IdeDriver::RENDER_ALPHATESTENABLE;
	States[3].Value = deTRUE;								//alpha testing speeds up blending
	States[4].State = IdeDriver::RENDER_ALPHAFUNCTION;
	States[4].Value = IdeDriver::COMPARE_GREATEREQUAL;		//normal alpha function
	States[5].State = IdeDriver::RENDER_ALPHAREF;
	States[5].Value = 0x08;									//a non-zero cutoff
	States[6].State = IdeDriver::RENDER_SOURCEBLEND;
	States[6].Value = IdeDriver::BLEND_SOURCEALPHA;			//src * srcalpha
	States[7].State = IdeDriver::RENDER_DESTBLEND;
	States[7].Value = IdeDriver::BLEND_INVERSESOURCEALPHA;	//dest * (1-srcalpha)
	States[8].State = IdeDriver::RENDER_HWLIGHTINGENABLE;
	States[8].Value = deFALSE;								//no lighting thanks
	States[9].State = IdeDriver::RENDER_CULLMODE;
	States[9].Value = IdeDriver::CULL_NONE;					//no backface culling desired
	States[10].State= IdeDriver::RENDER_FSAAENABLE;
	States[10].Value= deFALSE;								//no FSAA for 2D stuff
	States[10].State= IdeDriver::RENDER_FOGENABLE;
	States[10].Value= 0;									//no fog for 2D stuff

	//loop thru and get the already-set states
	for(Counter = 0; Counter < NumStates; Counter++)
	{
		TempStateVal = Driver->GetRenderState(States[Counter].State);
		if(!Driver->SetRenderState(States[Counter].State, States[Counter].Value))
		{
			DEBUGMSG1("Failed to Set RenderState: %i", Counter);
			DEBUGRETURN(deFALSE, "Render: SetRenderState() failed");
		}
		States[Counter].Value = TempStateVal;
	}
		
	{
		deTransform position; // default identity matrix
		if (!Driver->SetWorldTransform(&position))
			DEBUGRETURN(deFALSE, "Render: IdeDriver::SetWorldTransform() failed");
		if (!Driver->SetCameraTransform(&position))
			DEBUGRETURN(deFALSE, "Render: IdeDriver::SetCameraTransform() failed");
	}

	if (!Driver->UpdateVertexBuffer(s_BackdropQuad))
	{
		DEBUGRETURN(deFALSE, "Render: IdeDriver::UpdateVertexBuffer(s_BackdropQuad) failed");
	}
	if(!Driver->SetRenderState(NullTex))
	{
		DEBUGRETURN(deFALSE, "Render: IdeDriver::SetRenderState(NullTex) failed - before loop");
	}
	PrevTexture = NULL;

	// set the first-time viewport
	FullRect = Driver->GetDisplayRect();
	FullWidth = (short)(FullRect.Right - FullRect.Left);
	FullHeight = (short)(FullRect.Bottom - FullRect.Top);
	PrevRect.Top = PrevRect.Bottom = PrevRect.Left = PrevRect.Right = -1;

	// start off with the base collection
	tempstate.List = &m_ChildList;
	tempstate.Entry = NULL;
	tempstate.ViewRect = m_ClipRect;
	tempstate.Scroll = m_Scroll;
	tempstate.BGColor = m_BGColor;
	CollectionStack.Push(tempstate);

	Rect = m_ClipRect;
	PrevRect = m_ClipRect;
	if (!Set2DViewport(FullWidth, FullHeight, Rect, Driver))
		DEBUGRETURN(deFALSE, "Render: Set2DViewport() failed");

	if (!FillBGColor(Driver, Rect, deZeroVec, m_BGColor, PrevTexture))
		return deFALSE;

	while (CollectionStack.Length() > 0)
	{
		CollectionStack.Pop(tempstate);
		CurrentCollectionList = tempstate.List;
		ptr = tempstate.Entry;
		Rect = tempstate.ViewRect;
		ObjectTransform.Translation = tempstate.Scroll;
		CurrentBGColor = tempstate.BGColor;
		RectSet = deFALSE;
		
		// this goes in order of farthest to nearest
		ptr = CurrentCollectionList->GetNext(ptr, tempobj);
		while (ptr)
		{
			if (tempobj->IsObject)
			{
				// if it's an actual renderable object, we shall render it!
				temp2d = tempobj->Object;
				if (temp2d->Update(DeltaTime))
				{
					// if the view-rect has been changed but not set, need to set it
					if (!RectSet)
					{
						// if it isn't different, don't send any changes
						if (Rect.Left != PrevRect.Left || Rect.Right != PrevRect.Right || 
							Rect.Top != PrevRect.Top || Rect.Bottom != PrevRect.Bottom)
						{
							// remember what this was set to, and set the new one
							PrevRect = Rect;
							if (!Set2DViewport(FullWidth, FullHeight, Rect, Driver))
								DEBUGRETURN(deFALSE, "Render: Set2DViewport() failed");
						}
						
						if (!Driver->SetWorldTransform(&ObjectTransform))
						{
							DEBUGRETURN(deFALSE, "Render: IdeDriver::SetWorldTransform(&ObjectTransform) failed");
						}
						// now the rect has been set
						RectSet = deTRUE;
					}
					
					Texture = temp2d->GetTexture();
					VBuffer = temp2d->GetVBuffer();
					//set the render state for the driver
					if (Texture != PrevTexture)
					{
						if (Texture != NULL)
						{
							if(!Driver->SetRenderState(Texture))
							{
								DEBUGRETURN(deFALSE, "Render: IdeDriver::SetRenderState(Texture) failed");
							}
							RenderStats->TexturesTotal++;
						}
						else
						{
							if(!Driver->SetRenderState(NullTex))
							{
								DEBUGRETURN(deFALSE, "Render: IdeDriver::SetRenderState(NullTex) failed");
							}
						}
						RenderStats->TexturesChanged++;
						PrevTexture = Texture;
					}
					
					//tell the driver to add the vertex buffer
					if(!Driver->UpdateVertexBuffer(VBuffer))
					{
						DEBUGRETURN(deFALSE, "Render: IdeDriver::UpdateVertexBuffer() failed");
					}

					//render it
					if(!Driver->Render(VBuffer, 0))
					{
						DEBUGRETURN(deFALSE, "Render: IdeDriver::Render() failed");
					}
					
					RenderStats->PrimitiveRenders += VBuffer->GetTriCount();
					RenderStats->VertexRenders += VBuffer->GetVertexCount();
					RenderStats->IndexRenders += VBuffer->GetIndexCount();
					RenderStats->VBufferUpdates++;
					RenderStats->VBufferRenders++;
					if (VBuffer->GetVBufferType() == IdeVertexBuffer::BUFFERTYPE_SUB)
						RenderStats->VBufferSubRenders++;
				}
				else
					VBuffer = NULL;
				
				if (temp2d->HasExpired())
				{
					tempstate.List = CurrentCollectionList;
					tempstate.Entry = ptr;
					ToBeReleased.AddElement(tempstate);
				}
			}
			else
			{
				// no need to do anything if the collection isn't active
				if (tempobj->Collection->IsEnabled())
				{
					// push our current state onto the stack
					tempstate.List = CurrentCollectionList;
					tempstate.Entry = ptr;
					tempstate.ViewRect = Rect;
#pragma todo (verify that this scrolling business works)
					tempstate.Scroll = ObjectTransform.Translation;
					tempstate.BGColor = CurrentBGColor;
					CollectionStack.Push(tempstate);
					
					// grab the list of the sub-collection
					CurrentCollectionList = &( ((de2DCollection*)(tempobj->Collection))->m_ChildList );
					// start the list at the beginning
					ptr = NULL;
					
					// grab the rect and offset & clip it to the parent
					deRect TempRect;
					TempRect = tempobj->Collection->GetRect();
					TempRect.Top += (long)ObjectTransform.Translation.y;
					TempRect.Left += (long)ObjectTransform.Translation.x;
					TempRect.Right += (long)ObjectTransform.Translation.x;
					TempRect.Bottom += (long)ObjectTransform.Translation.y;
					
					tempobj->Collection->GetScrollValue(ObjectTransform.Translation);

					if (TempRect.Left < 0)
						ObjectTransform.Translation.x += TempRect.Left;
					if (TempRect.Top < 0)
						ObjectTransform.Translation.y += TempRect.Top;

					Rect.Bottom = min(Rect.Bottom, Rect.Top + TempRect.Bottom);
					Rect.Right = min(Rect.Right, Rect.Left + TempRect.Right);
					Rect.Left = max(Rect.Left, Rect.Left + TempRect.Left);
					Rect.Top = max(Rect.Top, Rect.Top + TempRect.Top);
					// mention that it needs to be set before rendering things
					RectSet = deFALSE;

					// if it isn't different, don't send any changes
					if (Rect.Left != PrevRect.Left || Rect.Right != PrevRect.Right || 
						Rect.Top != PrevRect.Top || Rect.Bottom != PrevRect.Bottom)
					{
						// remember what this was set to, and set the new one
						PrevRect = Rect;
						if (!Set2DViewport(FullWidth, FullHeight, Rect, Driver))
							DEBUGRETURN(deFALSE, "Render: Set2DViewport() failed");
					}

					// fill with BG color
					CurrentBGColor = tempobj->Collection->GetBGColor();
					if (!FillBGColor(Driver, TempRect, ObjectTransform.Translation, CurrentBGColor, PrevTexture))
						return deFALSE;
				}
			}

			ptr = CurrentCollectionList->GetNext(ptr, tempobj);
		}
	}

	ptr = ToBeReleased.GetNext(NULL, tempstate);
	while (ptr)
	{
		// grab the data
		tempstate.List->GetData(tempstate.Entry, tempobj);
		temp2d = tempobj->Object;

		// release it
		VBuffer = temp2d->GetVBuffer();
		if (VBuffer)
			Driver->RemoveVertexBuffer(VBuffer);
		temp2d->Release();

		// remove from the list
		tempstate.List->RemoveElement(tempstate.Entry);

		// get next
		ptr = ToBeReleased.GetNext(ptr, tempstate);
	}

	// if the function returns deFALSE, these states will not be restored

	//loop thru and set the states back for the driver as we are nice
	for(Counter = 0; Counter < NumStates; Counter++)
		Driver->SetRenderState(States[Counter].State, States[Counter].Value);

	return deTRUE;
}

void de2DCollection::DestroyAllObjects(deBoolean SubCollectionsAlso)
{
	de2DObjectUnion		*tempobj;
	void				*ptr;
	
	ptr = m_ChildList.GetNext(NULL, tempobj);
	while (ptr)
	{
		if (tempobj)
		{
			if (tempobj->IsObject)
			{
				tempobj->Object->Release();
			}
			else
			{
				if (SubCollectionsAlso)
				{
					tempobj->Collection->DestroyAllObjects(deTRUE);
				}
				tempobj->Collection->Release();
			}
		}

		ptr = m_ChildList.RemoveElementGetNext(ptr, tempobj);
	}
}


