/*
Jeffrey Grafton <jgrafton at tjhsst dot edu>
10/1/2002

This program is released under the GNU General Public License.
A copy of this license is available online at
http://www.gnu.org/copyleft/gpl.html.
*/

#include <GL/glut.h>
#include <math.h>
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>

#define MINX -0.8
#define MINY -0.8
#define MAXX 0.8
#define MAXY 0.8
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 800
#define MAX_MOVES 50000
#define WORK_MSG 1
#define RESULT_MSG 2
#define DONE_MSG 3

typedef struct
{
	double a;
	double b;
} complex;

typedef struct
{
	double column;
	double colors[WINDOW_WIDTH][3];
} row;

void display_init(void);
void display(void);
void draw_graph(void);
void master(int, char **);
void slave(int);
void send_work(void);
void draw_screen(void);
void do_work(row *);

int
main (int argc, char *argv[])
{
	int my_rank;
	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

	if (my_rank == 0)
	{
		master(argc, argv);
	}
	else
	{
		slave(my_rank);
	}
	
	MPI_Finalize();
	
	return 0;
}

void master(int argc, char **argv)
{
    printf("master: running\n");
	
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	glutCreateWindow("julia");
	display_init();
	glutDisplayFunc(display);

	glutMainLoop();	
}

void slave(int my_rank)
{
	row the_row;
	double i, range[2];
	int number_tasks;
	MPI_Status status;
	
	printf("slave: running\n");

	MPI_Comm_size(MPI_COMM_WORLD, &number_tasks);
	
	while (1)
	{
		MPI_Recv(&range, 2, MPI_DOUBLE, 0, WORK_MSG, MPI_COMM_WORLD, &status);
		
		for (i = range[0]; i < range[1]; i += (MAXX - MINX) / (WINDOW_WIDTH))
		{
			the_row.column = i;
			do_work(&the_row);
			MPI_Send(&the_row, sizeof(row), MPI_BYTE, 0, RESULT_MSG, MPI_COMM_WORLD);
		}
		
		MPI_Send(0, 0, MPI_INT, 0, DONE_MSG, MPI_COMM_WORLD);
	}
}

/* initialization routines */
void
display_init (void)
{
	glClearColor(0.0, 0.0, 0.2, 0.0);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(MINX, MAXX, MINY, MAXY, -1.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);
}

/* main display function */
void
display (void)
{
   double start_time, end_time;
	glClear(GL_COLOR_BUFFER_BIT);
   start_time = MPI_Wtime();
	send_work();
	draw_screen();
   end_time = MPI_Wtime();
   
   printf("master: operation took %f seconds.\n", end_time - start_time);
}

void
send_work (void)
{
	int number_tasks, i; 
	double interval;
	
	MPI_Comm_size(MPI_COMM_WORLD, &number_tasks);
	
	if (number_tasks < 2)
	{
		printf("Please use 2 or more tasks.\n");
		exit(1);
	}
	
	interval = (MAXX - MINX) / (number_tasks - 1);
	
	for (i = 0; i < number_tasks - 1; i++)
	{
		double range[2];
		range[0] = i * interval + MINX;
		range[1] = (i + 1) * interval + MINX;
      if (i == number_tasks - 2)
         range[1] = MAXX;
		MPI_Send(&range, 2, MPI_DOUBLE, i + 1, WORK_MSG, MPI_COMM_WORLD);
		
	}
}

void
draw_screen (void)
{
	int i, j, number_tasks;
	MPI_Status status;
	row the_row;
	
	MPI_Comm_size(MPI_COMM_WORLD, &number_tasks);
	
	glBegin(GL_POINTS);
	for (i = 0; i < number_tasks - 1; )
	{
		MPI_Recv(&the_row, sizeof(row), MPI_BYTE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
		
		if (status.MPI_TAG == DONE_MSG)
		{
			i++;
			printf("master: slave finished\n");
			continue;
		}
		
		for (j = 0; j < (int)(WINDOW_HEIGHT); j++)
		{
			glColor3d(the_row.colors[j][0], the_row.colors[j][1], the_row.colors[j][2]);
			glVertex2d(the_row.column, (MAXY - MINX) * (double)j / (WINDOW_HEIGHT) + MINY);
		}
      
		glFlush();
	}
	glEnd();
	glFlush();
}

void
do_work (row *the_row)
{
	int i;
	complex z, last_z, c;
	double x, y;
	
	c.a = -0.2875;
	c.b = 0.63508;
	
	x = the_row->column;

	
	for (i = 0; i < WINDOW_HEIGHT; i++)
	{
		int moves; 
		
		y = (MAXY - MINY) * (double)i / (double)(WINDOW_HEIGHT) + MINY;
		
		z.a = 0;
		z.b = 0;
		last_z.a = x;
		last_z.b = y;
		for (moves = 0; (z.a * z.a + z.b * z.b) < 8.0 && moves < MAX_MOVES; moves++)
		{
			z.a = last_z.a * last_z.a - last_z.b * last_z.b + c.a;
			z.b = 2.0 * last_z.a * last_z.b + c.b;
			last_z.a = z.a;
			last_z.b = z.b;
		}
		
		the_row->colors[i][0] = (double)(MAX_MOVES - moves)/ (MAX_MOVES * 2);
		the_row->colors[i][1] = (double)moves * moves / MAX_MOVES;
		the_row->colors[i][2] = (double)moves / MAX_MOVES;		
	}	
}

