//
// Tools_EnvironmentProcessor_SM3.cpp
//
// Created by KJFL - 23 April 2009
//
// Copyright (C) 2009 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "MeshProcessing/Tools_EnvironmentProcessor_SM3.h"

// Asura includes
#include "Debugging/Asura_Assert.h"
#include "Rendering/Asura_Module.h"

// Tools includes
#include "Lighting/Tools_EnvironmentalLightTransport.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "MeshProcessing/Tools_TangentSpace.h"
#include "MeshProcessing/Tools_VertexCacheOptimiser.h"
#include "Smoothing/Tools_ModuleSmoothing.h"


Tools_EnvironmentProcessor_SM3::Tools_EnvironmentProcessor_SM3()
{
	// KJFL - 23 April 2009 - first we need to do anything which isn't thread-safe,
	// so we have the option to later multithread based on modules/material packets.

	// Check the environment mesh normal data is valid, and smoothed across
	// adjacent modules. This may need to trigger the calculation of the adjacency
	// data - which is not threadsafe at the time of writing.
	const int iNumberOfModules = Asura_Module_System::GetNumberOfModules();

	for ( int i=0; i<iNumberOfModules; ++i )
	{
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule( i );
	
		if(!pxMesh->AreNormalsValid() || !pxMesh->m_bSmoothedAcrossModules)
		{
		 	Tools_ModuleSmoothing::CalculateNormals(i);
		}
	}

	// TODO - are vertex adjacencies up to data at this point? We'll need them.

	// Allocate our root data to hold all the modules (and thus packets)
	m_pxModules = new Tools_EP_Module[ iNumberOfModules ];

	// Now the engine data is initialised and we can act multithreaded...

	// ... except while in writing we'll just go single threaded. ;)
	for ( int i=0; i<iNumberOfModules; ++i )
	{
		Tools_Level_Export::UpdateStatus(Tools_Level_Export::EXPORT_STAGE_STRIPIFYING_ENVIRONMENT, "Processing environment...", i * 100 / iNumberOfModules);

		if( !Tools_Level_Export::ShouldIncludeModuleInExport( i ) )
		{
			continue;
		}

		ProcessModule( i );
	}

	// TODO - could do this via an atomic add in the above loop?
	m_uNumberOfPackets = 0;
	m_uTotalNumberOfIndices = 0;
	m_uTotalNumberOfVertices = 0;

	for ( int i=0; i<iNumberOfModules; ++i )
	{
		m_uNumberOfPackets += m_pxModules[i].m_uNumberOfPackets;
	}

	m_pxPackets = new Tools_EP_Packet[m_uNumberOfPackets];

	u_int u=0;
	for ( int i=0; i<iNumberOfModules; ++i )
	{
		Tools_EP_Packet* pxPacket = m_pxModules[i].m_pxFirstPacket;

		while ( pxPacket )
		{
			m_pxPackets[u] = *pxPacket;
			m_pxPackets[u].m_pxNextPacket = 0;
			++u;

			m_uTotalNumberOfIndices += pxPacket->m_uNumberOfIndices;
			m_uTotalNumberOfVertices += pxPacket->m_uNumberOfVertices;

			pxPacket = pxPacket->m_pxNextPacket;
		}
	}

	Asura_Assert( u==m_uNumberOfPackets, "Code bug: wrong number of packets?" );

	SortPackets();

	Tools_Level_Export::UpdateStatus(Tools_Level_Export::EXPORT_STAGE_STRIPIFYING_ENVIRONMENT, 0, 100);

	// now assign vertex and index buffers
	m_uNumberOfIndexBuffers = 0;
	m_uNumberOfVertexBuffers = 0;

	m_puVertexBufferSizes = new u_int[m_uNumberOfPackets];
	m_puIndexBufferSizes = new u_int[m_uNumberOfPackets];

	m_puVertexBufferSizes[0] = 0;
	m_puIndexBufferSizes[0] = 0;

	for ( u_int u=0; u<m_uNumberOfPackets; ++u )
	{
		const u_int uVerts = m_pxPackets[u].m_uNumberOfVertices;
		const u_int uIndices = m_pxPackets[u].m_uNumberOfIndices;
	
		if ( uVerts + m_puVertexBufferSizes[m_uNumberOfVertexBuffers] > 2000000 )
		{
			m_puVertexBufferSizes[++m_uNumberOfVertexBuffers] = 0;
		}

		m_pxPackets[u].m_uVertexBuffer = m_uNumberOfVertexBuffers;
		m_pxPackets[u].m_uVertexBuffer_Offset = m_puVertexBufferSizes[m_uNumberOfVertexBuffers];
		m_puVertexBufferSizes[m_uNumberOfVertexBuffers] += uVerts;

		if ( uIndices + m_puIndexBufferSizes[m_uNumberOfIndexBuffers] > 2000000 )
		{
			m_puIndexBufferSizes[++m_uNumberOfIndexBuffers] = 0;
		}

		m_pxPackets[u].m_uIndexBuffer = m_uNumberOfIndexBuffers;
		m_pxPackets[u].m_uIndexBuffer_Offset = m_puIndexBufferSizes[m_uNumberOfIndexBuffers];
		m_puIndexBufferSizes[m_uNumberOfIndexBuffers] += uIndices;
	}

	++m_uNumberOfIndexBuffers;
	++m_uNumberOfVertexBuffers;

}

void Tools_EnvironmentProcessor_SM3::ProcessModule( int iModuleIndex )
{
	Tools_EP_Module& xModule = m_pxModules[ iModuleIndex ];

	const int iFlagsToIgnore = AsuraPolygonFlag_DontRender | AsuraPolygonFlag_DontExport | AsuraPolygonFlag_Water;

	// Ok, first thing is to find out how many material packets we have
	const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule( iModuleIndex );

	Tools_EP_Packet** ppxPolyToPacket = new Tools_EP_Packet*[pxMesh->NumberOfPolygons];

	for( int iPoly=0; iPoly<pxMesh->NumberOfPolygons; ++iPoly ) 
	{
		const int iFlags = pxMesh->PolygonFlagDataPtr ? pxMesh->PolygonFlagDataPtr[iPoly].Flags : 0;

		// Should we skip this polygon entirely?
		if ( iFlags & iFlagsToIgnore)
		{
			ppxPolyToPacket[iPoly] = 0;
			continue;
		}

		// Do we have a packet with the materials used by this poly?
		const Asura_Hash_ID* puMaterialHashes = pxMesh->TextureDataPtr[iPoly].m_auMaterialResponseHashID;

		// On the assumption of low numbers of materials per module, just scan through material list
		// TODO - check this assumption! Could use some form of hash table. 
		Tools_EP_Packet* pxPacket = xModule.m_pxFirstPacket;
		{
			while ( pxPacket )
			{
				if ( pxPacket->CompareHashes( puMaterialHashes ) )
				{
					break;
				}
				pxPacket = pxPacket->m_pxNextPacket;
			}

			// If we didn't find a packet, add a new one. By adding it to the head of the list
			// we'll check it first for the next polygon.
			if ( !pxPacket )
			{
				pxPacket = new Tools_EP_Packet( puMaterialHashes, iModuleIndex );

				pxPacket->m_pxNextPacket = xModule.m_pxFirstPacket;
				xModule.m_pxFirstPacket = pxPacket;

				++xModule.m_uNumberOfPackets;
			}
		}

		// pxPacket is now the material for this polygon. Store it so we don't have to
		// recalculate it on the next pass.
		ppxPolyToPacket[iPoly] = pxPacket;

		int iNumVerts = pxMesh->PolygonDataPtr[iPoly].NumberOfVertices();
	
		// if poly is double-sided, then double the number of indices/verts required
		if( (iFlags & AsuraPolygonFlag_DoubleSided) )
		{
			iNumVerts += iNumVerts;
		}
	
		// at this stage we have a vertex for every index, and
		// 3 indices for a tri, 6 indices for a quad: (n-2)*3
		pxPacket->m_uNumberOfIndices += (iNumVerts-2)*3;
		pxPacket->m_uNumberOfVertices += (iNumVerts-2)*3;
	}

	{
		Tools_EP_Packet* pxPacket = xModule.m_pxFirstPacket;

		// If we have no packets, there are no polygons to export. Job done.
		if ( !pxPacket )
		{
			delete [] ppxPolyToPacket;
			return;
		}

		// We now know how many packets we have, and how many indices in each packet.
		// Go through and allocate storage space accordingly.
		while ( pxPacket )
		{
			pxPacket->m_pxVertices = new Tools_EP_Vertex[ pxPacket->m_uNumberOfVertices ];
			pxPacket->m_pusIndices = new u_short[ pxPacket->m_uNumberOfIndices ];

			// perversely reset to zero so we can count up as we add the vertices/indices
			pxPacket->m_uNumberOfIndices = 0;
			pxPacket->m_uNumberOfVertices = 0;

			pxPacket = pxPacket->m_pxNextPacket;
		}
	}

	// Find our lighting values for our vertices - we do this once over the vertex positions,
	// rather than per poly as that should save time with reused positions.
	// TODO - is this threadsafe? may be issues with vertex adjacency
	u_int uNumberOfVertexPositions = static_cast<u_int>(pxMesh->NumberOfVertices);
	Asura_SphericalHarmonics_Order2_RGB* pxHarmonics = new Asura_SphericalHarmonics_Order2_RGB[ uNumberOfVertexPositions ];
    
	for (u_int u=0; u<uNumberOfVertexPositions; ++u )
	{
		Tools_EnvironmentalLightTransport::GetDiffuseForVertex
		(
			pxHarmonics[u],
			u,
			iModuleIndex
		);
	}

	// Initialise tangent and binormal calculation
	Tools_Mesh_TangentSpace xMeshTangentSpace;
	xMeshTangentSpace.Initialise(iModuleIndex);

	// Each vertex is actually offset by the module position
	Asura_Vector_3 xModuleOffset;
	Asura_Module_System::GetPositionOfModule( iModuleIndex, xModuleOffset );

	u_int uDegenerates = 0;

	// And then reiterate over the polys and add them to the right packet
	for( int iPoly=0; iPoly<pxMesh->NumberOfPolygons; ++iPoly ) 
	{
		Tools_EP_Packet* pxPacket = ppxPolyToPacket[iPoly];

		// skip any polys we didn't associate with a packet 
		if (!pxPacket) continue; 

		const Asura_Mesh_Normals& xNormals = pxMesh->NormalsDataPtr[iPoly];
		const Asura_Mesh_Polygon& xPolygon = pxMesh->PolygonDataPtr[iPoly];
		const Asura_Mesh_TextureInfo& xTexture = pxMesh->TextureDataPtr[iPoly];

		const int iNumVerts = xPolygon.NumberOfVertices();
		const int iFlags = pxMesh->PolygonFlagDataPtr ? pxMesh->PolygonFlagDataPtr[iPoly].Flags : 0;

		const u_int uNumberOfSides = (iFlags & AsuraPolygonFlag_DoubleSided) ? 2 : 1;

		for ( u_int uDoubleSided=0; uDoubleSided<uNumberOfSides; ++uDoubleSided )
		{
			u_int auIndex[4];

			for ( int i=0; i<iNumVerts; ++i )
			{
				// Next spare vertex
				Tools_EP_Vertex& xVertex = pxPacket->m_pxVertices[ pxPacket->m_uNumberOfVertices ];

				const int iVertexID = xPolygon.VertexID[i];
				
				// POSITION
				// TODO - marginally faster to add in these positions as a post-process?
				xVertex.m_xPosition = pxMesh->VertexDataPtr[iVertexID].Position + xModuleOffset;

				// TEXTURE CO-ORDS
				xVertex.m_fU  = xTexture.U[0][i];
				xVertex.m_fV  = xTexture.V[0][i];
				xVertex.m_fU2  = xTexture.U[1][i];
				xVertex.m_fV2  = xTexture.V[1][i];

				// NORMAL & TANGENT-SPACE
				xVertex.m_xNormal = xNormals.VertexNormal[i];
				
				// KJFL - 23 April 2009 - Flip the normal for double-side polys.
				// TODO - if this poly is flagged as "SPLAYED", should we do something else?
				// One pipeline seems to flip in the plane of the polygon, but the in-Tools
				// version doesn't.
				if (uDoubleSided) xVertex.m_xNormal *= -1.0f;
				
				xMeshTangentSpace.GetTangentAndBinormal(iPoly, iVertexID, 0, xVertex.m_xNormal, xVertex.m_xTangent, xVertex.m_xBinormal );
				xMeshTangentSpace.GetTangentAndBinormal(iPoly, iVertexID, 1, xVertex.m_xNormal, xVertex.m_xTangent2, xVertex.m_xBinormal2 );

				// ALPHA BLEND VALUE
				const u_int uAlpha = (int)(xTexture.m_afAlpha[i] * 255.0f);
				xVertex.m_uDiffuseRGBA = (uAlpha << 24);

				// GI LIGHTING HARMONIC
				Asura_SphericalHarmonics_Order2_RGB xHarmonic = pxHarmonics[ iVertexID ];

				// Potential for per poly vertex override with the vertex painting colour 
				Tools_EnvironmentalLightTransport::UpdateLightingFromVertexPainting( xHarmonic, pxMesh, iPoly, i );
				xVertex.m_xDiffuseSH = xHarmonic;

				//xVertex.SetHashID();
			
				// Vertex is now ready. This will return it's index position in the buffer,
				// adding it to the end if it's a new vertex.

				bool bDualMaterial = pxPacket->m_auMaterialResponseHashID[1] != 0;
				auIndex[i] = pxPacket->AddVertex( xVertex, bDualMaterial );
			}

			if ( !uDoubleSided )
			{
				if ( auIndex[0]==auIndex[1] || auIndex[1]==auIndex[2] || auIndex[2]==auIndex[0] )
				{
					uDegenerates++;
				}
				else
				{
					pxPacket->AddIndex( auIndex[0] );
					pxPacket->AddIndex( auIndex[1] );
					pxPacket->AddIndex( auIndex[2] );
				}

				if ( iNumVerts == 4 )
				{
					if ( auIndex[0]==auIndex[2] || auIndex[2]==auIndex[3] || auIndex[3]==auIndex[0] )
					{
						uDegenerates++;
					}
					else
					{
						pxPacket->AddIndex( auIndex[2] );
						pxPacket->AddIndex( auIndex[3] );
						pxPacket->AddIndex( auIndex[0] );
					}
				}
			}
			else // reverse winding order		
			{
				if ( auIndex[0]==auIndex[1] || auIndex[1]==auIndex[2] || auIndex[2]==auIndex[0] )
				{
					uDegenerates++;
				}
				else
				{
					pxPacket->AddIndex( auIndex[2] );
					pxPacket->AddIndex( auIndex[1] );
					pxPacket->AddIndex( auIndex[0] );
				}

				if ( iNumVerts == 4 )
				{
					if ( auIndex[0]==auIndex[2] || auIndex[2]==auIndex[3] || auIndex[3]==auIndex[0] )
					{
						uDegenerates++;
					}
					else
					{
						pxPacket->AddIndex( auIndex[0] );
						pxPacket->AddIndex( auIndex[3] );
						pxPacket->AddIndex( auIndex[2] );
					}
				}
			}
		}

	}
	delete [] ppxPolyToPacket;
	delete [] pxHarmonics;

	// We now have the packets filled out. Next stage is to optimise them.
	for ( Tools_EP_Packet* pxPacket = xModule.m_pxFirstPacket; pxPacket!=NULL; pxPacket = pxPacket->m_pxNextPacket )
	{
		// Send the packet to the optimiser to reorder the triangles and the vertex buffer order
		Tools_VertexCacheOptimiser xOpt( pxPacket->m_uNumberOfIndices/3, pxPacket->m_uNumberOfVertices, pxPacket->m_pusIndices, true );

		// Make a new reordered vertex buffer and copy the vertices in from the old buffer.
		// Then delete the old buffer. The number of vertices may change if there was some
		// that weren't actually used in the index list.
		const u_int uNumberOfUsedVertices = xOpt.GetNumberOfUsedVertices();
		Tools_EP_Vertex* pxReorderedVertices = new Tools_EP_Vertex[ uNumberOfUsedVertices ];
		
		const u_short* pusNewOrder = xOpt.GetNewVertexOrder();
		for (u_int u=0; u<uNumberOfUsedVertices; ++u)
		{
			pxReorderedVertices[u] = pxPacket->m_pxVertices[ pusNewOrder[u] ];
		}
		
		delete [] (pxPacket->m_pxVertices);
		pxPacket->m_pxVertices = pxReorderedVertices;
		pxPacket->m_uNumberOfVertices = uNumberOfUsedVertices;

		// Blat over the old index buffer list with the new one size they are going to be the same size.
		Asura_Mem::Copy( pxPacket->m_pusIndices, xOpt.GetNewIndexList(), sizeof(u_short)*pxPacket->m_uNumberOfIndices );
	}

	if (uDegenerates)
	{
//		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "Degenerates: %i", uDegenerates );
	}

}

Tools_EnvironmentProcessor_SM3::~Tools_EnvironmentProcessor_SM3()
{
	for (u_int u=0; u<m_uNumberOfPackets; ++u)
	{
		m_pxPackets[u].m_pxVertices = 0;
		m_pxPackets[u].m_pusIndices = 0;
		m_pxPackets[u].m_pxVertexHashTable = 0;
	}
	delete [] m_pxPackets;
	delete [] m_pxModules;

	delete [] m_puVertexBufferSizes;
	delete [] m_puIndexBufferSizes;

}


static int PacketSortCompare(const void *p1, const void *p2)
{
	const Tools_EP_Packet* pxPacket1 = (static_cast<const Tools_EP_Packet*>( p1 ));
	const Tools_EP_Packet* pxPacket2 = (static_cast<const Tools_EP_Packet*>( p2 ));
	
	// Crude comparison for new materials for now
	for(int iMatSlot=0; iMatSlot<TOOLS_NUM_MATERIALRESPONSE_PER_POLY; iMatSlot++)
	{
		if(pxPacket1->m_auMaterialResponseHashID[iMatSlot] < pxPacket2->m_auMaterialResponseHashID[iMatSlot]) return -1;
		if(pxPacket1->m_auMaterialResponseHashID[iMatSlot] > pxPacket2->m_auMaterialResponseHashID[iMatSlot]) return 1;
	}

	if ( pxPacket1->m_uOriginalModule > pxPacket2->m_uOriginalModule ) return -1;
	if ( pxPacket1->m_uOriginalModule < pxPacket2->m_uOriginalModule ) return 1;

	return 0;
}

void Tools_EnvironmentProcessor_SM3::SortPackets()
{
    qsort( m_pxPackets, m_uNumberOfPackets, sizeof(Tools_EP_Packet), PacketSortCompare );
}