#include "gamelogic.h"

#include <stdio.h>
#include <string.h>

/**
 * Values for table squares
 */
#define TB_EMPTY     -1
#define TB_MISSED     0
#define TB_FOUND      1
#define TB_DESTROYED  2


/**
 *
 */
sModel_t smRaft      = { RAFT,      "raft",      1, NRAFTS,      NRAFTS };
sModel_t smBoat      = { BOAT,      "boat",      2, NBOATS,      NBOATS };
sModel_t smSubmarine = { SUBMARINE, "submarine", 3, NSUBMARINES, NSUBMARINES };
sModel_t smVessel    = { VESSEL,    "vessel",    4, NVESSELS,    NVESSELS };


/**
 *
 */
sShip_t vssShips[NSHIPS];
int iNumberOfShips;
int iNumberOfShots;
int iNumberOfHits;


/**
 * 
 */
int miShipsTable[TB_NSQUARES][TB_NSQUARES];
int miFoundTable[TB_NSQUARES][TB_NSQUARES];



/**
 * Auxiliary Functions
 */
void defineShip(int iShipIdx, sModel_t *psModel);
void destroyShip(int iShipIdx);


/**
 * Game log color vetor and strings array
 */
#define N_GAME_STRINGS 8
double vdGameStringsColor[] = { 1.0, 1.0, 1.0 };
char  vvcGameStrings[N_GAME_STRINGS][60];



/**
 *
 */
void startGame(void)
{
  /* Initing game log */
  char vcString[STRING_LENGHT];

  strcpy(vvcGameStrings[N_GAME_STRINGS-1], "Game state log:");

  sprintf(vcString, "-> %d ships placed:", NSHIPS);
  setLastGameString(vcString);

  sprintf(vcString, ":: %d raft(s)", NRAFTS);
  setLastGameString(vcString);

  sprintf(vcString, ":: %d boat(s)", NBOATS);
  setLastGameString(vcString);

  sprintf(vcString, ":: %d submarine(s)", NSUBMARINES);
  setLastGameString(vcString);

  sprintf(vcString, ":: %d vessel(s)", NVESSELS);
  setLastGameString(vcString);

  setLastGameString("-> Destroy all ships to win");
  setLastGameString("-> Good game");


  /* Initing gloabal variables */
  smRaft.iCurrentNumber      = NRAFTS;
  smBoat.iCurrentNumber      = NBOATS;
  smSubmarine.iCurrentNumber = NSUBMARINES;
  smVessel.iCurrentNumber    = NVESSELS;

  iNumberOfShips = NSHIPS;
  iNumberOfShots = 0;
  iNumberOfHits  = 0;


  /* Initing ships */
  initShips();
}


/**
 *
 */
void initShips(void)
{
  int iShipIdx;
  int iTableIdx, jTableIdx;


  /* Iniatilizing tables with TB_EMPTY */
  for (iTableIdx = 0; iTableIdx < TB_NSQUARES; ++iTableIdx)
    for (jTableIdx = 0; jTableIdx < TB_NSQUARES; ++jTableIdx)
      {
	miShipsTable[iTableIdx][jTableIdx] = TB_EMPTY;
	miFoundTable[iTableIdx][jTableIdx] = TB_EMPTY;
      }


  /* Filling ships with vessels */
  for (iShipIdx = 0; iShipIdx < NVESSELS; ++iShipIdx)
    defineShip(iShipIdx, &smVessel);

  /* Filling ships with submarines */
  for (; iShipIdx < NVESSELS+NSUBMARINES; ++iShipIdx)
    defineShip(iShipIdx, &smSubmarine);

  /* Filling ships with boats */
  for (; iShipIdx < NVESSELS+NSUBMARINES+NBOATS; ++iShipIdx)
    defineShip(iShipIdx, &smBoat);

  /* Filling ships with rafts */
  for (; iShipIdx < NVESSELS+NSUBMARINES+NBOATS+NRAFTS; ++iShipIdx)
    defineShip(iShipIdx, &smRaft);


  /* Printing ships places */
  for (iTableIdx = 0; iTableIdx < TB_NSQUARES; ++iTableIdx)
    {
      for (jTableIdx = 0; jTableIdx < TB_NSQUARES; ++jTableIdx)
	if (miShipsTable[jTableIdx][iTableIdx] == TB_EMPTY)
	  printf("## ");
	else
	  printf("%02d ", miShipsTable[jTableIdx][iTableIdx]);

      puts("");
    }
}


/**
 *
 */
void drawShipsOnDay(void)
{
  int iShipIdx;


  for (iShipIdx = 0; iShipIdx < NSHIPS; ++iShipIdx)

    if (vssShips[iShipIdx].iCounter == 0)
      {
	glPushMatrix();
	glTranslatef(TB_X0+(vssShips[iShipIdx].iPositionX+0.5)*TB_SQUARE_SIZE,
		     TB_Yn-(vssShips[iShipIdx].iPositionY+0.5)*TB_SQUARE_SIZE,
		     0.0);

	switch (vssShips[iShipIdx].eDirection)
	  {
	  case LEFT:  glRotatef( 90.0, 0.0, 0.0, 1.0); break;
	  case RIGHT: glRotatef(-90.0, 0.0, 0.0, 1.0); break;
	  case UP:    glRotatef(  0.0, 0.0, 0.0, 1.0); break;
	  case DOWN:  glRotatef(180.0, 0.0, 0.0, 1.0); break;
	  }

	glTranslatef(0.0, -0.5*TB_SQUARE_SIZE, 0.0);
	glScalef(TB_SQUARE_SIZE, TB_SQUARE_SIZE, TB_SQUARE_SIZE);
	glCallList(vssShips[iShipIdx].psModel->eModel);
	glPopMatrix();
      }
}


/**
 *
 */
void drawShipsAtNight(void)
{
  int iShipIdx;


  for (iShipIdx = 0; iShipIdx < NSHIPS; ++iShipIdx)

    if (vssShips[iShipIdx].iCounter == 0)
      {
	glPushMatrix();
	glTranslatef(TB_X0+(vssShips[iShipIdx].iPositionX+0.5)*TB_SQUARE_SIZE,
		     TB_Yn-(vssShips[iShipIdx].iPositionY+0.5)*TB_SQUARE_SIZE,
		     0.0);

	switch (vssShips[iShipIdx].eDirection)
	  {
	  case LEFT:  glRotatef( 90.0, 0.0, 0.0, 1.0); break;
	  case RIGHT: glRotatef(-90.0, 0.0, 0.0, 1.0); break;
	  case UP:    glRotatef(  0.0, 0.0, 0.0, 1.0); break;
	  case DOWN:  glRotatef(180.0, 0.0, 0.0, 1.0); break;
	  }

	glTranslatef(0.0, -0.5*TB_SQUARE_SIZE, 0.0);
	glScalef(TB_SQUARE_SIZE, TB_SQUARE_SIZE, TB_SQUARE_SIZE);
	glCallList(vssShips[iShipIdx].psModel->eModel+EMISSIVE_PARTS);
	glCallList(vssShips[iShipIdx].psModel->eModel);
	glPopMatrix();
      }
}


/**
 *
 */
void drawTable(void)
{
  int iTableX;
  int iTableY;


  glCallList(TABLE);

  for (iTableX = 0; iTableX < TB_NSQUARES; ++iTableX)
    for (iTableY = 0; iTableY < TB_NSQUARES; ++iTableY)
      {
	if (miFoundTable[iTableX][iTableY] == TB_FOUND)
	  glColor3f(1.0, 0.0, 0.0);
	else if (miFoundTable[iTableX][iTableY] == TB_MISSED)
	  glColor3f(0.2, 0.2, 0.65);
	else
	  continue;

	glPushMatrix();
	glTranslatef(TB_X0+(iTableX+0.5)*TB_SQUARE_SIZE,
		     TB_Yn-(iTableY+0.5)*TB_SQUARE_SIZE, 0.0);
	glScalef(0.80*TB_SQUARE_SIZE, 0.80*TB_SQUARE_SIZE, 0.25);
	glutSolidCube(1.0);
	glPopMatrix();
      }
}


/**
 * 
 */
void drawAuxTable(void)
{
  glCallList(AUXTABLE);
}


/**
 * 
 */
void drawSelectedSquare(int iTableX, int iTableY)
{
  if (miFoundTable[iTableX][iTableY] == TB_FOUND)
    glColor3f(1.0, 1.0, 0.0);
  else if (miFoundTable[iTableX][iTableY] == TB_MISSED)
    glColor3f(0.2, 1.0, 0.65);
  else
    glColor3f(0.0, 1.0, 0.0);

  glPushMatrix();
  glTranslatef(TB_X0+(iTableX+0.5)*TB_SQUARE_SIZE,
	       TB_Yn-(iTableY+0.5)*TB_SQUARE_SIZE, 0.0);
  glScalef(TB_SQUARE_SIZE, TB_SQUARE_SIZE, 0.07);
  glutSolidCube(1.0);
  glPopMatrix();
}


/**
 * 
 */
void drawSun3fv(float vfSunPosition[])
{
  glPushMatrix();
  {
    glTranslatef(vfSunPosition[0],
		 vfSunPosition[1],
		 vfSunPosition[2]);
    glCallList(SUN);
  }
  glPopMatrix();
}


/**
 *
 */
void drawMoon3fv(float vfMoonPosition[])
{
  glPushMatrix();
  {
    glTranslatef(vfMoonPosition[0],
		 vfMoonPosition[1],
		 vfMoonPosition[2]);
    glCallList(MOON);
  }
  glPopMatrix();
 
}


/**
 *
 */
void destroyShip(int iShipIdx)
{ 
  switch (vssShips[iShipIdx].eDirection)
    {
      int iTableIdx;

    case LEFT:
      for (iTableIdx =  vssShips[iShipIdx].iPositionX;
	   iTableIdx > (vssShips[iShipIdx].iPositionX-
			vssShips[iShipIdx].psModel->iSize); --iTableIdx)
	{
	  miFoundTable[iTableIdx][vssShips[iShipIdx].iPositionY] =
	    TB_DESTROYED;
	  printf("Destroying %2d %2d\n", iTableIdx,
		 vssShips[iShipIdx].iPositionY);
	}
      break;

    case RIGHT:
      for (iTableIdx =  vssShips[iShipIdx].iPositionX;
	   iTableIdx < (vssShips[iShipIdx].iPositionX+
			vssShips[iShipIdx].psModel->iSize); ++iTableIdx)
	{
	  miFoundTable[iTableIdx][vssShips[iShipIdx].iPositionY] =
	    TB_DESTROYED;
	  printf("Destroying %2d %2d\n", iTableIdx,
		 vssShips[iShipIdx].iPositionY);
	}
      break;

    case UP:
      for (iTableIdx =  vssShips[iShipIdx].iPositionY;
	   iTableIdx > (vssShips[iShipIdx].iPositionY-
			vssShips[iShipIdx].psModel->iSize); --iTableIdx)
	{
	  miFoundTable[vssShips[iShipIdx].iPositionX][iTableIdx] =
	    TB_DESTROYED;
	  printf("Destroying %2d %2d\n", vssShips[iShipIdx].iPositionX,
		 iTableIdx);
	}
      break;

    case DOWN:
      for (iTableIdx =  vssShips[iShipIdx].iPositionY;
	   iTableIdx < (vssShips[iShipIdx].iPositionY+
			vssShips[iShipIdx].psModel->iSize); ++iTableIdx)
	{
	  miFoundTable[vssShips[iShipIdx].iPositionX][iTableIdx] =
	    TB_DESTROYED;
	  printf("Destroying %2d %2d\n", vssShips[iShipIdx].iPositionX,
		 iTableIdx);
	}
      break;
    }
}


/**
 * Tests if the shot at (iTableX, iTableY) hits a ship.
 * Returns 'GL_TRUE', if all ships are destroyed, i.e. the game is over.
 */
GLboolean testHit(int iTableX, int iTableY)
{
  if (miFoundTable[iTableX][iTableY] == TB_EMPTY)
    if (miShipsTable[iTableX][iTableY] != TB_EMPTY)
      {
	int iShipIdx = miShipsTable[iTableX][iTableY];

	if (--vssShips[iShipIdx].iCounter == 0)
	  {
	    char vcString[80];
	    sModel_t * psModel = vssShips[iShipIdx].psModel;


	    /* Destroying ship */
	    destroyShip(iShipIdx);

	    /* Writing "This type of ship was destroyed" */
	    sprintf(vcString, ":: A %s was destroyed", psModel->pcName);
	    puts(vcString);
	    setLastGameString(vcString);

	    /* How many ships of this type are remaing? */
	    if (--psModel->iCurrentNumber == 0)
	      sprintf(vcString, ":: All %d %s(s) destroyed",
		      psModel->iInitialNumber, psModel->pcName);
	    else
	      sprintf(vcString, ":: %d %s(s) remaining",
		      psModel->iCurrentNumber, psModel->pcName);

	    puts(vcString);
	    setLastGameString(vcString);

	    /* Testing for ending game */
	    if (--iNumberOfShips == 0)
	      {
		setLastGameString("-> Game Over:");

		/* Writing accuracy */
		sprintf(vcString, ":: You've shotted %d times and hitted %d",
			iNumberOfShots, iNumberOfHits);
		puts(vcString);
		setLastGameString(vcString);

		/* Writing accuracy */
		sprintf(vcString, ":: Your accuracy is %5.1f%%",
			100.0*iNumberOfHits/iNumberOfShots);
		puts(vcString);
		setLastGameString(vcString);

		return GL_TRUE;
	      }
	    else printf("Number of ships remaining: %d\n", iNumberOfShips);
	  }

	else
	  {
	    miFoundTable[iTableX][iTableY] = TB_FOUND;
	    setLastGameString(":: Ship found");
	  }

	/* Updating hits and shots */
	++iNumberOfHits;
	++iNumberOfShots;

	printf(" -- Target Hitted\n");
      }

    else
      {
	miFoundTable[iTableX][iTableY] = TB_MISSED;
	setLastGameString("!! Shot missed");

	/* Updating shots */
	++iNumberOfShots;

	printf(" -- Target Missed\n");
      }

  else
    setLastGameString("!! Region already fired");


  return GL_FALSE;
}


/**
 * 
 */
void defineShip(int iShipIdx, sModel_t *psModel)
{
  GLboolean bShipNotAccepted = GL_TRUE;


  while (bShipNotAccepted)
    {
      int iPosition;
      GLboolean bPositionFilled;

      int iPositionX;
      int iPositionY;
      eDirection_t eDirection;


      /* Select random position for X coordinate */
      do
	{
	  iPositionX = rand()%TB_NSQUARES;
	  iPositionY = rand()%TB_NSQUARES;
	}
      while (miShipsTable[iPositionX][iPositionY] != TB_EMPTY);

      /* Select direction of drawing */
      eDirection = rand()%4;

      switch (eDirection)
	{
	case LEFT:
      
	  if (iPositionX-psModel->iSize < -1) break;

	  for (bPositionFilled = GL_FALSE, iPosition = iPositionX;
	       iPosition > iPositionX-psModel->iSize; --iPosition)

	    if (miShipsTable[iPosition][iPositionY] != TB_EMPTY)
	      {
		bPositionFilled = GL_TRUE;
		break;
	      }

	  if (bPositionFilled) break;

	  for (iPosition = iPositionX;
	       iPosition > iPositionX-psModel->iSize; --iPosition)
	    miShipsTable[iPosition][iPositionY] = iShipIdx;

	  vssShips[iShipIdx].psModel    = psModel;
	  vssShips[iShipIdx].iPositionX = iPositionX;
	  vssShips[iShipIdx].iPositionY = iPositionY;
	  vssShips[iShipIdx].eDirection = LEFT;
	  vssShips[iShipIdx].iCounter   = psModel->iSize;

	  bShipNotAccepted = GL_FALSE;

	  break;


	case RIGHT:

	  if (iPositionX+psModel->iSize > TB_NSQUARES) break;

	  for (bPositionFilled = GL_FALSE, iPosition = iPositionX;
	       iPosition < iPositionX+psModel->iSize; ++iPosition)

	    if (miShipsTable[iPosition][iPositionY] != TB_EMPTY)
	      {
		bPositionFilled = GL_TRUE;
		break;
	      }

	  if (bPositionFilled) break;

	  for (iPosition = iPositionX;
	       iPosition < iPositionX+psModel->iSize; ++iPosition)
	    miShipsTable[iPosition][iPositionY] = iShipIdx;

	  vssShips[iShipIdx].psModel    = psModel;
	  vssShips[iShipIdx].iPositionX = iPositionX;
	  vssShips[iShipIdx].iPositionY = iPositionY;
	  vssShips[iShipIdx].eDirection = RIGHT;
	  vssShips[iShipIdx].iCounter   = psModel->iSize;

	  bShipNotAccepted = GL_FALSE;

	  break;


	case UP:
      
	  if (iPositionY-psModel->iSize < -1) break;

	  for (bPositionFilled = GL_FALSE, iPosition = iPositionY;
	       iPosition > iPositionY-psModel->iSize; --iPosition)

	    if (miShipsTable[iPositionX][iPosition] != TB_EMPTY)
	      {
		bPositionFilled = GL_TRUE;
		break;
	      }

	  if (bPositionFilled) break;

	  for (iPosition = iPositionY;
	       iPosition > iPositionY-psModel->iSize; --iPosition)
	    miShipsTable[iPositionX][iPosition] = iShipIdx;

	  vssShips[iShipIdx].psModel    = psModel;
	  vssShips[iShipIdx].iPositionX = iPositionX;
	  vssShips[iShipIdx].iPositionY = iPositionY;
	  vssShips[iShipIdx].eDirection = UP;
	  vssShips[iShipIdx].iCounter   = psModel->iSize;

	  bShipNotAccepted = GL_FALSE;

	  break;


	case DOWN:

	  if (iPositionY+psModel->iSize > TB_NSQUARES) break;

	  for (bPositionFilled = GL_FALSE, iPosition = iPositionY;
	       iPosition < iPositionY+psModel->iSize; ++iPosition)

	    if (miShipsTable[iPositionX][iPosition] != TB_EMPTY)
	      {
		bPositionFilled = GL_TRUE;
		break;
	      }

	  if (bPositionFilled) break;

	  for (iPosition = iPositionY;
	       iPosition < iPositionY+psModel->iSize; ++iPosition)
	    miShipsTable[iPositionX][iPosition] = iShipIdx;

	  vssShips[iShipIdx].psModel    = psModel;
	  vssShips[iShipIdx].iPositionX = iPositionX;
	  vssShips[iShipIdx].iPositionY = iPositionY;
	  vssShips[iShipIdx].eDirection = DOWN;
	  vssShips[iShipIdx].iCounter   = psModel->iSize;

	  bShipNotAccepted = GL_FALSE;

	  break;
	}
    }
}


/**
 * Render Bitmap String 
 */
void renderBitmapString(void * pvFont, char * pcString)
{
  int iChar;

  for (iChar = 0; pcString[iChar]; ++iChar)
    glutBitmapCharacter(pvFont, pcString[iChar]);
}


/**
 * Render Stroke String
 */
void renderStrokeString(void * pvFont, char * pcString)
{
  int iChar;

  for (iChar = 0; pcString[iChar]; ++iChar)
    glutStrokeCharacter(pvFont, pcString[iChar]);
}


/**
 *
 */
void drawGameStrings(int iBaselineX, int iBaselineY)
{
  drawStrings(vvcGameStrings, N_GAME_STRINGS, vdGameStringsColor,
	      iBaselineX, iBaselineY);
}


/**
 *
 */
void drawStrings(char vvcStrings[][STRING_LENGHT],
		 int iNStrings, double vdStringsColor[],
		 int iBaselineX, int iBaselineY)
{
  int iString;

  glColor3dv(vdStringsColor);

  for (iString = 0; iString < iNStrings; ++iString) {
    glWindowPos2i(iBaselineX, iBaselineY+iString*15);
    renderBitmapString(GLUT_BITMAP_HELVETICA_12, vvcStrings[iString]);
  }
}


/**
 *
 */
void setLastGameString(char * pcGameString)
{
  setLastString(vvcGameStrings, N_GAME_STRINGS, pcGameString);
}


/**
 *
 */
void setLastString(char vvcStrings[][STRING_LENGHT],
		   int iNStrings, char * pcString)
{
  int iString;

  for (iString = iNStrings-2; iString > 0; --iString)
    strcpy(vvcStrings[iString], vvcStrings[iString-1]);

  strcpy(vvcStrings[0], pcString);
}
