//Copyright (c) 2000 Nam Tran

/*--------------*
 * header files *
 *--------------*/
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <math.h>




/*------------------*
 * global variables *
 *------------------*/
 const int MAX_LENGTH = 9;		//how many bits in the dna code
 const int MAX_POPULATION = 20;		//how many chromosomes in a population; you need to use an even number
 const int MAX_GENERATION = 200;	//how many iterations/generations
 const int PER_LINE = 5;		//the max you can have per line (used in displaying stuff)
 int prob_crossover = 80;		//equivalent to a 80% rate
 int prob_mutation = 1; 		//equivalent to a 1% rate




/*---------*
 * classes *
 *---------*/
 class neurochromosome
 {
	public:
		int fitness;
		char c[MAX_LENGTH];		//c standing for chromosome.
 };




/*---------------------*
 * function prototypes *
 *---------------------*/
 int binary_decimal(neurochromosome *string);	//converts a binary string to decimal

 int selection(neurochromosome *ebenhack);	//returns the "number" of the fit parent; going to have
						//to check the code out on this one

 void reproduction(neurochromosome *p_1, neurochromosome *p_2,  neurochromosome *o_1, neurochromosome *o_2);
						//used in reproduction (actual method is crossover)

 void mutation(neurochromosome *offspring);	//does any potential mutations (flip a bit)

 int total_fitness(neurochromosome *beth);	//returns the total fitness of the entire generation

 void elitism(neurochromosome *old_c, neurochromosome *new_c);
						//going to move the best to the next generation
						//needs to be called before the life loop "erases" the old generation




/*--------------------------------------------------------------------*
 * name     : binary_decimal()					      *
 * purpose  : converts a binary text string to a decimal    	      *
 * accepts  : neurochromosome *				      	      *
 * returns  : int						      *
 * comments : none						      *
 *--------------------------------------------------------------------*/
 int binary_decimal(neurochromosome *string)
 {
	int total = 0;
	int x = 0;

	int correction = -7;			//used to correct the physical value of array to the correct
						//logical place. used in addition
						//this is going to be changed every iteration by adding +2
						//draw on paper to see the relationships


	for(x = (MAX_LENGTH - 2); x >= 0; x--)
	{
		if (string->c[x] == '1')	//this would be the only time you add anything
		{
			total = total + (int) pow((double) 2, (double) (x + correction));
		}

		correction = correction + 2;	//read above for explanation
	}


	return total;
 }




/*--------------------------------------------------------------------*
 * name     : selection()					      *
 * purpose  : selects a parent to be part of reproduction     	      *
 * accepts  : neurochromosome *				      	      *
 * returns  : int						      *
 * comments : determines parents by combination of chance and fitness *
 *--------------------------------------------------------------------*/
 int selection(neurochromosome *ebenhack)
 {
	int sum_f = 0;		//the total sum of the fitnesses
	int sum_c = 0;		//the cumalitive/current sum, or adding as i go along
	int r;			//r is a random number
	int x = 0;


	sum_f = total_fitness(ebenhack);

	r = (rand() % sum_f);

//	printf("r: %d\n", r);

	//going to have to add code to make sure no out of bounds
	//technically, it should be from 0 to (MAX_POPULATION - 1)

	for(x = 0; x < MAX_POPULATION; x++)
	{
		sum_c = sum_c + (ebenhack + x)->fitness;

		if (sum_c >= r)
		{
			return x;
		}
	}

	return 0;	//just in case
 }




/*--------------------------------------------------------------------*
 * name     : reproduction()					      *
 * purpose  : performs crossover, and stores offspring in variables   *
 * accepts  : 4x neurochromosome *			      	      *
 * returns  : void						      *
 * comments : probability of crossover is a global variable	      *
 *--------------------------------------------------------------------*/
 void reproduction(neurochromosome *p_1, neurochromosome *p_2,  neurochromosome *o_1, neurochromosome *o_2)
 {
	int x = 0;
	int crossover = (rand() % (MAX_LENGTH - 1));	//crossover point



	for(x = 0; x < crossover; x++)
	{
		o_1->c[x] = p_1->c[x];
	}

	for(x = crossover; x <= (MAX_LENGTH - 1); x++)		//8 is what we want. it's also copying the null zero automatically
	{
		o_1->c[x] = p_2->c[x];
	}


	//now you're just kind of fliping the logic


	for(x = 0; x < crossover; x++)
	{
		o_2->c[x] = p_2->c[x];
	}

	for(x = crossover; x <= (MAX_LENGTH - 1); x++)
	{
		o_2->c[x] = p_1->c[x];
	}


 /*
	printf("\ncrossover: %d\n", crossover);
	printf("parent 1: %s\n", p_1->c);
	printf("parent 2: %s\n", p_2->c);
	printf("offspring 1: %s\n", o_1->c);
	printf("offspring 2: %s\n\n\n", o_2->c);
 */
 }




/*--------------------------------------------------------------------*
 * name     : mutation()					      *
 * purpose  : performs potential mutations	      	      	      *
 * accepts  : neurochromosome *				      	      *
 * returns  : void						      *
 * comments : probability of mutation is a global variable	      *
 *--------------------------------------------------------------------*/
 void mutation(neurochromosome *offspring)
 {
	int x = 0;

	for(x = 0; x < (MAX_LENGTH - 1); x++)
	{
		if ((rand() % 101) <= prob_mutation)
		{
			if (offspring->c[x] = '1')
			{
				offspring->c[x] = '0';
			}

			else
			{
				offspring->c[x] = '1';
			}
		}
	}
 }




/*--------------------------------------------------------------------*
 * name     : total_fitness()					      *
 * purpose  : calculations total fitness of population	      	      *
 * accepts  : neurochromosome *				      	      *
 * returns  : int						      *
 * comments : none						      *
 *--------------------------------------------------------------------*/
 int total_fitness(neurochromosome *beth)
 {
	int x = 0;
	int total = 0;

	for(x = 0; x < MAX_POPULATION; x++)
	{
		total = total + (beth + x)->fitness;
	}

	return total;
 }




/*--------------------------------------------------------------------*
 * name     : elitism()						      *
 * purpose  : copies best chromosome to the next population	      *
 * accepts  : 2x neurochromosome *				      *
 * returns  : void						      *
 * comments : none						      *
 *--------------------------------------------------------------------*/
 void elitism(neurochromosome *old_c, neurochromosome *new_c)
 {
	int highest = 0;
	int x = 0;


	//DETERMINING HIGHEST MATCH
	for(x = 0; x < MAX_POPULATION; x++)
	{
		if (old_c[highest].fitness < old_c[x].fitness)
		{
			highest = x;
		}
	}


	strcpy(new_c[(rand() % MAX_POPULATION)].c, old_c[highest].c);
 }




/*--------------------------------------------------------------------*
 * name     : main()						      *
 * purpose  : beginning of program				      *
 * accepts  : void						      *
 * returns  : int						      *
 * comments : none						      *
 *--------------------------------------------------------------------*/
 int main()
 {
	neurochromosome genome[MAX_POPULATION];
	neurochromosome genome_new[MAX_POPULATION];	//it's a relative thing. see algorithm in work later

	char temp_string[MAX_LENGTH];
	int x = 0;
	int y = 0;
	int z = 0;

	int generation = 0;

	//this is used to help in statistics testing
	double expected = 0;
	double observed = 0;
	double total_chi = 0;
	double base = 1.043609084;





	FILE *neuropunk;
	FILE *stats; 		//mainly used for statistics work
	if ((neuropunk = fopen("output.txt", "w")) == NULL)
	{
		printf("[ERROR] unable to create file");
		exit(1);
	}

	if ((stats = fopen("stats.txt", "w")) == NULL)
	{
		printf("[ERROR] unable to create file");
		exit(1);
	}


	srand((unsigned)time(NULL));


	//INITALIZING FIRST GENERATION
	for(x = 0; x < MAX_POPULATION; x++)
	{
		for(y = 0; y < (MAX_LENGTH - 1); y++)
		{
			if ((rand() % 2) == 0)
			{
				genome[x].c[y] = '0';
			}

			else
			{
				genome[x].c[y] = '1';
			}
		}
		genome[x].c[(MAX_LENGTH - 1)] = '\0';

		genome[x].fitness = binary_decimal((genome + x));
	}




	//THE LOOP OF LIFE
	for(generation = 0; generation < MAX_GENERATION; generation++)
	{
		//PARTS OF STATISTICS WORK
		expected = pow(base, (double) generation);
//		printf("expected: %lf\n", expected);

		observed = total_fitness(genome);
//		printf("observed: %lf\n", observed);

		total_chi = total_chi + ((pow((observed - expected), 2)) / expected);
		fprintf(stats, "((%lf - %lf)^2 / %lf) + ", observed, expected, expected);
//		printf("total chi: %lf\n\n", total_chi);




		//PRINTING THE PARENTS (they're the "current generation." offspring are the next generation)
		fprintf(neuropunk, "generation: %d\n", generation);
		fprintf(neuropunk, "fitness: %d\n", total_fitness(genome));
		printf("generation: %d\n", generation);
		printf("fitness: %d\n", total_fitness(genome));


		for(x = 0; x < (MAX_POPULATION); x++)
		{
			fprintf(neuropunk, "%s : %d\n", genome[x].c, genome[x].fitness);
			printf("%s : %d\n", genome[x].c, genome[x].fitness);
		}

		fprintf(neuropunk, "\n\n");
		printf("\n\n");





		z = 0;		//need to reset the variable, or otherwise, it will go out of bounds



		//BEGINNING THE PROCESS OF CROSSOVER (REPRODUCTION)
		for(x = 0; x < (MAX_POPULATION / 2); x++)
		{
			if ((rand() % 101) <= prob_crossover)
			{
				reproduction((genome + selection(genome)), (genome + selection(genome)), (genome_new + z), (genome_new + (z + 1)));
			}

			else
			{
				strcpy(genome_new[z].c, genome[selection(genome)].c);
				strcpy(genome_new[(z + 1)].c, genome[selection(genome)].c);
			}

			mutation((genome_new + z));
			mutation((genome_new + (z+1)));

			z = z + 2;
		}


		//BEFORE THE "ERASING" COMPLETES, COPY THE BEST CHROMOSOME OVER
		elitism(genome, genome_new);


		//REPLACING PARENTS WITH THE NEWLY GENERATED OFFSPRING
		for(x = 0; x < MAX_POPULATION; x++)
		{
			strcpy(genome[x].c, genome_new[x].c);			//"erasing" the old information, filling with new
			genome[x].fitness = binary_decimal((genome + x));	//determining the "new" fitness
		}
	}


	fprintf(stats, "\n\n\n%lf", total_chi);

	fclose(neuropunk);
	fclose(stats);

	return 0;
 }




