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


		#include "gpu/gpu_renderer.h"
		#include "core/terrain.h"
		#include "log/log.h"

		using namespace GS::GPU;

		// Terrain patch size.
#define	_PatchSize		64
		// Terrain page count.
#if	__PLATFORM_ANDROID_NDK__ || __PLATFORM_IOS__
	#define	_PageCount	4
#else
	#define	_PageCount	32
#endif


//-----------------------------------------------------------------------------
bool				Renderer::CreateTerrainPatch()
{
	// Allocate terrain cache.
	if	(!terrain_patch_cache.Allocate(_PageCount))
		__ERR__(__LOG_E__ << "Failed to allocate terrain cache.\n", false)

	// Compute page stride.
	size_t stride = 0;

	size_t vertex_offset = stride;
	stride += 3 * 4;						// Vertex buffer (3x float).
	size_t normal_offset = stride;
	stride += (3 + 1) * 1;					// Normal buffer (3x byte + 1 padding).
	size_t uv_offset = stride;
	stride += 2 * 4;						// Large UV (2x float).

	// Setup default page layout.
	size_t byte_size = stride * (_PatchSize + 1) * (_PatchSize + 1);

	__LOG_H__ << "Setup terrain cache: " << terrain_patch_cache.GetCount() << " pages (" << uint(byte_size * terrain_patch_cache.GetCount()) << " bytes).\n";

	terrain_patch_vtx.Allocate((uint)byte_size);
	if	(char *p = terrain_patch_vtx)
	{
		Vector4 wp(0, 0, 0);
		for	(int v = 0; v < (_PatchSize + 1); ++v)
		{
			for	(int u = 0; u < (_PatchSize + 1); ++u)
			{
				float *pv = (float *)p;
				pv[0] = wp.x; pv[1] = 0; pv[2] = wp.z;

				p += stride;
				wp.x += 1.f;
			}
			wp.x = 0.f;
			wp.z += 1.f;
		}
	}

	// Allocate pages.
	for	(uint n = 0; n < terrain_patch_cache.GetCount(); ++n)
	{
		DisplayList *patch = terrain_patch_cache[n].patch = NewDisplayList();

		// Create the patch index buffer.
		patch->idx = NewVBO();

		if	(patch->idx->Create(6 * _PatchSize * _PatchSize * sizeof(ushort), VBO::Index, VBO::Static))
		{
			SetIndexSource(patch->idx);
			if	(ushort *p_idx = (ushort *)patch->idx->Map())
			{
				for	(int v = 0; v < _PatchSize; ++v)
					for	(int u = 0; u < _PatchSize; ++u)
					{
						*p_idx++ = (ushort)(u + v * (_PatchSize + 1));
						*p_idx++ = (ushort)(u + (v + 1) * (_PatchSize + 1) + 1);
						*p_idx++ = (ushort)(u + v * (_PatchSize + 1) + 1);
						*p_idx++ = (ushort)(u + v * (_PatchSize + 1));
						*p_idx++ = (ushort)(u + (v + 1) * (_PatchSize + 1));
						*p_idx++ = (ushort)(u + (v + 1) * (_PatchSize + 1) + 1);
					}

				patch->idx->Unmap();
				patch->index_count = 6 * _PatchSize * _PatchSize;
			}
			SetIndexSource(NULL);
		}

		// Create the patch vertex buffer.
		patch->vertex_offset = vertex_offset;
		patch->normal_offset = normal_offset;
		patch->uv_offset[0] = uv_offset;
		patch->stride = stride;

		patch->vtx = NewVBO();
		if	(patch->vtx->Create(byte_size, VBO::Vertex, VBO::Dynamic))
		{
			SetVertexSource(patch->vtx, patch->stride);
			patch->vtx->Update(terrain_patch_vtx);
			SetVertexSource(NULL, 0);
		}
	}
	return true;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
void				Renderer::InvalidateTerrainCache(int, int, int, int)
{
	// Very naive invalidation that ignores the input region.
	for	(uint n = 0; n < terrain_patch_cache.GetCount(); ++n)
		terrain_patch_cache[n].terrain = NULL;
}
void				Renderer::DecayTerrainCache()
{
	for	(uint n = 0; n < terrain_patch_cache.GetCount(); ++n)
		if	(terrain_patch_cache[n].score > 0)
			--terrain_patch_cache[n].score;
}
void				Renderer::UploadTerrainPatchToPage(CachedTerrainPatch &page, const GS::Core::Item &item, const GS::Core::Patch &patch, const DrawContext &dc)
{
	if	(!patch.terrain || !page.patch)
		return;

	Core::Terrain &terrain = *patch.terrain;
	DisplayList &terrain_patch = *page.patch;

	float pixel_size = terrain.GetUnit();
	Vector4 patch_scale(pixel_size * patch.decimation, 1, pixel_size * patch.decimation);

#if	1

	// Update patch CPU-side data.
	Core::Terrain::Attrib *psa = terrain.GetAttributesMap() + patch.v * terrain.GetHeightmapPitch() + patch.u;
	float *psh = terrain.GetHeightmap() + patch.v * terrain.GetHeightmapPitch() + patch.u;

	char *p = terrain_patch_vtx;

	Vector4	uv(0, 0, patch.v * pixel_size);
	for	(int v = 0; v < (_PatchSize + 1); ++v)
	{
		uv.x = patch.u * pixel_size;

		Core::Terrain::Attrib *pa = psa;
		float *ph = psh;

		for	(int u = 0; u < (_PatchSize + 1); ++u)
		{
			// Vertex.
			float *pv = (float *)(p + terrain_patch.vertex_offset);
			pv[1] = ph[0];

			// Normal.
			char *pn = (char *)(p + terrain_patch.normal_offset);
			pn[0] = pa[0].nx;
			pn[1] = pa[0].ny;
			pn[2] = pa[0].nz;

			// UV.
			float *pu = (float *)(p + terrain_patch.uv_offset[0]);
			pu[0] = uv.x / terrain.GetWidth();
			uv.x += patch_scale.x;
			pu[1] = uv.z / terrain.GetDepth();

			p += terrain_patch.stride;

			pa += patch.decimation;
			ph += patch.decimation;
		}

		uv.z += patch_scale.z;

		psa += terrain.GetHeightmapPitch() * patch.decimation;
		psh += terrain.GetHeightmapPitch() * patch.decimation;
	}

	// Update VBO.
	SetVertexSource(terrain_patch.vtx, terrain_patch.stride);
	terrain_patch.vtx->Update(terrain_patch_vtx);

	// [EJ 9 Mar] the display list cache MUST be reset here otherwise an invalid
	// VBO reference will be used by the next draw calls for this patch.
	dls_cache.dls = NULL;

#endif

	// Synchronize cache.
	page.terrain = patch.terrain;
	page.u = patch.u; page.v = patch.v;
	page.w = patch.w; page.h = patch.h;
	page.decimation = patch.decimation;
}
void				Renderer::RenderTerrainPatch(const GS::Core::Patch &patch, const GS::Core::Item &item, const DrawContext &dc)
{
	// Cache query.
	++gpu_stats.terrain_page_query_count;

	DisplayList	*dlist = NULL;
	CachedTerrainPatch *page = NULL;
	uint lowest_score = (uint)~0;

	for	(uint n = 0; n < terrain_patch_cache.GetCount(); ++n)
		if	(CachedTerrainPatch *c_page = &terrain_patch_cache[n])
		{
			if	(
					(c_page->terrain == patch.terrain) &&
					(c_page->u == patch.u) && (c_page->v == patch.v) &&
					(c_page->w == patch.w) && (c_page->h == patch.h) &&
					(c_page->decimation == patch.decimation)
				)
			{
				dlist = c_page->patch;
				c_page->score++;		// Increase score.
				break;
			}

			// Track the lowest scoring page.
			if	(c_page->score <= lowest_score)
			{
				lowest_score = c_page->score;
				page = c_page;
			}
		}

	// Cache miss.
	if	(!dlist)
	{
		++gpu_stats.terrain_page_query_miss;

		if	(!page || !page->patch)
			__ERRRAW__(__LOG_E__ << "No page to upload terrain patch.\n")	// Unexpected...

		// Create new page.
		UploadTerrainPatchToPage(*page, item, patch, dc);
		dlist = page->patch;
		page->score = 1;				// Reset score (+1 for being used).
	}

	// Draw patch scaled and translated on the GPU.
	float pixel_size = patch.terrain->GetUnit();
	Vector4 patch_scale(pixel_size * patch.decimation, 1, pixel_size * patch.decimation);

	Matrix4 patch_matrix =
			Matrix4::TranslationMatrix(Vector4(patch.u * pixel_size - patch.terrain->GetWidth() * 0.5f, 0, patch.v * pixel_size - patch.terrain->GetDepth() * 0.5f)) *
			Matrix4::ScaleMatrix(patch_scale);

	SetWorldMatrix(item.GetMatrix() * patch_matrix);
	m_previous_world = item.GetPreviousMatrix() * patch_matrix;

	dls_cache.item = NULL;		// Force matrices upload to program.

	// Draw.
	if	(patch.terrain->render_data)
	{
		dlist->material = patch.terrain->render_data->material;
		DrawDisplayListCached(*dlist, item, dc);
	}
}
//-----------------------------------------------------------------------------
