/*
Copyright (C) 2015  E.J.M. Martens

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/


#include <stdio.h>
#include <iostream>
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <map>
#include <vector>

#include "Paralax.h"
#include "Engine.h"
#include "Bullet.h"
#include "Smoke.h"
#include "Explosion.h"
#include "Universe.h"

#include "Enterprise.h"
#include "Romulan_BOP.h"
#include "Klingon_BC.h"
#include "SpaceObject.h"
#include "Starbase.h"
#include "Federation_Ship.h"
#include "KeyMapper.h"



FILE  * g_pFile;
char   g_szFileName[] = "MyGame.sav";

#ifdef DEBUG
char   g_szSector[20];
#endif // DEBUG

// The display for graphics...
ALLEGRO_DISPLAY * g_pDisplay = NULL;

// The event queue
ALLEGRO_EVENT_QUEUE *	g_pEventQueue = NULL;

// Global Path
ALLEGRO_PATH   * g_pFontPath  = NULL;
ALLEGRO_PATH   * g_pSoundPath = NULL;

// a font for text
ALLEGRO_FONT * g_pFont;
ALLEGRO_FONT * g_pInfoFont;
ALLEGRO_FONT * g_pBigFont;


// Other Globals
bool g_blQuit = false;
bool g_blRedraw = false;
bool g_blGameRunning = true;
bool g_blEscape = false;
double g_nCyclesPerSecond = 60;
double g_dLastTime;
int g_nScreenMode = MODE_MAINSCREEN;
vector<int> g_vGameState;

int g_nScreenWidth;
int g_nScreenHeight;


ALLEGRO_TIMER * g_pTimer =0;

TKeyMapper * g_pKeyMapper = NULL;
TEngine * g_pEngine = NULL;
Paralax * g_pParalax = NULL;
TEnterprise * g_pEnterprise = NULL;
Universe * g_pUniverse = NULL;



//Sound
ALLEGRO_SAMPLE * g_pSample[MAX_SAMPLE_DATA] = {NULL};
int g_nFrame =-1;

//Forward functions
void get_desktop_resolution(int a_nAdapter, int *a_pWidth, int * a_pHeighth);
void NewUniverse();
void NewSector(int a_nX, int a_nY);
void CheckSectorBounds();
void DoKeymap();
void DoMenu();
void DoGame();



bool InitObjects()
{
	if (!TBullet::Init()) return false;
	if (!TSmoke::Init()) return false;
	if (!TExplosion::Init()) return false;
	if (!TEnterprise::Init()) return false;
	if (!TRomulanBop::Init()) return false;
	if (!TKlingonBC::Init()) return false;
    if (!TSpaceObject::Init()) return false;
	if (!TStarbase::Init()) return false;
	if (!TFederation_Ship::Init()) return false;
	return true;
}


// default keymapping


// Setup Allegro
bool Setup()
{
	if(al_init())
	{
#ifdef DEBUG
        strcpy(g_szSector,"");
#endif // DEBUG

		g_nScreenWidth  = 1024;
	    g_nScreenHeight =  768;
		//al_set_new_display_option(ALLEGRO_VSYNC, 1, ALLEGRO_SUGGEST);
#ifdef DEBUG
		al_set_new_display_flags(ALLEGRO_WINDOWED);
#else
		al_set_new_display_flags(ALLEGRO_FULLSCREEN);
#endif
		g_pDisplay = al_create_display(g_nScreenWidth, g_nScreenHeight);
		if (g_pDisplay != NULL)
		{
			// Install allegro modules
			al_init_primitives_addon();
			al_init_image_addon();
			al_init_font_addon();
			al_init_ttf_addon();
			al_install_keyboard();
			al_install_mouse();
			al_install_audio();
			al_init_acodec_addon();


			g_pTimer=al_create_timer(1.000/g_nCyclesPerSecond);

			// install event queue
			g_pEventQueue = al_create_event_queue();
			al_register_event_source(g_pEventQueue, al_get_keyboard_event_source());
			al_register_event_source(g_pEventQueue, al_get_mouse_event_source());
			al_register_event_source(g_pEventQueue, al_get_display_event_source(g_pDisplay));
			al_register_event_source(g_pEventQueue, al_get_timer_event_source(g_pTimer));


			// Set Fontpath
            g_pFontPath  = al_get_standard_path(ALLEGRO_RESOURCES_PATH);
            al_append_path_component(g_pFontPath, "font");

            // set Soundpath
            g_pSoundPath = al_get_standard_path(ALLEGRO_RESOURCES_PATH);
            al_append_path_component(g_pSoundPath, "sound");


			// Load a font
			al_set_path_filename(g_pFontPath,"font.ttf");
			g_pFont=al_load_ttf_font(al_path_cstr(g_pFontPath,ALLEGRO_NATIVE_PATH_SEP),10,ALLEGRO_TTF_MONOCHROME);
			if (g_pFont == NULL)
			{
				cout << "Could not load font";
				return false;
			}

            g_pInfoFont=al_load_ttf_font(al_path_cstr(g_pFontPath,ALLEGRO_NATIVE_PATH_SEP),15,0 /*ALLEGRO_TTF_MONOCHROME*/);
            if (g_pInfoFont == NULL)
			{
				cout << "Could not load font";
				return false;
			}


			g_pBigFont = al_load_ttf_font(al_path_cstr(g_pFontPath,ALLEGRO_NATIVE_PATH_SEP),20,0);
			if (g_pBigFont == NULL)
			{
				cout << "Could not load big font";
				return false;
			}

			if (!al_reserve_samples(RESERVED_SAMPLES))
			{
				cout << "Could not Reserve samples";
				return false;
			}

			// Load sounds
			for (int i=0;i<SND_MAX;i++)
			{
				char szFileName[255];
				sprintf(szFileName,"%04d.wav",i+1);
				al_set_path_filename(g_pSoundPath,szFileName);
				g_pSample[i] = al_load_sample(al_path_cstr(g_pSoundPath,ALLEGRO_NATIVE_PATH_SEP));
				if (g_pSample[i]==NULL)
				{
				    cout << "Wrong sample";
				    return false;
				}
			}

			if (!InitObjects())
			{
				cout << "Could not init Objects";
				return false;
			}


			// set quit to false
			g_blQuit = false;

            g_pEngine = new TEngine(g_nScreenWidth,g_nScreenHeight);
			g_pParalax = new Paralax(g_nScreenWidth,g_nScreenHeight,300,5);
			g_pEnterprise = new TEnterprise();
			g_pEngine->Add(g_pEnterprise);

            g_pKeyMapper = new TKeyMapper(g_pEngine->m_clGREEN, g_pEngine->m_clYELLOW,g_pBigFont);

			NewUniverse();
			NewSector(g_pEnterprise->m_nSectorPositionX,g_pEnterprise->m_nSectorPositionY);
            g_pEnterprise->SetPosition(g_pUniverse->m_nSectorCenter, g_pUniverse->m_nSectorCenter);
            g_pEngine->SetOrigin(g_pUniverse->m_nSectorCenter, g_pUniverse->m_nSectorCenter);

            g_vGameState.push_back(GS_GAME);


#ifdef DEBUG
            sprintf(g_szSector,"SECTOR: %d,%d",g_pEnterprise->m_nSectorPositionX,g_pEnterprise->m_nSectorPositionY);
#endif // DEBUG

			al_hide_mouse_cursor(g_pDisplay);
			al_start_timer(g_pTimer);
			g_dLastTime = al_get_time();
		}
		else
		{
			return false;
		}
	}
	else
	{
		return false;
	}

	return true;
}



void get_desktop_resolution(int a_nAdapter, int *a_pWidth, int * a_pHeighth)
{
  ALLEGRO_MONITOR_INFO info;
  al_get_monitor_info(a_nAdapter, &info);

  *a_pWidth = info.x2 - info.x1;
  *a_pHeighth = info.y2 - info.y1;
}



void NewUniverse()
{
    delete g_pUniverse;
    g_pUniverse = new Universe();
    g_pUniverse->Generate();
    g_pEnterprise->m_nSectorPositionX = g_pUniverse->GetHomeX();
    g_pEnterprise->m_nSectorPositionY = g_pUniverse->GetHomeY();
    g_pEnterprise->SetDefaultNavigationOrigin();
}

void NewSector(int a_nX, int a_nY)
{
    g_pEngine->Clear(true);
    g_pUniverse->BuildSector(a_nX,a_nY,g_pEngine);
    g_pEnterprise->SetDefaultNavigationOrigin();
}




// Shutdown Allegro
void ShutDown()
{
    delete g_pEngine;
    delete g_pParalax;

	al_destroy_event_queue(g_pEventQueue);
	al_destroy_timer(g_pTimer);
	al_destroy_display(g_pDisplay);
	al_destroy_font(g_pFont);
	al_destroy_path(g_pSoundPath);
	al_destroy_path(g_pFontPath);
	al_uninstall_system();
}



// Render the view
void Render()
{

    al_clear_to_color(g_pEngine->m_clBLACK);
    if (!g_vGameState.empty())
    {
        switch(g_vGameState.back())
        {
            case GS_INTRO:

            break;

            case GS_MENU:
                 DoMenu();
            break;

            case GS_KEYMAP:
                g_pKeyMapper->DrawMapping();
            break;

            case GS_GAME:
                 DoGame();
            break;
        }
        al_flip_display();
	}
	else
    {
        // exit game
    }
}


void GameCycle()
{
	g_pEngine->Do_ai();
}


// ToDo: Fix sector skip !!

void CheckSectorBounds()
{
    bool blNewSector = false;
    int XX = 0;
    int YY = 0;
    double secsize = double(SECTORSIZE);

    if (g_pEnterprise->GetX() < 0)
    {
        XX = -1;
        g_pEnterprise->SetX(secsize + g_pEnterprise->GetX());
        blNewSector = true;
    }
    else if (g_pEnterprise->GetX() > secsize)
    {
        XX = 1;
        g_pEnterprise->SetX(g_pEnterprise->GetX()-secsize );
        blNewSector = true;
    }
    if (g_pEnterprise->GetY() < 0)
    {
        YY= -1;
        g_pEnterprise->SetY(secsize + g_pEnterprise->GetY());
        blNewSector = true;
    }
    else if (g_pEnterprise->GetY() > secsize)
    {
        YY = 1;
        g_pEnterprise->SetY(g_pEnterprise->GetY()-secsize);
        blNewSector = true;
    }

    if (blNewSector)
    {
        g_pUniverse->StoreSector(g_pEnterprise->m_nSectorPositionX,g_pEnterprise->m_nSectorPositionY,g_pEngine);
        g_pEnterprise->m_nSectorPositionX += XX;
        g_pEnterprise->m_nSectorPositionY += YY;

        if (g_pEnterprise->m_nSectorPositionX==UNIVERSE_SIZE)
        {
            g_pEnterprise->m_nSectorPositionX =0;
        }
        else if (g_pEnterprise->m_nSectorPositionX<0)
        {
            g_pEnterprise->m_nSectorPositionX=UNIVERSE_SIZE-1;
        }

        if (g_pEnterprise->m_nSectorPositionY==UNIVERSE_SIZE)
        {
            g_pEnterprise->m_nSectorPositionY =0;
        }
        else if (g_pEnterprise->m_nSectorPositionY<0)
        {
            g_pEnterprise->m_nSectorPositionY =UNIVERSE_SIZE-1;
        }
        NewSector(g_pEnterprise->m_nSectorPositionX,g_pEnterprise->m_nSectorPositionY);

#ifdef DEBUG
        sprintf(g_szSector,"SECTOR: %d,%d",g_pEnterprise->m_nSectorPositionX,g_pEnterprise->m_nSectorPositionY);
#endif // DEBUG

        g_pEngine->SetOrigin(g_pEnterprise->GetX(),g_pEnterprise->GetY());
        g_pEnterprise->LooseTarget();
    }
}




void KeyDown(int a_nKey)
{
    if (g_vGameState.back() == GS_KEYMAP)
    {
        if (!g_pKeyMapper->MapKey(a_nKey))
        {
            g_vGameState.pop_back();
        }
    }

    else if (g_pKeyMapper->IsKey(a_nKey,UP))
	{
	    g_pEngine->m_blKeys[UP] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,DOWN))
	{
	    g_pEngine->m_blKeys[DOWN] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,LEFT))
	{
	    g_pEngine->m_blKeys[LEFT] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,RIGHT))
	{
	    g_pEngine->m_blKeys[RIGHT] = true;
	}
    else if (g_pKeyMapper->IsKey(a_nKey,PHASER))
	{
	    g_pEngine->m_blKeys[PHASER] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,TARGET_UP))
	{
	    if ((g_pEnterprise!=NULL)&&(!g_pEnterprise->m_blDestroyed))
	    {
	        g_pEnterprise->LockUp();
	    }
	}
	else if (g_pKeyMapper->IsKey(a_nKey,TARGET_DOWN))
	{
	    if ((g_pEnterprise!=NULL)&&(!g_pEnterprise->m_blDestroyed))
	    {
            g_pEnterprise->LockDown();
	    }
	}
	else if (g_pKeyMapper->IsKey(a_nKey,PHOTON))
	{
	    g_pEngine->m_blKeys[PHOTON] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,PHOTON_BACK))
	{
	    g_pEngine->m_blKeys[PHOTON_BACK] = true;
	}
    else if (g_pKeyMapper->IsKey(a_nKey,DOCK))
	{
	    g_pEngine->m_blKeys[DOCK] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,RELEASE))
	{
	    g_pEngine->m_blKeys[RELEASE] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,SEEK_TARGET))
	{
	    g_pEngine->m_blKeys[SEEK_TARGET] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,SEEK_FRIEND))
	{
	    g_pEngine->m_blKeys[SEEK_FRIEND] = true;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,SCREEN_MAIN))
	{
	    al_hide_mouse_cursor(g_pDisplay);
		g_nScreenMode = MODE_MAINSCREEN;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,SCREEN_ENGINEERING))
	{
	    al_show_mouse_cursor(g_pDisplay);
		g_nScreenMode = MODE_ENGINEERING;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,SCREEN_NAVIGATION))
	{
	    al_show_mouse_cursor(g_pDisplay);
		g_nScreenMode = MODE_NAVIGATION;
	}
	else if ((g_pKeyMapper->IsKey(a_nKey,NAVIGATION_VIEW_TOPO))&&(g_pEnterprise!= NULL)&& (! g_pEnterprise->m_blDestroyed))
	{
        g_pEnterprise->SetNavigationMode(NAV_TOPO);
	}
	else if ((g_pKeyMapper->IsKey(a_nKey,NAVIGATION_VIEW_TACTICAL))&&(g_pEnterprise!= NULL)&& (! g_pEnterprise->m_blDestroyed))
	{
        g_pEnterprise->SetNavigationMode(NAV_TACTICAL);
	}
	else if ((g_pKeyMapper->IsKey(a_nKey,NAVIGATION_VIEW_OCCUPATION))&&(g_pEnterprise!= NULL)&& (! g_pEnterprise->m_blDestroyed))
	{
        g_pEnterprise->SetNavigationMode(NAV_OCCUPATION);
	}
    else
    {
        // handle fixed keys
        switch(a_nKey)
        {
            case ALLEGRO_KEY_F10:
                {
                 ofstream SaveFile(g_szFileName,ios::out | ios::binary);
                 if (SaveFile)
                 {
                    g_pUniverse->StoreSector(g_pEnterprise->m_nSectorPositionX,g_pEnterprise->m_nSectorPositionY,g_pEngine);
                    g_pEngine->Save(SaveFile);
                    g_pUniverse->Save(SaveFile);
                    SaveFile.close();
                 }
                 else
                 {
                    // handle error
                 }
                }
            break;

            case ALLEGRO_KEY_F11:
                {
                ifstream LoadFile(g_szFileName,ios::in | ios::binary);
                if (LoadFile)
                {
                    g_pEngine->Load(LoadFile);
                    g_pUniverse->Load(LoadFile);
                    LoadFile.close();
                    g_pEnterprise->SetDefaultNavigationOrigin();
#ifdef DEBUG
                    sprintf(g_szSector,"SECTOR: %d,%d",g_pEnterprise->m_nSectorPositionX,g_pEnterprise->m_nSectorPositionY);
#endif // DEBUG

                }
                else
                {
                    // handle error
                }
                }
            break;

            case ALLEGRO_KEY_F8:
            if (g_vGameState.back() != GS_KEYMAP)
            {
                g_vGameState.push_back(GS_KEYMAP);
            }
            break;
        }
    }

}

void KeyUp(int a_nKey)
{

    // programmable keys
	if (g_pKeyMapper->IsKey(a_nKey,UP))
	{
	    g_pEngine->m_blKeys[UP] = false;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,DOWN))
	{
	    g_pEngine->m_blKeys[DOWN] = false;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,LEFT))
	{
	    g_pEngine->m_blKeys[LEFT] = false;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,RIGHT))
	{
	    g_pEngine->m_blKeys[RIGHT] = false;
	}
    else if (g_pKeyMapper->IsKey(a_nKey,PHASER))
	{
	    g_pEngine->m_blKeys[PHASER] = false;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,PHOTON))
	{
	    g_pEngine->m_blKeys[PHOTON] = false;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,PHOTON_BACK))
	{
	    g_pEngine->m_blKeys[PHOTON_BACK] = false;
	}
    else if (g_pKeyMapper->IsKey(a_nKey,DOCK))
	{
	    g_pEngine->m_blKeys[DOCK] = false;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,RELEASE))
	{
	    g_pEngine->m_blKeys[RELEASE] = false;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,SEEK_TARGET))
	{
	    g_pEngine->m_blKeys[SEEK_TARGET] = false;
	}
	else if (g_pKeyMapper->IsKey(a_nKey,SEEK_FRIEND))
	{
	    g_pEngine->m_blKeys[SEEK_FRIEND] = false;
	}
    else
    {
        // handle fixed keys
        switch(a_nKey)
        {
            case ALLEGRO_KEY_ESCAPE:
            g_vGameState.pop_back();
            break;
        }
    }

}

void Mouse(ALLEGRO_MOUSE_EVENT * mouse_event)
{

}

void MouseButtonDown(ALLEGRO_MOUSE_EVENT * mouse_event)
{

}

void MouseButtonUp(ALLEGRO_MOUSE_EVENT * mouse_event)
{

}



void DoIntro()
{

	//	g_blGameRunning =true;
	//	g_nGameState = GS_GAME;
	//	g_nScreenMode = MODE_MAINSCREEN;

}


void DoMenu()
{
    al_draw_text(g_pBigFont,g_pEngine->m_clGREEN, 10, 10, 0,"MENU");


}

void DoGame()
{
     g_pEngine->Kill();
     double dDeltaTime = al_get_time() - g_dLastTime;
     g_dLastTime = al_get_time();
     g_pEngine->Move(dDeltaTime);

     if((g_pEnterprise!=NULL)&& (! g_pEnterprise->m_blDestroyed))
     {
        CheckSectorBounds();
        g_pEngine->Folow(g_pEnterprise);
     }
     g_pParalax->Move(g_pEngine->GetDx(), g_pEngine->GetDy());


     if ((g_pEnterprise==NULL)||(g_pEnterprise->m_blDestroyed))
     {
         g_nScreenMode = MODE_MAINSCREEN;
     }

     switch (g_nScreenMode)
     {
        case MODE_MAINSCREEN:
        g_pParalax->Draw(g_pDisplay);
        g_pEngine->Draw();

        if ((g_pEnterprise!=NULL)&&(!g_pEnterprise->m_blDestroyed))
        {
            g_pEngine->DrawHud(g_pEnterprise);
            g_pEngine->Draw_sensor(60,g_nScreenHeight-60,g_pEnterprise);
            g_pEnterprise->DrawTargetInfo(0,g_nScreenWidth-140, g_nScreenHeight-90);
#ifdef DEBUG
            al_draw_text(g_pFont,al_map_rgb(255,255,255), 10, 10, 0,g_szSector);
#endif // DEBUG
         }
         else
         {
             g_pEnterprise=NULL;
         }
         break;

         case MODE_ENGINEERING:
         if ((g_pEnterprise!=NULL)&&(!g_pEnterprise->m_blDestroyed))
         {
             g_pEnterprise->draw_engineering();
         }
         break;

         case MODE_NAVIGATION:
         if ((g_pEnterprise!=NULL)&&(!g_pEnterprise->m_blDestroyed))
         {
             g_pEnterprise->Draw_Navigation(g_pUniverse);
         }
         break;

   }

   if ((g_pEnterprise==NULL)||(g_pEnterprise->m_blDestroyed))
   {
       // ToDo render avatar message system
   }
}

void Timer(ALLEGRO_TIMER_EVENT * timer_event)
{
	if (!g_vGameState.empty())
    {
        switch(g_vGameState.back())
        {
            case GS_INTRO:
            DoIntro();
            break;


            case GS_GAME:
            GameCycle();
            break;
        }
    }
	else
    {
        g_blQuit = true;
    }

}


int main(int argc, char **argv)
{
	try
	{


	if (Setup())
	{
		while (!g_blQuit)
		{
			ALLEGRO_EVENT ev;
			if (al_get_next_event(g_pEventQueue, &ev))
			{

				switch (ev.type)
				{
				case ALLEGRO_EVENT_KEY_DOWN:
					KeyDown(ev.keyboard.keycode);
					break;
				case ALLEGRO_EVENT_KEY_UP:
					KeyUp(ev.keyboard.keycode);
					break;

				case ALLEGRO_EVENT_DISPLAY_CLOSE:
					g_blQuit = true;
					break;

				case ALLEGRO_EVENT_MOUSE_AXES:
					 Mouse(&ev.mouse);
					 break;

                case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
					 MouseButtonDown(&ev.mouse);
					 break;

                case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
					 MouseButtonUp(&ev.mouse);
					 break;

				case ALLEGRO_EVENT_TIMER:
					 Timer(&ev.timer);
					 break;
                }
			}
			else
			{
				Render();
			}
    	}
	}
	else
	{
		return -1;
	}
	}

	catch (A5Exception except)
	{
        cout << except.what();


	}

	ShutDown();
	return 0;
}



