/*
todo standards
*/

#include "RSPiX.h"
#include "pvr.h"

/// <summary>
/// Constructor
/// </summary>
PVR::cRenderMgr::cRenderMgr()
{
	int index = 0;

	for (index = 0; index < cRenderMgr::NUM_OPAQUE_LAYERS; index++)
	{
		opaqueList[index] = new cDisplayListQueue();
	}

	for (index = 0; index < cRenderMgr::NUM_PUNCHTHRU_LAYERS; index++)
	{
		punchThruList[index] = new cDisplayListQueue();
	}

	for (index = 0; index < cRenderMgr::NUM_TRANSPARENCY_LAYERS; index++)
	{
		transparencyQueueList[index] = new cDisplayListQueue();
	}

	m_pGuiTextString = new cGuiTextString();

	m_bLoadedCtxOp = true;
	m_bLoadedCtxTr = true;

	pvr_poly_cxt_col(&m_cxtFlatOpCol, PVR_LIST_OP_POLY);
	pvr_poly_compile(&m_hdrFlatOpCol, &m_cxtFlatOpCol);
	pvr_poly_cxt_col(&m_cxtFlatTrCol, PVR_LIST_TR_POLY);
	pvr_poly_compile(&m_hdrFlatTrCol, &m_cxtFlatTrCol);		
}

///
// todo
///
void PVR::cRenderMgr::DrawGuiText()
{	
	m_pGuiTextString->Print(0, 0);
}

/// <summary>
/// Clears the screen and optionally the draw queues.
/// </summary>
void PVR::cRenderMgr::ClearScreen(bool bFlushQueues)
{
	if (bFlushQueues)
	{
		FlushBatchedDrawQueues();
	}
	
	pvr_set_bg_color(0, 0, 0);
	DrawQuad(
		(uint32_t)PVR_PACK_COLOR(1.0f, 0.0f, 0.0f, 0.0f),
		int16_t(0),
		int16_t(0),
		640,
		480,
		cRenderMgr::LayerHud);		
}

/// <summary>
///
/// </summary>
void PVR::cRenderMgr::RenderBatchedFrame()
{
	bool isEmpty = true;
	int index = 0;
	vector<cBatchedQuad *>::iterator item;

	/**
	 * Begin opaque poly list
	 */

	pvr_wait_ready();
	pvr_scene_begin();
	pvr_list_begin(PVR_LIST_OP_POLY);

	for (index = 0; index < NUM_OPAQUE_LAYERS; index++)
	{
		cDisplayListQueue *queue = opaqueList[index];

		if (queue->m_vQuads.size() == 0)
		{
			continue;
		}

		for (item = queue->m_vQuads.begin(); item != queue->m_vQuads.end(); ++item)
		{
			int count = 0;			
			pvr_poly_cxt_t cxt;
			pvr_poly_hdr_t hdr;
			cBatchedQuad *it = (*item);

			if (it->m_pTexture != nullptr)
			{
				// Textured poly.
				cTexture *pTexture;

				pTexture = it->m_pTexture;
				if (pTexture->m_ucImageType == cTexture::RGB16)
				{
					pvr_poly_cxt_txr(
						&cxt,
						PVR_LIST_OP_POLY,
						PVR_TXRFMT_RGB565 | PVR_TXRFMT_NONTWIDDLED,
						pTexture->m_szTextureWidth,
						pTexture->m_szTextureHeight,
						pTexture->m_pTextureTiles[0]->m_pPvrTexture,
						PVR_FILTER_BILINEAR);
				}
				else
				{
					cTextureTile *pTile = pTexture->m_pTextureTiles[it->m_ucTileId];
					if (pTile->m_bIsVQ)
					{
						pvr_poly_cxt_txr(
							&cxt,
							PVR_LIST_OP_POLY,
							PVR_TXRFMT_RGB565 | PVR_TXRFMT_VQ_ENABLE | PVR_TXRFMT_TWIDDLED,
							pTile->m_szTileWidth,
							pTile->m_szTileHeight,
							pTile->m_pPvrTexture,
							PVR_FILTER_BILINEAR);
					}
					else
					{
						pvr_poly_cxt_txr(
							&cxt,
							PVR_LIST_OP_POLY,
							((pTile->m_ucImageType == cTexture::PAL4) ? PVR_TXRFMT_PAL4BPP : PVR_TXRFMT_PAL8BPP) | PVR_TXRFMT_8BPP_PAL(pTexture->m_ucPaletteNum),
							pTile->m_szTileWidth,
							pTile->m_szTileHeight,
							pTile->m_pPvrTexture,
							PVR_FILTER_BILINEAR);
					}
				}

				cxt.txr.uv_clamp = PVR_UVCLAMP_UV;
			}
			else
			{
				// Flat-shaded poly
				pvr_poly_cxt_col(&cxt, PVR_LIST_OP_POLY);
			}

			pvr_poly_compile(&hdr, &cxt);
			pvr_prim(&hdr, sizeof(hdr));

			// Top left
			pvr_vertex_t p;
			p.x = it->m_sX;
			p.y = it->m_sY;
			p.z = it->m_sLayer;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fStartU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fStartV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));

			// top right			
			p.x = it->m_sX + it->m_sW;
			p.y = it->m_sY;
			p.z = it->m_sLayer;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fEndU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fStartV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));
		
			// bottom left
			p.x = it->m_sX;
			p.y = it->m_sY + it->m_sH;
			p.z = it->m_sLayer;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fStartU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fEndV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));

			// bottom right
			p.x = it->m_sX + it->m_sW;
			p.y = it->m_sY + it->m_sH;
			p.z = it->m_sLayer;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fEndU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fEndV : 0.f;			
			p.flags = PVR_CMD_VERTEX_EOL;
			pvr_prim(&p, sizeof(p));
		}
	}

	pvr_list_finish();

	/**
	 * Begin transparency list
	 */

	pvr_list_begin(PVR_LIST_TR_POLY);

	for (index = 0; index < NUM_TRANSPARENCY_LAYERS; index++)
	{
		cDisplayListQueue *queue = transparencyQueueList[index];

		if (queue->m_vQuads.size() == 0)
		{
			continue;
		}

		for (item = queue->m_vQuads.begin(); item != queue->m_vQuads.end(); ++item)
		{
			int count = 0;
			pvr_vertex_t vert;
			pvr_poly_cxt_t cxt;
			pvr_poly_hdr_t hdr;
			cBatchedQuad *it = (*item);

			if (it->m_pTexture != nullptr)
			{
				// Textured poly.
				cTexture *pTexture;
				cTextureTile *pTile = it->m_pTexture->m_pTextureTiles[it->m_ucTileId];

				if (it->m_pTexture != nullptr && pTile != nullptr)
				{
					pTexture = it->m_pTexture;
					if (pTexture->m_ucImageType == cTexture::RGB16)
					{
						if (pTile->m_bIsARGB4 || pTile->m_bIsARGB1)
						{
							pvr_poly_cxt_txr(
								&cxt,
								PVR_LIST_TR_POLY,
								pTile->m_bIsARGB4 ? PVR_TXRFMT_ARGB4444 : PVR_TXRFMT_ARGB1555 | PVR_TXRFMT_NONTWIDDLED,
								pTexture->m_szTextureWidth,
								pTexture->m_szTextureHeight,
								pTexture->m_pTextureTiles[0]->m_pPvrTexture,
								PVR_FILTER_BILINEAR);
						}
						else
						{
							pvr_poly_cxt_txr(
								&cxt,
								PVR_LIST_TR_POLY,
								PVR_TXRFMT_RGB565 | PVR_TXRFMT_NONTWIDDLED,
								pTexture->m_szTextureWidth,
								pTexture->m_szTextureHeight,
								pTexture->m_pTextureTiles[0]->m_pPvrTexture,
								PVR_FILTER_BILINEAR);
						}
					}
					else
					{
						if (pTile->m_bIsVQ)
						{
							pvr_poly_cxt_txr(
								&cxt,
								PVR_LIST_TR_POLY,
								((pTile->m_bIsARGB4) ? PVR_TXRFMT_ARGB4444 : PVR_TXRFMT_RGB565) | PVR_TXRFMT_VQ_ENABLE | PVR_TXRFMT_TWIDDLED,
								pTile->m_szTileWidth,
								pTile->m_szTileHeight,
								pTile->m_pPvrTexture,
								PVR_FILTER_BILINEAR);
						}
						else
						{
							pvr_poly_cxt_txr(
								&cxt,
								PVR_LIST_TR_POLY,
								((pTile->m_ucImageType == cTexture::PAL4) ? PVR_TXRFMT_PAL4BPP : PVR_TXRFMT_PAL8BPP) | PVR_TXRFMT_8BPP_PAL(pTexture->m_ucPaletteNum),
								pTile->m_szTileWidth,
								pTile->m_szTileHeight,
								pTile->m_pPvrTexture,
								PVR_FILTER_BILINEAR);
						}
					}

					cxt.txr.uv_clamp = PVR_UVCLAMP_UV;
				}			
			}
			else
			{
				// Flat-shaded poly
				pvr_poly_cxt_col(&cxt, PVR_LIST_TR_POLY);
			}

			pvr_poly_compile(&hdr, &cxt);
			pvr_prim(&hdr, sizeof(hdr));

			// Top left
			pvr_vertex_t p;
			p.x = it->m_sX;
			p.y = it->m_sY;
			p.z = it->m_sLayer + 1;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fStartU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fStartV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));

			// top right			
			p.x = it->m_sX + it->m_sW;
			p.y = it->m_sY;
			p.z = it->m_sLayer + 1;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fEndU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fStartV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));
		
			// bottom left
			p.x = it->m_sX;
			p.y = it->m_sY + it->m_sH;
			p.z = it->m_sLayer + 1;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fStartU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fEndV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));

			// bottom right
			p.x = it->m_sX + it->m_sW;
			p.y = it->m_sY + it->m_sH;
			p.z = it->m_sLayer + 1;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fEndU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fEndV : 0.f;			
			p.flags = PVR_CMD_VERTEX_EOL;
			pvr_prim(&p, sizeof(p));			
		}
	}

	pvr_list_finish();

	pvr_list_begin(PVR_LIST_PT_POLY);

	for (index = 0; index < NUM_PUNCHTHRU_LAYERS; index++)
	{
		cDisplayListQueue *queue = punchThruList[index];

		if (queue->m_vQuads.size() == 0)
		{
			continue;
		}

		for (item = queue->m_vQuads.begin(); item != queue->m_vQuads.end(); ++item)
		{
			int count = 0;
			pvr_vertex_t vert;
			pvr_poly_cxt_t cxt;
			pvr_poly_hdr_t hdr;
			cBatchedQuad *it = (*item);

			if (it->m_pTexture != nullptr)
			{
				// Textured poly.
				cTexture *pTexture;

				if (it->m_pTexture != nullptr)
				{
					pTexture = it->m_pTexture;

					if (pTexture->m_ucImageType == cTexture::RGB16)
					{
						pvr_poly_cxt_txr(
							&cxt,
							PVR_LIST_PT_POLY,
							PVR_TXRFMT_ARGB1555 | PVR_TXRFMT_NONTWIDDLED,
							pTexture->m_szTextureWidth,
							pTexture->m_szTextureHeight,
							pTexture->m_pTextureTiles[0]->m_pPvrTexture,
							PVR_FILTER_BILINEAR);
					}
					else
					{
						pvr_poly_cxt_txr(
							&cxt,
							PVR_LIST_PT_POLY,
							((pTexture->m_ucImageType == cTexture::PAL4) ? PVR_TXRFMT_PAL4BPP : PVR_TXRFMT_PAL8BPP) | PVR_TXRFMT_8BPP_PAL(pTexture->m_ucPaletteNum),
							pTexture->m_szTextureWidth,
							pTexture->m_szTextureHeight,
							pTexture->m_pTextureTiles[0]->m_pPvrTexture,
							PVR_FILTER_BILINEAR);

						cxt.txr.uv_clamp = PVR_UVCLAMP_UV;
					}
				}
			}
			else
			{
				// Flat-shaded poly
				pvr_poly_cxt_col(&cxt, PVR_LIST_PT_POLY);
			}

			pvr_poly_compile(&hdr, &cxt);
			pvr_prim(&hdr, sizeof(hdr));

					// Top left
			pvr_vertex_t p;
			p.x = it->m_sX;
			p.y = it->m_sY;
			p.z = it->m_sLayer;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fStartU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fStartV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));

			// top right			
			p.x = it->m_sX + it->m_sW;
			p.y = it->m_sY;
			p.z = it->m_sLayer;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fEndU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fStartV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));
		
			// bottom left
			p.x = it->m_sX;
			p.y = it->m_sY + it->m_sH;
			p.z = it->m_sLayer;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fStartU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fEndV : 0.f;			
			p.flags = PVR_CMD_VERTEX;
			pvr_prim(&p, sizeof(p));

			// bottom right
			p.x = it->m_sX + it->m_sW;
			p.y = it->m_sY + it->m_sH;
			p.z = it->m_sLayer;			
			p.argb = it->m_ulColour;
			p.oargb = 0;
			p.u = (it->m_pTexture != nullptr) ? it->m_fEndU : 0.f;
			p.v = (it->m_pTexture != nullptr) ? it->m_fEndV : 0.f;			
			p.flags = PVR_CMD_VERTEX_EOL;
			pvr_prim(&p, sizeof(p));	
		}
	}

	pvr_list_finish();

	//////////////////////////////////////////
	/// /////////

	pvr_scene_finish();
}

/// <summary>
///
/// </summary>
void PVR::cRenderMgr::FlushBatchedDrawQueue(int16_t index)
{
	cDisplayListQueue *queue = GetQueue(index);
	if (!queue->IsLocked())
	{
		queue->Clear();
	}
}

/// <summary>
///
/// </summary>
void PVR::cRenderMgr::FlushBatchedDrawQueues()
{
	for (int index = 0; index < (NUM_OPAQUE_LAYERS + NUM_PUNCHTHRU_LAYERS + NUM_TRANSPARENCY_LAYERS); index++)
	{
		FlushBatchedDrawQueue(index);
	}
}

/// <summary>
/// Draws a textured quad filled using a given segment of a texture.
/// </summary>
void PVR::cRenderMgr::DrawQuad(cTexture *pTexture, uint8_t ucTileId, float fStartU, float fStartV, float fEndU, float fEndV, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t sLayer, float fOpacity, float fR, float fG, float fB)
{
	cTextureTile *pTile = pTexture->m_pTextureTiles[ucTileId];

	uint32_t colour = PVR_PACK_COLOR(fOpacity, fR * m_fGlobalRScale, fG * m_fGlobalGScale, fB * m_fGlobalBScale);

	if (m_szDrawMode == Batched)
	{	
		cBatchedQuad *quad = new cBatchedQuad(
			sX,	sY,
			sW,	sH,
			pTexture, ucTileId,
			fStartU, fStartV, 
			fEndU, fEndV, 
			colour, sLayer + 1);
		
		cDisplayListQueue *queue = GetQueue(sLayer, fOpacity);
		queue->Add(quad);
	}	
}

/// <summary>
/// Draws a textured quad, scaled to the given width & height.
/// </summary>
void PVR::cRenderMgr::DrawQuad(cTexture *pTexture, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t layer, float opacity, float r, float g, float b)
{
	uint16_t tileCount = pTexture->m_sNumTiles;

	if (tileCount == 1)
	{
		cTextureTile *pTile = pTexture->m_pTextureTiles[0];
		DrawTileScaled(pTile, sX, sY, sW, sH, layer, opacity, r, g, b);
	}
	else if (tileCount > 1)
	{
		for (int index = 0; index < tileCount; index++)
		{
			cTextureTile *pTile = pTexture->m_pTextureTiles[index];
			int16_t x = sX;
			int16_t y = sY;
			int16_t row = (int)(index / (pTexture->m_szTextureWidth / TEXTURE_GRID_SIZE));
			int16_t column = index % (pTexture->m_szTextureWidth / TEXTURE_GRID_SIZE);

			x += column * TEXTURE_GRID_SIZE;
			y += row * TEXTURE_GRID_SIZE;

			DrawTile(pTile, x, y, layer, opacity, r * m_fGlobalRScale, g * m_fGlobalGScale, b * m_fGlobalBScale);
		}
	}
}

/// <summary>
///	Draws a flat-shaded quad filled with the given colour.
/// </summary>
void PVR::cRenderMgr::DrawQuad(uint32_t colour, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t layer)
{
	if (m_szDrawMode == cRenderMgr::Batched)
	{				
		cBatchedQuad *quad = new cBatchedQuad(
			sX,	sY,
			sW,	sH,		
			colour, layer + 1);

		cDisplayListQueue *queue = GetQueue(layer);
		queue->Add(quad);
	}	
}

/// <summary>
///	Draws a flat-shaded quad filled with the given colour.
/// </summary>
void PVR::cRenderMgr::DrawQuad(uint32_t colour, float sX, float sY, uint16_t sW, uint16_t sH, int16_t layer)
{			
	switch (m_szDrawMode)
	{
		case DirectOpaque:		
			pvr_prim(&m_hdrFlatOpCol, sizeof(m_hdrFlatOpCol));
		break;
		case DirectTransparent:			
			pvr_prim(&m_hdrFlatTrCol, sizeof(m_hdrFlatTrCol));
		break;
	}

	// Hack to ensure 2D & 3D objects are layered correctly
	float sceneZ = (layer + 1) * LAYER_SCALE;

	pvr_dr_state_t state;
	pvr_vertex_t *vert;
	pvr_dr_init(state);

	// top left 0,0
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = sX;
	vert->y = sY;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// top right
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = sX + sW;
	vert->y = sY;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// bottom left
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = sX;
	vert->y = sY + sH;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// bottom right
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX_EOL;
	vert->x = sX + sW;
	vert->y = sY + sH;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);	
}

///////////////////////////////////



/// <summary>
///	Draws a flat-shaded quad filled with the given colour.
/// </summary>
// todo: floats
void PVR::cRenderMgr::DrawQuadDirect(uint32_t colour, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t layer)
{	
	switch (m_szDrawMode)
	{
		case DirectOpaque:					
			pvr_prim(&m_hdrFlatOpCol, sizeof(m_hdrFlatOpCol));
		break;
		case DirectTransparent:			
			pvr_prim(&m_hdrFlatTrCol, sizeof(m_hdrFlatTrCol));
		break;
	}
	
	// Hack to ensure 2D & 3D objects are layered correctly
	float sceneZ = (layer + 1) * LAYER_SCALE;

	pvr_dr_state_t state;
	pvr_vertex_t *vert;
	pvr_dr_init(state);

	// top left 0,0
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = sX;
	vert->y = sY;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// top right
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = sX + sW;
	vert->y = sY;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// bottom left
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = sX;
	vert->y = sY + sH;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// bottom right
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX_EOL;
	vert->x = sX + sW;
	vert->y = sY + sH;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);
}

/////////////////////////////////

/// <summary>
///
/// </summary>
PVR::cDisplayListQueue *PVR::cRenderMgr::GetQueue(int16_t index, float opacity)
{
	cDisplayListQueue *queue;

	switch (index)
	{
	case cRenderMgr::LayerBg:
		queue = opaqueList[0];
		break;

	case cRenderMgr::LayerSprite1:
		queue = punchThruList[0];

		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[1];
		}

		break;

	case cRenderMgr::LayerOpaque1:
		queue = transparencyQueueList[0];
		break;

	case cRenderMgr::LayerSprite2:
		queue = punchThruList[1];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[1];
		}
		break;

	case cRenderMgr::LayerAlpha1:
		queue = transparencyQueueList[1];
		break;

	case cRenderMgr::LayerSprite3:
		queue = punchThruList[2];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[3];
		}

		break;

	case cRenderMgr::LayerOpaque2:
		queue = transparencyQueueList[2];
		break;

	case cRenderMgr::LayerSprite4:
		queue = punchThruList[3];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[3];
		}

		break;

	case cRenderMgr::LayerAlpha2:
		queue = transparencyQueueList[3];
		break;

	case cRenderMgr::LayerSprite5:
		queue = punchThruList[4];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[5];
		}

		break;

	case cRenderMgr::LayerOpaque3:
		queue = transparencyQueueList[4];
		break;

	case cRenderMgr::LayerSprite6:
		queue = punchThruList[5];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[5];
		}

		break;

	case cRenderMgr::LayerAlpha3:
		queue = transparencyQueueList[5];
		break;

	case cRenderMgr::LayerSprite7:
		queue = punchThruList[6];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[7];
		}

		break;

	case cRenderMgr::LayerOpaque4:
		queue = transparencyQueueList[6];

		break;

	case cRenderMgr::LayerSprite8:
		queue = punchThruList[7];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[7];
		}

		break;

	case cRenderMgr::LayerAlpha4:
		queue = transparencyQueueList[7];
		break;

	case cRenderMgr::LayerSprite9:
		queue = punchThruList[8];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[9];
		}

		break;

	case cRenderMgr::LayerOpaque5:

		queue = transparencyQueueList[8];
		break;

	case cRenderMgr::LayerSprite10:
		queue = punchThruList[9];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[9];
		}

		break;

	case cRenderMgr::LayerAlpha5:
		queue = transparencyQueueList[9];
		break;

	case cRenderMgr::LayerSprite11:
		queue = punchThruList[10];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[11];
		}

		break;

	case cRenderMgr::LayerOpaque6:

		queue = transparencyQueueList[10];
		break;

	case cRenderMgr::LayerSprite12:
		queue = punchThruList[11];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[11];
		}

		break;

	case cRenderMgr::LayerAlpha6:
		queue = transparencyQueueList[11];
		break;

	case cRenderMgr::LayerSprite13:
		queue = punchThruList[12];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[13];
		}

		break;

	case cRenderMgr::LayerOpaque7:
		queue = transparencyQueueList[12];
		break;

	case cRenderMgr::LayerSprite14:
		queue = punchThruList[13];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[13];
		}

		break;

	case cRenderMgr::LayerAlpha7:
		queue = transparencyQueueList[13];
		break;

	case cRenderMgr::LayerSprite15:
		queue = punchThruList[14];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[15];
		}

		break;

	case cRenderMgr::LayerOpaque8:
		queue = transparencyQueueList[14];
		break;

	case cRenderMgr::LayerSprite16:
		queue = punchThruList[15];
		// todo: clean up
		if (opacity < 1.0f)
		{
			queue = transparencyQueueList[15];
		}

		break;

	case cRenderMgr::LayerHud:
		queue = transparencyQueueList[15];
		break;

	default:	
		break;
	}

	return queue;
}

/// <summary>
/// Draws a full texture tile using its native dimensions.
/// </summary>
void PVR::cRenderMgr::DrawTile(cTextureTile *pTile, int16_t sX, int16_t sY, int16_t layer, float opacity, float r, float g, float b)
{
	uint16_t tileWidth = pTile->m_szTileWidth;
	uint16_t tileHeight = pTile->m_szTileHeight;
	uint16_t imageWidth = pTile->m_szImageWidth;
	uint16_t imageHeight = pTile->m_szImageHeight;
	uint32_t colour = PVR_PACK_COLOR(opacity, r * m_fGlobalRScale, g * m_fGlobalGScale, b * m_fGlobalBScale);

	if (m_szDrawMode == Batched)
	{		
		float adjustedMaxU = (1.0 / tileWidth) * imageWidth;
		float adjustedMaxV = (1.0 / tileHeight) * imageHeight;
		cBatchedQuad *quad = new cBatchedQuad(
			sX,	sY,
			imageWidth,	imageHeight,
			pTile->m_pParentTexture, pTile->m_szId,
			0.f, 0.f, 
			adjustedMaxU, adjustedMaxV,
			colour, layer + 1);

		cDisplayListQueue *queue = GetQueue(layer, opacity);

		queue->Add(quad);
	}	
}

/// <summary>
/// Draws a full texture tile scaled to the given dimensions.
/// </summary>
void PVR::cRenderMgr::DrawTileScaled(cTextureTile *pTile, int16_t sX, int16_t sY, int16_t sW, int16_t sH, int16_t layer, float opacity, float r, float g, float b)
{

	uint32_t colour = PVR_PACK_COLOR(opacity, r * m_fGlobalRScale, g * m_fGlobalGScale, b * m_fGlobalBScale);

	if (m_szDrawMode == Batched)
	{		
		uint16_t tileWidth = pTile->m_szTileWidth;
		uint16_t tileHeight = pTile->m_szTileHeight;
		uint16_t imageWidth = pTile->m_szImageWidth;
		uint16_t imageHeight = pTile->m_szImageHeight;

		// todo: clean up

		float adjustedMaxU = (1.0 / tileWidth) * imageWidth;
		float adjustedMaxV = (1.0 / tileHeight) * imageHeight;

		cBatchedQuad *quad = new cBatchedQuad(
			sX,	sY,
			sW,	sH,
			pTile->m_pParentTexture, pTile->m_szId,
			0.f, 0.f, 
			adjustedMaxU, adjustedMaxV,
			colour, layer + 1);

		cDisplayListQueue *queue = GetQueue(layer, opacity);

		queue->Add(quad);
	}	
}

void PVR::cRenderMgr::DrawDebugQuad(uint16_t colour, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t layer)
{
	uint32_t col;
	switch (colour)
	{
	case 0:
		col = (uint32_t)PVR_PACK_COLOR(0.25f, 0.0f, 1.0f, 1.0f);
		break;
	case 1:
		col = PACK_ARGB8888(64, 128, 0, 0);
		break;
	case 2:
		col = PACK_ARGB8888(64, 0, 128, 0);
		break;
	case 3:
		col = PACK_ARGB8888(64, 0, 0, 128);
		break;
	}

	switch (m_szDrawMode)
	{
	case Batched:
		// todo: magic number
		DrawQuad(col, sX, sY, sW, sH, layer);
	break;
	case DirectOpaque:
		// todo: magic number
		DrawQuadDirect(col, sX, sY, sW, sH, layer);
		break;
	case DirectTransparent:
		// todo: magic number
		DrawQuadDirect(col, sX, sY, sW, sH, layer);
		break;
	}
}

void PVR::cRenderMgr::DrawLine2D(uint32_t colour, float x1, float y1, float x2, float y2, uint16_t layer, float opacity, uint8_t thickness)
{
	switch (m_szDrawMode)
	{
	case DirectOpaque:		
		pvr_prim(&m_hdrFlatOpCol, sizeof(m_hdrFlatOpCol));
		break;
	case DirectTransparent:		
		pvr_prim(&m_hdrFlatTrCol, sizeof(m_hdrFlatTrCol));
		break;
	}

	// Hack to ensure 2D & 3D objects are layered correctly
	float sceneZ = ((opacity < 1.0f) ? (float(layer) + 1.99f) * LAYER_SCALE : (layer + 1) * LAYER_SCALE);

	pvr_dr_state_t state;
	pvr_vertex_t *vert;
	pvr_dr_init(state);

	// top left 0,0
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = x1;
	vert->y = y1;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// top right
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = x2;
	vert->y = y2;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// bottom left
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX;
	vert->x = x1 + thickness;
	vert->y = y1;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);

	// bottom right
	vert = pvr_dr_target(state);
	vert->flags = PVR_CMD_VERTEX_EOL;
	vert->x = x2 + thickness;	
	vert->y = y2;
	vert->z = sceneZ;
	vert->argb = colour;
	pvr_dr_commit(vert);	
}

void PVR::cRenderMgr::DrawTiledQuad(cTexture *pTexture, int16_t sX, int16_t sY, RRect *clip, float opacity, float r, float g, float b)
{
	int index = 0;

	for (index = 0; index < pTexture->m_sNumTiles; index++)
	{
		cTextureTile *pTile = pTexture->m_pTextureTiles[index];
		if (pTile->m_bIsVQ)
		{
			int16_t row = (int)(index / (pTexture->m_szTextureWidth / TEXTURE_GRID_SIZE));
			int16_t column = index % (pTexture->m_szTextureWidth / TEXTURE_GRID_SIZE);
			float x = sX + (column * TEXTURE_GRID_SIZE);
			float y = sY + (row * TEXTURE_GRID_SIZE);

			uint16_t tileWidth = pTile->m_szTileWidth;
			uint16_t tileHeight = pTile->m_szTileHeight;
			uint16_t imageWidth = pTile->m_szImageWidth;
			uint16_t imageHeight = pTile->m_szImageHeight;

			uint32_t colour = PVR_PACK_COLOR(1.0f, 1.0f * m_fGlobalRScale, 1.0f * m_fGlobalGScale, 1.0f * m_fGlobalBScale);

			float adjustedMaxU = (1.0 / tileWidth) * imageWidth;
			float adjustedMaxV = (1.0 / tileHeight) * imageHeight;

			int x2 = x + tileWidth;
			int y2 = y + tileHeight;

			// todo: optimise?
			bool inClip = ((
							   (x >= clip->sX || x2 >= clip->sX) &&
							   (x <= clip->sX + clip->sW || x2 <= clip->sX + clip->sW)) &&
						   ((y >= clip->sY || y2 >= clip->sY) &&
							(y <= clip->sY + clip->sH || y2 <= clip->sY + clip->sH)));

			if (!inClip)
			{
				continue;
			}

			DrawTile(pTile, x, y, LayerBg);
		}
	}
}

// todo
void PVR::cRenderMgr::DrawTriShaded(uint32_t colour1, uint32_t colour2, uint32_t colour3, RP3d *p1, RP3d *p2, RP3d *p3, int16_t layer)
{
	
}

/// todo :: errrr

void PVR::cRenderMgr::BeginDirectDrawMode(int8_t mode)
{	
	m_szDrawMode = mode;
	pvr_list_begin(mode);
}

void PVR::cRenderMgr::EndDirectDrawMode()
{
	pvr_list_finish();
	m_szDrawMode = Batched;
}

void PVR::cRenderMgr::InitRenderTarget()
{
	if (renderTarget != nullptr)
	{
		delete renderTarget;
		renderTarget = nullptr;
	}

	renderTarget = cTextureFactory::Stub(cTexture::RGB16, 640, 480);
}

bool PVR::cRenderMgr::BeginScene(bool renderToTexture)
{	
	bool bCanDraw = true;

	if (m_bFrameSkip)
	{
		bCanDraw = (pvr_check_ready() == 0);		
	}

	if (bCanDraw)
	{				
		m_bFrameSkip = false;
		m_lLastFrameStart = rspGetMilliseconds();

		if (m_cCurrentFrameTokenLevel)
		{	
			m_lFrameTokenCumulativeFrames += m_lNumPrevVerts;
				
			if (++m_cCurrentFrameToken == 4)
			{
				m_cCurrentFrameToken = 1;
			}		

			if (++m_sFrameTokenClearCount == 128)
			{								
				int32_t polys = (m_lFrameTokenCumulativeFrames >> 7);
				m_sFrameTokenClearCount = 0;
				m_lFrameTokenCumulativeFrames = 0;

				if (m_cCurrentFrameTokenLevel > 0 && polys < PVR_MAX_MESH_VERTS)
				{								
					m_cCurrentFrameTokenLevel--;					
				}					
			}
		}
						
		pvr_wait_ready();
		if (renderToTexture)
		{
			InitRenderTarget();
			uint32_t rx = renderTarget->m_szTextureWidth;
			uint32_t ry = renderTarget->m_szTextureHeight;
			pvr_scene_begin_txr(renderTarget->m_pTextureTiles[0]->m_pPvrTexture, &rx, &ry);
		}
		else
		{
			pvr_scene_begin();
		}		
	}			
	
	return bCanDraw;		
}

void PVR::cRenderMgr::EndScene()
{
	if (m_szDrawMode >= 0)
	{
		EndDirectDrawMode();
	}

	pvr_scene_finish();

	if (m_bIncFrameTokenLevel || m_lNumVerts > PVR_MAX_VERTS)
	{
		m_cCurrentFrameTokenLevel++;
		m_bIncFrameTokenLevel = false;
	}
}

void PVR::cRenderMgr::DrawLetterBoxOverlay(uint8_t top, uint8_t bottom, uint8_t left, uint8_t right, int8_t cYoffset)
{	
	// Left
	DrawQuad(uint32_t(PACK_ARGB8888(255, 0, 0, 0)), int16_t(0), int16_t(0), left, int16_t(480), cRenderMgr::LayerHud);

	// Right
	DrawQuad(uint32_t(PACK_ARGB8888(255, 0, 0, 0)), int16_t(640 - right), int16_t(0), int16_t(640 - right), int16_t(480), cRenderMgr::LayerHud);

	// top
	DrawQuad(uint32_t(PACK_ARGB8888(255, 0, 0, 0)), left, int16_t(0 + cYoffset), int16_t(640 - right), int16_t(top - cYoffset), cRenderMgr::LayerHud);

	// bottom
	DrawQuad(uint32_t(PACK_ARGB8888(255, 0, 0, 0)), left, int16_t(480 - bottom + cYoffset), int16_t(640 - right), int16_t(480 + cYoffset), cRenderMgr::LayerHud);
}

