//
// Asura_GX_Hierarchy.cpp
//
// Created by KOR - 10 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"
#include "Core/Asura_Wii_Mem2.h"

// This file's header.
#include "Rendering/Asura_GX_Hierarchy.h"

// Asura headers
#include "Animation/Asura_Hierarchy.h"
#include "Animation/Asura_TextureAnimation.h"
#include "Camera/Asura_Camera.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Material_System.h"
#include "Core/Asura_Maths.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Rendering/Asura_Render.h"

// Platform headers
#include "ChunkSystem/Asura_Platform_Chunk_ResourceFile.h"
#include "Rendering/Asura_GX.h"

// Library headers
#include <revolution/gx.h>

#define ASURA_MAX_NO_OF_HIERARCHIES 64

// Static members
Asura_TemplateHashTree<Asura_GX_Hierarchy> Asura_GX_HierarchyRenderer::s_xHashTree;
static const u_int uASURA_WII_HIERARCHY_RENDER_ALPHA_BUFFER_SIZE = 256;
static u_int* s_puAlphaVertexBuffer = NULL;

void Asura_GX_HierarchyRenderer::Initialise()
{
	s_puAlphaVertexBuffer = ASURA_WII_MEM2_ALLOC_ARRAY(u_int, uASURA_WII_HIERARCHY_RENDER_ALPHA_BUFFER_SIZE);
	for(u_int u = 0; u < uASURA_WII_HIERARCHY_RENDER_ALPHA_BUFFER_SIZE; ++u)
	{
		s_puAlphaVertexBuffer[u] = 0xffffff00 | u;
	}
	DCFlushRange(s_puAlphaVertexBuffer, uASURA_WII_HIERARCHY_RENDER_ALPHA_BUFFER_SIZE * sizeof(u_int));
}

void Asura_GX_HierarchyRenderer::Reset()
{
	s_xHashTree.Reset();
}

void Asura_GX_HierarchyRenderer::Shutdown()
{
	ASURA_WII_MEM2_FREE(s_puAlphaVertexBuffer);
	s_xHashTree.Reset();
}

bool Asura_GX_HierarchyRenderer::AddHierarchy(Asura_Handle fileHandle, int iChunkVersion)
{
	Asura_Hash_ID uHashID;
	Asura_FileAccess::ReadData(fileHandle, &uHashID, sizeof(Asura_Hash_ID));

	Asura_GX_Hierarchy* pxHier = s_xHashTree.AddNode(uHashID);
	pxHier->LoadFromChunk(fileHandle, iChunkVersion);

	return (pxHier != NULL);
}

int Asura_GX_HierarchyRenderer::GetMaterialIndex(Asura_Hash_ID uHashID)
{
	const Asura_GX_Hierarchy* pxHier = GetHierarchy(uHashID);
	if(pxHier)
	{
		// Return first material ID that isn't none
		for(u_int uStrip = 0; uStrip < pxHier->GetNumStrips(); ++uStrip)
		{
			const int iMaterialID = pxHier->GetMaterialID(uStrip);
			if(iMaterialID != ASURA_MATERIAL_NONE)
			{
				return iMaterialID;
			}
		}
	}
	return ASURA_MATERIAL_NONE;
}

const Asura_GX_Hierarchy* Asura_GX_HierarchyRenderer::GetHierarchy(Asura_Hash_ID uHashID)
{
	return s_xHashTree.GetDataByHash(uHashID);
}

void Asura_GX_HierarchyRenderer::RenderHierarchy(Asura_Hash_ID uHashID, u_int uCamVisFlags, float fAlpha /* = 1.f */)
{
	const Asura_GX_Hierarchy* pxHier = GetHierarchy(uHashID);
	Asura_Assert(pxHier, "Could not find hierarchy to render.");
	if(pxHier)
	{
		pxHier->BeginRender();
		if(fAlpha == 1.f)
		{
			pxHier->Render(uCamVisFlags);
		}
		else
		{
			pxHier->Render(uCamVisFlags, true);
			pxHier->Render(uCamVisFlags, false, fAlpha);
		}
	}
}

bool Asura_GX_HierarchyRenderer::IsSkinOpaque(Asura_Hash_ID uHashID)
{
	const Asura_GX_Hierarchy* pxHier = GetHierarchy(uHashID);
	Asura_Assert(pxHier, "");
	if(pxHier)
	{
		// TODO - cache this?
		const u_int uNumStrips = pxHier->GetNumStrips();
		for(u_int u = 0; u < uNumStrips; ++u)
		{
			if(Asura_Material_System::IsMaterialTransparent(pxHier->GetMaterialID(u)))
			{
				return false;
			}
		}
	}
	return true;
}

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

Asura_GX_Hierarchy::Asura_GX_Hierarchy()
: m_uHierarchyExportFlags(0)
, m_uNumberOfStrips(0)
, m_pxStrips(NULL)
, m_uNumberOfVertices(0)
, m_pxVertices(NULL)
, m_iVertexSize(0)
, m_iNormalOffset(0)
, m_iColourOffset(0)
, m_iAlphaOffset(0)
, m_iUVOffset(0)
, m_uFractionalBits(0)
{
}

void Asura_GX_Hierarchy::LoadFromChunk(Asura_Handle iFileHandle, int iChunkVersion)
{
	Asura_Chunk_File_Stream xStream(iFileHandle);
	xStream >> m_uHierarchyExportFlags;
	Asura_Assert(m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_UV1, "Unsupported Hierarchy Export flags.");
	//horrible calc but it fits it all in one assert
	// basically check that either ExportHierarchyNormals OR
	Asura_Assert((m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_NORMALS == ASURA_WII_HIERARCHY_CHUNK_FLAG_NORMALS) ^ 
		((m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_COLOURS) == ASURA_WII_HIERARCHY_CHUNK_FLAG_COLOURS)
		, "Unsupported Hierarchy Export flags.");
	m_iVertexSize = 3 * sizeof(short);
	m_iNormalOffset = -1;
	m_iColourOffset = -1;
	m_iUVOffset = -1;

	if (m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_NORMALS)
	{
		m_iNormalOffset = m_iVertexSize;
		m_iVertexSize += 3 * sizeof(short);
	}
	if (m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_COLOURS)
	{
		if ((m_iVertexSize & 3) != 0)
		{
			m_iVertexSize += 4 - (m_iVertexSize & 3);
		}
		m_iColourOffset = m_iVertexSize;
		m_iVertexSize += 4;
	}
	if(m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_UV1)
	{
		m_iUVOffset = m_iVertexSize;
		m_iVertexSize += 4;
	}

	xStream >> m_uNumberOfStrips;
	Asura_Assert(m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_DISPLAYLIST, "Only hierarchy display list loading supported");
	u_int uDisplayListSize;
	xStream >> uDisplayListSize;

	m_xDisplayListBlock.Initialise(uDisplayListSize, false);

	xStream >> m_uNumberOfVertices;
	if(iChunkVersion >= 4)
	{
		xStream >> m_uFractionalBits;
	}
	else
	{
		m_uFractionalBits = 10;
	}

	m_pxStrips = new Asura_GX_Hierarchy_Strip[m_uNumberOfStrips];
	Asura_GX_DisplayListBlock::Iterator xDisplayListRunner = m_xDisplayListBlock.GetIterator();

	u_int uPreviousStripsIndex = 0;
	for(u_int uStrip = 0; uStrip < m_uNumberOfStrips; ++uStrip)
	{
		Asura_GX_Hierarchy_Strip& xStrip = m_pxStrips[uStrip];
		xStream >> xStrip.m_iMaterialID;

		xStrip.m_iMaterialID = Asura_Material_System::ConvertIndex(xStrip.m_iMaterialID);
		if(iChunkVersion >= 5)
		{
			xStream >> xStrip.m_usBoneIndex;
		}
		else
		{
			xStrip.m_usBoneIndex = static_cast<u_short>(uStrip);
		}
		u_int uDisplayListSize;
		xStream >> uDisplayListSize;
		xStrip.m_xDisplayList = xDisplayListRunner.GetDisplayList(uDisplayListSize, false);
		xStream >> xStrip.m_uNumTris;
	}
	xDisplayListRunner.Finalize(m_xDisplayListBlock);

	if(m_uNumberOfVertices)
	{
		m_pxVertices = static_cast<char*>(ASURA_WII_MEM2_ALLOC(m_iVertexSize * m_uNumberOfVertices));
		xStream.Read(m_uNumberOfVertices * m_iVertexSize, m_pxVertices);
	}
	Asura_GX_DisplayList::Iterator Iter = m_xDisplayListBlock.DisplayList().GetIterator();

	Iter.ReadFromStream(xStream, m_xDisplayListBlock.GetSize());
	Iter.Finalize(m_xDisplayListBlock.DisplayList(), false);
}

Asura_GX_Hierarchy::~Asura_GX_Hierarchy()
{
    delete [] m_pxStrips;
	ASURA_WII_MEM2_FREE(m_pxVertices);

	m_xDisplayListBlock.Release();
}

DEBUGVAR char dbg_cHierAlphaLevel = 0;

void Asura_GX_Hierarchy::BeginRender() const
{
	Asura_GX::Set3DProjection(true);

	// Render states
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_CCW);
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);
	Asura_Render::CurrentStates.RequestAlphaTest(true);

	GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XYZ, GX_S16, m_uFractionalBits);

	// Vertex setup
	GXClearVtxDesc();
	GXSetVtxDesc(GX_VA_POS, GX_INDEX16);
	GXSetArray(GX_VA_POS, m_pxVertices, m_iVertexSize);
	if (m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_NORMALS)
	{
		GXSetVtxDesc(GX_VA_NRM, GX_INDEX16);
		GXSetArray(GX_VA_NRM, (char*)m_pxVertices + m_iNormalOffset, m_iVertexSize);
	}
	if (m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_COLOURS)
	{
		GXSetVtxDesc(GX_VA_CLR0, GX_INDEX16);
		GXSetArray(GX_VA_CLR0, (char*)m_pxVertices + m_iColourOffset, m_iVertexSize);
	} 
	if(m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_ALPHA)
	{
		GXSetVtxDesc(GX_VA_CLR0, GX_INDEX8);
		GXSetArray(GX_VA_CLR0, (char*)s_puAlphaVertexBuffer, 4);
	}
	if (m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_UV1)
	{
		GXSetVtxDesc(GX_VA_TEX0, GX_INDEX16);
		GXSetArray(GX_VA_TEX0, (char*)m_pxVertices + m_iUVOffset, m_iVertexSize);
	}
	GXInvalidateVtxCache();
}

void Asura_GX_Hierarchy::Render(u_int uCamVisFlags, bool bDepthOnly /* = false */, float fAlpha /* = 1.f */) const
{
	if(bDepthOnly)
	{
		Asura_GX::SetupDepthOnly();
	}
	// Camera loop
	for( int iCam = Asura_Camera::GetNumberOfCameras(); iCam--; )
	{
		if((1 << iCam) & uCamVisFlags)
		{
			Asura_Camera::SetCameraIndex( iCam );
			if (!Asura_Camera::IsCameraActive())
			{
				continue;
			}
			Asura_Render::ApplyCameraSettings();

			if(!bDepthOnly)
			{
				Asura_GX::SetUpEntityLights(*Asura_Hierarchy_System::s_pxPos, (m_uHierarchyExportFlags & ASURA_WII_HIERARCHY_CHUNK_FLAG_ALPHA) != 0);
			}

			// Render
			Asura_GX_Hierarchy_Strip* pxStrip = m_pxStrips;
			for(unsigned int u = 0; u < m_uNumberOfStrips; ++u, ++pxStrip)
			{
				if(!bDepthOnly)
				{
					const int iMaterialID = pxStrip->m_iMaterialID;
					Asura_GX::SetupMaterial(iMaterialID, 0, 0, ASURA_TEXTURE_NONE, fAlpha);
					Asura_GX::SetUpEntitySpecular(*Asura_Hierarchy_System::s_pxPos, iMaterialID);
				}

				Mtx xPosTransform;
				Mtx mv, mvi;
				const u_short usBoneIndex = pxStrip->m_usBoneIndex;
				Asura_GX::OriPosToMTX(Asura_Hierarchy_System::GetWorldspaceBoneOri(usBoneIndex), Asura_Hierarchy_System::GetWorldspaceBonePos(usBoneIndex), xPosTransform);
				const Mtx& xViewMatrix = Asura_GX::GetViewMatrix();
				MTXConcat(xViewMatrix, xPosTransform, mv);
				GXLoadPosMtxImm(mv, GX_PNMTX0);
				MTXInverse(mv, mvi); 
				MTXTranspose(mvi, mv); 
				GXLoadNrmMtxImm(mv, GX_PNMTX0);

				Asura_Assert(pxStrip->m_xDisplayList.IsValid(), "");
				pxStrip->m_xDisplayList.Render();
				Asura_Render::AddToTrianglesRendered(pxStrip->m_uNumTris, 1, ASURA_RENDER_TTYPE_HIER);
			}
		}
	}
	GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);
}


