#include<string.h>
#include<stdio.h>
#include<allegro.h>
#include"graph.h"   // graphics
#include"error.h"
#include"game.h"
#include"pause.h"
#include"consts.h"
#include"player.h"
#include"globals.h"

#include"genback.h"

GAMESTATUS gmstat;

unsigned char k_up[4]={KEY_UP,KEY_S,KEY_Y,KEY_9};
unsigned char k_left[4]={KEY_LEFT,KEY_Z,KEY_G,KEY_I};
unsigned char k_right[4]={KEY_RIGHT,KEY_C,KEY_J,KEY_P};
unsigned char k_shoot[4]={KEY_RSHIFT,KEY_TAB,KEY_V,KEY_7};

struct LEVEL
{
 int rock,metal,gold; // Odds
 int number;          // how many asteroids
};

LEVEL lvl[]={{100,0,0,5},
             {80,100,0,10},
             {70,90,100,10},
             {50,90,100,15},
             {40,80,100,17},
             {20,60,100,20},
             {20,50,100,22},
             {0,40,100,25},
             {0,20,100,25},
             {0,0,100,25}};
const int max_levels=9;
int levelnum=0,active_players;
bool level_complete,quit=false;

PLAYER player[4];
OBJECT obj[MAX_OBJECTS];
BULLET bul[MAX_BULLETS];
EXPLOSION explo[MAX_EXPLOSIONS];

DATAFILE *graph;
BITMAP *screen2;
BITMAP *level;
BITMAP *panel;

void draw_stats();

int create_asteroid(fix x,fix y,fix xn,fix yn,fix ang,int type,int size)
{
 int n,img;
 for(n=0;obj[n].active!=0 && n<MAX_OBJECTS;n++);
 if(n<MAX_OBJECTS)
 {
  obj[n].active=1;
  obj[n].x=x;
  obj[n].y=y;
  obj[n].xn=xn;
  obj[n].yn=yn;
  obj[n].ang=ang;
  obj[n].size=size;
  obj[n].dir=((rand()%4001-2000)/1000.0)/(size+1);
  obj[n].type=OBJTYPE_ASTEROID;
  if(type==ASTEROID_ROCKY)
  {
   obj[n].hp=obj[n].size*8;
   if(size>0 || size<4) img=as_small00-(size-1);
   if(size==0) img=as_gravel00;
  }
  if(type==ASTEROID_METAL)
  {
   obj[n].hp=obj[n].size*8+30;
   if(size>0 || size<4) img=mt_small00-(size-1);
   if(size==0) img=mt_gravel00;
  }
  if(type==ASTEROID_GOLD)
  {
   obj[n].hp=obj[n].size*10+50;
   if(size>0 || size<4) img=gl_small00-(size-1);
   if(size==0) img=gl_gravel00;
  }
  obj[n].subtype=type;


  obj[n].image=(BITMAP*)graph[(int)(img)].dat;
  obj[n].back=create_bitmap((int)(obj[n].image->w*2),(int)(obj[n].image->h*2)); //make sure it is enough
  if(obj[n].back==NULL) gen_error(ERR_MEMORY);
  clear(obj[n].back);
  obj[n].imgw=obj[n].image->w/2;
  obj[n].imgh=obj[n].image->h/2;
  obj[n].backw=obj[n].back->w/2;
  obj[n].backh=obj[n].back->h/2;
  obj[n].spr_size=obj[n].image->w/2;
  return TRUE;
 }
 return FALSE;
}
int create_bonus(fix x,fix y,fix xn,fix yn,int type)
{
 int n;
 for(n=0;obj[n].active!=0 && n<MAX_OBJECTS;n++);
 if(n<MAX_OBJECTS)
 {
  obj[n].active=1;
  obj[n].x=x;
  obj[n].y=y;
  obj[n].xn=xn;
  obj[n].yn=yn;
  obj[n].ang=0;
  obj[n].size=0;
  obj[n].dir=0;
  obj[n].type=OBJTYPE_BONUS;
  obj[n].subtype=type;
  obj[n].hp=1000;
  obj[n].image=(BITMAP*)graph[(int)(powerup00+type)].dat;
  obj[n].back=create_bitmap((int)(obj[n].image->w),(int)(obj[n].image->h));
  if(obj[n].back==NULL) gen_error(ERR_MEMORY);
  clear(obj[n].back);
  obj[n].imgw=obj[n].image->w/2;
  obj[n].imgh=obj[n].image->h/2;
  obj[n].backw=obj[n].back->w/2;
  obj[n].backh=obj[n].back->h/2;
  obj[n].spr_size=obj[n].image->w/2;
  return TRUE;
 }
 return FALSE;
}
void destroy_object(int n)
{
 obj[n].active=0;
 destroy_bitmap(obj[n].back);
}

int create_bullet(fix x,fix y,fix xn,fix yn,fix ang,int type,int own)
{
 int n,size;
 fix cosa,sina,spd;
 for(n=0;bul[n].active!=0 && n<MAX_BULLETS;n++);
 if(n<MAX_BULLETS)
 {
  bul[n].active=1;

  cosa=cos(ang);
  sina=sin(ang);

  spd=4;
  if(type==4) spd=2;
  if(type==5) spd=1;
  bul[n].xn=cosa*spd+xn;
  bul[n].yn=sina*spd+yn;
  bul[n].x=x+cosa*20;
  bul[n].y=y+sina*20;
  bul[n].ang=ang;
  bul[n].type=1;
  bul[n].owner=own;
  if(type==0) bul[n].power=1;
  if(type==1) bul[n].power=5;
  if(type==2) bul[n].power=8;
  if(type==3) bul[n].power=8;
  if(type==4) bul[n].power=15;
  if(type==5) bul[n].power=10;
  bul[n].time=0;
  bul[n].deathtime=100;
  if(type==0) bul[n].deathtime=50;
  if(type==3) bul[n].deathtime=50;
  if(type==4) bul[n].deathtime=75;
  if(type==5) bul[n].deathtime=30;

  bul[n].image=(BITMAP*)graph[(int)(shot_a00+type)].dat;
  bul[n].back=create_bitmap((int)(bul[n].image->w+10),(int)(bul[n].image->h+10)); //make sure it is enough
  if(bul[n].back==NULL) gen_error(ERR_MEMORY);
  clear(bul[n].back);
  bul[n].imgw=bul[n].image->w/2;
  bul[n].imgh=bul[n].image->h/2;
  bul[n].backw=bul[n].back->w/2;
  bul[n].backh=bul[n].back->h/2;
  bul[n].spr_size=bul[n].image->w/2;
  return TRUE;
 }
 return FALSE;
}
void destroy_bullet(int n)
{
 bul[n].active=0;
 destroy_bitmap(bul[n].back);
}

int create_explo(fix x,fix y,int type,int size)
{
 int n;
 for(n=0;explo[n].active!=0 && n<MAX_EXPLOSIONS;n++);
 if(n<MAX_EXPLOSIONS)
 {
  explo[n].active=1;
  explo[n].x=x;
  explo[n].y=y;
  explo[n].size=size;
  explo[n].type=1;
  explo[n].time=0;
  explo[n].deathtime=size*2;

  explo[n].back=create_bitmap(size*2+4,size*2+4); //make sure it is enough
  if(explo[n].back==NULL) gen_error(ERR_MEMORY);
  clear(explo[n].back);
  explo[n].backw=explo[n].back->w/2;
  explo[n].backh=explo[n].back->h/2;
  return TRUE;
 }
 return FALSE;
}
void destroy_explo(int n)
{
 explo[n].active=0;
 destroy_bitmap(explo[n].back);
}

void screenshot()
{
 char name[14];
 PALLETE pal;
 BITMAP *tmp;
 int shots=0;

 get_pallete(pal);
 tmp=create_bitmap(SCREEN_W,SCREEN_H);
 do
 {
  sprintf(name,"shot%04d.pcx",shots);
  shots++;
 } while(file_exists(name,0,NULL) && (shots<999999) );
 blit(screen,tmp,0,0,0,0,SCREEN_W,SCREEN_H);
 save_pcx(name,tmp,pal);
 destroy_bitmap(tmp);
 clear_keybuf();
}

void init_game()
{
 panel=create_bitmap(100,20);
 level=create_bitmap(gmstat.level_w,gmstat.level_h);
 if(level==NULL) gen_error(ERR_MEMORY);

 for(int n=0;n<MAX_OBJECTS;n++)
 {
  obj[n].active=0;
 }
 for(int n=0;n<MAX_BULLETS;n++)
 {
  bul[n].active=0;
 }

 for(int n=0;n<gmstat.numpl;n++)
 {
  player[n].num=n;
  player[n].init();
  player[n].respawn();
 }

 clear(screen);
 clear(level);
 clear(screen2);
 gen_back(level);
}
void post_game_stats()
{
 clear(level);
 draw_stats();
 blit(level,screen,0,0,0,0,SCREEN_W,SCREEN_H);
 textprintf_centre(screen,font,SCREEN_W/2,SCREEN_H/2,255,"GAME OVER");
 clear_keybuf();
 rest(500);
 readkey();
}
void deinit_game()
{
 post_game_stats();

 destroy_bitmap(panel);
 for(int n=0;n<MAX_OBJECTS;n++)
 {
  if(obj[n].active)
  {
   if(obj[n].type==OBJTYPE_ASTEROID)
   {
    destroy_object(n);
   }
  }
 }
 for(int n=0;n<MAX_BULLETS;n++)
 {
  if(bul[n].active)
  {
   destroy_bullet(n);
  }
 }
 for(int n=0;n<MAX_EXPLOSIONS;n++)
 {
  if(explo[n].active)
  {
   destroy_explo(n);
  }
 }
 for(int n=0;n<gmstat.numpl;n++)
  player[n].deinit();
 destroy_bitmap(level);
}
void deinit_level()
{
 for(int n=0;n<MAX_OBJECTS;n++)
 {
  if(obj[n].active)
  {
   if(obj[n].type==OBJTYPE_ASTEROID)
   {
    destroy_object(n);
   }
  }
 }
 for(int n=0;n<MAX_BULLETS;n++)
 {
  if(bul[n].active)
  {
   destroy_bullet(n);
  }
 }
 for(int n=0;n<MAX_EXPLOSIONS;n++)
 {
  if(explo[n].active)
  {
   destroy_explo(n);
  }
 }
}

void erase_sprites()
{
 for(int n=MAX_EXPLOSIONS-1;n>-1;n--)
 {
  if(explo[n].active>0)
  {
   blit(explo[n].back,level,0,0,explo[n].x-explo[n].backw,explo[n].y-explo[n].backh,explo[n].back->w,explo[n].back->h);
   if(explo[n].active==2)
    destroy_explo(n);
  }
 }
 for(int n=MAX_BULLETS-1;n>-1;n--)
 {
  if(bul[n].active>0)
  {
   blit(bul[n].back,level,0,0,bul[n].x-bul[n].backw,bul[n].y-bul[n].backh,bul[n].back->w,bul[n].back->h);
   if(bul[n].active==2)
    destroy_bullet(n);
  }
 }
 for(int n=gmstat.numpl-1;n>-1;n--)
 {
  blit(player[n].back,level,0,0,player[n].x-player[n].backw,player[n].y-player[n].backh,player[n].back->w,player[n].back->h);
 }
 for(int n=MAX_OBJECTS-1;n>-1;n--)
 {
  if(obj[n].active>0)
  {
   blit(obj[n].back,level,0,0,obj[n].x-obj[n].backw,obj[n].y-obj[n].backh,obj[n].back->w,obj[n].back->h);
   if(obj[n].active==2)
    destroy_object(n);
  }
 }
}
void draw_sprites()
{
 for(int n=0;n<MAX_OBJECTS;n++)
 {
  if(obj[n].active)
  {
   blit(level,obj[n].back,obj[n].x-obj[n].backw,obj[n].y-obj[n].backh,0,0,obj[n].back->w,obj[n].back->h);
   rotate_sprite(level,obj[n].image,obj[n].x-obj[n].imgw,obj[n].y-obj[n].imgh,obj[n].ang.v);
//   circle(level,obj[n].x,obj[n].y,obj[n].spr_size,255);
  }
 }
 for(int n=0;n<gmstat.numpl;n++)
 {
  blit(level,player[n].back,player[n].x-player[n].backw,player[n].y-player[n].backh,0,0,player[n].back->w,player[n].back->h);
  if(player[n].active)
   rotate_sprite(level,player[n].image,player[n].x-player[n].imgw,player[n].y-player[n].imgh,player[n].ang.v);
//  circle(level,player[n].x,player[n].y,player[n].spr_size,255);
 }
 for(int n=0;n<MAX_BULLETS;n++)
 {
  if(bul[n].active)
  {
   blit(level,bul[n].back,bul[n].x-bul[n].backw,bul[n].y-bul[n].backh,0,0,bul[n].back->w,bul[n].back->h);
   rotate_sprite(level,bul[n].image,bul[n].x-bul[n].imgw,bul[n].y-bul[n].imgh,bul[n].ang.v);
  }
 }
 int c;
 for(int n=0;n<MAX_EXPLOSIONS;n++)
 {
  if(explo[n].active)
  {
   blit(level,explo[n].back,explo[n].x-explo[n].backw,explo[n].y-explo[n].backh,0,0,explo[n].back->w,explo[n].back->h);
   c=255-explo[n].time*(175/explo[n].deathtime);
   color_map=&additive;
   drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
   circlefill(level,explo[n].x,explo[n].y,explo[n].time/2,makecol(c,c,c));
   solid_mode();
  }
 }
}
void draw_screen()
{
// stretch_blit(level,screen,0,0,level->w,level->h,0,0,SCREEN_W,SCREEN_H);
 blit(level,screen,0,0,0,0,SCREEN_W,SCREEN_H);
}

void split_asteroid(int n)
{
 fix xn,yn,spd;
 int a;
 if(obj[n].size>0)
 {
  for(int l=0;l<2;l++)
  {
   a=rand()%256;
   spd=(((rand()%(int)((max_speed-min_speed)*4096))/4096.0)+min_speed);
   xn=cos(a)*spd;
   yn=sin(a)*spd;
   create_asteroid(obj[n].x+xn*5,obj[n].y+yn*5,xn,yn,a,obj[n].subtype,obj[n].size-1);
  }
  for(int l=0;l<rand()%obj[n].size;l++)
  {
   a=rand()%256;
   xn=cos(a)*max_speed;
   yn=sin(a)*max_speed;
   create_asteroid(obj[n].x+xn*5,obj[n].y+yn*5,xn,yn,a,obj[n].subtype,0);
  }
 }
 if((rand()%(int)((obj[n].size*10)+1))>14)
 {
  a=rand()%256;
  xn=cos(a)*min_speed;
  yn=sin(a)*min_speed;
  create_bonus(obj[n].x+xn*5,obj[n].y+yn*5,xn,yn,BONUS_29+rand()%2);
 }
 create_explo(obj[n].x,obj[n].y,1,obj[n].size*10+6);
 obj[n].active=2;
}

void col_detect()
{
 for(int n=0;n<gmstat.numpl;n++)
 {
  // collision detection (player vs. objects)
  if(player[n].active)
  for(int m=0;m<MAX_OBJECTS;m++)
  {
   if(obj[m].active)
   {
    if(vector_length_f(player[n].x-obj[m].x,player[n].y-obj[m].y,0)<(obj[m].spr_size+player[n].spr_size))
    {
     if(obj[m].type==OBJTYPE_ASTEROID)
     {
      player[n].xn=-player[n].xn;
      player[n].yn=-player[n].yn;
      player[n].hp-=obj[m].size*4+1;
      if(player[n].hp<0) player[n].die();
     }
     if(obj[m].type==OBJTYPE_BONUS)
     {
      obj[m].active=2;
      player[n].score+=100;
      if(obj[m].subtype==BONUS_29)
      {
       for(int l=0;l<29;l++)
       {
        fix x,y,xn,yn;
        xn=cos(l*8.8)*4;
        yn=sin(l*8.8)*4;
        x=player[n].x+xn*5;
        y=player[n].y+yn*5;
        create_bullet(x,y,xn,yn,l*8.8,1,n);
       }
      }
      if(obj[m].subtype==BONUS_UPGRADE)
      {
       player[n].upgr+=1;
       // Too powerful!
/*       if(player[n].upgr>=max_upgr)
       {
        for(int l=1;l<max_upgr;l++)
        {
         player[n].shoot_time[l]=(int)(player[n].shoot_time[l]*0.8);
        }
       }*/
      }
     }
    }
   }
  }
 }

 // bullet vs. object
 for(int n=0;n<MAX_BULLETS;n++)
 {
  if(bul[n].active==1) // so we don't kill killed objects again
  {
   for(int m=0;m<MAX_OBJECTS;m++)
   {
    if(obj[m].active)
    {
     if(vector_length_f(bul[n].x-obj[m].x,bul[n].y-obj[m].y,0)<(bul[n].spr_size+obj[m].spr_size))
     {
      if(obj[m].type==OBJTYPE_ASTEROID)
      {
       obj[m].hp-=bul[n].power;
       bul[n].active=2;
       if(obj[m].hp<0)
       {
        split_asteroid(m);
        player[bul[n].owner].score+=obj[m].size*10+10+obj[m].subtype*5;
       }
       else
       {
        create_explo(bul[n].x,bul[n].y,1,bul[n].power+2);
       }
      }
     }
    }
   }
  }
 }
}

void move_objects()
{
 int numobj=0;
 for(int n=0;n<MAX_OBJECTS;n++)
 {
  if(obj[n].active)
  {
   numobj++;
   obj[n].ang+=obj[n].dir;
   obj[n].x+=obj[n].xn;
   obj[n].y+=obj[n].yn;
   if(obj[n].x<0) obj[n].x=level->w;
   if(obj[n].x>level->w) obj[n].x=0;
   if(obj[n].y<0) obj[n].y=level->h;
   if(obj[n].y>level->h) obj[n].y=0;
  }
 }
 if(numobj==0) level_complete=true;
}
void move_bullets()
{
 for(int n=0;n<MAX_BULLETS;n++)
 {
  if(bul[n].active)
  {
   bul[n].x+=bul[n].xn;
   bul[n].y+=bul[n].yn;
   bul[n].time++;

   if(bul[n].x<0) bul[n].x=level->w;
   if(bul[n].x>level->w) bul[n].x=0;
   if(bul[n].y<0) bul[n].y=level->h;
   if(bul[n].y>level->h) bul[n].y=0;

   if(bul[n].time>bul[n].deathtime) bul[n].active=2;
  }
 }
}
void update_explo()
{
 for(int n=0;n<MAX_EXPLOSIONS;n++)
 {
  if(explo[n].active)
  {
   explo[n].time++;
   if(explo[n].time>explo[n].deathtime) explo[n].active=2;
  }
 }
}

void init_level(int ln)
{
 clear(screen);
 fix x,y,xn,yn,ang,spd;
 int size,type;
 for(int n=0;n<lvl[ln].number;n++)
 {
  x=(rand()%gmstat.level_w);
  y=(rand()%gmstat.level_h);
  spd=(((rand()%(int)((max_speed-min_speed)*4096))/4096.0)+min_speed);
  ang=(rand()%25600)/100.0;
  xn=cos(ang)*spd;
  yn=sin(ang)*spd;
  size=rand()%3+1;

  type=rand()%100;
  if(type<lvl[ln].rock) type=ASTEROID_ROCKY;
  else
  {
   if(type<lvl[ln].metal) type=ASTEROID_METAL;
   else
    type=ASTEROID_GOLD;
  }

  create_asteroid(x,y,xn,yn,ang,type,size);
 }
 gen_back(level);
 rest(250);
 textprintf_centre(screen,font,SCREEN_W/2,SCREEN_H/2,255,"LEVEL %i",levelnum+1);
 clear_keybuf();
 readkey();
}

void draw_stats()
{
 // do this!
 for(int n=0;n<gmstat.numpl;n++)
 {
  clear(panel);
  textprintf(panel,font,0,0,255,"LIVES %03i/%i",player[n].hp,player[n].lives);
  textprintf(panel,font,0,10,255,"SCORE %06i",player[n].score);
  if(n==0) blit(panel,level,0,0,0,0,panel->w,panel->h);
  if(n==1) blit(panel,level,0,0,SCREEN_W-panel->w,0,panel->w,panel->h);
  if(n==2) blit(panel,level,0,0,0,SCREEN_H-panel->h,panel->w,panel->h);
  if(n==3) blit(panel,level,0,0,SCREEN_W-panel->w,SCREEN_H-panel->h,panel->w,panel->h);
 }
}

int play()
{
 init_game();
 levelnum=0;

 quit=false;
 while(!quit)
 {
  if(levelnum<max_levels)
  {
   init_level(levelnum);
  }
  else
  {
   init_level(max_levels);
  }
  for(int n=0;n<gmstat.numpl;n++)
   player[n].respawn();
  level_complete=false;
  counter=0;
  while(!quit && !level_complete)
  {
   if(key[KEY_F12]) screenshot();
   if(key[KEY_ESC]) quit=true;
   if(key[KEY_PAUSE]) pause();
   if(key[KEY_U]) player[0].upgr++;
   active_players=0;
   for(int n=0;n<gmstat.numpl;n++)
   {
    if(player[n].active)
    {
     active_players++;
     if(key[k_up[n]]) player[n].accel(1);
//     if(key[k_down[n]]) player[n].accel(-1);
     if(key[k_left[n]]) player[n].turn(-1);
     if(key[k_right[n]]) player[n].turn(1);

     if(key[k_shoot[n]]) player[n].shoot();

     player[n].update();
     if(player[n].x<0) player[n].x=level->w;
     if(player[n].x>level->w) player[n].x=0;
     if(player[n].y<0) player[n].y=level->h;
     if(player[n].y>level->h) player[n].y=0;
    }
   }
   if(active_players==0) quit=true;

   move_objects();
   move_bullets();
   update_explo();

   col_detect();

   counter--;
   while(counter<0);
   if(counter<1 || counter>100)
   {
    framecounter++;
    draw_sprites();
//    textprintf(level,font,0,0,255,"%i",fps);
    draw_stats();
    draw_screen();
    erase_sprites();
   }

  }
  levelnum++;
  deinit_level();
  clear_keybuf();
 }
 deinit_game();
 return 0;
}
