//*****************************************************************************************
// Disclaimer, Authorship and License
/*
	This code uses parts of the Polygons.cpp and Lines.cpp demo files.  All of the rest of this
	code was written by Tom Oldani and Felix Zhai.
*/
//******************************************************************************************


#include <stdlib.h>
#include <math.h>
#include <iostream>
#include <GL/glut.h>

using namespace std;

typedef struct
{
	float x;
	float y;
	float z;
}point;

// shape stores a shape type, RGB values, four points, and two floats.  The floats are
// used for different things depending on the shape type (ie, l is the radius for a circle).
// velocity is also stored in the form of an x velocity and a y velocity.
typedef struct
{
    int shapetype; // type of shape
    float red; // RGB values of the shape color
    float green;
    float blue;
    point p1;   // up to 4 points stored per shape
    point p2;
    point p3;
    point p4;
    float l;    // l and m are values necessary to generate the shape, ie l is the radius for circles
    float m;
    float vx;   // vx and vy store the horizontal and vertical velocity
    float vy;
}shape;

// sh stores all shape information.  currently, this only allows for 100 shapes, but
// allowing more would be simple enough.  other global variables are defined.
unsigned int drawmode = 1;
unsigned int shapeIndex = 1;
shape sh[100];
float linex;
float liney;
bool anim = false;
bool initial = true;

// defining global constants for later use
int randomvelocity = 50;
float decayrate = 0.96;
float stopped = 0.05;
float clickforce = 5000;

// Writes text on the screen
void writetext( int x, int y, char text[])
{
    for(int i=0; text[i] != '\0'; i++)
    {
        glRasterPos2i( i*9+x, y );
        glutBitmapCharacter(GLUT_BITMAP_9_BY_15, text[i]);
    }
}

// shape-drawing functions draw the shape given the shape object data
void drawTriangle( point p1, point p2, point p3 )
{
	float scale = 1.;
	if( scale > 0 )
	{
		glBegin( GL_TRIANGLES );
			glVertex3f( p1.x, p1.y, 0. );
			glVertex3f( p2.x, p2.y, 0. );
			glVertex3f( p3.x, p3.y, 0. );
            glEnd();
	}
}

void drawRectangle( point p1, point p2, point p3, point p4 )
{
	float scale = 1.;
	if( scale > 0 )
	{
		glBegin( GL_POLYGON );
			glVertex3f( p1.x, p1.y, 0. );
			glVertex3f( p2.x, p2.y, 0. );
			glVertex3f( p3.x, p3.y, 0. );
			glVertex3f( p4.x, p4.y, 0. );
            glEnd();
	}
}

void drawCircle( point p1, float rad )
{
    float scale = 1.;
    if( scale > 0 )
    {
        // technically, this is not a circle, but a 36-sided polygon
        glBegin( GL_TRIANGLE_FAN );
            glVertex2f( p1.x, p1.y );
            for( float i=0; i <= 36; i++ )
            {
                glVertex2f( p1.x + rad * cos( i*M_PI/18 ), p1.y + rad * sin( i*M_PI/18 ));
            }
            glEnd();
    }

}

void drawLine( point p1, point p2, float thick, unsigned short stip )
{
	float scale = 1.;
	glEnable(GL_LINE_STIPPLE);
	glLineStipple( 1, stip );
	glLineWidth( thick );

	if( scale > 0 )
	{
		glBegin( GL_LINES );
			glVertex2f( p1.x, p1.y );
			glVertex2f( p2.x, p2.y );
            glEnd();
	}
}

void drawEllipse( point p1, float rad, float e )
{
    float scale = 1.;
    if( scale > 0 )
    {
        // very similar to the drawCircle function, but it multiplies the y-dimension
        // by a scaling factor.
        glBegin( GL_TRIANGLE_FAN );
            glVertex2f( p1.x, p1.y );
            for( float i=0; i <= 36; i++ )
            {
                glVertex2f( p1.x + rad * cos( i*M_PI/18 ), p1.y + rad * sin( i*M_PI/18 ) * e);
            }
            glEnd();
    }

}

void keyBoardFunc( unsigned char key, int x, int y )
{
	switch( key )
	{
	case 't':
		{
			drawmode = 1;
		} break;
	case 'r':
		{
			drawmode = 2;
		} break;
    case 'c':
		{
			drawmode = 3;
		} break;
    case 'l':
		{
			drawmode = 4;
		} break;
    case 'e':
		{
			drawmode = 5;
		} break;
    case 'a':
        {
            // pool table mode!  set the back color to green and disable object placing.
            if( anim )
            {
                anim = false;
                glClearColor( 0., 0., 0., 0. );
                cout << "Shape-placing mode.\n";
            }
            else
            {
                anim = true;
                glClearColor( 0., 0.2, 0., 0. );
                cout << "Pool table mode!\n";
            }
        } break;
    case 'z':
        {
            // give all the objects a random velocity, if in animation mode
            if( anim )
            {
                for( int i = 0; i <= shapeIndex; i++ )
                {
                    sh[i].vx = rand() %(randomvelocity *2) - randomvelocity * 1.;
                    sh[i].vy = rand() %(randomvelocity *2) - randomvelocity * 1.;
                }
            }
            cout << "Zoom!\n";
        }
	}
	glutSwapBuffers();
}

void mouseClickFunc( int button, int state, int x, int y )
{
  initial = false; // get rid of the initial text once the user clicks something

  if( anim )
  {
    if( state == GLUT_DOWN )
	{
	    //calculate center of mass and give gravitational-type velocity. a = c/r^2
	    float centerx;
	    float centery;
	    float r;
	    float a;

	    for( int i = 0; i <= shapeIndex; i++ )
	    {
	        switch( sh[i].shapetype )
	        {
	            case 1:
	            {
	                centerx = (sh[i].p1.x + sh[i].p2.x + sh[i].p3.x) / 3.;
	                centery = (sh[i].p1.y + sh[i].p2.y + sh[i].p3.y) / 3.;
	            } break;
	            case 2:
	            {
	                centerx = (sh[i].p1.x + sh[i].p2.x) / 2.;
	                centery = (sh[i].p1.y + sh[i].p4.y) / 2.;
	            } break;
	            case 4:
	            {
	                centerx = (sh[i].p1.x + sh[i].p2.x) / 2.;
	                centery = (sh[i].p1.y + sh[i].p2.y) / 2.;
	            } break;
	            default:
	            {
	                centerx = sh[i].p1.x;
	                centery = sh[i].p1.y;
	            }
	        }

            // r is radius, a is acceleration.  a constant is added to r so that objects
            // very close to the click point don't have an infinite velocity
            r = sqrtf( powf( (centerx-x), 2. ) + powf( (centery-y), 2. ) ) + 20;
            a = clickforce / powf( r, 2. );

            // mouse clicks attract polygons if the right button is clicked.  they repel
            // them for left clicks.
	        if( button == GLUT_RIGHT_BUTTON )
                a *= -1.;

            sh[i].vx += a * (centerx-x) / r;
            sh[i].vy += a * (centery-y) / r;
	    }
	}
  }
  else
  {
	if( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN )
	{
	    // come up with a shape color, even though we're not sure what the shape is yet
        sh[shapeIndex].red = rand() %11/10.;
        sh[shapeIndex].green = rand() %11/10.;
        sh[shapeIndex].blue = rand() %11/10.;

        // calculate and store the various shape parameters.
        switch( drawmode )
        {
            case 1:
            {
                // triangles have one point on the mouse position, the other two points are random
                sh[shapeIndex].shapetype = 1;
                sh[shapeIndex].p1.x = x;
                sh[shapeIndex].p1.y = y;
                sh[shapeIndex].p2.x = x + rand() %50 + 30;
                sh[shapeIndex].p2.y = y + rand() %30 - 15;
                sh[shapeIndex].p3.x = x + rand() %50 - 20;
                sh[shapeIndex].p3.y = y + rand() %60 + 20;
                shapeIndex++;
            } break;
            case 2:
            {
                sh[shapeIndex].shapetype = 2;
                sh[shapeIndex].p1.x = x;
                sh[shapeIndex].p1.y = y;
                sh[shapeIndex].p2.x = x + rand() %70 + 30;
                sh[shapeIndex].p2.y = y;
                sh[shapeIndex].p3.x = sh[shapeIndex].p2.x;
                sh[shapeIndex].p3.y = y + rand() %70 + 30;
                sh[shapeIndex].p4.x = x;
                sh[shapeIndex].p4.y = sh[shapeIndex].p3.y;
                shapeIndex++;
            } break;
            case 3:
            {
                sh[shapeIndex].shapetype = 3;
                sh[shapeIndex].p1.x = x;
                sh[shapeIndex].p1.y = y;
                sh[shapeIndex].l = rand() %40 + 10;
                shapeIndex++;
            } break;
            case 4:
            {
                // store the coordinates in case the next click is a right click
                linex = x;
                liney = y;
            } break;
            case 5:
            {
                sh[shapeIndex].shapetype = 5;
                sh[shapeIndex].p1.x = x;
                sh[shapeIndex].p1.y = y;
                sh[shapeIndex].l = rand() %50 + 10;
                sh[shapeIndex].m = rand() %20 / 30. + 0.1;
                shapeIndex++;
            }
        }
	}
	else if( button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN )
	{
	    if( drawmode == 4 )
	    {
	        // draw a line segment if right mouse button is clicked
            sh[shapeIndex].shapetype = 4;
            sh[shapeIndex].p1.x = x;
            sh[shapeIndex].p1.y = y;
            sh[shapeIndex].p2.x = linex;
            sh[shapeIndex].p2.y = liney;
            sh[shapeIndex].l = rand() %7 + 1;
            sh[shapeIndex].m = rand() % 65536;
            shapeIndex++;
	    }
	}
  }
}

// this updates the shape properties every 50 milliseconds
void timerFunc( int t )
{
    if( anim )
    {
        // learn the window size in order to calculate where the "walls" are
        int hh = glutGet( GLUT_WINDOW_HEIGHT );
        int ww = glutGet( GLUT_WINDOW_WIDTH );

        for( int i = 0; i <= shapeIndex; i++ )
        {
            // Each iteration, moves each object by its velocity, then lowers velocity
            sh[i].p1.x += sh[i].vx;
            sh[i].p1.y += sh[i].vy;
            sh[i].p2.x += sh[i].vx;
            sh[i].p2.y += sh[i].vy;
            sh[i].p3.x += sh[i].vx;
            sh[i].p3.y += sh[i].vy;
            sh[i].p4.x += sh[i].vx;
            sh[i].p4.y += sh[i].vy;
            sh[i].vx *= decayrate;
            sh[i].vy *= decayrate;
            if( sqrtf(powf(fabs(sh[i].vx), 2.) + powf(fabs(sh[i].vy), 2.)) < stopped )
            {
                sh[i].vx = 0;
                sh[i].vy = 0;
            }

            // lots of if-then statements to determine if an object has collided with a wall.
            // obviously, rules are different for different object types.  objects' velocities
            // are reversed if they hit a wall (ie bounce)
            switch( sh[i].shapetype )
            {
                case 1:
                {
                    if( sh[i].p1.x < 0 || sh[i].p2.x < 0 || sh[i].p3.x < 0 )
                        sh[i].vx = fabs(sh[i].vx);
                    if( sh[i].p1.x > ww || sh[i].p2.x > ww || sh[i].p3.x > ww )
                        sh[i].vx = fabs(sh[i].vx) * -1.;
                    if( sh[i].p1.y < 0 || sh[i].p2.y < 0 || sh[i].p3.y < 0 )
                        sh[i].vy = fabs(sh[i].vy);
                    if( sh[i].p1.y > hh || sh[i].p2.y > hh || sh[i].p3.y > hh )
                        sh[i].vy = fabs(sh[i].vy) * -1.;
                } break;
                case 2:
                {
                    if( sh[i].p1.x < 0 || sh[i].p2.x < 0 || sh[i].p3.x < 0 || sh[i].p4.x < 0 )
                        sh[i].vx = fabs(sh[i].vx);
                    if( sh[i].p1.x > ww || sh[i].p2.x > ww || sh[i].p3.x > ww || sh[i].p4.x > ww )
                        sh[i].vx = fabs(sh[i].vx) * -1.;
                    if( sh[i].p1.y < 0 || sh[i].p2.y < 0 || sh[i].p3.y < 0 || sh[i].p4.y < 0 )
                        sh[i].vy = fabs(sh[i].vy);
                    if( sh[i].p1.y > hh || sh[i].p2.y > hh || sh[i].p3.y > hh || sh[i].p4.y > hh )
                        sh[i].vy = fabs(sh[i].vy) * -1.;
                } break;
                case 3:
                {
                    if((sh[i].p1.x - sh[i].l) < 0 )
                        sh[i].vx = fabs(sh[i].vx);
                    if((sh[i].p1.x + sh[i].l) > ww )
                        sh[i].vx = fabs(sh[i].vx) * -1.;
                    if((sh[i].p1.y - sh[i].l) < 0 )
                        sh[i].vy = fabs(sh[i].vy);
                    if((sh[i].p1.y + sh[i].l) > hh )
                        sh[i].vy = fabs(sh[i].vy) * -1.;
                } break;
                case 4:
                {
                    if( sh[i].p1.x < 0 || sh[i].p2.x < 0 )
                        sh[i].vx = fabs(sh[i].vx);
                    if( sh[i].p1.x > ww || sh[i].p2.x > ww )
                        sh[i].vx = fabs(sh[i].vx) * -1.;
                    if( sh[i].p1.y < 0 || sh[i].p2.y < 0 )
                        sh[i].vy = fabs(sh[i].vy);
                    if( sh[i].p1.y > hh || sh[i].p2.y > hh )
                        sh[i].vy = fabs(sh[i].vy) * -1.;
                } break;
                case 5:
                {
                    if((sh[i].p1.x - sh[i].l) < 0 )
                        sh[i].vx = fabs(sh[i].vx);
                    if((sh[i].p1.x + sh[i].l) > ww )
                        sh[i].vx = fabs(sh[i].vx) * -1.;
                    if((sh[i].p1.y - sh[i].l * sh[i].m) < 0 )
                        sh[i].vy = fabs(sh[i].vy);
                    if((sh[i].p1.y + sh[i].l * sh[i].m) > hh )
                        sh[i].vy = fabs(sh[i].vy) * -1.;
                } break;
            }
        }
    }

    glutTimerFunc( 50, timerFunc, 0 );
}

void reshape( int w, int h)
{
	// nothing to see here, please move along

}

void display( )
{

    // clears the screen and draws all polygons
    glClear( GL_COLOR_BUFFER_BIT );

    if( initial )
    {
        glColor3f( 0, 1, 0 );

        writetext( 80, 90, "Click on the screen to draw shapes" );
        writetext( 80, 110, "Press the following keys to change shapes:" );
        writetext( 90, 130, "t - Triangle" );
        writetext( 90, 150, "r - Rectangle" );
        writetext( 90, 170, "c - Circle" );
        writetext( 90, 190, "e - Ellipse" );
        writetext( 90, 210, "l - Line" );
        writetext( 80, 250, "Note that for lines, you need to press the left" );
        writetext( 80, 270, "mouse button to specify one endpoint of the line," );
        writetext( 80, 290, "and the right mouse button to specify the other." );
        writetext( 80, 330, "Press 'a' to turn on animation (pool table) mode" );
        writetext( 80, 350, "If animation mode is on, press 'z' to give objects" );
        writetext( 80, 370, "random velocities.  Left-clicking repels shapes," );
        writetext( 80, 390, "and right-clicking attracts them." );
    }

    for( unsigned int i = 1; i <= shapeIndex; i++)
    {
        glColor3f( sh[i].red, sh[i].green, sh[i].blue);
        switch( sh[i].shapetype )
        {
            case 1:
            {
                drawTriangle( sh[i].p1, sh[i].p2, sh[i].p3 );
            } break;
            case 2:
            {
                drawRectangle( sh[i].p1, sh[i].p2, sh[i].p3, sh[i].p4 );
            } break;
            case 3:
            {
                drawCircle( sh[i].p1, sh[i].l );
            } break;
            case 4:
            {
                drawLine( sh[i].p1, sh[i].p2, sh[i].l, sh[i].m );
            } break;
            case 5:
            {
                drawEllipse( sh[i].p1, sh[i].l, sh[i].m );
            } break;
        }
    }

	// Ensure all drawing commands are executed
	glutSwapBuffers( );
}

void init ( )
{
	// Set the background
	glClearColor( 0., 0., 0. , 0. );

	// Setting up a rudimentary camera
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );
	gluOrtho2D( 0, 640, 480, 0 );
}

void idle()
{
    glutPostRedisplay();
}

int main(int argc, char* argv[])
{
	// Initialize a window with command line arguments
	glutInit( &argc, argv );
	// Provide window with buffering, coloring information
	//glutInitDisplayMode( GLUT_SINGLE | GLUT_RGB );
	glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
	// Position and Size the window
	glutInitWindowPosition( 50, 50 );
	glutInitWindowSize( 640, 480 );
	// Create the window with the given features with a 'suitable title'
	glutCreateWindow( "Homework 1" );

	// do some initialization like setting up background color, camera, projection type etc.
	init( );

	// Assign a display 'callback function'
	glutDisplayFunc( display );

    // Idle function
    glutIdleFunc( idle );

	// The reshape function is called everytime the window is relocated or resized ! duh
	glutReshapeFunc( reshape );

	// add the function to handle keyboard calls
	glutKeyboardFunc( keyBoardFunc );

    // mouse clicks
    glutMouseFunc( mouseClickFunc );

    // timer function
    glutTimerFunc( 100, timerFunc, 0 );

	// Enter the graphics loop
	glutMainLoop( );

	return 0;
}

