#include "gamelogic.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include <stdio.h>
#include <math.h>


/**
 * Ratio (rotation angle)/(rotation size)
 */
#define RANGLE_RATIO 100.0


/**
 * Keyboard approach parameter
 */
GLfloat fApproach = 0.0;


/**
 * Rotation boolean parameters
 */
GLboolean bLeftButtonDown     = GL_FALSE;
GLboolean bDrawRotationVector = GL_FALSE;


/**
 * Rotation vector coordinates
 */
GLdouble fRotPoint1X, fRotPoint1Y, fRotPoint1Z;
GLdouble fRotPoint2X, fRotPoint2Y, fRotPoint2Z;
GLdouble fRotVectorX, fRotVectorY, fRotAngle = 0.0;


/**
 * Rotation matrices
 */
GLdouble rotation1Matrix[16] = { 1.0, 0.0, 0.0, 0.0,
				 0.0, 1.0, 0.0, 0.0,
				 0.0, 0.0, 1.0, 0.0,
				 0.0, 0.0, 0.0, 1.0 };
GLdouble rotation2Matrix[16] = { 1.0, 0.0, 0.0, 0.0,
				 0.0, 1.0, 0.0, 0.0,
				 0.0, 0.0, 1.0, 0.0,
				 0.0, 0.0, 0.0, 1.0 };

/**
 * Current foregrounded square
 */
GLboolean bSquareSelected = GL_FALSE;
GLint iCurrentX;
GLint iCurrentY;


/**
 * Window size
 */
GLint iWinWidth  = 500;
GLint iWinHeight = 500;


/**
 * Light Parameters
 */
GLfloat material_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat material_shininess = 50;
GLfloat light_position[] = { 20.0, 10.0, 0.0, 1.0 };


/**
 * Callback functions
 */
void display(void);
void reshape(int width, int heigth);
void keyboard(unsigned char key, int x, int y);
void mouse(int button, int state, int x, int y);
void motion(int x, int y);
void init();
void processMenu(int option);
void passiveMotion(int x, int y);


/**
 * Definition of game mode enumarate type
 */
typedef enum {

  FIREMODE = 1,
  IDLEMODE = 2,
  LINEMODE = 3,
  FILLMODE = 4

} eGameMode_t;

eGameMode_t eGameMode;



int main(int argc, char * argv[])
{
  /* Initiating GLUT */
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGB);
  glutInitWindowSize(iWinWidth, iWinHeight);
  glutInitWindowPosition(100, 100);
  glutCreateWindow(argv[0]);
  glutSetCursor(GLUT_CURSOR_CROSSHAIR);

  /* Initiating OpenGL */
  init();

  /* Creating Menu */
  eGameMode = glutCreateMenu(processMenu);
  glutAddMenuEntry("Fire Mode", 1);
  glutAddMenuEntry("Idle Mode", 2);
  glutAddMenuEntry("Line Mode", 3);
  glutAddMenuEntry("Fill Mode", 4);
  glutAttachMenu(GLUT_RIGHT_BUTTON);


  /* Callback functions for event control */
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutPassiveMotionFunc(passiveMotion);

  /* Calling glut main loop */
  glutMainLoop();


  return 0;
}


void processMenu(int option)
{
  switch (option)
    {
    case FIREMODE:
      eGameMode = FIREMODE;
      glutPostRedisplay();
      break;

    case IDLEMODE:
      eGameMode = IDLEMODE;
      glutPostRedisplay();
      break;

    case LINEMODE:
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      glutPostRedisplay();
      break;

    case FILLMODE:
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glutPostRedisplay();
      break;
    }
}


void display(void)
{
  /* Clear color and depth buffers and selecting back buffer */
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

  /* Setting scene parameters */
  glLoadIdentity();
  gluLookAt(0.0, 0.0, 25.0-fApproach, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);


  /* Drawing all stuff */
  glPushMatrix();
  glMultMatrixd(rotation2Matrix);
  glMultMatrixd(rotation1Matrix);

  drawTable();
  drawShips();

  if (bSquareSelected && eGameMode == FIREMODE)
    {
      glutSetCursor(GLUT_CURSOR_CROSSHAIR);
      drawSelectedSquare(iCurrentX, iCurrentY);
    }
  else glutSetCursor(GLUT_CURSOR_CYCLE);

  glPopMatrix();


  /* Drawing rotation vector */
  if (bDrawRotationVector)
    {
      glDisable(GL_LIGHTING);
      glBegin(GL_LINES);
      {
	glColor3f(0.0, 1.0, 0.0);
	glVertex3f(fRotPoint1X, -fRotPoint1Y, fRotPoint1Z);
	glColor3f(1.0, 0.0, 0.0);
	glVertex3f(fRotPoint2X, -fRotPoint2Y, fRotPoint2Z);
      }
      glEnd();
      glEnable(GL_LIGHTING);
    }


  /* Swapping double buffers */
  glutSwapBuffers();


  /* Drawing auxiliary table */
  {
    GLint iPolygonModes[2];

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushMatrix();
    glMultMatrixd(rotation2Matrix);
    glMultMatrixd(rotation1Matrix);

    /* Disabling lighting */
    glDisable(GL_LIGHTING);

    /* Getting current polygon mode and setting fill polygon mode */
    glGetIntegerv(GL_POLYGON_MODE, iPolygonModes);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    drawAuxTable();

    /* Restoring to original polygon mode */
    glPolygonMode(GL_FRONT, iPolygonModes[0]);
    glPolygonMode(GL_BACK,  iPolygonModes[1]);

    /* Enabling linghing */
    glEnable(GL_LIGHTING);

    glPopMatrix();
  }
}


/**
 *
 */
void reshape(int width, int height)
{
  GLdouble fovy   = 60.0;
  GLdouble aspect = (GLdouble)width/(GLdouble)height;
  GLdouble near   = 1.0;
  GLdouble far    = 100.0;


  iWinWidth  = width;
  iWinHeight = height;

  glViewport(0, 0, (GLsizei)width, (GLsizei)height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(fovy, aspect, near, far);
  glMatrixMode(GL_MODELVIEW);
}


/**
 *
 */
void keyboard(unsigned char key, int x, int y)
{
  switch (key)
    {
      int iKbdX;
      int iKbdY;

    case ' ':
      printf("X coordinate: "); scanf("%d", &iKbdX);
      printf("Y coordinate: "); scanf("%d", &iKbdY);

      if (iKbdX >= 0 && iKbdX < TB_NSQUARES &&
	  iKbdY >= 0 && iKbdY < TB_NSQUARES &&
	  testHit(iKbdX, iKbdY) == GL_TRUE)
	printf(" -- Target Hitted\n");

      glutPostRedisplay();

      break;

    case 'x':
      glPushMatrix();
      glLoadIdentity();
      glRotatef(-5.0, 1.0, 0.0, 0.0);
      glMultMatrixd(rotation1Matrix);
      glGetDoublev(GL_MODELVIEW_MATRIX, rotation1Matrix);
      glPopMatrix();
      glutPostRedisplay();
      break;

    case 'X':
      glPushMatrix();
      glLoadIdentity();
      glRotatef(5.0, 1.0, 0.0, 0.0);
      glMultMatrixd(rotation1Matrix);
      glGetDoublev(GL_MODELVIEW_MATRIX, rotation1Matrix);
      glPopMatrix();
      glutPostRedisplay();
      break;

    case 'y':
      glPushMatrix();
      glLoadIdentity();
      glRotatef(-5.0, 0.0, 1.0, 0.0);
      glMultMatrixd(rotation1Matrix);
      glGetDoublev(GL_MODELVIEW_MATRIX, rotation1Matrix);
      glPopMatrix();
      glutPostRedisplay();
      break;

    case 'Y':
      glPushMatrix();
      glLoadIdentity();
      glRotatef(5.0, 0.0, 1.0, 0.0);
      glMultMatrixd(rotation1Matrix);
      glGetDoublev(GL_MODELVIEW_MATRIX, rotation1Matrix);
      glPopMatrix();
      glutPostRedisplay();
      break;

    case 'z':
      glPushMatrix();
      glLoadIdentity();
      glRotatef(-5.0, 0.0, 0.0, 1.0);
      glMultMatrixd(rotation1Matrix);
      glGetDoublev(GL_MODELVIEW_MATRIX, rotation1Matrix);
      glPopMatrix();
      glutPostRedisplay();
      break;

    case 'Z':
      glPushMatrix();
      glLoadIdentity();
      glRotatef(5.0, 0.0, 0.0, 1.0);
      glMultMatrixd(rotation1Matrix);
      glGetDoublev(GL_MODELVIEW_MATRIX, rotation1Matrix);
      glPopMatrix();
      glutPostRedisplay();
      break;

    case '-':
      if (fApproach >  0.0) fApproach -= 1.0;
      glutPostRedisplay();
      break;

    case '+':
      if (fApproach < 20.0) fApproach += 1.0;
      glutPostRedisplay();
      break;

    case 27:
      exit(0);
      break;
    }
}


/**
 *
 */
void mouse(int button, int state, int x, int y)
{
  if (button == GLUT_LEFT_BUTTON)

    if (state == GLUT_DOWN)
      {
	if (!bSquareSelected || eGameMode == IDLEMODE) /* Rotation mode */
	  {
	    GLdouble  modelviewMatrix[16];
	    GLdouble projectionMatrix[16];
	    GLint      viewport[4];

	    glGetDoublev (GL_MODELVIEW_MATRIX,   modelviewMatrix);
	    glGetDoublev (GL_PROJECTION_MATRIX, projectionMatrix);
	    glGetIntegerv(GL_VIEWPORT,            viewport);

	    gluUnProject(x, y, 0.0,
			 modelviewMatrix, projectionMatrix, viewport,
			 &fRotPoint1X, &fRotPoint1Y, &fRotPoint1Z);

	    fRotPoint2X = fRotPoint1X;
	    fRotPoint2Y = fRotPoint1Y;
	    fRotPoint2Z = fRotPoint1Z;

	    bDrawRotationVector = GL_TRUE;
	    bLeftButtonDown     = GL_TRUE;

	    glutPostRedisplay();
	  }
      }

    else /* state == GLUT_UP */
      {
	if (bSquareSelected && eGameMode == FIREMODE) /* Hit mode */
	  {
	    if (testHit(iCurrentX, iCurrentY) == GL_TRUE)
	      printf(" -- Target Hitted\n");

	    glutPostRedisplay();
	  }

	else /* Rotation mode */
	  {
	    fRotVectorX = fRotPoint2Y-fRotPoint1Y;
	    fRotVectorY = fRotPoint2X-fRotPoint1X;
	    fRotAngle = RANGLE_RATIO*sqrt(fRotVectorX*fRotVectorX+
					  fRotVectorY*fRotVectorY);

	    glPushMatrix();
	    glLoadIdentity();
	    glGetDoublev(GL_MODELVIEW_MATRIX, rotation2Matrix);
	    glRotatef(fRotAngle, fRotVectorX, fRotVectorY, 0.0);
	    glMultMatrixd(rotation1Matrix);
	    glGetDoublev(GL_MODELVIEW_MATRIX, rotation1Matrix);
	    glPopMatrix();

	    bDrawRotationVector = GL_FALSE;
	    bLeftButtonDown     = GL_FALSE;

	    glutPostRedisplay();
	  }
      }
}


/**
 *
 */
void motion(int x, int y)
{
  if (bLeftButtonDown)
    {
      GLdouble  modelviewMatrix[16];
      GLdouble projectionMatrix[16];
      GLint      viewport[4];


      glGetDoublev (GL_MODELVIEW_MATRIX,   modelviewMatrix);
      glGetDoublev (GL_PROJECTION_MATRIX, projectionMatrix);
      glGetIntegerv(GL_VIEWPORT,            viewport);

      gluUnProject(x, y, 0.0, modelviewMatrix, projectionMatrix, viewport,
		   &fRotPoint2X, &fRotPoint2Y, &fRotPoint2Z);
 
      fRotVectorX = fRotPoint2Y-fRotPoint1Y;
      fRotVectorY = fRotPoint2X-fRotPoint1X;
      fRotAngle = RANGLE_RATIO*sqrt(fRotVectorX*fRotVectorX+
				    fRotVectorY*fRotVectorY);

      glPushMatrix();
      glLoadIdentity();
      glRotatef(fRotAngle, fRotVectorX, fRotVectorY, 0.0);
      glGetDoublev(GL_MODELVIEW_MATRIX, rotation2Matrix);
      glPopMatrix();

      glutPostRedisplay();
    }
}


/**
 *
 */
void passiveMotion(int x, int y)
{
  GLubyte ubRed;
  GLubyte ubGreen;
  GLubyte ubBlue;


  glReadBuffer(GL_BACK);

  glReadPixels(x, iWinHeight-y, 1, 1, GL_RED,   GL_UNSIGNED_BYTE, &ubRed);
  glReadPixels(x, iWinHeight-y, 1, 1, GL_GREEN, GL_UNSIGNED_BYTE, &ubGreen);
  glReadPixels(x, iWinHeight-y, 1, 1, GL_BLUE,  GL_UNSIGNED_BYTE, &ubBlue);

  if (ubBlue == 255)
    {
      bSquareSelected = GL_TRUE;

      if (iCurrentX != ubRed || iCurrentY != ubGreen)
	{
	  iCurrentX = ubRed;
	  iCurrentY = ubGreen;

	  glutPostRedisplay();
	}
    }
  else
    if (bSquareSelected == GL_TRUE)
      {
	bSquareSelected = GL_FALSE;
	iCurrentX = -1;
	iCurrentY = -1;

	glutPostRedisplay();
      }
}


/**
 *
 */
void init(void)
{
  /* Configuring depth and shade */
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_SMOOTH);

  /* Configuring material parameters */
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
  glMaterialf(GL_FRONT, GL_SHININESS, material_shininess);
  glEnable(GL_COLOR_MATERIAL);

  /* Configuring and enabling lighting */
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);

  /* Configuring normal vectors */
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);

  /* Booting game */
  createAllModels();
  createAuxTable();
  initShips();
}
