/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.

	------------------------------------------------------------------------
	Original implementation of the following publication:

	Linear-Speed Vertex Cache Optimization
	Tom Forsyth, RAD Game Tools: tom.forsyth@eelpi.gotdns.org

----------------------------------------------------------------------------- */


		#include <cmath>
		#include "core/triangle_list.h"
		#include "container/nlist.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


struct			VertexScore
{
	uint		triangle_to_draw;
	float		score;
	int			position;				///< Position in cache.
};
struct			TriangleData
{
	bool		processed;
};

#define			MaxSizeVertexCache					32

#define			FindVertexScore_CacheDecayPower		1.5f
#define			FindVertexScore_LastTriScore		0.75f
#define			FindVertexScore_ValenceBoostScale	2.0f
#define			FindVertexScore_ValenceBoostPower	0.5f


//------------------------------------------------------------------------------
static	void		ComputeVertexScore(VertexScore *vertex)
{
	// Unneeded vertex.
	if	(!vertex->triangle_to_draw)
	{
		vertex->score = -1;
		return;
	}

	float score = 0;

	if	(vertex->position < 0)
		;	// Not in cache, no score.
	else
	{
		/*
			This vertex was used in the last triangle,
			so it has a fixed score, whichever of the three
			it's in. Otherwise, you can get very different
			answers depending on whether you add the triangle
			1,2,3 or 3,1,2 - which is silly.
		*/
		if	(vertex->position < 3)
				score = FindVertexScore_LastTriScore;
		else	score = std::pow(1.f - (vertex->position - 3) / (MaxSizeVertexCache - 3), FindVertexScore_CacheDecayPower);
	}

	/*
		Bonus points for having a low number of triangles still to 
		use the vertex, so we get rid of lone vertice quickly.
	*/
	float boost = std::pow((float)vertex->triangle_to_draw, -FindVertexScore_ValenceBoostPower);
	vertex->score = score + FindVertexScore_ValenceBoostScale * boost;
}
static	float		ComputeTriangleScore(uint n, uint *idx, const VertexScore *vertex)
{	return vertex[idx[n * 3]].score + vertex[idx[n * 3 + 1]].score + vertex[idx[n * 3 + 2]].score;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static	void		CacheVertex(uint idx, VertexScore *vertex, Array <int> &LRUCache)
{
	// Seek for vertex in cache.
	int m;
	for (m = 0; m < MaxSizeVertexCache; ++m)
		if (LRUCache[m] == (int)idx)
			break;

	// Handle corner case where a vertice is pushed out of cache.
	if	(m == MaxSizeVertexCache)
	{
		m--;
		if	(LRUCache[m] != -1)
		{
			vertex[LRUCache[m]].position = -1;
			ComputeVertexScore(&vertex[LRUCache[m]]);
		}
	}

	// Shift cache, insert vertex to top.
	for (int t = (m - 1); t >= 0; --t)
		if	(LRUCache[t] != -1)
		{
			vertex[LRUCache[t]].position++;
			LRUCache[t + 1] = LRUCache[t];
		}

	LRUCache[0] = idx;
	vertex[idx].position = 0;
}
void				Trilist::Optimize(const List <Trilist *> &list, int /*cache_size*/)
{
	__LOG__ << "Optimizing triangle list...\n";

	uint total_vtx_count = 0, total_tri_count = 0, n;

	ListForeachPtr(Trilist *, t, list)
	{
		if	(t->vtx.GetCount() > total_vtx_count)
			total_vtx_count = t->vtx.GetCount();
		if	(t->GetTriangleCount() > total_tri_count)
			total_tri_count = t->GetTriangleCount();
	}

	Array <VertexScore> vertex(total_vtx_count);
	Array <TriangleData> triangle(total_tri_count);

	if	(!vertex || !triangle)
		__ERRRAW__(__LOG_E__ << "Failed to allocate vertex optimization array.\n");

	// Process each list.
	Array <int> LRUCache(MaxSizeVertexCache);
	Array <uint> ordered_idx;

	ListForeachPtr(Trilist *, t, list)
	{
		// Reset scores.
		for (n = 0; n < total_vtx_count; ++n)
		{
			vertex[n].triangle_to_draw = 0;
			vertex[n].score = 0;
		}
		for (n = 0; n < total_tri_count; ++n)
			triangle[n].processed = false;
		for (n = 0; n < MaxSizeVertexCache; ++n)
			LRUCache[n] = -1;

		// Compute triangle count per vertex index.
		for (n = 0; n < t->GetTriangleCount(); ++n)
			for (uint m = 0; m < 3; ++m)
				vertex[t->idx[n * 3 + m]].triangle_to_draw++;

		// Start optimization cycles.
		if	(!ordered_idx.Allocate(t->idx.GetCount()))
		{
			__LOG_W__ << "Failed to allocate triangle list ordered index list, skipping.\n";
			continue;
		}

		for (uint processed_tri_count = 0; processed_tri_count < t->GetTriangleCount(); ++processed_tri_count)
		{
			int best_score_triangle_index = -1, m;
			float best_score_triangle = -1;

			/// Pick up best triangle.
			for (n = 0; n < t->GetTriangleCount(); ++n)
			{
				if	(triangle[n].processed)
					continue;

				float score = ComputeTriangleScore(n, t->idx, vertex);

				if	(score > best_score_triangle)
				{
					best_score_triangle_index = n;
					best_score_triangle = score;
				}
			}

			// Failsafe match.
			if	(best_score_triangle_index == -1)
			{
				for (n = 0; n < t->GetTriangleCount(); ++n)
					if	(!triangle[n].processed)
						break;
				best_score_triangle_index = n;
			}

			// Add best triangle to the new list.
			for (m = 0; m < 3; ++m)
			{
				uint idx = t->idx[best_score_triangle_index * 3 + m];

				ordered_idx[processed_tri_count * 3 + m] = idx;
				CacheVertex(idx, vertex, LRUCache);
				vertex[idx].triangle_to_draw--;
			}

			// Update cached vertices score.
			for (m = 0; m < MaxSizeVertexCache; ++m)
				if	(LRUCache[m] != -1)
					ComputeVertexScore(&vertex[LRUCache[m]]);

			triangle[best_score_triangle_index].processed = true;
		}

		// Commit optimized index list.
		t->idx.Transfer(ordered_idx);
	}
}
//------------------------------------------------------------------------------
