#include "BstmTerrain.h"

namespace Brainstem
{
	Terrain::Terrain(IDirect3DDevice9* device, ID3DXEffect* effect)
	{
		Device = device;
		Effect = effect;

		SetTechnique("Default");
		SetDefaultProperties();
	}

	Terrain::~Terrain() { }

	bool Terrain::Init()
	{
		Map = new Heightmap(Dimension2<int>(256, 256), 30.0f);
		return true;
	}

	void Terrain::Update()
	{
		Matrix4<float> scale;
		Matrix4<float> trans;
		Matrix4<float> rot;

		scale.SetScale(Scale);
		trans.SetTranslation(Position);
		rot.SetRotationRadians(Rotation);

		World = trans * rot * scale;
	}

	void Terrain::Render(ICamera* camera)
	{
		TotalPatches = 0;

		Texture* textureData = (Texture*)((ITexture*)TerrainTexture);

		if (textureData != NULL)
			if (textureData->Data != NULL)
			{
				float textureScaling[2] = { TextureScaling.X, TextureScaling.Y };

				Effect->SetTexture("colorMapTexture", textureData->Data);
				Effect->SetValue("textureScale", textureScaling, sizeof(textureScaling));
			}

		Effect->CommitChanges();

		for(int j = 0; j < Patches.size(); j++)
		{
			if(camera->FrustumCubeTest(Patches[j]->Bounds.Min, Patches[j]->Bounds.Max))
			{
				Patches[j]->Render(0);
				TotalPatches++;
			}
		}
	}

	void Terrain::Release()
	{
		for(int i = 0; i < Patches.size(); i++)
		{
			if(Patches[i] != NULL)
			{	
				TerrainPatch *p = Patches[i];
				p->Release();
				delete p;
			}
		}

		Patches.clear();

		Map->Release();
	}

	void Terrain::GenerateFromFile(unsigned int patches, const char* filename, float scaleFactor) 
	{ 
		Map->LoadFromFile(Device, filename);

		ScaleFactor = scaleFactor;
		CreatePatches(patches);
	}

	float Terrain::GetHeight(float x, float z)
	{
		x /= ScaleFactor;
		z /= ScaleFactor;

		x += ((float)Map->GetSize().Width * 0.5f);
		z += ((float)Map->GetSize().Height * 0.5f);

		x -= Position.X;
		z -= Position.Z;

		float col = floorf(z);
		float row = floorf(x);

		float A = Map->GetHeightAt(row, col);
		float B = Map->GetHeightAt(row, col + 1);
		float C = Map->GetHeightAt(row + 1, col);
		float D = Map->GetHeightAt(row + 1, col + 1);

		float dx = z - col;
		float dz = x - row;

		float height = 0.0f;
		if(dz < 1.0f - dx)
		{
			float uy = B - A;
			float vy = C - A;

			height = A + Lerp(0.0f, uy, dx) + Lerp(0.0f, vy, dz);
		}
		else
		{
			float uy = C - D;
			float vy = B - D;

			height = D + Lerp(0.0f, uy, 1.0f - dx) + Lerp(0.0f, vy, 1.0f - dz);
		}

		return height * ScaleFactor;
	}

	void Terrain::CreatePatches(int numPatches)
	{	
		for(int i = 0; i < Patches.size(); i++ )
			if(Patches[i] != NULL)
				Patches[i]->Release();

		Patches.clear();
	
		for( int y = 0; y < numPatches; y++ )
		{
			for( int x = 0; x < numPatches; x++ )
			{
				RECT r = {x * (Map->GetSize().Width - 1) / (long)numPatches, 
					y * (Map->GetSize().Height - 1) / (long)numPatches, 
					(x+1) * (Map->GetSize().Width - 1) / (long)numPatches,
					(y+1) * (Map->GetSize().Height - 1) / (long)numPatches};

				TerrainPatch *p = new TerrainPatch();
				p->CreateMesh(Device, Map, r, ScaleFactor);
				Patches.push_back(p);
			}
		}
	}

	void Terrain::GenerateRandom(unsigned int patches, int seed, float noiseSize, float persistence, int octaves, float scaleFactor) 
	{ 
		Map->CreateRandomHeightMap(seed, noiseSize, persistence, octaves);

		ScaleFactor = scaleFactor;
		CreatePatches(patches);
	}

	void Terrain::ApplyTexture(ITexture* texture)
	{
		TerrainTexture = texture;
	}
	
	ITexture* Terrain::GetTexture()
	{
		return TerrainTexture;
	}

	TerrainPatch::TerrainPatch() { }

	TerrainPatch::~TerrainPatch()
	{
		Release();	
	}

	void TerrainPatch::Release()
	{
		SAFE_RELEASE(Mesh);
	}

	HRESULT TerrainPatch::CreateMesh(LPDIRECT3DDEVICE9 device, Heightmap* Map, RECT source, float scaleFactor)
	{
		HRESULT hr = S_OK;

		MapRect = source;

		int width = source.right - source.left;
		int length = source.bottom - source.top;

		int nrVert = (width + 1) * (length + 1);
		int nrTri = width * length * 2;

		Dimension2<int> size = Map->GetSize();

		float xOffset = (float)size.Width * 0.5f; 
		float zOffset = (float)size.Height * 0.5f;	

		D3DXCreateMeshFVF(nrTri, nrVert, D3DXMESH_DYNAMIC, Vertex::FVF, device, &Mesh);

		Vertex* ver = 0;
		Mesh->LockVertexBuffer(0,(void**)&ver);			

		for(int z = source.top, z0 = 0; z <= source.bottom; z++, z0++)			
		{
			for(int x = source.left, x0 = 0; x <= source.right; x++, x0++)	
			{
				float height = Map->GetHeightTable()[x + z * size.Width];
				D3DXVECTOR3 pos = D3DXVECTOR3((x - xOffset) * scaleFactor, height * scaleFactor, (z - zOffset) * scaleFactor);								

				ver[z0 * (width + 1) + x0].Position = pos;
				ver[z0 * (width + 1) + x0].tU = ((z / (float)size.Height) * 20.0f) * scaleFactor;
				ver[z0 * (width + 1) + x0].tV = ((x / (float)size.Width) * 20.0f) * scaleFactor;

				if(pos.x < Bounds.Min.X) 
					Bounds.Min.X = pos.x;

				if(pos.x > Bounds.Max.X) 
					Bounds.Max.X = pos.x;

				if(pos.y < Bounds.Min.Y) 
					Bounds.Min.Y = pos.y;

				if(pos.y > Bounds.Max.Y) 
					Bounds.Max.Y = pos.y;

				if(pos.z < Bounds.Min.Z) 
					Bounds.Min.Z = pos.z;

				if(pos.z > Bounds.Max.Z) 
					Bounds.Max.Z = pos.z;
			}
			int x = 0;
		}
		Mesh->UnlockVertexBuffer();

		WORD* ind = 0;
		Mesh->LockIndexBuffer(0,(void**)&ind);	
		int i = 0;

		for( int z = source.top, z0 = 0; z < source.bottom; z++, z0++ )
		{
			for( int x = source.left, x0 = 0; x < source.right; x++, x0++ )
			{
				ind[i++] = z0 * (width + 1) + x0;			
				ind[i++] = (z0+1) * (width + 1) + x0;		
				ind[i++] = z0 * (width + 1) + x0 + 1;

				ind[i++] = (z0+1) * (width + 1) + x0;			
				ind[i++] = (z0+1) * (width + 1) + x0 + 1;
				ind[i++] = z0 * (width + 1) + x0 + 1;
			}
		}

		Mesh->UnlockIndexBuffer();

		DWORD *att = 0, a = 0;
		Mesh->LockAttributeBuffer(0,&att);
		memset(att, 0, sizeof(DWORD)*nrTri);
		Mesh->UnlockAttributeBuffer();

		D3DXComputeNormals(Mesh, NULL);
		D3DXComputeTangentFrame(Mesh, NULL);

		return hr;
	}

	void TerrainPatch::Render(int texture)
	{
		Mesh->DrawSubset(texture);
	}
}