#define PI        3.1415

//tamanho de posições que ocupam
//#define SUBMARINO 3
//#define BARCO     2
//#define NAVIO     4

#define SUB       'S' //Submarino oculto
#define SUBA      'T' //Submatino afundado
#define SUBM      'U' //Submarino visualizado

#define BAR       'B' //Barco oculto
#define BARA      'C' //Barco afundado
#define BARM      'D' //Barco visualizado

#define NAV       'N' //Navio oculto
#define NAVA      'O' //Navio afundado
#define NAVM      'P' //Navio visualizado

#define BANDEIRA  'X' //Bandeira


//Variáveis que guardam  a primeira posição das Embarcações
//Lembrando que elas sempre a parecem na horizontal ->
int intSubIni;
int intNavIni;
int intBarIni;

//essa matriz irá controlar as posições do plano ->
char plano[10][10];

//Controla se o jogo acabou ->
char chrFim[3];

//Controla a iluminação entre noite e dia...padrão = dia
//int EhDia=1;

void keyboard(unsigned char key, int x, int y);
void init();
void ConfiguraIluminacao();
void ConfiguraTextura();
bool PosicaoOcupada(int intPosx, int intPosy, int intTamEmbarc);
void pickRects(int button, int state, int x, int y);
void processHits(GLint hits, GLuint buffer[]);
void MarcaPosicoes(int x, int y);
void display();
int  LoadBMP(char *filename);

GLint WIDTH=800;
GLint HEIGHT=600;

GLfloat obs[3]={0.0,19.0,0.0};
GLfloat look[3]={0.0,3.0,0.0};


GLfloat tetaxz=0;
GLfloat raioxz=11;
//GLfloat win=150.0;

// Buffer para selecção
GLuint s_buffer[512];

// Dimensões da janela
GLint w0 = 0;
GLint h0 = 0;

// Acumuladores de movimento do rato
signed int mx0 = 0;
signed int my0 = 0;

// Ponto de vista
int viewpoint = 0;

// Modo pick
int pick = 1;


time_t  hor_ini, hor_fim;
double tempo_final;




void ConfiguraIluminacao()
{	
   //Configuração padrão -> dia
   GLfloat lightpos[4] = { 1.0, 3.0, 5.0, 0.0 };
   GLfloat lightamb[4] = { 0.5, 0.5, 0.5, 1.0 };  
   GLfloat lightdif[4] = { 0.8, 0.8, 0.8, 1.0 };  
   GLfloat lightesp[4] = { 0.5, 0.5, 0.5, 1.0 };  

   //Configuração noite
   if(EhDia==0){
      lightamb[0] = 0.1;
      lightamb[1] = 0.1;
      lightamb[2] = 0.1;

      lightdif[0] = 0.5;
      lightdif[1] = 0.5;
      lightdif[2] = 0.5;

      lightesp[0] = 0.1;
      lightesp[1] = 0.1;
      lightesp[2] = 0.1;

	  glClearColor(0.0,0.0,0.1,1);
   }
   else
      glClearColor(0.8,0.8,0.9,1);
      


   //Configuração para tonalização pelo método de Gouraud
	glShadeModel (GL_SMOOTH);
	glEnable(GL_DEPTH_TEST);
	glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightamb);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightdif);
    glLightfv(GL_LIGHT0, GL_SPECULAR, lightesp);
	glEnable(GL_LIGHTING);
    glDepthMask(GL_TRUE);
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_LIGHT0);
}

//---------------------------------------------------------------------------
void ConfiguraTextura(){

   /*
     Habilita a Texturizacao.
     Criacao inicial das texturas.     
   */
   
   glEnable ( GL_TEXTURE_2D );                 // Habilitar o uso de texturas 
   glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );   // Definir a forma de armazenamento dos pixels na textura (1= alinhamento por byte) 
   glGenTextures ( 6, texture_id );			   // vetor com os números das texturas	

   //Textura 0 - mar
   glBindTexture ( GL_TEXTURE_2D, texture_id[txId_Mar] );  // Define a textura corrente 
   LoadBMP ("mar.bmp");						           // Carrega o arquivo

   //Textura 1 - tecido
   glBindTexture ( GL_TEXTURE_2D, texture_id[txId_Tecido] );
   LoadBMP ("tecido_zig.bmp");	

   //Textura 2 - metal1
   glBindTexture ( GL_TEXTURE_2D, texture_id[txId_Metal1] );
   LoadBMP ("metal4.bmp");	

   //Textura 3 - metal2
   glBindTexture ( GL_TEXTURE_2D, texture_id[txId_Metal2] );
   LoadBMP ("metal1.bmp");	

   //Textura 4 - madeira
   glBindTexture ( GL_TEXTURE_2D, texture_id[txId_Madeira] );
   LoadBMP ("madeira.bmp");	

   //Textura 5 - metal
   glBindTexture ( GL_TEXTURE_2D, texture_id[txId_MetalEncaixe] );
   LoadBMP ("metal2.bmp");	

   //Textura 6 - bomba
   glBindTexture ( GL_TEXTURE_2D, texture_id[txId_Bomba] );
   LoadBMP ("lua.bmp");	

   //Textura 7 - prata
   glBindTexture ( GL_TEXTURE_2D, texture_id[txId_Prata] );
   LoadBMP ("prata.bmp");	


   glBindTexture ( GL_TEXTURE_2D, 0);

}


//---------------------------------------------------------------------------
//Função utilizada para posicionar as embarções ->
void Posiciona(int intTam, char chrID)
{
   int intPosx, intPosy;
   bool bolControle = true;

   intPosx = intPosy = 0;

   //sortear onde o submarino será colocado
   while (bolControle)
   {
      intPosx = rand() % (10-intTam);
      intPosy = rand() % 10;

      //a linha abaixo exibe a posição dos objetos, só deve ser usada para testes.
	  //printf ("\nx = %i, y = %i", intPosx, intPosy);

      
      if (!PosicaoOcupada(intPosx, intPosy, intTam))  //verifica se as posições para acomodar a embarcação estão ocupadas por outra embarcação
         bolControle = false;                         //Se retornar true, está ocupada caso contrário não está 
   }
   //Neste momento tem-se as coordenadas da embarcação
   if (chrID == SUB)
      intSubIni = intPosx;
   else if (chrID == BAR)
      intBarIni = intPosx;
   if (chrID == NAV)
      intNavIni = intPosx;

   for (int intCount = 0; intCount < intTam; intCount++)
   {
      plano[intPosx][intPosy] = chrID;
      intPosx++;
   }

}


//---------------------------------------------------------------------------
void PosicionaEmbarc()
{
   time_t t;
   //inicializa o gerador de número randomico
   srand((unsigned) time(&t));

   Posiciona(SUBMARINO, SUB);
   Posiciona(BARCO, BAR);
   Posiciona(NAVIO, NAV);

   	//Informação para o usuário de como jogar
   // Teclas:  T:tiro		Setas:movimentam o tabuleiro   R/shift+R:movimentam tabuleiro
   printf("                       Batalha Naval                                    \n");
   printf("------------------------------------------------------------------------\n");
   printf("  Teclas:  T:tiro		Setas, R, Shitf+R:movimentam o tabuleiro       \n");
   printf("------------------------------------------------------------------------\n");

}
//---------------------------------------------------------------------------
void desenha(void){
   int i, j;

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  //¤ Limpando o color e o depth buffer ->

   
      glColor3f(0.9, 0.9, 1.0);
      glLoadIdentity();

   
      /* calcula a posicao do observador */
      obs[0]=raioxz*cos(2*PI*tetaxz/360);
      obs[2]=raioxz*sin(2*PI*tetaxz/360);
      gluLookAt(obs[0],obs[1],obs[2],look[0],look[1],look[2],0.0,1.0,0.0);

	
   	// Modelando o chão
      desenha_tabuleiro();
	
	
      for(j=0; j<10; j++) //linha
      {
         for(i=0; i<10; i++) //coluna
         {
            //Se a posição já foi escolhida e está marcada como errada, ou seja, tem bomba
            if (plano[i][j] == BANDEIRA)  constroe_bandeira(i, j);
            else if (plano[i][j] == SUBM) {constroe_submarino(intSubIni, j); i+=2;}
            else if (plano[i][j] == BARM) {constroe_barco(intBarIni, j);     i++; }
            else if (plano[i][j] == NAVM) {constroe_navio(intNavIni, j);     i++; }
            else if (plano[i][j] == SUBA || plano[i][j] == NAVA || plano[i][j] == BARA) constroe_bomba(i, j);
         }
      }
	  
      
      glCallList(submarino);
      glCallList(navio);
	  glCallList(barco);

	glFlush();

   glutSwapBuffers();

}

//---------------------------------------------------------------------------
//Gerenciamento do menu com as coordenadas ->
void MenuXY(int op)
{
   int x, y;
   bool bolControle = true;


   while (bolControle)
   {
      bolControle = false;

      do
      {
         fflush(stdin);  //->limpando o buffer do teclado
		 printf("Coordenada do ponto x (de 0 a 9):");
         scanf("%d", &x);
      }while  (x > 9 || x < 0);
      do
      {
         fflush(stdin);  //->limpando o buffer do teclado
		 printf("Coordenada do ponto y (de 0 a 9):");
         scanf("%d", &y);
      }while  (y > 9 || y < 0);


      if (plano[x][y] == '0') plano[x][y] = BANDEIRA;
      else if (plano[x][y] == SUB) 
      {
         int intBombas = 0; plano[x][y] = SUBA;

         //verificando se todas as posições estão com bomba
         for (int intCount = intSubIni; intCount < (SUBMARINO+intSubIni); intCount++)
         { if (plano[intCount][y] == SUBA) intBombas++; }

         if (intBombas == SUBMARINO)//Indica que o Submarino foi descoberto por inteiro
         {  for (int intCount = intSubIni; intCount < (SUBMARINO+intSubIni); intCount++)
			{ plano[intCount][y] = SUBM; chrFim[0] = '1'; }
         }
      }
      else if (plano[x][y] == BAR)
      {
         int intBombas = 0; plano[x][y] = BARA;

         //verificando se todas as posições estão com bomba
         for (int intCount = intBarIni; intCount < (BARCO+intBarIni); intCount++)
         { if (plano[intCount][y] == BARA) intBombas++; }

         if (intBombas == BARCO)  //Indica que o Barco foi descoberto por inteiro
         { for (int intCount = intBarIni; intCount < (BARCO+intBarIni); intCount++)
			{ plano[intCount][y] = BARM; chrFim[1] = '1'; }
         }
      }
      else if (plano[x][y] == NAV)
      {
         int intBombas = 0; plano[x][y] = NAVA;

         //verificando se todas as posições estão com bomba
         for (int intCount = intNavIni; intCount < (NAVIO+intNavIni); intCount++)
         { if (plano[intCount][y] == NAVA) intBombas++;}

         if (intBombas == NAVIO)  //Indica que o Navio foi descoberto por inteiro
         { for (int intCount = intNavIni; intCount < (NAVIO+intNavIni); intCount++)
			{ plano[intCount][y] = NAVM; chrFim[2] = '1'; }
         }
      }
      else
         bolControle = true;
   }

   glutPostRedisplay();

   //Verifica se o jogo terminou
   if (chrFim[0] == '1' && chrFim[1] == '1' && chrFim[2] == '1')
   {
      char chrOP;
      fflush(stdin);  //->limpando o buffer...
      desenha(); //Para que mostre o último barco antes de recomeçar ->

	   time(&hor_fim);
       tempo_final = difftime( hor_fim, hor_ini);
       printf( "\nTempo de jogo: %6.0f segundos.\n", tempo_final);
	   printf("\nParabens, voce ganhou!\n");
       printf("Deseja jogar novamente?<S/N>: ");
       scanf("%c", &chrOP);
      if (chrOP == 'S' || chrOP == 's')
      {
         
		 init();
		 desenha();
      }
     // else
     //    exit(0);
   }
}


//---------------------------------------------------------------------------
void teclado(unsigned char key, int x, int y){
   switch (key) {
   case 27:   //esc
      exit(0);
      break;
   case 'A':   case 'a':
	   if(EhDia == 1) EhDia=0;
       else           EhDia=1;  //alternando entre noite e dia
	   ConfiguraIluminacao();
	   glutPostRedisplay();
   break;
   case 'r':
      raioxz=raioxz+1;
      glutPostRedisplay();
      break;
   case 'R':
      raioxz=raioxz-1;
      if(raioxz==0) raioxz=1;
      glutPostRedisplay();
      break;
   case 't': case 'T':
      MenuXY(1);
	  break;
   }
}

//---------------------------------------------------------------------------
void especial(int key, int x, int y){
  switch (key) {
  case GLUT_KEY_UP:
    obs[1]=obs[1]+1;
    glutPostRedisplay();
    break;
  case GLUT_KEY_DOWN:
    obs[1] =obs[1]-1;
    glutPostRedisplay();
    break;
  case GLUT_KEY_LEFT:
    tetaxz=tetaxz+2;
    glutPostRedisplay();
    break;
  case GLUT_KEY_RIGHT:
    tetaxz=tetaxz-2;
    glutPostRedisplay();
    break;
  }
}



//---------------------------------------------------------------------------
void MontaChao(){
    // Modelando o chão
    glColor3f(0.1f,0.1f,1.0f);
    glTranslatef(8,-2.0,8);
    GLfloat Line = 0;
    int Grid=0;
    int Cont = 0;

	glBindTexture ( GL_TEXTURE_2D, texture_id[txId_Mar] ); 
    for (int Col =0; Col <=9; Col++)
    {
        Cont = 0;
            for(Line = 0; Line <= 18; Line+=2,Cont++)
            {
                glLoadName (Col);
                glPushName(Cont);
                glPushMatrix();
                    glTranslatef(-Col*2, 0, -Line);
                    glBegin(GL_QUAD_STRIP);
						glTexCoord2f(1.0f, 1.0f);glVertex3f(0, 0, 0);
                        glTexCoord2f(0.0f, 0.0f);glVertex3f(2, 0, 0);
                        glTexCoord2f(0.0f, 1.0f);glVertex3f(0, 0, 2);
                        glTexCoord2f(1.0f, 0.0f);glVertex3f(2, 0, 2);
                    glEnd();
                glPopMatrix();
                glPopName();                
            }
    }
	glBindTexture ( GL_TEXTURE_2D, 0); 

}

//---------------------------------------------------------------------------
// Define matriz de Projecção
void setViewPoint(int pick_mode)
{

	int w = w0;
	int h = h0-10;
	GLint viewport[4];

	// Definir matriz de projecção (Perspectiva)
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	if (pick_mode) {
		glGetIntegerv(GL_VIEWPORT, viewport);
		gluPickMatrix(mx0,h0-my0,1,1,viewport);
	}

    gluPerspective(70.0,WIDTH/(float)HEIGHT,0.1,30.0);

	glMatrixMode(GL_MODELVIEW);
}


//---------------------------------------------------------------------------
void checkSelection()
{
    int x, y;
    //Inicializando variável
    x = y = -999;

	unsigned int t,i, j, k, a;
	glSelectBuffer(sizeof(s_buffer), s_buffer);
	glRenderMode(GL_SELECT);
	glInitNames();
    glPushName(0);
	setViewPoint(1);
    glPushMatrix();
	MontaChao();
    glPopMatrix();
	t = glRenderMode(GL_RENDER);
	printf("SELECTION: %d :", t);
	k = 0;
	for (i = 0; i< t; i++) {
		printf(" [%u,%u] " , s_buffer[k+1], s_buffer[k+2]);
		for (j = 0 ; j < s_buffer[k]; j++) {
			a = s_buffer[k+j+3];
            if (y == -999)
                y = a;
            else if (x == -999)
                x = a;

			printf(" %u " , a);
		}
		k += s_buffer[k] + 3;
	}

    MarcaPosicoes(x, y);
	printf("\n");
	setViewPoint(0);
	desenha();
}



//---------------------------------------------------------------------------
//Verifica se existe espaço para posicionar a embarcação
//Se for possível, retorna false, indicando que a posição está livre,
//caso contrário retorna false ->
bool PosicaoOcupada(int intPosx, int intPosy, int intTamEmbarc)
{
   bool bolRet = false;

   for (int intCountx = intPosx; intCountx < (intTamEmbarc+intPosx); intCountx++)
   {
      if (plano[intCountx][intPosy] != '0')
      {
         bolRet = true;
      }
   }
   return bolRet;
}


//---------------------------------------------------------------------------
// Função callback chamada para gerenciar eventos do mouse

/*  pickRects() sets up selection mode, name stack,
 *  and projection matrix for picking.  Then the objects
 *  are drawn.
 */
#define BUFSIZE 512

void GerenciaMouse(int button, int state, int x, int y)
{
    /*if (button == GLUT_RIGHT_BUTTON)
    {
         if (state == GLUT_DOWN)
            CriaMenu();
    }
    else */
	if(button==GLUT_LEFT_BUTTON && state == GLUT_DOWN)
	{
		if (pick) {
			// Actualizar objectos selecionados
			mx0 = x;
			my0 = y;
			checkSelection();
			desenha();
		} else {
			// Iniciar acumuladores quando se clica no botão
			mx0 = x;
			my0 = y;
		}
	}

    glutPostRedisplay();
}


//---------------------------------------------------------------------------
void MarcaPosicoes(int x, int y)
{

    if (plano[x][y] == '0')
    {
     plano[x][y] = BANDEIRA;
    }
    else if (plano[x][y] == SUB)
    {
     int intBombas = 0;
     plano[x][y] = SUBA;

     //verificando se todas as posições estão com bomba
     for (int intCount = intSubIni; intCount < (SUBMARINO+intSubIni); intCount++)
     {
        if (plano[intCount][y] == SUBA)
           intBombas++;
     }

     //Indica que o Submarino foi descoberto por inteiro
     if (intBombas == SUBMARINO)
     {
        for (int intCount = intSubIni; intCount < (SUBMARINO+intSubIni); intCount++)
        {
           plano[intCount][y] = SUBM;
           chrFim[0] = '1';
        }
     }
    }
    else if (plano[x][y] == BAR)
    {
     int intBombas = 0;
     plano[x][y] = BARA;

     //verificando se todas as posições estão com bomba
     for (int intCount = intBarIni; intCount < (BARCO+intBarIni); intCount++)
     {
        if (plano[intCount][y] == BARA)
           intBombas++;
     }

     //Indica que o Barco foi descoberto por inteiro
     if (intBombas == BARCO)
     {
        for (int intCount = intBarIni; intCount < (BARCO+intBarIni); intCount++)
        {
           plano[intCount][y] = BARM;
           chrFim[1] = '1';
        }
     }
    }
    else if (plano[x][y] == NAV)
    {
     int intBombas = 0;
     plano[x][y] = NAVA;

     //verificando se todas as posições estão com bomba
     for (int intCount = intNavIni; intCount < (NAVIO+intNavIni); intCount++)
     {
        if (plano[intCount][y] == NAVA)
           intBombas++;
     }

     //Indica que o Navio foi descoberto por inteiro
     if (intBombas == NAVIO)
     {
        for (int intCount = intNavIni; intCount < (NAVIO+intNavIni); intCount++)
        {
           plano[intCount][y] = NAVM;
           chrFim[2] = '1';
        }
     }
    }

   glutPostRedisplay();


   if (chrFim[0] == '1' && chrFim[1] == '1' && chrFim[2] == '1')
   {
      char chrOP;
      setViewPoint(0);
      desenha(); //Para que mostre o último barco antes de recomeçar ->
      pick = 0;

	   time(&hor_fim);
       tempo_final = difftime( hor_fim, hor_ini);
       printf( "\nTempo de jogo: %6.0f segundos.\n", tempo_final);
	   printf("\nParabens, voce ganhou!\n");
       printf("Deseja jogar novamente?<S/N>: ");
       fflush(stdin);  //->limpando o buffer...
       scanf("%c", &chrOP);
      if (chrOP == 'S' || chrOP == 's')
      {
         pick = 1;
		 init();
		 desenha();
      }
   }

}



//=======================================================================================
//=======================================================================================
// Carrega o arquivo BMP
  int  LoadBMP(char *filename)
 {
    #define SAIR        {fclose(fp_arquivo); return -1;}
    #define CTOI(C)     (*(int*)&C)
 
    GLubyte     *image;
    GLubyte     Header[0x54]; 
    GLuint      DataPos, imageSize;
    GLsizei     Width,Height;

    int nb = 0;
    
    
    // Abre o arquivo e efetua a leitura do Header do arquivo BMP
    FILE * fp_arquivo = fopen(filename,"rb");
    if (!fp_arquivo) 
        return -1;
    if (fread(Header,1,0x36,fp_arquivo)!=0x36) 
        SAIR;
    if (Header[0]!='B' || Header[1]!='M')   
        SAIR;
    if (CTOI(Header[0x1E])!=0)              
        SAIR;
    if (CTOI(Header[0x1C])!=24)             
	    SAIR;
    
    // Recupera a informação dos atributos de 
    // altura e largura da imagem

    Width   = CTOI(Header[0x12]);
    Height  = CTOI(Header[0x16]);
    ( CTOI(Header[0x0A]) == 0 ) ? ( DataPos=0x36 ) : ( DataPos = CTOI(Header[0x0A]) );
 
    imageSize=Width*Height*3;
    
    // Efetura a Carga da Imagem
    image = (GLubyte *) malloc ( imageSize );
    int retorno;
    retorno = fread(image,1,imageSize,fp_arquivo);
    
    if (retorno !=imageSize) 
     {
        free (image);
        SAIR;
     }

    // Inverte os valores de R e B
    int t, i;
    
    for ( i = 0; i < imageSize; i += 3 )
     {
        t = image[i];
        image[i] = image[i+2];
        image[i+2] = t;
     } 

    // Tratamento da textura para o OpenGL

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);

    glTexEnvf ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
   
    // Faz a geraçao da textura na memória
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Width, Height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
                        
    fclose (fp_arquivo);
    free (image);
    return 1;
 }