#include "Heightmap.h"

namespace Brainstem
{
	Heightmap::Heightmap(Dimension2<int> size, float maxHeight )
	{
		Size = size;
		MaxHeight = maxHeight;

		Map = new float[Size.Width * Size.Height];
		memset(Map, 0, sizeof(float) * Size.Width * Size.Height);
	}

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

	void Heightmap::operator*=(const Heightmap &rhs)
	{
		for(int y = 0; y < Size.Height; y++)
		{
			for(int x = 0; x < Size.Width; x++)
			{
				float a = Map[x + y * Size.Width] / MaxHeight;
				float b = 1.0f;
				if(x <= rhs.Size.Width && y <= rhs.Size.Height)
					b = rhs.Map[x + y * Size.Width] / rhs.MaxHeight;

				Map[x + y * Size.Width] = a * b * MaxHeight;
			}
		}

		MinFinalHeight = MaxHeight;
		MaxFinalHeight = 0.f;

		for(int y = 0; y < Size.Height; y++)
		{
			for(int x = 0; x < Size.Width; x++)
			{
				float height = Map[x + y * Size.Width];
				MinFinalHeight = min(height, MinFinalHeight);
				MaxFinalHeight = max(height, MaxFinalHeight);
			}
		}
	}

	void Heightmap::Release()
	{
		SAFE_DELETE_ARRAY(Map);	
	}

	void Heightmap::LoadFromFile(LPDIRECT3DDEVICE9 device, const char* filename)
	{
		memset(Map, 0, sizeof(float) * Size.Width * Size.Height);

		LPDIRECT3DTEXTURE9 heightMapTexture = NULL;
		D3DXIMAGE_INFO info;

		D3DXCreateTextureFromFileExA(device, filename, Size.Width, Size.Height, 1, D3DUSAGE_DYNAMIC, 
			D3DFMT_L8, D3DPOOL_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 
			NULL, &info, NULL, &heightMapTexture);

		D3DLOCKED_RECT sRect;
		heightMapTexture->LockRect(0, &sRect, NULL, NULL);
		BYTE *bytes = (BYTE*)sRect.pBits;

		for(int y = 0; y < Size.Height; y++)
		{
			for(int x = 0; x < Size.Width; x++)
			{
				BYTE *b = bytes + y * sRect.Pitch + x;
				float height = (*b / 255.0f)  * MaxHeight;
				Map[x + y * Size.Width] = height;

			}
		}

		heightMapTexture->UnlockRect(0);
		heightMapTexture->Release();
	}

	void Heightmap::CreateRandomHeightMap(int seed, float noiseSize, float persistence, int octaves)
	{
		for(int y = 0; y < Size.Height; y++)
			for(int x = 0; x < Size.Width; x++)
			{
				float xf = ((float)x / (float)Size.Width) * noiseSize;
				float yf = ((float)y / (float)Size.Height) * noiseSize;

				float total = 0;			

				for(int i =0; i < octaves; i++)
				{
					float freq = pow(2.0f, i);
					float amp = pow(persistence, i);

					float tx = xf * freq;
					float ty = yf * freq;
					int tx_int = (int)tx;
					int ty_int = (int)ty;

					float fracX = tx - tx_int;
					float fracY = ty - ty_int;

					float v1 = Noise(tx_int + ty_int * 57 + seed);
					float v2 = Noise(tx_int+ 1 + ty_int * 57 + seed);
					float v3 = Noise(tx_int + (ty_int+1) * 57 + seed);
					float v4 = Noise(tx_int + 1 + (ty_int+1) * 57 + seed);

					float i1 = CosInterpolate(v1 , v2 , fracX);
					float i2 = CosInterpolate(v3 , v4 , fracX);
					total += CosInterpolate(i1 , i2 , fracY) * amp;
				}

				int b = (int)(128.f + total * 128.f);
				if(b < 0)b = 0;
				if(b > 255)b = 255;

				float height = (b / 255.0f) * MaxHeight;			
				Map[x + y * Size.Width] = height;								
			}

			MinFinalHeight = MaxHeight;
			MaxFinalHeight = 0.0f;

			for(int y = 0; y < Size.Height; y++)
			{
				for(int x = 0; x < Size.Width; x++)
				{
					float height = Map[x + y * Size.Width];
					MinFinalHeight = min(height, MinFinalHeight);
					MaxFinalHeight = max(height, MaxFinalHeight);
				}
			}
	}

	void Heightmap::GenerateHeightMapTexture(LPDIRECT3DDEVICE9 device, LPDIRECT3DTEXTURE9 *texture)
	{
		D3DXCreateTexture(device, Size.Width, Size.Height, 1, D3DUSAGE_DYNAMIC, 
			D3DFMT_L8, D3DPOOL_DEFAULT, texture);

		D3DLOCKED_RECT sRect;

		((LPDIRECT3DTEXTURE9)*texture)->LockRect(0, &sRect, NULL, NULL);
		BYTE *bytes = (BYTE*)sRect.pBits;

		for(int y = 0; y < Size.Height; y++)
		{
			for(int x = 0; x < Size.Width; x++)
			{
				BYTE *bDest = (BYTE*)sRect.pBits;
				bDest += y * sRect.Pitch + x;

				int b = (int)((Map[x + y * Size.Width] / MaxHeight) * 255.f);

				if(b < 0) b = 0;
				if(b > 255) b = 255;

				*bDest = b;
			}
		}

		((LPDIRECT3DTEXTURE9)*texture)->UnlockRect(0);
	}

	void Heightmap::Raise(RECT r, float f)
	{
		for(int y = r.top; y <= r.bottom; y++)
		{
			for(int x = r.left; x <= r.right; x++)
			{
				Map[x + y * Size.Width] += f;

				if(Map[x + y * Size.Width] < -MaxHeight)
					Map[x + y * Size.Width] = -MaxHeight;

				if(Map[x + y * Size.Width] > MaxHeight)
					Map[x + y * Size.Width] = MaxHeight;
			}
		}
	}

	void Heightmap::Raise(Vector2<int> pt, int radius, float f)
	{
		float RadiusPowerTwo = pow((float)radius, 2);	

		for(int x = -radius; x < radius; x++)
		{
			for(int y = -radius; y < radius; y++)
			{
				if (x * x + y * y <= RadiusPowerTwo)
				{	
					int indexx = pt.X + x;
					int indexy = pt.Y + y;

					int index = indexx + indexy * Size.Width;

					if (index < 0)
						continue;

					Map[index] += f;	
				}
			}
		}
	}

	void Heightmap::Smooth()
	{
		float *heightMap = new float[Size.Width * Size.Height];
		memset(heightMap, 0, sizeof(float) * Size.Width * Size.Height);

		for(int y = 0; y < Size.Height; y++) 
		{
			for(int x = 0; x < Size.Width; x++)
			{
				float totalHeight = 0.0f;
				int noNodes = 0;

				for(int y1 = y - 1; y1 <= y + 1; y1++)
				{
					for(int x1 = x - 1; x1 <= x + 1; x1++)
					{
						if(x1 >= 0 && x1 < Size.Width && y1 >= 0 && y1 < Size.Height)
						{
							totalHeight += Map[x1 + y1 * Size.Width];
							noNodes++;
						}

						heightMap[x + y * Size.Width] = totalHeight / (float)noNodes;
					}
				}
			}
		}

		SAFE_DELETE_ARRAY(Map);	
		Map = heightMap;
	}

	void Heightmap::Cap(float capHeight)
	{
		MaxHeight = 0.0f;

		for(int y = 0; y < Size.Height; y++) 
		{
			for(int x = 0; x < Size.Width; x++)
			{
				Map[x + y * Size.Width] -= capHeight;
				if(Map[x + y * Size.Width] < 0.0f)
					Map[x + y * Size.Width] = 0.0f;

				if(Map[x + y * Size.Width] > MaxHeight)
					MaxHeight = Map[x + y * Size.Width];
			}
		}
	}

	bool Heightmap::Within(Vector2<int> p)
	{
		return p.X >= 0 && p.Y >= 0 && p.X < Size.Width && p.Y < Size.Height;
	}
}