#include <allegro.h>
#include <pc.h>
#include <time.h>

#include "anim.h"
#include "timer.h"
#include "enemy.h"
#include "levels.h"
#include "player.h"
#include "bullet.h"

#include "data.h"

#include <string>
#include <iostream>


DATAFILE *dat;
BITMAP *buffer;

struct Options
{
    bool follow_bullets;
    bool cheats;
} Options;

void beep()
{
    sound(1000);
    rest(100);
    nosound();
}


// WORLD WORLD WORLD WORLD WORLD WORLD WORLD WORLD WORLD WORLD WORLD

class World
{
    public:
	World();
	~World();

	int GameLoop(Level *cur_level);
	void Run();
	void ResetForLevel(Level *cur_level);

	void AddLevelType(Level *type);

	void SetPlayer(Player play);
	void SetBullet(Bullet bull);

	void StartLevel();
	void EndLevel();

	void KillPlayer(); 

	void DrawStatus(BITMAP *bmp);
	void Draw(Level *cur_level);

    private:
	vector<Level *> LevelTypes;

	Player cur_player;
	Bullet bullet;

	bool running;
	int stage;

	vector<Bullet *> EnemyBullets;
};

World::World()
{
}

World::~World()
{
}

int World::GameLoop(Level *cur_level)
{
    timer_ticker = 0;

    do
    {
	while(timer_ticker)
	{
	    timer_ticker--;
	    frame_count++;
	    cur_player.SubEnergy(1);

	    // update enemies
	    cur_level->Move();

	    // next animation frame
	    if(frame_count%10 == 0)  { cur_level->UpdateFrame(); }

	    // player movement
	    if(key[KEY_LEFT])   cur_player.AddX(-2);
	    if(key[KEY_RIGHT])   cur_player.AddX(2);
	    if(key[KEY_SPACE])
	    {
		if(bullet.Active() == false)
		{
		    bullet.SetY(cur_player.Y());
		    bullet.SetX(cur_player.X()+8);
		}
	    } 

	    // player bullet collision detection
	    if(bullet.Active())
	    {
		if(Options.follow_bullets)
		{
		    bullet.SetX(cur_player.X()+8);
		}
		bullet.Update();

		if(cur_level->Collide(bullet.GetMask(), bullet.X(), bullet.Y()))
		{
		    bullet.SetActive(false);
		    cur_player.AddScore(cur_level->Points());
		}
	    }

	    // enemy vs. player collision
	    if(cur_level->Collide(cur_player.GetMask(), cur_player.X(), cur_player.Y()))
	    {
		return 0;
	    }

	    // do we end yet?
	    if(cur_level->Empty())
	    {
		return 1;
	    }

	    if(Options.cheats && key[KEY_BACKSPACE])
	    {
		return 1;
	    }

	    if(Options.cheats && key[KEY_ENTER])
	    {
		cur_player.AddLife();
		cur_player.AddLife();
		cur_player.AddLife();
		cur_player.AddLife();
		cur_player.AddLife();
		cur_player.AddLife();
	    }

	    // for each enemy bullet
	    for(int i=0; i<5; ++i)
	    {
		// do we need more?
		if(!EnemyBullets[i]->Active())
		{
		    if(rand() % 500 == 0)
		    {
			EnemyBullets[i]->SetActive(true);

			float x,y;
			cur_level->GetRandData(&x, &y);

			EnemyBullets[i]->SetX(x);
			EnemyBullets[i]->SetY(y);
		    }
		}
		// check enemy bullet to player collisions
		else
		{
		    EnemyBullets[i]->Update();
		    if(EnemyBullets[i]->Y() > 320)
		    {
			EnemyBullets[i]->SetActive(false);
		    }

		    if(check_collision(cur_player.GetMask(), (int) cur_player.X(), (int) cur_player.Y(),
			EnemyBullets[i]->GetMask(), (int) EnemyBullets[i]->X(), (int) EnemyBullets[i]->Y()))
		    {
			return 0;
		    }
		}
	    }

	    if(!cur_player.GetEnergy())  return 0;
	}

	// draw it all
	Draw(cur_level);

	// screenshot maker
	if(key[KEY_F12])
	    save_bitmap("screen.pcx", buffer, (PALETTE)dat[PAL].dat);

	// if esc, quit
	if(key[KEY_ESC])    { running = 0; return -1; }
    } while(running);

    return 0;
}

void World::Run()
{
    stage = 1;

    running = true;
    cur_player.Reset();

    while(running)
    {
	for(unsigned int i=0; i < LevelTypes.size(); ++i)
	{ 
	    int ret;
	    Level *cur_level = LevelTypes[i];

	    cur_level->Reset(stage);

	    do
	    {
		ResetForLevel(cur_level);
		StartLevel();
		ret = GameLoop(cur_level);
		if(!ret)    KillPlayer();
		if(!cur_player.Alive())    running = false; 
		if(!running)    break;
	    } while(!ret);

	    if(!running)    break; 
	    EndLevel();
	}

	stage++;
    }

    clear(screen);
    textprintf(screen, (FONT *) dat[BIGFONT].dat, 0,200,15, 
	"%d points", cur_player.GetScore());

    clear_keybuf(); 
    readkey();
}

void World::StartLevel()
{
    clear(buffer);
    cur_player.Draw(buffer);

    for(int i=0; i<4500; i+=15)
    {
	cur_player.SetEnergy(i);
	DrawStatus(buffer);

	blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
    }
}

void World::EndLevel()
{
    clear(buffer);
    cur_player.Draw(buffer);

    while(cur_player.GetEnergy())
    {
	if(cur_player.GetEnergy() > 15)
	{
	    cur_player.SubEnergy(15);
	    cur_player.AddScore(15 * stage);
	}
	else
	{
	    cur_player.AddScore(cur_player.GetEnergy() * stage);
	    cur_player.SubEnergy(cur_player.GetEnergy());
	}

	DrawStatus(buffer);

	blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H); 
    }
}

void World::KillPlayer()
{
    DrawStatus(buffer);
    blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
    fade_out_range(1, 33, 33);
    cur_player.RemoveLife();
}

void World::DrawStatus(BITMAP *bmp)
{
    rectfill(bmp, 0, 350, SCREEN_W, SCREEN_H, 22);
    for(int i=0; i<cur_player.GetLives(); ++i)
    {
	draw_sprite(bmp, (BITMAP *) dat[PLAYER_LIFE].dat, 150+(i*30), 380);
    }

    int width = SCREEN_W - 150 - 50;
    int dist = (int) ((float) ((float)cur_player.GetEnergy() / 4500) * (float) width);

    rectfill(bmp, 150, 360, SCREEN_W-50, 370, 12);
    rectfill(bmp, 150, 360, dist+150, 370, 14); 

    text_mode(-1);
    textprintf(bmp, (FONT *) dat[SMALLFONT].dat, 50, 360, 2, "ENERGY");
    textprintf(bmp, (FONT *) dat[BIGFONT].dat, 250, 430, 38,
	"%6d", cur_player.GetScore());
}

void World::Draw(Level *cur_level)
{
    clear(buffer);
    cur_level->Draw(buffer);
    cur_player.Draw(buffer);
    bullet.Draw(buffer);

    for(int i=0; i<5; ++i)
    {
	EnemyBullets[i]->Draw(buffer);
    }

    DrawStatus(buffer);

    blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);
}

void World::ResetForLevel(Level *cur_level)
{
    set_palette((PALETTE) dat[PAL].dat);

    clear(buffer);

    cur_player.ResetPos();
    bullet.SetActive(false);

    cur_level->ResetPos();

    for(unsigned int i=0; i<EnemyBullets.size(); ++i)
    {
	if(EnemyBullets[i]) delete EnemyBullets[i];
    }
    EnemyBullets.clear();

    for(int i=0; i<5; ++i)
    {
	Bullet *b;
	b = new Bullet;
	b->SetBitmap((BITMAP *) dat[ENEMY_BULLET].dat);

	int speed;
	speed = (stage > 2) ? 6 : 4;
	b->Set(DIR_DOWN, speed);

	b->SetActive(false);
	EnemyBullets.push_back(b);
    } 
}

void World::AddLevelType(Level *type)
{ 
    LevelTypes.push_back(type);
}

void World::SetPlayer(Player play)
{
    cur_player = play;
}

void World::SetBullet(Bullet bull)
{
    bullet = bull;
}

// MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN

void Usage(bool a)
{
    if(a)   cout << "Invalid argument" << endl << endl;
    cout << "Arguments:" << endl;
    cout << "  --help         this screen" << endl;
    cout << "  --cheat/-c     enable cheats" << endl;
    cout << "  --follow/-f    your bullets dont follow ship" << endl;
    exit(0);
}

void Notes()
{
    vector<string> Notes;
    Notes.push_back("Welcome to my version of MEGAMANIA!");
    Notes.push_back("");
    Notes.push_back("KEYS");
    Notes.push_back("  <-     Move Left");
    Notes.push_back("  ->     Move Right");
    Notes.push_back("  SPACE  Fire");
    Notes.push_back("  ESC    Quit");
    Notes.push_back("");
    Notes.push_back("DIFFERENCES FROM ORIGINAL");
    Notes.push_back("  Levels 6 and 8 missing");
    Notes.push_back("  Collision detection is inaccurate");
    Notes.push_back("  Level 4 is slightly different than original");
    Notes.push_back("  Level 2 and 4 enemy generation is different");
    Notes.push_back("  Enemy firing is not the same");
    Notes.push_back("  No sound");
    Notes.push_back("");
    Notes.push_back("");
    Notes.push_back("");
    Notes.push_back("");
    Notes.push_back("Copyright (c) 1999 Tom Gwozdz");
    Notes.push_back("All original code by Tom Gwozdz");
    Notes.push_back("  except the collision detection which is based");
    Notes.push_back("  on PPCOL by Neil Chinnery");
    Notes.push_back("");
    Notes.push_back("Created for SpeedHack 1999");
    Notes.push_back("For updated downloads go to:");
    Notes.push_back("  http://www.execulink.com/~turul/speed/");
    Notes.push_back("");
    Notes.push_back("(line 29 ;)");

    clear(screen);

    for(unsigned int i=0; i<Notes.size(); ++i)
    {
	textprintf(screen, font, 100, (i*text_height(font)) + 50, 15,
	    (char *) Notes[i].c_str());
    }

    readkey();
}

int main(int argc, char *argv[])
{
    allegro_init();
    install_keyboard();
    install_timer();

    timer_init();

    srand(time(NULL));

    Options.cheats = false;
    Options.follow_bullets = true;

    if(argc > 0)
    {
	for(int i=1; i<argc; ++i)
	{
	    string val = argv[i];
	    if(val == "--help") Usage(false);
	    else if(val == "--cheat")    Options.cheats = true;
	    else if(val == "-c")    Options.cheats = true;
	    else if(val == "--follow")   Options.follow_bullets = false;
	    else if(val == "-f")   Options.follow_bullets = false;
	    else Usage(true);
	}
    }

    if(set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) < 0)
    {
	cout << "Allegro Error: " << allegro_error << endl;
	exit(-1);
    }

    dat = load_datafile("mega.dat");
    if(dat == NULL)
    {
	allegro_exit();
	cout << "Can't open mega.dat" << endl;
	exit(-1);
    }

    buffer = create_bitmap(SCREEN_W, SCREEN_H);
    if(buffer == NULL)
    {
	allegro_exit();
	cout << "No memory for buffer" << endl;
	exit(-1);
    }

    World world;

    Anim a;
    a.AddFrame((BITMAP *) dat[SHIP11].dat);
    a.AddFrame((BITMAP *) dat[SHIP12].dat);
    a.AddFrame((BITMAP *) dat[SHIP13].dat);

    Anim b;
    b.AddFrame((BITMAP *) dat[SHIP21].dat);
    b.AddFrame((BITMAP *) dat[SHIP22].dat);
    b.AddFrame((BITMAP *) dat[SHIP23].dat);

    Anim c;
    c.AddFrame((BITMAP *) dat[SHIP31].dat);
    c.AddFrame((BITMAP *) dat[SHIP32].dat);
    c.AddFrame((BITMAP *) dat[SHIP33].dat);

    Anim d;
    d.AddFrame((BITMAP *) dat[SHIP41].dat);
    d.AddFrame((BITMAP *) dat[SHIP42].dat);
    d.AddFrame((BITMAP *) dat[SHIP43].dat);

    Anim e;
    e.AddFrame((BITMAP *) dat[SHIP51].dat);
    e.AddFrame((BITMAP *) dat[SHIP52].dat);
    e.AddFrame((BITMAP *) dat[SHIP53].dat);
    e.AddFrame((BITMAP *) dat[SHIP52].dat);

    Anim g;
    g.AddFrame((BITMAP *) dat[SHIP71].dat);
    g.AddFrame((BITMAP *) dat[SHIP72].dat);
    g.AddFrame((BITMAP *) dat[SHIP73].dat);
    g.AddFrame((BITMAP *) dat[SHIP72].dat);

    Level_TypeOne level1(500);
    level1.SetBase(a);
    level1.SetPoints(20);
    world.AddLevelType(&level1);

    Level_TypeTwo level2;
    level2.SetBase(b);
    level2.SetPoints(40);
    world.AddLevelType(&level2);

    Level_TypeOne level3(50);
    level3.SetBase(c);
    level3.SetPoints(80);
    world.AddLevelType(&level3);

    Level_TypeTwo level4;
    level4.SetBase(d);
    level4.SetPoints(160);
    world.AddLevelType(&level4);

    Level_TypeOne level5(10);
    level5.SetBase(e);
    level5.SetPoints(320);
    world.AddLevelType(&level5);

    Level_TypeOne level7(1);
    level7.SetBase(g);
    level7.SetPoints(1280);
    world.AddLevelType(&level7);


    Player player;
    player.SetBitmap((BITMAP *) dat[PLAYER_SHIP].dat);
    world.SetPlayer(player);

    Bullet bullet;
    bullet.Set(DIR_UP, 4);
    bullet.SetBitmap((BITMAP *) dat[PLAYER_BULLET].dat);
    world.SetBullet(bullet);

    bool going = true;
    int selected = 1;

    vector<string> Menulist;
    Menulist.push_back("Start Game");
    Menulist.push_back("Notes/Help");
    Menulist.push_back("Quit");

    set_palette((PALETTE) dat[PAL].dat);

    while(going)
    {
	clear(buffer);

	draw_sprite(buffer, (BITMAP *) dat[TITLE].dat, 0, 0);

	for(unsigned int i=0; i<Menulist.size(); ++i)
	{
	    textprintf(buffer, (FONT *) dat[SMALLFONT].dat, 100, 100+(i*30), 2,
		(char *) Menulist[i].c_str());
	}

	draw_sprite(buffer, (BITMAP *) dat[ARROW].dat, 80, 103+((selected-1)*30));

	blit(buffer, screen, 0,0,0,0, SCREEN_W, SCREEN_H);

	int input = readkey() >> 8; 
	beep();

	if(input == KEY_UP)  selected--;
	if(input == KEY_DOWN)    selected++;
	if(input == KEY_ESC) going = false;

	if(selected > 3)    selected = 1;
	if(selected < 1)    selected = 3;

	if(input == KEY_ENTER)
	{
	    switch(selected)
	    {
		case 1:
		    world.Run();
		    break;

		case 2:
		    Notes();
		    break;

		case 3:
		    going = false;
		    break;
	    }
	}
    }

    return 0;
}
