/*
 * child.c
 *
 *  Created on: Nov 23, 2009
 *      Author: brian
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <mpi.h>
#include "child.h"
#include "genetic.h"
#include "constants.h"

int findNeighbors(int neighbors[], int homeX, int homeY);
int rank(int x, int y);

int child(int rank, int size)
{
	int i;
	int x, y;
	int gen = 0;
	int bestIndex;
	char message[BUFFSIZE];
	char bestPoly[BUFFSIZE];
	int cont;
	MPI_Status rootstat;


	//migration variables
#ifdef MIGRATE
	int r;
	MPI_Request send_result[4];	//Used to indicate if the send operation ever finished (so we can re-use the travelersOut buffer)
	MPI_Status waitstat[4];
	MPI_Status recv_stat[4];

	//Used to hold chromosomes going to/from other populations
	int travelersIn[4][N_VERTICES];
	int travelersOut[4][N_VERTICES];

	int neighbors[4];	//holds the ranks of the left, right, up, and down neighbors (-1 means no neighbor)
	int nNeighbors;
#endif

	//Adjacency Table
	int adjTable[N_VERTICES][N_VERTICES];


	//Population
	int population[POPSIZE][N_VERTICES];

	//Parents
	int parents[POPSIZE / 2][N_VERTICES];

	//Offspring
	int offspring[POPSIZE / 2][N_VERTICES];



	double rawfitness[POPSIZE];
	double highFitness[POPSIZE];
	double lowFitness[POPSIZE];
	double bestFitness;
	double avgFitness;
	double sumHigh;
	double sumLow;
	//smallest fitness is better

	//Determine my X and Y position
	x = (rank - 1) % LANDSCAPE_WIDTH;
	y = (rank - 1) / LANDSCAPE_WIDTH;

	//Pick the local mutation rate
#ifdef MUTGRAD
	//local mutation rate (depends on Y), scaled between 0 and P_MUTATE
	double p_mutate = ((double) y) * ((double)P_MUTATE) / (2.0 * (double) LANDSCAPE_HEIGHT - 1) + ((double) P_MUTATE / 4.0);
#endif
#ifndef MUTGRAD
	//local mutation rate is P_MUTATE / 2
	double p_mutate = (double) P_MUTATE / 2.0;
#endif

	//Initialize the RNG (TIME * RANK--prevents different ranks from
	//getting the same seed if they're started at the same time
	srand(time(NULL) * rank);
      
	//This gives a constant seed for each run
	//But each rank has a unique seed.  //4642 is just some random number
	//Deterministic Mode
	//srand(4642 * rank);

	//Load the adjacency table from a file
	if ( loadAdjTable(ADJFILE, N_VERTICES ,adjTable) == -1)
	{
		printf("COULDN'T OPEN ADJACENCY TABLE!");
		MPI_Finalize();
		return;
	}


	//Generate an initial population
	for (i = 0; i < POPSIZE; i++)
	{
		randChr(population[i], N_VERTICES);
	}

#ifdef MIGRATE
	//Find my neighbors
	nNeighbors = findNeighbors(neighbors, x, y);

	//Generate some initial travelers
	//These will be added into the population randomly in the first generation
	for (i = 0; i < 4; i++)
	{
		randChr(travelersIn[i], N_VERTICES);
	}
#endif

	//Loop until the root stops telling us to continue
	for (;;)
	{



#ifdef MIGRATE
		//Add the travelers to the population at random locations
		for (i = 0; i < 4; i++)
		{
			//If the neighbor exists
			if (neighbors[i] != -1)
			{
				//Pick a random location in the population
				r = randInt(POPSIZE);

				//Copy the chromosome received from that neighbor
				//Into a random spot in the population
				//Overwriting the organism already there (tough luck!)
				copyChr(travelersIn[i], population[r], N_VERTICES);
			}
		}
#endif


		//Calculate the fitness of each individual
		//Also get the average fitness and maximum fitness
		bestFitness = 0;
		avgFitness = 0;
		for (i = 0; i < POPSIZE; i++)
		{
			rawfitness[i] = fitness(population[i], N_VERTICES, adjTable);

			avgFitness += rawfitness[i];

			if (rawfitness[i] > bestFitness)
			{
				bestIndex = i;
				bestFitness = rawfitness[i];
			}
		}
		//Divide avgFitness (which is currently the sum of all raw fitness scores) by the population size
		avgFitness = avgFitness / ((double) POPSIZE);


		//Send status message to root
		printChr(bestPoly, population[bestIndex], N_VERTICES);
		sprintf(message,"gen, %d, avg, %f, best, %f, rank, %d, popsize, %d, x, %d, y, %d, p_mutate, %f, %s", gen, avgFitness, bestFitness,rank, POPSIZE, x, y, p_mutate, bestPoly);
		MPI_Send(message, BUFFSIZE, MPI_CHAR, 0, STATUS_TAG, MPI_COMM_WORLD);

		//Send the best fitness
		MPI_Send(&bestFitness, 1, MPI_DOUBLE, 0, BEST_TAG, MPI_COMM_WORLD);

		//Scale the fitness scores
		//It is possible for the fitness function to give negative fitness scores
		//prescaling adds a constant to each fitness score s.t. the smallest fitness is zero
		prescaleFitness(rawfitness, POPSIZE);
		//Construct the roulette wheel for selecting for high fitness
		sumHigh = scaleHigh(rawfitness, highFitness, POPSIZE);
		//Construct the roulette wheel for selecting for low fitness
		sumLow = scaleLow(rawfitness, lowFitness, POPSIZE);


#ifdef MIGRATE
		//Pick the chromosomes to send to neighbors

		//Left and right get random chromosomes
		r = randInt(POPSIZE);
		copyChr(population[r], travelersOut[LEFT], N_VERTICES);
		r = randInt(POPSIZE);
		copyChr(population[r], travelersOut[RIGHT], N_VERTICES);

#ifdef DIRMIG
		//Up gets a chromosome with higher fitness
		r = propSel(highFitness, sumHigh, POPSIZE);
		copyChr(population[r], travelersOut[UP], N_VERTICES);

		//Down gets a chromosome with lower fitness
		r = propSel(lowFitness, sumLow, POPSIZE);
		copyChr(population[r], travelersOut[DOWN], N_VERTICES);
#endif
#ifndef DIRMIG
		//Up gets a chromosome with random fitness
		r = randInt(POPSIZE);
		copyChr(population[r], travelersOut[UP], N_VERTICES);

		//Down gets a chromosome with random fitness
		r = randInt(POPSIZE);
		copyChr(population[r], travelersOut[DOWN], N_VERTICES);
#endif

		//Send Travelers to neighbors
		for (i = 0; i < 4; i++)
		{
			if (neighbors[i] != -1)	//only if the neighbor exists
			{
				//send travelerOut[i] to rank neighbors[i]
				MPI_Isend(travelersOut[i], N_VERTICES, MPI_DOUBLE, neighbors[i], TRAVELER_TAG, MPI_COMM_WORLD, &(send_result[i]));
			}
		}

		//Receive Travelers from neighbors
		for (i = 0; i < 4; i++)
		{
			if (neighbors[i] != -1)
			{
				//receive travelerIn[i] from rank neighbors[i]
				MPI_Recv(travelersIn[i], N_VERTICES, MPI_DOUBLE, neighbors[i], TRAVELER_TAG, MPI_COMM_WORLD, &recv_stat[i]);
			}
		}

		//Wait for all the sends to finish
		for (i = 0; i < 4; i++)
		{
			if (neighbors[i] != -1)
			{

				//Wait for the send to complete
				MPI_Wait(&(send_result[i]), &(waitstat[i]));
			}
		}
#endif

		//Conduct prop. sel. to pick the next parents
		//This does not include the just received travelers.  They are added at the beginning of the next generation

		//Pick POPSIZE/2 Parents
		for (i = 0; i < (POPSIZE / 2); i++)
		{
			//int j = propSel(highFitness, sumHigh, POPSIZE);
			int j = propSel(highFitness, sumHigh, POPSIZE);
			//printf("Picked %d", j);

			copyChr(population[j], parents[i], N_VERTICES);
		}

		//The order of the parents is already non-deterministic
		//No need to shuffle them.  Conduct pairwise reproduction
		//To re-populate the population array
		for (i = 0; i < (POPSIZE / 2); i += 2)
		{
			//Generate two offspring, store them in the population
			onePoint(parents[i], parents[i+1], offspring[i], offspring[i+1], N_VERTICES);
		}

		//Add the parents back into the population
		for(i = 0; i < (POPSIZE / 2); i++)
		{
			copyChr(parents[i], population[i], N_VERTICES);
		}

		//Add the offspring back into the population
		for(i = (POPSIZE / 2); i < POPSIZE; i++)
		{
			copyChr(offspring[i - (POPSIZE / 2)], population[i], N_VERTICES);
		}

		//Apply mutation to the population

		for (i = 0; i < POPSIZE; i++)
		{
			mutate(population[i], N_VERTICES, p_mutate);
		}


		//Continue only if the root sends 0 as the continue message
		MPI_Recv(&cont, 1, MPI_INT, 0, CONTINUE_TAG, MPI_COMM_WORLD, &rootstat);
		if (cont != 0)
		{
			break;
		}

		gen++;
	}
	return 0;
}

//Populates the neighbor array
//returns the number of neighbors
int findNeighbors(int neighbors[], int homeX, int homeY)
{
	//The origin is in the upper left corner.
	//X and Y go from 0 to LANDSCAPE_<WIDTH/HEIGHT> - 1
	//Ranks go from 1 to LANDSCAPE_WIDTH * LANDSCAPE_HEIGHT

	int x,y;
	int n = 0;

	//Find the left neighbor
	x = homeX - 1;
	y = homeY;

	if (x < 0)
	{
		//There is no left neighbor
		neighbors[LEFT] = -1;
	}
	else
	{
		neighbors[LEFT] = rank(x,y);
		n++;
	}

	//Find right neighbor
	x = homeX + 1;
	y = homeY;

	if (x > (LANDSCAPE_WIDTH - 1))
	{
		//There is no right neighbor
		neighbors[RIGHT] = -1;
	}
	else
	{
		neighbors[RIGHT] = rank(x,y);
		n++;
	}

	//Find the upper neighbor
	x = homeX;
	y = homeY - 1;

	if (y < 0)
	{
		//There is no upper neighbor
		neighbors[UP] = -1;
	}
	else
	{
		neighbors[UP] = rank(x,y);
		n++;
	}

	//Find the lower neighbor
	x = homeX;
	y = homeY + 1;

	if (y > (LANDSCAPE_HEIGHT - 1))
	{
		//There is no lower neighbor
		neighbors[DOWN] = -1;
	}
	else
	{
		neighbors[DOWN] = rank(x,y);
		n++;
	}


	return n;
}

//Returns a rank from x and y coordinates
int rank(int x, int y)
{
	return y*LANDSCAPE_WIDTH + x + 1;
}
