/* StarBox OpenGL demo
 * (c) 2005-2006 David Burchanowski
 */

#include <GL/glfw.h>
#include <fmod/fmod.h>
#include <stdlib.h>
#include <png.h>

#include "Vector.h"
#include "windows.h"
#include "PlayerShip.h"

#include "Boss.h"
#include "GunBullet.h"
#include "GunMissle.h"
#include "GunBoat.h"
#include "Fighter.h"
#include "Islands.h"
#include "Smoke.h"

//define gamestates
#define TITLE 0
#define LEVELPLAY 3
#define BOSSENTER 4
#define BOSS 5
#define BOSSDIE 6

FSOUND_STREAM *gameMusic;

CObjMesh mesh, mesh2, mesh3;

CPlayerShip playerShip;
CIsland islands;
CGunBullet gunBullets;
CGunMissle gunMissles;
CGunBoat gunBoats;
CSmoke smoke;
CFighter fighters;
CBoss boss;

float post = 0;
float timer = 0;

int RandomNumber(int minvalue, int maxvalue);

const float fogColor[] = {1.0, 1.0, 0.2, 0.2};
const float waterFogColor[] = {0.2, 0.2, 0.2, 0.2};

//global variables
sVector cameraPos;
sVector cameraLookAt;
sVector cameraNormal;

BITMAPINFOHEADER infoheader;
unsigned char *imagedata;
unsigned int skyTexture, seaTexture, lifebarTexture, smokeTexture, titleBox;
float waterMovement = 0;
void UpdateLight(bool top);

int gamestate;
float fade = 0.0;
bool fadeIn = false;
bool escPressed = false;

unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader);

void TitleDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    glMatrixMode(GL_PROJECTION);
    glCullFace(GL_BACK);
    glLoadIdentity();
    glFrustum(-2, 2, -2.0, 2.0, 2.0, 5000.0);
    gluLookAt(cameraPos.x, cameraPos.y, cameraPos.z, cameraLookAt.x, cameraLookAt.y, cameraLookAt.z, 0, 1, 0);
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glColor4f(1.0,1.0,1.0, 1.0);
    
 //render the sky box
    glBindTexture (GL_TEXTURE_2D, skyTexture);
    glDisable(GL_LIGHTING);
    glDisable(GL_FOG);
    glColor3f(1.0,1.0,1.0);
    glBegin(GL_QUADS);
    glTexCoord2f(0,0);
    glVertex3f(-6000,0,4000);
    glTexCoord2f(0,1);
    glVertex3f(-6000,4000,4000);
    glTexCoord2f(1,1);
    glVertex3f(6000,4000,4000);
    glTexCoord2f(1,0);
    glVertex3f(6000,0,4000);
    glEnd();
    
    //render the water
    glBindTexture (GL_TEXTURE_2D, seaTexture);
    glEnable(GL_FOG);
    glFogfv(GL_FOG_COLOR, fogColor);
    //glEnable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    
    for(int i = 0; i < 10; i++)
    {
       glBegin(GL_QUADS);
       glColor4f(0.7,0.7,0.7,1.0);
       glTexCoord2f(10, 1);
       glVertex3f(8000, 0, i * 500 + 500 - waterMovement);
       glTexCoord2f(10, 0);
       glVertex3f(8000, 0, i * 500 - waterMovement);
       glTexCoord2f(0, 0);
       glVertex3f(-8000, 0, i * 500 - waterMovement);
       glTexCoord2f(0, 1);
       glVertex3f(-8000, 0, i * 500 + 500 - waterMovement);  
       glEnd();
    }
    
    //render the reflected stuff 
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    UpdateLight(false);
    glPushMatrix();
    glScalef(1.0, -1.0, 1.0);
    
    //render the reflected sky box
    glBindTexture (GL_TEXTURE_2D, skyTexture);
    glDisable(GL_LIGHTING);
    glDisable(GL_FOG);
    glColor4f(1.0,1.0,1.0,0.6);
    glBegin(GL_QUADS);
    glTexCoord2f(0,0);
    glVertex3f(-8000,0,4000);
    glTexCoord2f(0,1);
    glColor4f(1.0,1.0,1.0, playerShip.getPos().y * 0.005);
    glVertex3f(-8000,12000,0);
    glTexCoord2f(1,1);
    glVertex3f(8000,12000,0);
    glTexCoord2f(1,0);
    glColor4f(1.0,1.0,1.0,0.6);
    glVertex3f(8000,0,4000);
    glEnd();
          glEnable(GL_LIGHTING);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_FOG);
    glCullFace(GL_FRONT);
    glFogfv(GL_FOG_COLOR, fogColor);
    playerShip.Render(1.0);
    glColor4f(0.7, 0.7, 0.7, 0.6);
    gunBullets.Render();
    gunBoats.Render();
    fighters.Render();
    glColor4f(0.3, 0.3, 0.3, 0.6);
    boss.Render(1.0);
    glColor4f(0.2, 0.5, 0.2, 0.6);
    islands.Render();
    smoke.Render();
    glPopMatrix();
    
    //render the islands and stuff
    glEnable(GL_LIGHTING);
    UpdateLight(true);
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);
    glEnable(GL_FOG);
    glFogfv(GL_FOG_COLOR, fogColor);
    glBindTexture(GL_TEXTURE_2D, 0);
    playerShip.Render(1.0);
    glColor4f(0.7, 0.7, 0.7, 1.0);
    fighters.Render();
    gunBullets.Render();
    gunBoats.Render();
    glColor4f(0.3, 0.3, 0.3, 1.0);
    boss.Render(1.0);
    glColor4f(0.2, 0.5, 0.2, 1.0);
    islands.Render();
    smoke.Render();
    
    //render the title box
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_FOG);
    glDisable(GL_CULL_FACE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-30, 30, 30, -30, 1, -1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glBindTexture (GL_TEXTURE_2D, titleBox);
    glBegin(GL_QUADS);
    glColor3f(1.0,1.0,1.0);
    glTexCoord2f(0, 1);
    glVertex3f(-5, -29, 1); 
    glTexCoord2f(1, 1);
    glVertex3f(29, -29, 1);
    glTexCoord2f(1, 0);
    glVertex3f(29, -5, 1);
    glTexCoord2f(0, 0);
    glVertex3f(-5, -5, 1);
    glEnd();
    
    //render fadeout
    if(fade > 0)
    {
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            //timer += 0.03;
            glBindTexture (GL_TEXTURE_2D, 0);
            glBegin(GL_QUADS);
            glColor4f(0.0,0.0,0.0, fade);
            glVertex3f(-30, -30, 1); 
            glVertex3f(30, -30, 1);
            glVertex3f(30, 30, 1);
            glVertex3f(-30, 30, 1);
            glEnd();
            glDisable(GL_BLEND);
    }
    
    glfwSwapBuffers();
}

static void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    glMatrixMode(GL_PROJECTION);
    glCullFace(GL_BACK);
    glLoadIdentity();
    glFrustum(-2, 2, -2.0, 2.0, 2.0, 5000.0);
    gluLookAt(cameraPos.x, cameraPos.y, cameraPos.z, cameraLookAt.x, cameraLookAt.y, cameraLookAt.z, 0, 1, 0);
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glColor4f(1.0,1.0,1.0, 1.0);
    
    //render the sky box
    glBindTexture (GL_TEXTURE_2D, skyTexture);
    glDisable(GL_LIGHTING);
    glDisable(GL_FOG);
    glColor3f(1.0,1.0,1.0);
    glBegin(GL_QUADS);
    glTexCoord2f(0,0);
    glVertex3f(-6000,0,4000);
    glTexCoord2f(0,1);
    glVertex3f(-6000,4000,4000);
    glTexCoord2f(1,1);
    glVertex3f(6000,4000,4000);
    glTexCoord2f(1,0);
    glVertex3f(6000,0,4000);
    glEnd();
    
    //render the reflected stuff 
    UpdateLight(false);
    glPushMatrix();
    glScalef(1.0, -1.0, 1.0);
    //render the reflected sky box
    glBindTexture (GL_TEXTURE_2D, skyTexture);
    glDisable(GL_LIGHTING);
    glDisable(GL_FOG);
    glColor4f(1.0,1.0,1.0,0.0);
    glBegin(GL_QUADS);
    glTexCoord2f(0,0);
    glVertex3f(-8000,0,4000);
    glTexCoord2f(0,1);
    glColor4f(1.0,1.0,1.0, 1.0);
    glVertex3f(-8000,12000,-100);
    glTexCoord2f(1,1);
    glVertex3f(8000,12000,-100);
    glTexCoord2f(1,0);
    glColor4f(1.0,1.0,1.0,1.0);
    glVertex3f(8000,0,4000);
    glEnd();
    //render the rest of the reflected stuff 
    glEnable(GL_LIGHTING);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_FOG);
    glCullFace(GL_FRONT);
    glFogfv(GL_FOG_COLOR, fogColor);
    playerShip.Render(1.0);
    glColor4f(0.7, 0.7, 0.7, 1.0);
    gunBullets.Render();
    gunBoats.Render();
    fighters.Render();
    glColor4f(0.3, 0.3, 0.3, 1.0);
    boss.Render(1.0);
    glColor4f(0.2, 0.5, 0.2, 1.0);
    islands.Render();
    smoke.Render();
    glPopMatrix();
    
    //render the islands and stuff
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 1, 1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glEnable(GL_LIGHTING);
    UpdateLight(true);
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);
    glEnable(GL_FOG);
    glFogfv(GL_FOG_COLOR, fogColor);
    glBindTexture(GL_TEXTURE_2D, 0);
    playerShip.Render(1.0);
    glColor4f(0.7, 0.7, 0.7, 1.0);
    fighters.Render();
    gunBullets.Render();
    gunBoats.Render();
    glColor4f(0.3, 0.3, 0.3, 1.0);
    boss.Render(1.0);
    glColor4f(0.2, 0.5, 0.2, 1.0);
    islands.Render();
    
    //render the water
    glBindTexture (GL_TEXTURE_2D, seaTexture);
    glEnable(GL_FOG);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glFogfv(GL_FOG_COLOR, fogColor);
    glStencilFunc(GL_NOTEQUAL, 1, 1);
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    
    for(int i = 0; i < 10; i++)
    {
       float frontAlphaValue = playerShip.getPos().y * 0.002;
       float frontAlpha = 0.75 - ((0.5f - frontAlphaValue) / 2500.0f * (i * 500.0f - waterMovement) + frontAlphaValue);
       float backAlpha =  0.75 - ((0.5f - frontAlphaValue) / 2500.0f * ((i + 1) * 500.0f - waterMovement) + frontAlphaValue);
       glBegin(GL_QUADS);
       glColor4f(0.7,0.7,0.7, backAlpha);
       glTexCoord2f(10, 1);
       glVertex3f(8000, 0, i * 500 + 500 - waterMovement);
       glColor4f(0.7, 0.7, 0.7, frontAlpha);
       glTexCoord2f(10, 0);
       glVertex3f(8000, 0, i * 500 - waterMovement);
       glTexCoord2f(0, 0);
       glVertex3f(-8000, 0, i * 500 - waterMovement);
       glTexCoord2f(0, 1);
       glColor4f(0.7, 0.7, 0.7, backAlpha);
       glVertex3f(-8000, 0, i * 500 + 500 - waterMovement);  
       glEnd();
    }
    
    glDisable(GL_STENCIL_TEST);
    glDisable(GL_BLEND);
    
        
    //render the above water smoke
    glEnable(GL_DEPTH_TEST);
    smoke.Render();
    
    //lastly, render HUD stuff
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_FOG);
    glDisable(GL_CULL_FACE);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-30, 30, 30, -30, 1, -1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glBindTexture (GL_TEXTURE_2D, lifebarTexture);
    float health = playerShip.getHealth();
    
    glBegin(GL_QUADS);
    glColor3f(1.0,1.0,1.0);
    glTexCoord2f(0, 1);
    glVertex3f(-25, 20, 1); 
    glTexCoord2f(health * 0.01, 1);
    glVertex3f(-25 + health * 0.2, 20, 1);
    glTexCoord2f(health * 0.01, 0);
    glVertex3f(-25 + health * 0.2, 25, 1);
    glTexCoord2f(0, 0);
    glVertex3f(-25, 25, 1);
    glEnd();
    
      //render fadeout
    if(fade > 0.0)
    {
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            //timer -= 0.03;
            glBindTexture (GL_TEXTURE_2D, 0);
            glBegin(GL_QUADS);
            glColor4f(0.0,0.0,0.0, fade);
            glVertex3f(-30, -30, 1); 
            glVertex3f(30, -30, 1);
            glVertex3f(30, 30, 1);
            glVertex3f(-30, 30, 1);
            glEnd();
            glDisable(GL_BLEND);
    }
    
    glfwSwapBuffers();
}

void TitleCallback(int value)
{
     post+= 0.01;
     
     if(glfwGetKey(GLFW_KEY_SPACE))
     {
        fade = 0.01;
        fighters.setFire(true);
        fighters.reset();
     }
     else if(glfwGetKey(GLFW_KEY_ESC))
     {
          escPressed = true;
     }
     
     cameraLookAt.x = 0;
     cameraLookAt.y = 0;
     cameraLookAt.z = 1000;
     
     cameraPos.y = 250;
     cameraPos.x = sin(post) * 100;
     cameraPos.z = cos(post) * 50 + 500;
     
     fighters.update();
}

void Callback(int value)
{
     sVector accel;
     
     accel.x = 0;
     accel.y = 0;
     accel.z = 0;

     if(glfwGetKey(GLFW_KEY_LEFT))
         accel.x += 0.5;
     if(glfwGetKey(GLFW_KEY_RIGHT))
         accel.x -= 0.5;
     if(glfwGetKey(GLFW_KEY_UP))
         accel.y += 0.5;
     if(glfwGetKey(GLFW_KEY_DOWN))
         accel.y -= 0.5;
         
     if(glfwGetKey(GLFW_KEY_SPACE))
         gunBullets.addBullet(playerShip.getPos(), playerShip.getDirection());
     if(glfwGetKey(GLFW_KEY_ESC))
         escPressed = true;     
         
     playerShip.Accelerate(accel);
     playerShip.update();
     sVector shipPos = playerShip.getPos();
     
     if(playerShip.getStatus() == DEAD && fade <= 0)
     {
       fadeIn = false;
       fade = 0.01;
     }
     
     if(fade >= 0.0 && fadeIn)
        fade -= 0.01;
        
     else if(fade >= 0.0 && !fadeIn)
     {
        fade += 0.01;
        
        if(fade > 1.0)
        {
          fadeIn = true;
          playerShip.Reset();
        }
     }
        
     cameraLookAt.x = shipPos.x * 0.93;
     cameraLookAt.y = shipPos.y * 0.93;
     cameraLookAt.z = 20;
     
     cameraPos.x = shipPos.x * 0.92;
     cameraPos.y = shipPos.y * 0.92 + 10;
     cameraPos.z = 0;
	 
     waterMovement += 5;
     if(waterMovement > 500)
         waterMovement = 0;
         
     gunBullets.update();
     gunMissles.update();
     gunBoats.update();
     fighters.update();
     smoke.update();
     
     if(gamestate == BOSS)
                 boss.update();
     islands.update();
}

void UpdateLight(bool top)
{
	float lightAmbient[] = {0.01,0.0,0.0,0.1};
	float lightDiffuse[] = {0.9,0.9,0.2,0.9};
	float lightPosition[4];
	lightPosition[0] = -5000;
	lightPosition[1] = 2500;
	lightPosition[2] = 5000;
	lightPosition[3] = 1;
	
	if(!top)
	       lightPosition[1] = -500;
	
    glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
	glEnable(GL_LIGHT0);
}

//returns true if texture successfully loaded
bool LoadPNGTexture(char *filename, unsigned int *textureId)
{
     glGenTextures(1, textureId);
     glBindTexture (GL_TEXTURE_2D, *textureId);
     glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     
     FILE *fp = fopen(filename, "rb");
     if (!fp)
         return false;
         
     png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
     if (!pngPtr)
        return false;

     png_infop infoPtr = png_create_info_struct(pngPtr);
     if (!infoPtr)
     {
         png_destroy_read_struct(&pngPtr, (png_infopp)NULL, (png_infopp)NULL);
         return false;
     }

     png_infop endInfo = png_create_info_struct(pngPtr);
     if (!endInfo)
     {
         png_destroy_read_struct(&pngPtr, &infoPtr, (png_infopp)NULL);
         return false;
     }
     
     png_init_io(pngPtr, fp);
     png_read_png(pngPtr, infoPtr, PNG_TRANSFORM_PACKING, NULL);
     
     int width = png_get_image_width(pngPtr, infoPtr);
     int height = png_get_image_height(pngPtr, infoPtr);
     
     png_bytep *rowPointers;
     rowPointers = png_get_rows(pngPtr, infoPtr);
     
     unsigned char *imageBuffer;
     imageBuffer = (unsigned char *) malloc(width * height * 4);
     
     for(int i = 0; i < height; i++)
     {
             memcpy(&imageBuffer[i * width * 4], rowPointers[height - i - 1], width * 4);
     }
     
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, 
                                 GL_UNSIGNED_BYTE, imageBuffer);
     
     free(imageBuffer);
     
     png_destroy_read_struct(&pngPtr, &infoPtr, NULL);
     
     fclose(fp);
     
     return true;
}

int main(int argc, char *argv[])
{
    //first initialize sound subsystem
    FSOUND_Init(44100, 32, 0);

    //and then the graphics subsystem
    glfwInit();
    glfwOpenWindow(800, 600, 0, 0, 0, 0, 0, 8, GLFW_FULLSCREEN);

    glClearColor(0,0,0,0);
    glClearStencil(0);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    
    glEnable(GL_COLOR_MATERIAL);

    glEnable(GL_NORMALIZE);
    glEnable(GL_LIGHTING);
    
    glEnable(GL_FOG);
    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogf(GL_FOG_START, 2500.0);
    glFogf(GL_FOG_END, 4000.0);
    glFogf(GL_FOG_DENSITY, 0.2);
    glFogfv(GL_FOG_COLOR, fogColor);
    
    UpdateLight(true);
    
    cameraPos.x = 0;
    cameraPos.y = 20;
    cameraPos.z = 0;
    
   	glEnable(GL_TEXTURE_2D);
               
    LoadPNGTexture("./images/sky.png", &skyTexture); 
    LoadPNGTexture("./images/lifebar.png", &lifebarTexture);             
    LoadPNGTexture("./images/title.png", &titleBox);
    LoadPNGTexture("./images/sea.png", &seaTexture);
    LoadPNGTexture("./images/smoke.png", &smokeTexture);
    
                                
    playerShip.Load();     
    
    gunBoats.setGenerate(true);
    fighters.setGenerate(true);
    
    gunBullets.loadSound();
    
    islands.setGenerate(true);
    
    gamestate = TITLE;
    
    gameMusic = FSOUND_Stream_Open("./music/opening.mp3", FSOUND_MPEGACCURATE, 0, 0);
    FSOUND_Stream_Play(FSOUND_FREE, gameMusic);
    
    //prepare some random island action
    for(int i = 0; i < 1500; i++)
    {
            islands.update();
    }
    
    glfwSetTime(0);
    while(glfwGetWindowParam(GLFW_OPENED) && !escPressed)
    {          
               static double targetTime = 0.0;
               
               targetTime += 0.015;
               if(targetTime > 1000)
               {
                 targetTime = 0.015;
                 glfwSetTime(0);
               }
                 
               if(gamestate == TITLE)
               {
                   TitleCallback(0);
                   
                   if(fade > 0)
                      fade +=  0.01;
                      
                   if(fade > 1.0)
                   {
                            FSOUND_Stream_Stop(gameMusic);
                            FSOUND_Stream_Close(gameMusic);
                            gameMusic = FSOUND_Stream_Open("./music/level.mp3", FSOUND_MPEGACCURATE, 0, 0);
                            FSOUND_Stream_Play(0, gameMusic);
                            timer = 0;
                            gamestate = LEVELPLAY;
                   }
                   
                   double sleeptime = targetTime - glfwGetTime();
                   
                   if(sleeptime > 0)
                   {           
                     glfwSleep(sleeptime);
                     TitleDisplay();
                   }
                   
               }
               else if(gamestate == LEVELPLAY)
               {
                 timer += 1;
                 
                 if(timer > 6000)
                 {
                         islands.setGenerate(false);
                         gunBoats.setGenerate(false);
                         fighters.setGenerate(false);
                 }       
                 if(timer > 6700)
                 {
                         gamestate = BOSS;
                         FSOUND_Stream_Stop(gameMusic);
                         FSOUND_Stream_Close(gameMusic);
                         gameMusic = FSOUND_Stream_Open("./music/boss.mp3", FSOUND_MPEGACCURATE, 0, 0);
                         FSOUND_Stream_Play(0, gameMusic);
                 }
                         
                 Callback(0);
                 
                 double sleeptime = targetTime - glfwGetTime();
                 if(sleeptime > 0)
                 {
                  glfwSleep(sleeptime);
                  display();   
                 }
               }
               else if(gamestate == BOSS)
               {
                 Callback(0);
                 double sleeptime = targetTime - glfwGetTime();
                 if(sleeptime > 0)
                 {
                   glfwSleep(sleeptime);
                   display();   
                 }
               }
    }                  
    
    glfwTerminate();
    
    FSOUND_Close();
    return EXIT_SUCCESS;
}

