#include "Generator.h"
#include "Debugger.h"

Generator::Generator()
{
	sentence = "T";
	m_tree_size = 1;
	m_height_radius_ratio = 0.5;
	m_sphere_radius_ratio = 0.9;
	m_max_angle = 45;
	m_turbulence_angle = 10;

	m_branch_number_of_new_branches_spawned_by_one_trunk = 4;
	m_branch_number_of_new_branches = 3;
	m_branch_number_of_new_branches_fluctuation = 0.5; // If this is 1.0f the "m_branch_number_of_new_branches" will not be changed randomly
	
	minShrinkRate = 0.5;

	m_leaf_density = 0.2;
	m_leaf_height = 10;

	randomSeed = 9;
	srand(randomSeed);
}


Generator::Generator(float treeSize, float heightRadiusRatio, float radiusRatio, float maxAngle, float turbulenceAngle, int branchNumber, int branchHeight, float p_furcate, float p_leaf, int leafHeight, int randomSeed)
{
	this->sentence = "T";
	this->m_tree_size = treeSize; // actually, this is tree size not the groundSphereRadius...
	this->m_height_radius_ratio = heightRadiusRatio;
	this->m_sphere_radius_ratio = radiusRatio;
	this->m_max_angle = maxAngle;
	this->m_turbulence_angle = turbulenceAngle;
	
	this->m_branch_number_of_new_branches_spawned_by_one_trunk = branchNumber;
	this->m_branch_number_of_new_branches = branchHeight;
	this->m_branch_number_of_new_branches_fluctuation = p_furcate;

	this->minShrinkRate = 0.5;

	this->m_leaf_density = p_leaf;
	this->m_leaf_height = leafHeight;

	this->randomSeed = randomSeed;
	srand(randomSeed);
}


Generator::~Generator()
{

}

int Generator::addNewLeaf(float height, unsigned int ibindex, glm::mat4 modelToWorldMatrix) {
	// Meta model generator
	MetaModels meta = MetaModels(LLeaves);
	//meta.setStartIndex(ibindex);

	// Vertex buffer and index buffer
	float *vb;
	unsigned int *ib;
	meta.setLenWidth(height, height);
	meta.setGrid(1, 1);
	BSize size = meta.getDataSize(LLeaves);
	vb = (float *)malloc(size.vBuffer);
	ib = (unsigned int *)malloc(size.iBuffer);


	int unitSize = sizeof(float) * (3 + 3 + 2);
	int vertexNumber = size.vBuffer / unitSize;
	int indexNumber = size.iBuffer / (3 * sizeof(unsigned int));

	meta.bindBuffers(vb, ib);
	meta.genModels();

	// Translate and rotate (model to world)
	for (int j = 0; j < vertexNumber; j++)
	{
		// x, y, z
		glm::vec4 coord(vb[j * 8 + 0], vb[j * 8 + 1], vb[j * 8 + 2], 1);
		glm::vec4 coordNew = modelToWorldMatrix * coord;
		leafVertexVector.push_back(coordNew.x);
		leafVertexVector.push_back(coordNew.y);
		leafVertexVector.push_back(coordNew.z);
		// Nx, Ny, Nz
		glm::vec4 ncoord(vb[j * 8 + 3], vb[j * 8 + 4], vb[j * 8 + 5], 0);
		glm::vec3 ncoordNew = modelToWorldMatrix * ncoord;
		leafVertexVector.push_back(ncoordNew.x);
		leafVertexVector.push_back(ncoordNew.y);
		leafVertexVector.push_back(ncoordNew.z);
		// u, v
		leafVertexVector.push_back(vb[j * 8 + 6]);
		leafVertexVector.push_back(vb[j * 8 + 7]);
	}
	for (int j = 0; j < indexNumber; j++)
	{
		leafIndexVector.push_back(ib[j * 3 + 0] + ibindex);
		leafIndexVector.push_back(ib[j * 3 + 1] + ibindex);
		leafIndexVector.push_back(ib[j * 3 + 2] + ibindex);
	}
	/*lmodel.vertexBuffer = vb;
	lmodel.indexBuffer = ib;
	lmodel.size = size;
	lmodel.modelMatrix = modelToWorldMatrix;*/
	return vertexNumber;
}

int Generator::addNewBranch(float radiusUp, float radiusBottom, float height, unsigned int ibindex, glm::mat4 modelToWorldMatrix)
{
	// Meta model generator
	MetaModels meta = MetaModels(LBranch);
	//meta.setStartIndex(ibindex);

	// Vertex buffer and index buffer
	float *vb;
	unsigned int *ib;
	meta.setUDHA(radiusUp, radiusBottom, height, 90.f * (height / 30) * (height / 30));
	BSize size = meta.getDataSize(LBranch);
	vb = (float *)malloc(size.vBuffer);
	ib = (unsigned int *)malloc(size.iBuffer);
	

	int unitSize = sizeof(float) * (3 + 3 + 2);
	int vertexNumber = size.vBuffer / unitSize;
	int indexNumber = size.iBuffer / (3 * sizeof(unsigned int));
				
	meta.bindBuffers(vb, ib);
	meta.genModels(); 
	
	// Translate and rotate (model to world)
	for (int j = 0; j < vertexNumber; j++)
	{
		// x, y, z
		glm::vec4 coord(vb[j * 8 + 0], vb[j * 8 + 1], vb[j * 8 + 2], 1);
		glm::vec4 coordNew = modelToWorldMatrix * coord;
		branchVertexVector.push_back(coordNew.x);
		branchVertexVector.push_back(coordNew.y);
		branchVertexVector.push_back(coordNew.z);
		// Nx, Ny, Nz
		glm::vec4 ncoord(vb[j * 8 + 3], vb[j * 8 + 4], vb[j * 8 + 5], 0);
		glm::vec3 ncoordNew = modelToWorldMatrix * ncoord;
		branchVertexVector.push_back(ncoordNew.x);
		branchVertexVector.push_back(ncoordNew.y);
		branchVertexVector.push_back(ncoordNew.z);
		// u, v
		branchVertexVector.push_back(vb[j * 8 + 6]);
		branchVertexVector.push_back(vb[j * 8 + 7]);
	}
	for (int j = 0; j < indexNumber; j++)
	{
		branchIndexVector.push_back(ib[j * 3 + 0] + ibindex);
		branchIndexVector.push_back(ib[j * 3 + 1] + ibindex);
		branchIndexVector.push_back(ib[j * 3 + 2] + ibindex);
	}
	/*lmodel.vertexBuffer = vb;
	lmodel.indexBuffer = ib;
	lmodel.size = size;
	lmodel.modelMatrix = modelToWorldMatrix;*/
	return vertexNumber;
}


float getRandom(float max)
{
	return rand() / float(RAND_MAX) * max;
}


// return value: the index of L-system sentence
int Generator::generate(glm::vec3 positionUp, glm::vec3 positionBottom, int i, unsigned int& ibindex, unsigned int& ilindex, int& modelindex, float va, float ha, float h, float radiusup, float radiusbottom, int treeheight)
{
	// The position of two globes of spheres
	//glm::vec3 globePositionUp = positionUp, globePositionBottom = positionBottom;
	// The height of the branch, and two radius of spheres
	float height = h, radiusUp = radiusup, radiusBottom = radiusbottom;
	// Two angles that control the orientation of branch
	float verticalAngle= va, horizontalAngle=ha, horizontalAngleItr=360/m_branch_number_of_new_branches_spawned_by_one_trunk;
	// L-sentence index
	int index, treeHeight = treeheight;
	// Variables used in switch case
	float previousHeight, heightRatio, verticalAngleNew, horizontalAngleNew, shrinkRate, newHeight;
	glm::mat4 T(1.f), R1(1.f), R2(1.f), modelToWorldMatrix(1.f), Rr3(1.f), Rr4(1.f);

	for (index = i; index < sentence.length(); index++)
	{
		char word = sentence[index];
		switch (word)
		{
		case 'A':
			treeheight += 1;
			// Compute translate and rotate matrix
			computeModelMatrix(positionUp, positionBottom, Rr3, Rr4, T);
			// Model matrix
			modelToWorldMatrix = T * Rr3 * Rr4;
			// Update index start number
			// modelBuffer[modelindex].height = treeheight;
			ibindex = ibindex + addNewBranch(radiusUp, radiusBottom, height, ibindex, modelToWorldMatrix);
			// Update two radius and height
			radiusBottom = radiusUp;
			radiusUp = radiusUp * m_sphere_radius_ratio;
			previousHeight = height;
			height = height * m_sphere_radius_ratio;
			// Compute and update two globes
			glm::vec3 tempUp = modelToWorldMatrix * glm::vec4(0, previousHeight, 0, 1);
			heightRatio = height / previousHeight;
			positionUp = (tempUp - positionBottom) * heightRatio + tempUp;
			positionBottom = tempUp;
			modelindex += 1;
			break;
		case 'L':	
			// VerticalAngle random
			verticalAngleNew = getRandom(m_max_angle);
			// HorizontalAngle
			horizontalAngleNew = getRandom(360);
			// Compute translate and rotate matrix
			computeModelMatrix(positionUp, positionBottom, Rr3, Rr4, T);
			// Compute rotate matrix 2
			computeModelMatrix2(positionUp, positionBottom, verticalAngleNew, horizontalAngleNew, R1, R2);
			modelToWorldMatrix = T * R2 * R1 * Rr3 * Rr4;
			// Update index start number
			ilindex = ilindex + addNewLeaf(height, ilindex, modelToWorldMatrix);
			modelindex += 1;
			break;
		case 'T':	break;
		case '[':
			// VerticalAngle random
			verticalAngleNew = getRandom(m_max_angle);
			// HorizontalAngle
			horizontalAngle = horizontalAngle + horizontalAngleItr + getRandom(m_turbulence_angle);
			// Compute translate and rotate matrix
			computeModelMatrix(positionUp, positionBottom, Rr3, Rr4, T);
			// Compute rotate matrix 2
			computeModelMatrix2(positionUp, positionBottom, verticalAngleNew, horizontalAngle, R1, R2);
			modelToWorldMatrix = T * R2 * R1 * Rr3 * Rr4;
			// Compute shrinkage rate
			shrinkRate = 1 - verticalAngleNew / m_max_angle * minShrinkRate;
			// newHeight = height * shrinkRate;
			newHeight = height;
			glm::vec3 globePositionUp2 = modelToWorldMatrix * glm::vec4(0, newHeight, 0 , 1);
			glm::vec3 globePositionBottom2 = positionBottom;
			index = generate(globePositionUp2, globePositionBottom2, index+1, ibindex, ilindex, modelindex, verticalAngleNew, horizontalAngle, newHeight, radiusUp * shrinkRate, radiusBottom * shrinkRate, treeHeight);
			break;
		case ']':
			return index;
		default:
			break;
		}
	}
	return index;
}


void Generator::generate(int times)
{
	Debugger* debugger = Debugger::GetInstance();
	// Construct L-system sentence
	LReductor reductor(m_branch_number_of_new_branches,m_leaf_height,m_branch_number_of_new_branches_spawned_by_one_trunk, randomSeed, m_branch_number_of_new_branches_fluctuation, m_leaf_density);
	reductor.setLSentence(sentence);
	for (int i = 0; i < times; i++)
	{
		reductor.reduction();
		sentence = reductor.getLSentence();
		debugger->WriteMessage("L-system sentence: \n" + sentence + "\n");
	}
	// Count the number of A and L
	// countPointerNumber();
	generateTreeModel();
}


void Generator::generateOnce()
{
	Debugger* debugger = Debugger::GetInstance();
	// Construct L-system sentence
	LReductor reductor(m_branch_number_of_new_branches, m_leaf_height, m_branch_number_of_new_branches_spawned_by_one_trunk, randomSeed, m_branch_number_of_new_branches_fluctuation, m_leaf_density);
	reductor.setLSentence(sentence);
	reductor.reduction();
	sentence = reductor.getLSentence();
	debugger->WriteMessage("L-system sentence: \n"+ sentence + "\n");
	// Count the number of A and L
	// countPointerNumber();
	generateTreeModel();
}


//void Generator::countPointerNumber()
//{
//	int counter = 0;
//	for (int i = 0; i < sentence.length(); i++)
//	{
//		char word = sentence[i];
//		if (word == 'A')
//			counter += 1;
//		if (word == 'L')
//			counter += 1;
//	}
//	bufferNumber = counter;
//}
//
//
//int Generator::getBufferLength()
//{
//	return bufferNumber;
//}


void Generator::generateTreeModel()
{
	// Generate tree model
	unsigned int ibindex = 0;
	unsigned int ilindex = 0;
	int modelindex = 0;
	float height = m_tree_size * (1 + m_sphere_radius_ratio) * (2 * m_height_radius_ratio + 1);
	generate(glm::vec3(0, height, 0), glm::vec3(0, 0, 0), 0, ibindex, ilindex, modelindex, 0, 0, height, m_tree_size*m_sphere_radius_ratio, m_tree_size, 0);
}


void Generator::computeModelMatrix(glm::vec3 globePositionUp, glm::vec3 globePositionBottom, glm::mat4 &Rr3, glm::mat4 &Rr4, glm::mat4 &T)
{
	// Compute orientation of up to bottom
	glm::vec3 ori1 = globePositionUp - globePositionBottom;
	float theta1 = acos(ori1.y / glm::length(ori1));
	float lengthXZ = glm::length(glm::vec2(ori1.x, ori1.z));
	float theta2 = lengthXZ == 0 ? 0 : acos(ori1.z / lengthXZ);
	if (ori1.x < 0)	theta2 = glm::radians(360.f) - theta2;
	Rr3 = glm::rotate(glm::mat4(1.f), theta2, glm::vec3(0, 1, 0));
	Rr4 = glm::rotate(glm::mat4(1.f), theta1, glm::vec3(1, 0, 0));
	T = glm::translate(glm::mat4(1.f), globePositionBottom);
}


void Generator::computeModelMatrix2(glm::vec3 globePositionUp, glm::vec3 globePositionBottom, float verticalAngleNew, float horizontalAngle, glm::mat4 &R1, glm::mat4 &R2)
{
	glm::vec3 ori1 = globePositionUp - globePositionBottom;
	glm::vec3 axis1 = glm::vec3(ori1.y, -ori1.x, 0);
	R1 = glm::rotate(glm::mat4(1.f), glm::radians(verticalAngleNew), axis1);
	R2 = glm::rotate(glm::mat4(1.f), glm::radians(horizontalAngle), ori1);
}

BFSize Generator::getBranchBufferSize() {
	BFSize size;
	size.iBuffer = branchIndexVector.size();
	size.vBuffer = branchVertexVector.size();
	return size;
}
BFSize Generator::getLeafBufferSize() {
	BFSize size;
	size.iBuffer = leafIndexVector.size();
	size.vBuffer = leafVertexVector.size();
	return size;
}

void Generator::transferToBranchBuffer()
{
	if (!branchVertexVector.empty())
	{
		memcpy(branchVertexBuffer, &branchVertexVector[0], branchVertexVector.size() * sizeof(float));
	}

	if (!branchIndexVector.empty())
	{
		memcpy(branchIndexBuffer, &branchIndexVector[0], branchIndexVector.size() * sizeof(unsigned int));
	}

}

void Generator::transferToLeafBuffer()
{
	if (!leafVertexVector.empty())
	{
		memcpy(leafVertexBuffer, &leafVertexVector[0], leafVertexVector.size() * sizeof(float));
	}

	if (!leafIndexVector.empty())
	{
		memcpy(leafIndexBuffer, &leafIndexVector[0], leafIndexVector.size() * sizeof(unsigned int));
	}

}


/*
int main()
{
	//fast way to generate model but can't save as obj
	Generator* generator = new Generator();
	LModel* lmodelBuffer;
	generator->bindBuffer(lmodelBuffer);
	//The model at Day 20
	generator->generate(20);
	int length = generator->getBufferLength();
	for (int i = 0; i < length; i++)
	{
		LModel model = lmodelBuffer[i];
		//you need to get correct coordinates by using this matrix
		glm::mat4 modelToWorldMatrix = model.modelMatrix;
		int vertexBufferLength = model.size.vBuffer;
		int indexBufferLength = model.size.iBuffer;
		float* vertexBuffer = model.vertexBuffer;
		unsigned int* indexBuffer = model.indexBuffer;
		//you can use height to know when (at which day) that model generated
		int height = model.height;
	}
	//The model at Day 21
	generator->generateOnce();
	int length = generator->getBufferLength();
	for (int i = 0; i < length; i++)
	{
		LModel model = lmodelBuffer[i];
		glm::mat4 modelToWorldMatrix = model.modelMatrix;
		int vertexBufferLength = model.size.vBuffer;
		int indexBufferLength = model.size.iBuffer;
		float* vertexBuffer = model.vertexBuffer;
		unsigned int* indexBuffer = model.indexBuffer;
		//you can use height to know when (at which day) that model generated
		int height = model.height;
	}


	//slow because generator need to computer coordinate by multiplying the 
	//matrix with metamodel's coordinates and normals, and transfer from vector
	//to pointer, but you can save it as obj file
	Generator* generator = new Generator();
	float* vertexBuffer;
	unsigned int* indexBuffer;
	generator->bindBuffers(vertexBuffer,indexBuffer);
	//The model at Day 20
	generator->generate(20);
	generator->transferToBuffer();
	//then use vertexBuffer and indexBuffer
	
	Generator* generator = new Generator();
	//The model at Day 20
	generator->generate(20);
	// Generate tree model
	generator->generateTreeModel();
	generator->saveAsObj();
	std::cout << "FINISH!" << std::endl;
	getchar();
}*/



//void Generator::bindBuffer(LModel *modelbuffer)
//{
//	this->modelBuffer = modelbuffer;
//}

void Generator::bindBranchBuffers(float *vbuffer, unsigned int *ibuffer)
{
	branchVertexBuffer = vbuffer;
	branchIndexBuffer = ibuffer;
}

void Generator::bindLeafBuffers(float *vbuffer, unsigned int *ibuffer)
{
	leafVertexBuffer = vbuffer;
	leafIndexBuffer = ibuffer;
}

void Generator::saveAsObj()
{
	Debugger::GetInstance()->WriteMessage("Writing tree data to OBJ file...");

	int number_of_vertex = branchVertexVector.size() / 8;
	int number_of_index = branchIndexVector.size() / 3;

	std::ofstream file;
	file.open(Debugger::GetInstance()->GetProgramPath() + "\\580_Rendering_Project\\Resources\\Models\\Thank you and have a nice vacation.obj");
	int count = 0;
	while (count < number_of_vertex)
	{
		file << "v " << branchVertexVector[count * 8 + 0] << " " << branchVertexVector[count * 8 + 1] << " " << branchVertexVector[count * 8 + 2] << std::endl;
		count++;
	}
	count = 0;
	while (count < number_of_vertex)
	{
		file << "vn " << branchVertexVector[count * 8 + 3] << " " << branchVertexVector[count * 8 + 4] << " " << branchVertexVector[count * 8 + 5] << std::endl;
		count++;
	}
	count = 0;
	while (count < number_of_vertex)
	{
		file << "vt " << branchVertexVector[count * 8 + 6] << " " << branchVertexVector[count * 8 + 7] << std::endl;
		count++;
	}
	count = 0;
	while (count < number_of_index)
	{
		file << "f "
			<< branchIndexVector[count * 3 + 0] + 1 << "/" << branchIndexVector[count * 3 + 0] + 1 << "/" << branchIndexVector[count * 3 + 0] + 1 << " "
			<< branchIndexVector[count * 3 + 1] + 1 << "/" << branchIndexVector[count * 3 + 1] + 1 << "/" << branchIndexVector[count * 3 + 1] + 1 << " "
			<< branchIndexVector[count * 3 + 2] + 1 << "/" << branchIndexVector[count * 3 + 2] + 1 << "/" << branchIndexVector[count * 3 + 2] + 1 << std::endl;
		count++;
	}
	file.close();

	Debugger::GetInstance()->WriteMessage("OBJ file saved successfully...");
}
