#include <iostream.h>
#include <GL/glut.h>
#include <GL/glui.h>
#include <math.h>
#include <stdio.h>

#include "fb.h"

#ifndef M_PI
const double M_PI = 3.14159;
#endif


// COMP236 Programmers: please don't change this file.


// ----- frame buffer routines ---------------

struct pix
{
  unsigned char r,g,b;
};

// size of the opengl drawing window
const int fb_win_width = 500;
const int fb_win_height = 500;

// size of the virtual frame buffer
const int fb_num_rows = 500;
const int fb_num_cols = 500;

// sides of the fb clipping window
int fb_xl = 150.0;
int fb_xr = 170.0;
int fb_yt = 100.0;
int fb_yb = 120.0;

// the contents of the frame buffer
pix fb_pixels[fb_num_rows][fb_num_cols];

// the background color (becomes ccolor of the pixel margins)
pix margin_rgb = {30, 30, 30};

double fb_margin = 0.4;  // size of the margins
int fb_margin_flag = 1;  // whether to show margins
int fb_center_flag = 0;  // whether to show pixel centers
int fb_wireframe_flag = 0;  // whether to call the polygon fillers, or edge rasterizers
int fb_convex_flag = 1;     // whether to use convex or concave test sets
double fb_x = 160.0; // x coordinate of screen center 
double fb_y = 110.0; // y coordinate of screen center
double fb_s = 15.0; // zoom (scale)

int fb_window;  // id number of our glut window

void
fbClear(double r, double g, double b)
{
	// clamp the values
	if (r < 0.0) r = 0.0;
	if (r > 1.0) r = 1.0;
	if (g < 0.0) g = 0.0;
	if (g > 1.0) g = 1.0;
	if (b < 0.0) b = 0.0;
	if (b > 1.0) b = 1.0;

	// clear the buffer
	int i,j;
	for(i=0; i<fb_num_rows; i++)
		for(j=0; j<fb_num_cols; j++)
		{
			fb_pixels[i][j].r = (unsigned char)(r*255);
			fb_pixels[i][j].g = (unsigned char)(g*255);
			fb_pixels[i][j].b = (unsigned char)(b*255);
		}

	// have the window redrawn
	glutSetWindow(fb_window);
	glutPostRedisplay();
}

void
fbSetPixel(int i, int j, double r, double g, double b)
{
	// i: row, 0 is top row
	// j: column, 0 is left column

	if (i<0) return;
	if (i>=fb_num_rows) return;
	if (j<0) return;
	if (j>=fb_num_cols) return;

		// clamp the values
	if (r < 0.0) r = 0.0;
	if (r > 1.0) r = 1.0;
	if (g < 0.0) g = 0.0;
	if (g > 1.0) g = 1.0;
	if (b < 0.0) b = 0.0;
	if (b > 1.0) b = 1.0;

	// set the pixel
	fb_pixels[i][j].r = (unsigned char)(r*255);
	fb_pixels[i][j].g = (unsigned char)(g*255);
	fb_pixels[i][j].b = (unsigned char)(b*255);
}

// ----- interaction routines ----------------



poly_type fb_poly;

poly_type clip_polies[100];
int num_clip_polies;


void
doClipAndRaster()
{	
	myClipper(clip_polies, &num_clip_polies, fb_poly, fb_xl, fb_xr, fb_yt, fb_yb);
	if (fb_wireframe_flag) myLineRasterizer(clip_polies, num_clip_polies);
	else myFillRasterizer(clip_polies, num_clip_polies);
} 

// ------- fb interaction ---------------------
void
cbDisplay()
{
	glClearColor(margin_rgb.r/255.0f, margin_rgb.g/255.0f, margin_rgb.b/255.0f, 1.0f);
	glClear( GL_COLOR_BUFFER_BIT);

	// decide rectangle of pixels to draw
	int j1 = (int) floor(fb_x - fb_s - 1);
	int j2 = (int) ceil(fb_x + fb_s + 1);
	int i1 = (int) floor(fb_y - fb_s - 1);
	int i2 = (int) ceil(fb_y + fb_s + 1);
	if (i1 < 0) i1 = 0;  if (i1 >= fb_num_cols) i1 = fb_num_cols-1;
	if (i2 < 0) i2 = 0;  if (i2 >= fb_num_cols) i2 = fb_num_cols-1;
	if (j1 < 0) j1 = 0;  if (j1 >= fb_num_cols) j1 = fb_num_cols-1;
	if (j2 < 0) j2 = 0;  if (j2 >= fb_num_cols) j2 = fb_num_cols-1;
	
	// set the projection (orthogonal, for 2D drawing)
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(fb_x-fb_s, fb_x+fb_s, fb_y+fb_s, fb_y-fb_s, -1.0, 1.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// margin width, if wanted
	double m;
	if (fb_margin_flag) m = fb_margin;
	else m = 0.5;

	// draw pixels of fb
	int i,j;
	glBegin(GL_QUADS);
	for(i=i1; i<i2; i++)
		for(j=j1; j<j2; j++)
		{
			// draw pixel [i][j]
			glColor3ub(fb_pixels[i][j].r, fb_pixels[i][j].g, fb_pixels[i][j].b);
			glVertex2d(j-m, i-m);
			glVertex2d(j-m, i+m);
			glVertex2d(j+m, i+m);
			glVertex2d(j+m, i-m);
		}
	glEnd();
	
	// draw centers if wanted
	if (fb_center_flag)
	{
		glColor3d(0.0, 1.0, 0.0);
		glBegin(GL_POINTS);
		for(i=i1; i<i2; i++)
			for(j=j1; j<j2; j++)
			{
				// draw pixel [i][j]
				glVertex2d(j, i);
			}
		glEnd();
	}

	// draw the frame for the fb
	glColor3d(0.0, 1.0, 0.0);
	glBegin(GL_LINE_STRIP);
	glVertex2d(fb_xl-0.5, fb_yt-0.5);
	glVertex2d(fb_xl-0.5, fb_yb+0.5);
	glVertex2d(fb_xr+0.5, fb_yb+0.5);
	glVertex2d(fb_xr+0.5, fb_yt-0.5);
	glVertex2d(fb_xl-0.5, fb_yt -0.5);
	glEnd();

	// draw whatever polygon is in fb_poly;
	glColor3d(0.5, 0.5, 0.0); // in dark yellow
	glBegin(GL_LINE_STRIP);
	for(i=0; i<fb_poly.n; i++)
	{
		glVertex2d(fb_poly.x[i], fb_poly.y[i]);
	}
	glEnd();

	// draw whatever polygons are in clip_polies;
	glColor3d(1.0, 1.0, 0.0); // in bright yellow
	for(j=0; j<num_clip_polies; j++)
	{
		glBegin(GL_LINE_STRIP);
		for(i=0; i<clip_polies[j].n; i++)
		{
			glVertex2d(clip_polies[j].x[i], clip_polies[j].y[i]);
		}
		glEnd();
	}

	glutSwapBuffers();
}


double dx, dy;
double sx, sy;


void
cbMotionRight(int x, int y)
{
	double s = (2.0*fb_s) / 500.0; 
	dx = x - sx;
	dy = y - sy;

	fb_x -= dx * s;
	fb_y -= dy * s;

	sx = x;
	sy = y;

	glutPostRedisplay();
}

void
cbMotionMiddle(int x, int y)
{
	dx = x - sx;
	dy = y - sy;

	fb_s += dy * 0.01;

	sx = x;
	sy = y;

	if (fb_s < 1.0) fb_s = 1.0;

	glutPostRedisplay();
}

// called when user enters another vertex
void
fb_addvertex(int x, int y)
{
	if (fb_poly.status == 2) fb_poly.clear();  // start a new poly

	// convert x,y to fb coordinates, and add to fb_poly
	double px = fb_x - fb_s + (2.0*fb_s*x)/fb_win_width;
	double py = fb_y - fb_s + (2.0*fb_s*y)/fb_win_height;

	fb_poly.add_pair(px, py);

	glutPostRedisplay();
}


void
cbMouse(int b, int s, int x, int y)
{
	if (s == GLUT_DOWN)
	{
		sx = x;
		sy = y;
	}
	else if (GLUT_UP)
	{
		dx = 0;
		dy = 0;
	}


	if (b == GLUT_LEFT_BUTTON )
	{
		glutMotionFunc(0);
		num_clip_polies = 0;
		if (s == GLUT_DOWN) fb_addvertex(x,y);	
	}
	else if (b == GLUT_RIGHT_BUTTON && !(glutGetModifiers() & GLUT_ACTIVE_SHIFT))
	{
		glutMotionFunc(cbMotionRight);
	}
	else if (b == GLUT_RIGHT_BUTTON  && (glutGetModifiers() & GLUT_ACTIVE_SHIFT))
	{
		glutMotionFunc(cbMotionMiddle);	
	}
	else
	{
		// this should never happen
		cerr << "Eek!  Unknown button!\n";
		cerr.flush();
	}

}

void
cbQuit()
{
	exit(0);
}

void
cbClear(int id)
{
	fbClear(0.0, 0.0, 0.0);
	num_clip_polies = 0;
	fb_poly.clear();
}

void
cbKey(unsigned char k, int x, int y)
{
	if (k == 'q') cbQuit();

	if (k == ' ')
	{
		double px = fb_poly.x[0];
		double py = fb_poly.y[0];
		fb_poly.close();

		cout << "Polygon complete" << endl;
	    int i;
		for(i=0; i<fb_poly.n; i++)
			cout << "(" << fb_poly.x[i] << "," << fb_poly.y[i] << ")" << endl;
		cout << endl;
		// close off the polygon

		doClipAndRaster();
	}

	glutPostRedisplay();
}

int save_id = 0;

void
SavePoly(int id)
{
    char buf[512];
	if (fb_convex_flag) sprintf(buf, "convex%d.txt", id);
	else sprintf(buf, "concave%d.txt", id);
	FILE *fp = fopen(buf, "w");
	if (!fp)
	{
		cerr << "Couldn't open file '" << buf << "' for writing." << endl; 
		return;
	}

	fprintf(fp, "%d\n", fb_poly.n-1);
	int i;
	for(i=0; i<(fb_poly.n-1); i++)
	{
		fprintf(fp, "%lf %lf\n", fb_poly.x[i], fb_poly.y[i]);
	}
	fclose(fp);
}

void
cbSavePoly(int id)
{
	save_id = 1;
}

void
cbTestPoly(int id)
{
	int n;
	double x,y;

	if (save_id == 1)
	{
		SavePoly(id);
		save_id = 0;
		return;
	}

	char buf[512];
	if (fb_convex_flag) sprintf(buf, "convex%d.txt", id);
	else sprintf(buf, "concave%d.txt", id);

	FILE *fp = fopen(buf, "r");
	if (!fp)
	{
		cerr << "Couldn't open file '" << buf << "' for reading." << endl; 
		return;
	}

	fb_poly.clear();

	fscanf(fp, "%d", &n);
	int i;
	for(i=0; i<n; i++)
	{
		fscanf(fp, "%lf%lf", &x, &y);
		fb_poly.add_pair(x,y);
	}
	fb_poly.close();

	fclose(fp);

	doClipAndRaster();

	glutPostRedisplay();
}




int
main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowPosition(500, 10);
	glutInitWindowSize(fb_win_width, fb_win_height);
	fb_window = glutCreateWindow("comp236 HW 2: clipper and rasterizer");
	glutDisplayFunc(cbDisplay);
	glutMouseFunc(cbMouse);
	glutKeyboardFunc(cbKey);

	GLUI *glui = GLUI_Master.create_glui("comp236 HW 2: clipper and rasterizer");
	glui->add_button("Clear FrameBuffer", 0, cbClear);
	
	GLUI_RadioGroup *rg = glui->add_radiogroup(&fb_wireframe_flag);
    glui->add_radiobutton_to_group(rg, "fill rasterize");
    glui->add_radiobutton_to_group(rg, "line rasterize");

	rg = glui->add_radiogroup(&fb_convex_flag);
    glui->add_radiobutton_to_group(rg, "concave poly tests");
    glui->add_radiobutton_to_group(rg, "convex poly test");

	glui->add_checkbox("show pixel margins", &fb_margin_flag);
	glui->add_checkbox("show pixel centers", &fb_center_flag);
    glui->add_spinner( "Left", GLUI_SPINNER_INT, &fb_xl);
    glui->add_spinner( "Right", GLUI_SPINNER_INT, &fb_xr);
    glui->add_spinner( "Top", GLUI_SPINNER_INT, &fb_yt);
    glui->add_spinner( "Bottom", GLUI_SPINNER_INT, &fb_yb);
	glui->add_button("Save", 1, cbSavePoly);
	glui->add_button("Test1", 1, cbTestPoly);
	glui->add_button("Test2", 2, cbTestPoly);
	glui->add_button("Test3", 3, cbTestPoly);
	glui->add_button("Test4", 4, cbTestPoly);
	glui->add_button("Test5", 5, cbTestPoly);
	glui->add_button("Test6", 6, cbTestPoly);
	glui->add_button("Test7", 7, cbTestPoly);
	glui->add_button("Test8", 8, cbTestPoly);

    glui->set_main_gfx_window( fb_window );

	fbClear(0.0, 0.0, 0.0);
	fbSetPixel(110, 110, 1.0, 1.0, 1.0);

	glutMainLoop();
	return 0;
}
