#include "PerlinTerrainNode.h"

PerlinTerrainNode::PerlinTerrainNode(wstring name, wstring seed, float chunkSize, int widthX, int widthZ, float waterHeight, int cellSizeX, int cellSizeZ) : TerrainNode(name, seed, waterHeight, widthX, widthZ, cellSizeX, cellSizeZ)
{	
	_chunkSize = chunkSize;

	GeneratePerlinValues();
	if (!GeneratePerlinHeights())
	{
		_initError = true;
	}
}

void PerlinTerrainNode::GeneratePerlinValues()
{
	std::seed_seq seedGenerator(_seedString.begin(), _seedString.end());
	
	_generator.seed(seedGenerator);
	std::uniform_int_distribution<> dist(0, 511);
	char bufferChar;
	int newPosition;

	_p.clear();
	copy(_staticP.begin(), _staticP.end(), back_inserter(_p));

	for (int i = 0; i < 512; i++)
	{
		newPosition = dist(_generator);
		bufferChar = _p[i];
		_p[i] = _p[newPosition];
		_p[newPosition] = bufferChar;
	}
}

// Method for filling the height values array for the terrain generation
bool PerlinTerrainNode::GeneratePerlinHeights()
{	
	for (int z = 0; z < _gridRows; z++)
	{
		float mapZ = ((float)z / (float)_gridRows);

		for (int x = 0; x < _gridCols; x++)
		{
			float mapX = ((float)x / (float)_gridCols);
			
			//float currentHeightValue = GetPerlinValueAt(mapX, mapZ, 8, 0.85f) - 0.25f;
			float currentHeightValue = 0.0f;
			
			int octaves = 12;
			float currentOctaveValue = 1.0f;
			float stepOff = 1.0f;
			float stepTotal = 0.0f;
			// n covers the persistance, frequency and 
			for (int n = 1; n <= octaves; n++)
			{
				currentHeightValue += stepOff * GetNoiseValueAt(((float)x * currentOctaveValue) / ((float)_gridCols / _chunkSize), ((float)z * currentOctaveValue) / ((float)_gridRows / _chunkSize));
				currentOctaveValue *= 2;
				stepTotal += stepOff;
				stepOff *=  0.5f;

			}

			//currentHeightValue = currentHeightValue / stepTotal; // *(currentHeightValue * 2.0f);
			//currentHeightValue = currentHeightValue - floor(currentHeightValue);
			_heightValues.push_back((currentHeightValue + 1.0f) * 0.5f);
		}
	}

	return true;
}

float PerlinTerrainNode::Fade(float t)
{
	return t * t * t * (t * (t * 6 - 15) + 10);
}

float PerlinTerrainNode::Grad(int hash, float x, float y)
{
	int h = hash & 7;
	float u = y;
	float v = x;
	if (h < 4)
	{
		u = x;
		v = y;
	}
	
	float outA = u;
	if ((h & 1))
	{
		outA = -u;
	}

	float outB = 2.0f * v;
	if ((h & 2))
	{
		outB = -2.0f * v;
	}

	return outA + outB;
}

// Method to get the perlin noise value at x/y
float PerlinTerrainNode::GetNoiseValueAt(float x, float y)
{
	int ix0 = (int)floor(x);
	int iy0 = (int)floor(y);

	float fx0 = x - (float)ix0;
	float fy0 = y - (float)iy0;
	float fx1 = fx0 - 1.0f;
	float fy1 = fy0 - 1.0f;
	
	int ix1 = (ix0 + 1) & 0xff;
	int iy1 = (iy0 + 1) & 0xff;
	ix0 = ix0 & 0xff;
	iy0 = iy0 & 0xff;

	float t = Fade(fy0);
	float s = Fade(fx0);

	float nx0 = Grad(_p[ix0 + _p[iy0]], fx0, fy0);
	float nx1 = Grad(_p[ix0 + _p[iy1]], fx0, fy1);
	float n0 = SharedMethods::Lerp<float>(nx0, nx1, t);

	nx0 = Grad(_p[ix1 + _p[iy0]], fx1, fy0);
	nx1 = Grad(_p[ix1 + _p[iy1]], fx1, fy1);
	float n1 = SharedMethods::Lerp<float>(nx0, nx1, t);

	return 0.507f * SharedMethods::Lerp(n0, n1, s);
}