//NeuralNet.cpp

#include "NeuralNet.h"

NeuralNet::NeuralNet(int numInputs, int dimensions[], int numOutputs, int actFunction) //randomly created
{
	numberOfInputs = numInputs;
	numberOfOutputs = numOutputs;
	activationFunction = actFunction;
	fitness=0;
	numHiddenLayers = dimensions[0];
	hiddenLayerSizes = new int[numHiddenLayers];

	for (int i=0; i<numHiddenLayers; i++)
		hiddenLayerSizes[i] = dimensions[i+1];

	listOfLayers = new Neuron**[numHiddenLayers+2];

	//creating input layer
	listOfLayers[0] = new Neuron*[numberOfInputs];
	for (int i=0; i<numberOfInputs; i++)
	{
		listOfLayers[0][i] = new Neuron(0, 0, 0, activationFunction);
	}

	//creating hidden layers
	for (int i=1; i<=numHiddenLayers; i++)
	{
		listOfLayers[i] = new Neuron*[hiddenLayerSizes[i-1]];
		for (int j=0; j<hiddenLayerSizes[i-1]; j++)
		{
////////////////////////CHANGED
			if (i==1) //layer right after input layer
			{
				float* randomWeights = new float[numberOfInputs];
				for (int k=0; k<numberOfInputs; k++)
				{
					if (CONTINUOUS_WEIGHT_VALUES)
					{
						randomWeights[k] = FRAND; //continuous number between -1.0 and 1.0
					}
					else
					{
						randomWeights[k] = DISCRETE_FRAND; //discrete number between -1.0 and 1.0
					}
				}
				listOfLayers[i][j] = new Neuron(numberOfInputs, listOfLayers[0], randomWeights, activationFunction);
				delete [] randomWeights;
			}
			else
			{
				float* randomWeights = new float[hiddenLayerSizes[i-1]];
				for (int k=0; k<hiddenLayerSizes[i-1]; k++)
				{
					if (CONTINUOUS_WEIGHT_VALUES)
					{
						randomWeights[k] = FRAND; //continuous number between -1.0 and 1.0
					}
					else
					{
						randomWeights[k] = DISCRETE_FRAND; //discrete number between -1.0 and 1.0
					}
				}
				listOfLayers[i][j] = new Neuron(hiddenLayerSizes[i-1], listOfLayers[i-1], randomWeights, activationFunction);
				delete [] randomWeights;
			}
////////////////////////CHANGED

		}
	}

	//creating output layer
	listOfLayers[numHiddenLayers+1] = new Neuron*[numberOfOutputs];
	for (int i=0; i<numberOfOutputs; i++)
	{
		float* randomWeights = new float[hiddenLayerSizes[numHiddenLayers-1]];
		for (int k=0; k<hiddenLayerSizes[numHiddenLayers-1]; k++)
		{
			if (CONTINUOUS_WEIGHT_VALUES)
			{
				randomWeights[k] = FRAND; //continuous number between -1.0 and 1.0
			}
			else
			{
				randomWeights[k] = DISCRETE_FRAND; //discrete number between -1.0 and 1.0
			}
		}

		listOfLayers[numHiddenLayers+1][i] = 
			/*new Neuron(hiddenLayerSizes[numHiddenLayers-1], listOfLayers[numHiddenLayers-1], randomWeights, activationFunction);*/
			new Neuron(hiddenLayerSizes[numHiddenLayers-1], listOfLayers[numHiddenLayers], randomWeights, activationFunction);

		delete [] randomWeights;
	}

	//creating genes
	genes = new NNGeneString(numberOfInputs, numberOfOutputs, numHiddenLayers, hiddenLayerSizes, listOfLayers);
}

NeuralNet::NeuralNet(NNGeneString* newGenes, int actFunction) //create from given genes
{
	numberOfInputs = newGenes->numberOfInputs;
	numberOfOutputs = newGenes->numberOfOutputs;
	activationFunction = actFunction;
	fitness=0;
	numHiddenLayers = newGenes->numberOfHiddenLayers;
	hiddenLayerSizes = new int[numHiddenLayers];

	for (int i=0; i<numHiddenLayers; i++)
		hiddenLayerSizes[i] = newGenes->hiddenLayerSizes[i];

	listOfLayers = new Neuron**[numHiddenLayers+2];

	//creating input layer
	listOfLayers[0] = new Neuron*[numberOfInputs];
	for (int i=0; i<numberOfInputs; i++)
	{
		listOfLayers[0][i] = new Neuron(0, 0, 0, activationFunction);
	}

	//creating hidden layers
	for (int i=1; i<=numHiddenLayers; i++)
	{
		listOfLayers[i] = new Neuron*[hiddenLayerSizes[i-1]];
		for (int j=0; j<hiddenLayerSizes[i-1]; j++)
		{
////////////////////////CHANGED


			if (i==1) //layer right after input layer
			{
				float* weights = new float[numberOfInputs];
				for (int k=0; k<numberOfInputs; k++)
				{
					weights[k] = newGenes->weights[i-1][j][k];
				}
				listOfLayers[i][j] = new Neuron(numberOfInputs, listOfLayers[0], weights, activationFunction);
				delete [] weights;
			}
			else
			{
				float* weights = new float[hiddenLayerSizes[i-1]];
				for (int k=0; k<hiddenLayerSizes[i-1]; k++)
				{
					weights[k] = newGenes->weights[i-1][j][k];
				}
				listOfLayers[i][j] = new Neuron(hiddenLayerSizes[i-1], listOfLayers[i-1], weights, activationFunction);
				delete [] weights;	
			}


////////////////////////CHANGED
		}
	}

	//creating output layer
	listOfLayers[numHiddenLayers+1] = new Neuron*[numberOfOutputs];
	for (int i=0; i<numberOfOutputs; i++)
	{
		float* weights = new float[hiddenLayerSizes[numHiddenLayers-1]];
		for (int k=0; k<hiddenLayerSizes[numHiddenLayers-1]; k++)
		{
			weights[k] = newGenes->weights[numHiddenLayers][i][k];
		}

		listOfLayers[numHiddenLayers+1][i] = 
			//new Neuron(hiddenLayerSizes[numHiddenLayers-1], listOfLayers[numHiddenLayers-1], weights, activationFunction);
			new Neuron(hiddenLayerSizes[numHiddenLayers-1], listOfLayers[numHiddenLayers], weights, activationFunction);

		delete [] weights;
	}

	//creating genes
	genes = new NNGeneString(numberOfInputs, numberOfOutputs, numHiddenLayers, hiddenLayerSizes, listOfLayers);
}

NeuralNet::NeuralNet(int numInputs, int dimensions[], int numOutputs, FILE* inputFile, int actFunction) //read from file
{
	//inputFile must be already open.
	activationFunction = actFunction;
	fscanf(inputFile, "%f", &fitness); //don't use file's fitness value
	fitness=0;
	fscanf(inputFile, "%d", &numberOfInputs);
	fscanf(inputFile, "%d", &numHiddenLayers);
	hiddenLayerSizes = new int[numHiddenLayers];

	for (int i=0; i<numHiddenLayers; i++)
	{
		fscanf(inputFile, "%d", &hiddenLayerSizes[i]);
	}

	fscanf(inputFile, "%d", &numberOfOutputs);

	for (int i=0; i<numHiddenLayers; i++)
	{
		hiddenLayerSizes[i] = dimensions[i+1];
	}

	//make sure input file specs match the expected values
	if (numInputs != numberOfInputs || numHiddenLayers != dimensions[0] || numOutputs != numberOfOutputs)
	{
		std::cout << "Error: Input neural net file has an unexpected number of neurons.  Quitting..." << std::endl;
		exit(1);
	}
	for (int i=0; i<numHiddenLayers; i++)
	{
		if (hiddenLayerSizes[i] != dimensions[i+1])
		{
			std::cout << "Error: Input neural net file has an unexpected number of neurons.  Quitting..." << std::endl;
			exit(1);
		}
	}

	//Now build listOfLayers from input file values
	listOfLayers = new Neuron**[numHiddenLayers+2];

	//creating input layer
	listOfLayers[0] = new Neuron*[numberOfInputs];
	for (int i=0; i<numberOfInputs; i++)
	{
		listOfLayers[0][i] = new Neuron(0, 0, 0, activationFunction);
	}

	//creating hidden layers
	for (int i=1; i<=numHiddenLayers; i++)
	{
		listOfLayers[i] = new Neuron*[hiddenLayerSizes[i-1]];
		for (int j=0; j<hiddenLayerSizes[i-1]; j++)
		{
////////////////////////CHANGED


			if (i==1) //layer right after input layer
			{
				float* inputFileWeights = new float[numberOfInputs];
				for (int k=0; k<numberOfInputs; k++)
				{
					fscanf(inputFile, "%f", &inputFileWeights[k]);
				}
				listOfLayers[i][j] = new Neuron(numberOfInputs, listOfLayers[0], inputFileWeights, activationFunction);
				delete [] inputFileWeights;	
			}
			else
			{
				float* inputFileWeights = new float[hiddenLayerSizes[i-1]];
				for (int k=0; k<hiddenLayerSizes[i-1]; k++)
				{
					fscanf(inputFile, "%f", &inputFileWeights[k]);
				}
				listOfLayers[i][j] = new Neuron(hiddenLayerSizes[i-1], listOfLayers[i-1], inputFileWeights, activationFunction);
				delete [] inputFileWeights;
			}

			
////////////////////////CHANGED
		}
	}

	//creating output layer
	listOfLayers[numHiddenLayers+1] = new Neuron*[numberOfOutputs];
	for (int i=0; i<numberOfOutputs; i++)
	{
		float* inputFileWeights = new float[hiddenLayerSizes[numHiddenLayers-1]];
		for (int k=0; k<hiddenLayerSizes[numHiddenLayers-1]; k++)
		{
			fscanf(inputFile, "%f", &inputFileWeights[k]);
		}

		listOfLayers[numHiddenLayers+1][i] = 
			//new Neuron(hiddenLayerSizes[numHiddenLayers-1], listOfLayers[numHiddenLayers-1], inputFileWeights, activationFunction);
			new Neuron(hiddenLayerSizes[numHiddenLayers-1], listOfLayers[numHiddenLayers], inputFileWeights, activationFunction);

		delete [] inputFileWeights;
	}

	//creating genes
	genes = new NNGeneString(numberOfInputs, numberOfOutputs, numHiddenLayers, hiddenLayerSizes, listOfLayers);
}

NeuralNet::~NeuralNet()
{
	//deleting input layer
	for (int j=0; j<numberOfInputs; j++)
	{
		delete listOfLayers[0][j]; //deleting individual neurons
	}
	delete [] listOfLayers[0];

	//deleting hidden layers
	for (int i=1; i<=numHiddenLayers; i++)
	{
		for (int j=0; j<hiddenLayerSizes[i-1]; j++)
		{
			delete listOfLayers[i][j]; //deleting individual neurons
		}
		delete [] listOfLayers[i];
	}

	//deleting output layer
	for (int j=0; j<numberOfOutputs; j++)
	{
		delete listOfLayers[numHiddenLayers+1][j]; //deleting individual neurons
	}
	delete [] listOfLayers[numHiddenLayers+1];
	
	delete [] listOfLayers;
	delete [] hiddenLayerSizes;
	delete genes;
}

void NeuralNet::Compute(float inputs[], float outputs[])
{
	for (int i=0; i<numberOfInputs; i++) //send input values to input layer
		listOfLayers[0][i]->SetOutput(inputs[i]);

	for (int i=1; i<=numHiddenLayers; i++)//cycle through hidden layers
	{
		for (int j=0; j<hiddenLayerSizes[i-1]; j++)//cycle through neurons in this layer
		{
			listOfLayers[i][j]->CalculateOutput();
		}
	}

	for (int i=0; i<numberOfOutputs; i++)
	{
		listOfLayers[numHiddenLayers+1][i]->CalculateOutput();
		outputs[i] = listOfLayers[numHiddenLayers+1][i]->GetOutput();
	}
}
#include <iostream>
void NeuralNet::TestOutput()
{
	//use this to make sure each random brain is different
	for (int i=0; i<numberOfOutputs; i++)
	{
		if (i==0)
			std::cout<< listOfLayers[numHiddenLayers+1][i]->GetInputWeights()[0] << std::endl;
	}
}

float NeuralNet::GetFitness()
{
	return fitness;
}

void NeuralNet::SetFitness(float f)
{
	fitness = f;
}

NNGeneString* NeuralNet::GetGenes()
{
	return genes;
}

void NeuralNet::PrintGenesToFile(FILE* outputFile)
{
	//outputFile must already be open

	fprintf(outputFile, "%f\n", fitness);
	fprintf(outputFile, "%d\n", numberOfInputs);
	fprintf(outputFile, "%d ", numHiddenLayers);
	for (int i=0; i<numHiddenLayers; i++)
	{
		fprintf(outputFile, "%d ", hiddenLayerSizes[i]);
	}
	fprintf(outputFile, "\n");
	fprintf(outputFile, "%d\n", numberOfOutputs);

	//printing hidden layers' weights
	for (int i=1; i<=numHiddenLayers; i++) //i is hidden layer index
	{
		for (int j=0; j<hiddenLayerSizes[i-1]; j++) //j is neuron index within hidden layer
		{
			if (i==1) //first hidden layer after input layer
			{
				for (int k=0; k<numberOfInputs; k++)
				{
					fprintf(outputFile, "%f\n", listOfLayers[i][j]->GetInputWeights()[k]);
				}
			}
			else //all other hidden layers
			{
				for (int k=0; k<hiddenLayerSizes[i-1]; k++)
				{
					fprintf(outputFile, "%f\n", listOfLayers[i][j]->GetInputWeights()[k]);
				}
			}
		}
	}

	//printing output layer's weights
	for (int j=0; j<numberOfOutputs; j++) //j is neuron index within output layer
	{
		for (int k=0; k<hiddenLayerSizes[numHiddenLayers-1]; k++)
		{
			fprintf(outputFile, "%f\n", listOfLayers[numHiddenLayers+1][j]->GetInputWeights()[k]);
		}
	}
}

void CrossoverAndMutation(NeuralNet* parent1, NeuralNet* parent2, NNGeneString* &child1, NNGeneString* &child2)
{
	float crossoverPoint = (FRAND+1)/2; //get a number between 0.0 and 1.0
	/*std::cout << "Crossover point is: " << crossoverPoint << std::endl;*/
	//The new genes we are creating won't allocate extra memory until their constructors are called.
	//Mutation is actually performed in the NNGeneString constructor.

	//temporary arrays of pointers
	Neuron*** newListOfLayers1 = NULL;
	Neuron*** newListOfLayers2 = NULL;

	//creating 
	newListOfLayers1 = new Neuron**[parent1->numHiddenLayers+2];
	newListOfLayers2 = new Neuron**[parent1->numHiddenLayers+2];

	//setting pointers to the parents' input layers, which are both the same and 
	//won't really be used anyway...
	newListOfLayers1[0] = parent1->listOfLayers[0];
	newListOfLayers2[0] = parent1->listOfLayers[0];

	//setting weights for hidden layers
	for (int i=1; i<=parent1->numHiddenLayers; i++)
	{
		newListOfLayers1[i] = new Neuron*[parent1->hiddenLayerSizes[i-1]];
		newListOfLayers2[i] = new Neuron*[parent1->hiddenLayerSizes[i-1]];

		for (int j=0; j<parent1->hiddenLayerSizes[i-1]; j++)
		{
			if (j < (crossoverPoint*parent1->hiddenLayerSizes[i-1])) 
			{
				newListOfLayers1[i][j] = parent1->listOfLayers[i][j]; //get weights from parent1
				newListOfLayers2[i][j] = parent2->listOfLayers[i][j]; //get weights from parent2
			}
			else 
			{
				newListOfLayers1[i][j] = parent2->listOfLayers[i][j]; //get weights from parent2
				newListOfLayers2[i][j] = parent1->listOfLayers[i][j]; //get weights from parent1
			}
		}

		////testing crossover
		//std::cout << "Parent1 weights: " << std::endl;
		//for (int j=0; j<parent1->hiddenLayerSizes[i-1]; j++)
		//{
		//	std::cout << "   " << parent1->listOfLayers[i][j]->GetInputWeights()[j] << std::endl;
		//}
		//std::cout << "Parent2 weights: " << std::endl;
		//for (int j=0; j<parent1->hiddenLayerSizes[i-1]; j++)
		//{
		//	std::cout << "   " << parent2->listOfLayers[i][j]->GetInputWeights()[j] << std::endl;
		//}
		//std::cout << "Child1 weights: " << std::endl;
		//for (int j=0; j<parent1->hiddenLayerSizes[i-1]; j++)
		//{
		//	std::cout << "   " << newListOfLayers1[i][j]->GetInputWeights()[j] << std::endl;
		//}
		//std::cout << "Child2 weights: " << std::endl;
		//for (int j=0; j<parent1->hiddenLayerSizes[i-1]; j++)
		//{
		//	std::cout << "   " << newListOfLayers2[i][j]->GetInputWeights()[j] << std::endl;
		//}
	}

	//setting weights for output layer
	newListOfLayers1[parent1->numHiddenLayers+1] = new Neuron*[parent1->numberOfOutputs];
	newListOfLayers2[parent1->numHiddenLayers+1] = new Neuron*[parent1->numberOfOutputs];
	for (int j=0; j<parent1->numberOfOutputs; j++)
	{
		if (j < (crossoverPoint*parent1->numberOfOutputs))
		{
			newListOfLayers1[parent1->numHiddenLayers+1][j] = parent1->listOfLayers[parent1->numHiddenLayers+1][j];
			newListOfLayers2[parent1->numHiddenLayers+1][j] = parent2->listOfLayers[parent1->numHiddenLayers+1][j];
		}
		else
		{
			newListOfLayers1[parent1->numHiddenLayers+1][j] = parent2->listOfLayers[parent1->numHiddenLayers+1][j];
			newListOfLayers2[parent1->numHiddenLayers+1][j] = parent1->listOfLayers[parent1->numHiddenLayers+1][j];
		}
	}

	//These constructors will make deep copies of the arrays.
	child1 = new NNGeneString(parent1->numberOfInputs, parent1->numberOfOutputs, parent1->numHiddenLayers,
		parent1->hiddenLayerSizes, newListOfLayers1);

	child2 = new NNGeneString(parent1->numberOfInputs, parent1->numberOfOutputs, parent1->numHiddenLayers,
		parent1->hiddenLayerSizes, newListOfLayers2);

	//deleting hidden and output layers, the only ones that got allocated in this function
	for (int i=1; i<=parent1->numHiddenLayers+1; i++)
	{
		delete [] newListOfLayers1[i];
		delete [] newListOfLayers2[i];
	}

	delete [] newListOfLayers1;
	delete [] newListOfLayers2;
}
