#include "forest.h"
#include "messages.h"
#include "utils.h"

#define CHUNK 20

Forest::Forest(LPDIRECT3DDEVICE9 NewCreationDevice, float XPos, float YPos)
{
	TerrainObject = NULL;
	NumTrees = 0;
	MaxNumTrees = CHUNK;
	ForestTrees = new Tree*[MaxNumTrees];
	Position.x = XPos;
	Position.y = YPos;
    m_scale = 1.0f;
    D3DXMatrixIdentity(&m_rotationMatrix);
	CreationDevice = NewCreationDevice;

	NumNewTrees = 0;
}

Forest::~Forest()
{
	//Don't delete every member of the array; this is handled by the main loop
	delete []ForestTrees;
}

unsigned int Forest::GetAction(float TimeStep, Msg** SentMsgs)
{
	if(NumNewTrees > 0)
	{
		for(int i=0;i<NumNewTrees;i++)
		{
            NewEntityMsg* NewMsg = new NewEntityMsg();
			NewMsg->NewEntity = NewTrees[i];
			NewMsg->Sender = this;
			SentMsgs[i] = NewMsg;
		}
		int NumMessages = NumNewTrees;
		NumNewTrees = 0;			
		return NumMessages;
	}
	else
	{
		return 0;
	}
}

unsigned int Forest::SendMsg(Msg* RecievedMsg, Msg** SentMsgs)
{
	//at the moment, we aren't responding to any messages
	return 0;
}

void Forest::SetTerrainObject(Terrain* NewTerrainObject)
{
	TerrainObject = NewTerrainObject;
}

void Forest::SetCreationDevice(LPDIRECT3DDEVICE9 NewCreationDevice)
{
	CreationDevice = NewCreationDevice;
}

#define MINTREEDISTANCE 1.5f //the closest 2 trees can ever get
#define MINTREECREATIONDISTANCE 2.0f //the closest we will ever create 2 trees
#define TREESPAWNHEIGHT 2.0f
#define NUTRIENTTAKE 7
#define NUTRIENTTOGROWTHFACTOR .006f

void Forest::Grow()
{
	assert(TerrainObject!=NULL);
	assert(CreationDevice != NULL);

	//If there are no trees, create a tree at the origin of the forest
	if(NumTrees==0)
	{
		NumTrees=1;
		unsigned char Nutrients = TerrainObject->GetNutrientLevel(Position.x, Position.y);
		//unsigned char TakenNutrients = (unsigned char)(Nutrients*NUTRIENTTAKE);
		unsigned char TakenNutrients;
		if(Nutrients>0)
		{
			TakenNutrients = NUTRIENTTAKE;
		}
		else
		{
			TakenNutrients = 0;
		}
		TerrainObject->TakeNutrients(Position.x, Position.y, TakenNutrients);
		//right now, nutrients range from 0 to 255; we will take 1% of the nutrients
		// to make the tree, and 1 nutrient increases height by .1
		ForestTrees[0] = new Tree();
		ForestTrees[0]->Height = TakenNutrients*NUTRIENTTOGROWTHFACTOR;
		ForestTrees[0]->Position.x = Position.x;
		ForestTrees[0]->Position.y = Position.y;
		//we add height/2 to the terrainheight b/c the center of the cylinder is where the center of the object is considered for drawing.
		ForestTrees[0]->Position.z = TerrainObject->GetHeight(ForestTrees[0]->Position.x, ForestTrees[0]->Position.y) + ForestTrees[0]->Height/2.0f;
		ForestTrees[0]->ShaftMesh = new Mesh();

		LPD3DXMESH NewMesh;

		D3DXCreateCylinder(CreationDevice, ForestTrees[0]->Height/10.0f, ForestTrees[0]->Height/10.0f, ForestTrees[0]->Height, 5,2,&NewMesh,NULL);
		ForestTrees[0]->ShaftMesh->SetD3DXMesh(NewMesh);

		D3DMATERIAL9 NewMaterial;
		memset(&NewMaterial,0, sizeof(NewMaterial));
		NewMaterial.Ambient.a = 1;
		NewMaterial.Ambient.b = 0;
		NewMaterial.Ambient.r = 0;
		NewMaterial.Ambient.g = 1;
		NewMaterial.Diffuse = NewMaterial.Ambient;
		ForestTrees[0]->ShaftMesh->SetMaterials(1, &NewMaterial);

		NewTrees[0] = ForestTrees[0];
		NumNewTrees = 1;
	}
	else
	{
		bool Failed = true;
		
		//first we see if we can make a new tree somewhere.
		//HACK: This is a brute force solution to the problem, which should be
		// replaced if we intend to use this kind of method
		//We will try to create a new baby tree from any tree that has height of at least TREESPAWNHEIGHT
		// and hasn't already spawned
		for(int SelectedTree=0;SelectedTree<NumTrees;SelectedTree++)
		{
			if(!ForestTrees[SelectedTree]->HasSpawnedChildren && ForestTrees[SelectedTree]->Height > TREESPAWNHEIGHT)
			{
				ForestTrees[SelectedTree]->HasSpawnedChildren = true;

				//we need to try and create a tree at all 8 angles;
				//TODO:for now, all trees will face the same direction, but that
				// could be changed by modifying the starting angle.  Also,
				// we should add some random error term so the forest doesn't
				// look so geometrically exact
				float StartingAngle = RandFloat(PI/4.0f);
				for(int k=0;k<8;k++)
				{
					float SelectedAngle = StartingAngle + k*PI/4.0f +RandFloat(PI/16.0f);       
					Tree* CreatedTree = new Tree();
								
					//Decide how far away we want to be
					float Distance = RandFloat(2) + MINTREECREATIONDISTANCE + TREESPAWNHEIGHT/10.0f;
					//Calculate the position of the new tree
					CreatedTree->Position.x = ForestTrees[SelectedTree]->Position.x + (float)cos(SelectedAngle)*Distance;
					CreatedTree->Position.y = ForestTrees[SelectedTree]->Position.y + (float)sin(SelectedAngle)*Distance;
					CreatedTree->Position.z = TerrainObject->GetHeight(CreatedTree->Position.x, CreatedTree->Position.y) + CreatedTree->Height/2.0f;
					//See if it conflicts with any other trees.
					Failed = false;
	            
					//Make sure it is a valid position first
					if((CreatedTree->Position.x > TerrainObject->GetWidth()-1)||(CreatedTree->Position.y > TerrainObject->GetLength()-1)||(CreatedTree->Position.y<0)||(CreatedTree->Position.x<0))
					{
						Failed = true;
					}
					else
					{
						for(int i=0;i<NumTrees;i++)
						{
							if(D3DXVec3Length(&(CreatedTree->Position-ForestTrees[i]->Position)) < ForestTrees[i]->Height/5.0f + MINTREECREATIONDISTANCE)
							{
								Failed = true;
							}
						}

						//make sure there are some nutrients left as well.
						if(TerrainObject->GetNutrientLevel(CreatedTree->Position.x, CreatedTree->Position.y) <= 0)
							Failed = true;
					}		
				
					if(Failed)
					{
						delete CreatedTree;
					}
					else
					{
						if(NumTrees==MaxNumTrees)
						{
							Tree** TempTrees = new Tree*[MaxNumTrees+CHUNK];
							for(int j=0;j<NumTrees;j++)
							{
								TempTrees[j] = ForestTrees[j];
							}
							delete []ForestTrees;
							ForestTrees = TempTrees;
							MaxNumTrees+=CHUNK;
						}

						ForestTrees[NumTrees] = CreatedTree;

						//HACK: This is just thrown in so that it doesn't crash if
						// this program is being run in an automation mode;
						// in reality, we should only hit this line if the Grow function
						// is called a LOT of times at once (like when it is being initialized,
						// for example.  In reality, we should levae it at the assert, or
						// do something better to secure against the situation where we
						// queue up more messages than MAXMESSAGES.  Perhaps store them
						// in a sort of internal queue and send them next time?
						//assert(NumNewTrees < MAXMESSAGES);
						if(NumNewTrees>=MAXMESSAGES)
						{
							return;
						}

                        NewTrees[NumNewTrees] = CreatedTree;
						NumNewTrees++;
						

						unsigned char Nutrients = TerrainObject->GetNutrientLevel(ForestTrees[NumTrees]->Position.x, ForestTrees[NumTrees]->Position.y);
						//unsigned char TakenNutrients = (unsigned char)(Nutrients*NUTRIENTTAKE);
						unsigned char TakenNutrients;
						if(Nutrients>0)
						{
							TakenNutrients = NUTRIENTTAKE;
						}
						else
						{
							TakenNutrients = 0;
						}
						TerrainObject->TakeNutrients(ForestTrees[NumTrees]->Position.x, ForestTrees[NumTrees]->Position.y, TakenNutrients);
						ForestTrees[NumTrees]->Height = TakenNutrients*NUTRIENTTOGROWTHFACTOR;

						ForestTrees[NumTrees]->ShaftMesh = new Mesh();
						LPD3DXMESH NewMesh;

						D3DXCreateCylinder(CreationDevice, ForestTrees[NumTrees]->Height/10.0f, ForestTrees[NumTrees]->Height/10.0f, ForestTrees[NumTrees]->Height, 5,2,&NewMesh,NULL);	
						ForestTrees[NumTrees]->ShaftMesh->SetD3DXMesh(NewMesh);
						D3DMATERIAL9 NewMaterial;
						memset(&NewMaterial,0, sizeof(NewMaterial));
						NewMaterial.Ambient.a = 1;
						NewMaterial.Ambient.b = 0;
						NewMaterial.Ambient.r = 0;
						NewMaterial.Ambient.g = 1;
						NewMaterial.Diffuse = NewMaterial.Ambient;
						ForestTrees[NumTrees]->ShaftMesh->SetMaterials(1, &NewMaterial);						

						NumTrees++;
					}
				}
			}
		}
		

		//then, increase the size of every existing tree.  Suck up 2% of the nutrients into
		//every tree in the list
		for(int i=0;i<NumTrees;i++)
		{
			//see if there is more nutrient to suck.
			unsigned char Nutrients = TerrainObject->GetNutrientLevel(ForestTrees[i]->Position.x,ForestTrees[i]->Position.y);
			if(Nutrients > 0)
			{
				//If there are nutrients, we want to see if we are already to close to the nearest
				// tree; if we are, then we don't grow
				bool TooClose = false;
				for(int j=0;j<NumTrees;j++)
				{
					//TODO: Maybe make this ignore trees that are really small?
                    if((j!=i)&&(D3DXVec3Length(&(ForestTrees[i]->Position-ForestTrees[j]->Position))-(ForestTrees[i]->Height/5.0f+ForestTrees[j]->Height/5.0f)) < MINTREEDISTANCE)
					{
						TooClose = true;
						break;
					}
				}

				if(!TooClose)
				{
					//there are nutrients, so we need to readjust the height and such.
					//unsigned char TakenNutrients = (unsigned char)(Nutrients * 0.02f);
					unsigned char TakenNutrients;
					if(Nutrients>0)
					{
						TakenNutrients = NUTRIENTTAKE;
					}
					else
					{
						TakenNutrients = 0;
					}
					TerrainObject->TakeNutrients(ForestTrees[i]->Position.x,ForestTrees[i]->Position.y, TakenNutrients);
					
					ForestTrees[i]->Height += TakenNutrients*NUTRIENTTOGROWTHFACTOR;
					//we add height/2 to the terrainheight b/c the center of the cylinder is where the center of the object is considered for drawing.
					ForestTrees[i]->Position.z = TerrainObject->GetHeight(ForestTrees[i]->Position.x, ForestTrees[i]->Position.y) + ForestTrees[i]->Height/2.0f;

					//delete the old mesh of the tree
					ForestTrees[i]->ShaftMesh->DeleteD3DXMesh();

					LPD3DXMESH NewMesh;
					D3DXCreateCylinder(CreationDevice, ForestTrees[i]->Height/10.0f, ForestTrees[i]->Height/10.0f, ForestTrees[i]->Height, 5,2,&NewMesh,NULL);
					ForestTrees[i]->ShaftMesh->SetD3DXMesh(NewMesh);
				}
			} 
		}
	}
	TerrainObject->Update();
}

void Forest::KillTrees()
{
	if(ForestTrees != NULL)
	{
		for(int i=0; i<NumTrees; i++)
		{
			if( ForestTrees[i] != NULL )
			{
				ForestTrees[i]->ShaftMesh->cleanup();
			}
		}
	}
	NumNewTrees = 0;
	NumTrees = 0;
}

Tree::Tree()
{
	ShaftMesh = NULL;
	HasSpawnedChildren = false;

    m_scale = 1.0f;
	D3DXMatrixIdentity(&m_rotationMatrix);
}

Tree::~Tree()
{
	if(ShaftMesh!=NULL)
	{
		delete ShaftMesh;
	}
}

void Tree::GetMeshes(Mesh** MeshArray)
{
	MeshArray[0] = ShaftMesh;
}

int Tree::GetMeshCount()
{
	return 1;
}

unsigned int Tree::GetAction(float TimeStep, Msg** SentMsgs)
{
	return 0;
}

unsigned int Tree::SendMsg(Msg* RecievedMsg, Msg** SentMsgs)
{
	return 0;
}

#define SMALLESTNOTICABLETREE .5f
int Forest::GetTreesWithinDistance(float XPos, float YPos, float Distance, int& TreeCount, Tree**& CloseTrees)
{
	TreeCount = 0;
	float DistSquared = Distance*Distance;

	//The way I allocate the memory for the array here is kind of screwy; it 
	// is a bit faster, b/c we only need to go through the array once, but it
	// consumes more memory at first.
	Tree** TempTrees = new Tree*[NumTrees];
	for(int i=0;i<NumTrees;i++)
	{
		if(ForestTrees[i]->Height > SMALLESTNOTICABLETREE)
		{
			if((XPos-ForestTrees[i]->Position.x)*(XPos-ForestTrees[i]->Position.x)+(YPos-ForestTrees[i]->Position.y)*(YPos-ForestTrees[i]->Position.y)<DistSquared)
			{
				TempTrees[TreeCount] = ForestTrees[i];
				TreeCount++;
			}
		}
	}

	//now we copy the temptrees array over into an appropriately sized closetrees array
	CloseTrees = new Tree*[TreeCount];
	memcpy(CloseTrees, TempTrees, TreeCount*sizeof(Tree*));
	delete []TempTrees;
	return TreeCount;
}

// returns tree count
int Forest::GetTrees(Tree**& AllTrees)
{
	AllTrees = ForestTrees;
	return NumTrees;
}












