/* lala
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef __APPLE__
	#include <GLUT/glut.h>
#else
	#include <GL/glut.h>
#endif
#include <math.h>
#include "vowel.h"
#include <limits.h>
#include <float.h>

double screen_width;
double screen_height;
double screen_aspect;
double world_width;
double world_height;

int gi = 0;
double score[POOL_SIZE];
pool agentpool[2];
int swtch = 0;

static void 
Draw(void)
{	
	
	int i,j,k;
	
	agent p1, p2;

	srand(time(NULL));
	

	/* init pool */
	
	if (gi < NUM_CYCLES){
		gi++;
		glClearColor(1, 1, 1, 1);
		glClear(GL_COLOR_BUFFER_BIT);
		glColor3f(0.0, 0.4, 1.0);
		glPointSize(3.0);
		for(j = 0; j < POOL_SIZE; j++) {
			glColor3f(1.0, 0.0, 0.4);
			glBegin(GL_LINE_STRIP);
			glVertex2f(0,0);
			glVertex2f(agentpool[swtch][j].width*10,0);
			glVertex2f(agentpool[swtch][j].width*10,agentpool[swtch][j].height*10);
			glVertex2f(0,agentpool[swtch][j].height*10);
			glVertex2f(0,0);
			glEnd();
		}

		for(j = 0; j < POOL_SIZE; j++) {
			score[j] = 0;
			for(k = 0; k < NUM_COMS; k++) {
				score[j] += communicate(agentpool[swtch][j],agentpool[swtch][rand() % POOL_SIZE]);
			}
			glColor3f(0.0, 0.4, 1.0);
			glBegin(GL_POINTS);
			for(k = 0; k < NUM_VOWELS; k++) {
				glVertex2f(agentpool[swtch][j].vowels[k].f1*10,agentpool[swtch][j].vowels[k].f2 * 10);			
				
			}
			glEnd();
			
			score[j] = pow(score[j],2);
		}
		
		/* select parents and create new pool */
		for(j = 0; j < POOL_SIZE; j++) {
			rouletteSelect(agentpool[swtch],score,&p1,&p2);
			crossover(agentpool[(swtch + 1) % 2],j,&p1,&p2);
		}
		mutate(agentpool[(swtch + 1) % 2]);
		swtch = (swtch + 1) % 2;
		glutSwapBuffers();
		glutPostRedisplay();
	}
    
    /* while(1) { */


    
    

        
        /* Draw the control points of the unsubdivided curve */
        
    /*    for (i = 0; i < 10; i++)
		glVertex2f(i*10, i*10+0*(rand() % 30));
        glEnd();
			
	glColor3f(0.0, 0.8, 0.0);
	glBegin(GL_LINE_STRIP);
	for (i = 0; i < 10; i++)
		glVertex2f(i*5, i*5+(rand() % 30));
	glEnd(); */
	
    
    /* } */
}

static void 
Key(unsigned char key, int x, int y)
{
    switch (key) 
    {
    case 27:
        exit(0);
    

    case '-':
    case '+':
    case '=':


    case '[':

        glutPostRedisplay();
        break;
    case ']':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        break;
    case '0':
        break;	
    
	/* Switch between normal view and subdivided view */
    case ' ':

        break;
    
	/* Subdivision position on the curve */

    }
}

static void
mouse2world(float *wx, float *wy, int mx, int my)
{
    *wx = 1.0 * mx / screen_width * world_width;
    *wy = 1.0 * (screen_height-my) / screen_height * world_height;
}



/* We do the same thing in both the mouse "click" function (Mouse)
   and the mouse "move" function (Motion), so that a point is relocated
   as soon as the mouse is clicked, but it can also be dragged around. */

static void 
Mouse(int button, int state, int x, int y)
{
	//printf("Mouse! %d %d %d %d\n", button, state, x, y);
}

static void
Motion(int x, int y)
{
	//printf("Motion! %d %d\n", x, y);
}

static void 
Reshape(int width, int height)
{
    screen_width = width;
    screen_height = height;	
    screen_aspect = 1.0 * width / height;
    
    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    
    if (width > height)
    {
        /* make world height 100, scale world width accordingly */
        world_height = 100.0;
        world_width = screen_aspect * world_height;
    }
    else
    {
        /* make world width 100, scale world height accordingly */
        world_width = 100.0;
        world_height = world_width / screen_aspect;
    }
    
    glOrtho(0.0f, world_width, 0.0, world_height, -1.0f, 1.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();	
}

int 
main(int argc, char **argv)
{
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowSize(610, 610);
    glutCreateWindow("OpenGL Framework");

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutMouseFunc(Mouse);
    glutMotionFunc(Motion);
    glutDisplayFunc(Draw);
	initPool(agentpool[swtch]);

    glutMainLoop();
    
    return 0;
}

void rouletteSelect(pool agentpool,double fitnessArray[POOL_SIZE],agent *p1, agent *p2) { 
	int sum = 0;
	int i,rnd1,rnd2,found1=0,found2=0;
	
	for(i = 0; i < POOL_SIZE; i++) {
		sum += fitnessArray[i];
	}
	
	rnd1 = (rand() % sum) + 1;
	rnd2 = (rand() % sum) + 1;
	sum = 0;
	for(i = 0; i < POOL_SIZE; i++) {
		sum += fitnessArray[i];

		if(rnd1 <= sum && !found1) {
			*p1 = agentpool[i];
			found1 = 1;
		}
		
		if(rnd2 <= sum && !found2) {
			*p2 = agentpool[i];
			found2 = 1;
		}
		
		if(found1 && found2) 
			break;
	}
	
	return;
}


void initPool(pool agentpool) {
	int i,j;
	
	for(i = 0; i < POOL_SIZE; i++) {
		agentpool[i].width = 5;
		agentpool[i].height = 5;
		
		for(j = 0; j < NUM_VOWELS; j++) {
			agentpool[i].vowels[j].f1 = agentpool[i].width / 2.0;
			agentpool[i].vowels[j].f2 = agentpool[i].height / 2.0;
		}
	}
}

int communicate(agent a1, agent a2) {
	int vowelout, vowelin, vowelcheck; 
	double f1out, f2out, f1in, f2in;
	

	vowelout = rand() % NUM_VOWELS;
	f1out = randGauss(a1.vowels[vowelout].f1,COM_SIG);
	f1out = bound(f1out,0,a1.width);
	f2out = randGauss(a1.vowels[vowelout].f2,COM_SIG);
	f2out = bound(f2out,0,a1.height);
	

	vowelin = findMostClose(a2,f1out,f2out);
	f1in = randGauss(a2.vowels[vowelin].f1,COM_SIG);
	f1in = bound(f1in,0,a2.width);
	f2in = randGauss(a2.vowels[vowelin].f2,COM_SIG);
	f2in = bound(f2in,0,a2.height);
	

	vowelcheck = findMostClose(a1,f1in,f2in);
	

	if(vowelcheck == vowelout)
		return 1;
	else
		return 0;	
}

/* shortest euclidean distance given f1 and f2 */
int findMostClose(agent a,double f1, double f2) {
	int i;
	int best = 0;
	double mindist = DBL_MAX;
	
	for(i = 0; i < NUM_VOWELS; i++) {
		if( sqrt(pow(f2 - a.vowels[i].f2, 2) + pow(f1 - a.vowels[i].f1, 2)) < mindist) {
			best = i;
			mindist = sqrt(pow(f2 - a.vowels[i].f2, 2) + pow(f1 - a.vowels[i].f1, 2));
	} 
		/*if( fabs(f1 - a.vowels[i].f1) < mindist) {
			best = i;
			mindist = fabs(f1 - a.vowels[i].f1);
	} */
	}
	
	return best;
}
	
void mutate(pool agentpool) {
	int i,j;
	
	for(i = 0; i < POOL_SIZE; i++) {
		agentpool[i].height = randGauss(agentpool[i].height,HEIGHT_SIG);
		agentpool[i].height = bound(agentpool[i].height,HEIGHT_MIN,HEIGHT_MAX);
		agentpool[i].width = randGauss(agentpool[i].width,WIDTH_SIG);
		agentpool[i].width = bound(agentpool[i].width,WIDTH_MIN,WIDTH_MAX);
		
		for(j = 0; j < NUM_VOWELS; j++) {
			agentpool[i].vowels[j].f1 = randGauss(agentpool[i].vowels[j].f1,F_SIG);
			agentpool[i].vowels[j].f1 = bound(agentpool[i].vowels[j].f1,0,agentpool[i].width);
			agentpool[i].vowels[j].f2 = randGauss(agentpool[i].vowels[j].f2,F_SIG);
			agentpool[i].vowels[j].f2 = bound(agentpool[i].vowels[j].f2,0,agentpool[i].height);
		}
	}
	
	return;
}


void crossover(pool agentpool, int index, agent *parent1, agent *parent2) {
	int i;
	
	if(frand() < 0.5) 
		agentpool[index].height = parent1->height;
	else
		agentpool[index].height = parent2->height;

	if(frand() < 0.5)
		agentpool[index].width = parent1->width;
	else
		agentpool[index].width = parent2->width;

	if(frand() < 0.5) {
		for(i = 0; i < NUM_VOWELS; i++) {
			agentpool[index].vowels[i].f1 = bound(parent1->vowels[i].f1, 0, agentpool[index].width);
			agentpool[index].vowels[i].f2 = bound(parent1->vowels[i].f2, 0, agentpool[index].height);
		}
	}
	else {
		for(i = 0; i < NUM_VOWELS; i++) {
			agentpool[index].vowels[i].f1 = bound(parent2->vowels[i].f1, 0, agentpool[index].width);
			agentpool[index].vowels[i].f2 = bound(parent2->vowels[i].f2, 0, agentpool[index].height);
		}
	}
	
	return;
}

double frand() {
	return frandBound(0,1);
}

float frandBound(float min, float max) {
	float fraction = (float) rand() / (float) RAND_MAX;
	return (min + (fraction * (max - min)));
}

float randGauss(float mu, float sigma) {
	float u1, u2, s, rv;

	u1 = 0;
	u2 = 0;
	s = 2;

	while (s <= 0 || s > 1) {
		u1 = frandBound(-1, 1);
		u2 = frandBound(-1, 1);
		s = u1 * u1 + u2 * u2;
	}

	rv = u1 * sqrt((-2 * log(s))/s);
	return rv * sigma + mu;
}


