#include <stdlib.h>
#include <stdio.h>
#include <time.h>     // the randomize function requires this
#include <conio.h>    // cprintf...
#include <pc.h>

#include "allegro.h"
#include "apollo29.h"

/*** Size Optimizations ***/
BEGIN_JOYSTICK_DRIVER_LIST
END_JOYSTICK_DRIVER_LIST
/**************************/

#define MAX_NODES    22

BITMAP *bmp;
DATAFILE *data;

char path[256];
int SPLUNGEMODE = FALSE;
float view_x, view_y;
float gravity=.0588;  //lunar gravity = .588 meters/sec (1/6th Earth)
int RLIMIT, BLIMIT;
int sland=0, hland=0, cland=0;   //Safe Lands, Hard Lands, Crash Lands
int lland=0;

typedef struct NODE {
   int x, y;
   fixed tangent;
} NODE;

NODE nodes[MAX_NODES];
int node_count;
fixed curviness;

struct lander_typ {
   float x, y;      //current position
   float ox, oy;
   int dead;      //dead flag
   float fuel;    //fuel remaining
   float dx, dy;  //descent velocity
   int thrust_up; //TRUE if thrusting up
   int thrust_rl; //TRUE if thrusting right or left
} lander;

struct pad_typ {
   int x, y;      //landing pad upper left co-ordinates
   int colour;
} pad;

/*********************[ LANDSCAPE GENERATION ROUTINES ]*******************/
/* calculates the distance between two nodes */
fixed node_dist(NODE n1, NODE n2)
{
   #define SCALE  64

   fixed dx = itofix(n1.x - n2.x) / SCALE;
   fixed dy = itofix(n1.y - n2.y) / SCALE;

   return fsqrt(fmul(dx, dx) + fmul(dy, dy)) * SCALE;
}

/* constructs nodes to go at the ends of the list, for tangent calculations */
NODE dummy_node(NODE node, NODE prev)
{
   NODE n;

   n.x = node.x - (prev.x - node.x) / 8;
   n.y = node.y - (prev.y - node.y) / 8;

   return n;
}

/* calculates a set of node tangents */
void calc_tangents()
{
   int i;

   nodes[0] = dummy_node(nodes[1], nodes[2]);
   nodes[node_count] = dummy_node(nodes[node_count-1], nodes[node_count-2]);
   node_count++;

   for (i=1; i<node_count-1; i++)
      nodes[i].tangent = fatan2(itofix(nodes[i+1].y - nodes[i-1].y),
				itofix(nodes[i+1].x - nodes[i-1].x));
}

/* calculates the control points for a spline segment */
void get_control_points(NODE n1, NODE n2, int points[8])
{
   fixed dist = fmul(node_dist(n1, n2), curviness);

   points[0] = n1.x;
   points[1] = n1.y;

   points[2] = n1.x + fixtoi(fmul(fcos(n1.tangent), dist));
   points[3] = n1.y + fixtoi(fmul(fsin(n1.tangent), dist));

   points[4] = n2.x - fixtoi(fmul(fcos(n2.tangent), dist));
   points[5] = n2.y - fixtoi(fmul(fsin(n2.tangent), dist));

   points[6] = n2.x;
   points[7] = n2.y;
}

/* draws the spline paths */
void draw_splines(int color)
{
   int i, points[8];

   for (i=1; i<node_count-2; i++) {
      get_control_points(nodes[i], nodes[i+1], points);
      spline(bmp, points, color);
   }
}

/* actually generate the landscape using the above functions */
void generate_landscape() {
   int index, yoff1, yoff2;
   int pnode;

   clear(bmp);

   yoff1=rand()%50-25;
   yoff2=rand()%50-25;
   for (node_count=1; node_count<=MAX_NODES-2; node_count++) {
      nodes[node_count].x = (node_count-1) * (bmp->w/(MAX_NODES-3));
      nodes[node_count].y = (rand()%(bmp->h/2))+bmp->h/4;
   }
   nodes[MAX_NODES-2].x = bmp->w;
   nodes[MAX_NODES-2].y = nodes[1].y;
   nodes[MAX_NODES-3].y = nodes[1].y+yoff1;
   nodes[2].y = nodes[1].y+yoff2;
   
   pnode = rand()%(MAX_NODES-4)+1;  //pick a random node for landing pad
   pad.x = nodes[pnode].x-20; //pad upper left co-ordinates
   pad.y = nodes[pnode].y-5;
   pad.colour = makecol(255, 128, 0);
   triangle(bmp, pad.x, pad.y, pad.x+40, pad.y, pad.x+20, pad.y+40, pad.colour);

   for (index=1; index<=50; index++) {
      for (node_count=1; node_count<=MAX_NODES-2; node_count++) {
         nodes[node_count].y++;
      }
      calc_tangents();
      draw_splines(makecol(250-index*4, 250-index*4, 250-index*4));
   }

   floodfill(bmp, 0, 399, makecol(46, 46, 46));
}

/*********************[ BEEP ]*******************/
void beep() {
   play_sample(data[BEEP].dat, 255, 128, 1000, 0);
}

/*********************[ INITIALIZE GAME ]*******************/
void init_game() {
   PALETTE pal;

   allegro_init();
   install_keyboard();
   install_timer();

   srand((unsigned)time(NULL));  // seed the random number generator
   curviness = ftofix(0.30);  //Would be nice to make this user adjustable

   set_color_depth(15);
   if (set_gfx_mode(GFX_AUTODETECT, 320, 240, 0, 0) != 0) {
      set_color_depth(16);
      if (set_gfx_mode(GFX_AUTODETECT, 320, 240, 0, 0) != 0) {
         set_color_depth(32);
         if (set_gfx_mode(GFX_AUTODETECT, 320, 240, 0, 0) != 0) {
            set_color_depth(24);
            if (set_gfx_mode(GFX_AUTODETECT, 320, 240, 0, 0) != 0) {
               set_color_depth(8);
               if (set_gfx_mode(GFX_AUTODETECT, 320, 240, 0, 0) != 0) {
                  printf("Error initializing graphics mode. (320x200)\n%s\n\n",allegro_error);
                  exit(1);
               } //8
            } //24
         } //32
      } //16
   } //15
   generate_332_palette(pal);
   set_color_conversion(COLORCONV_TOTAL);

   bmp = create_bitmap(1600, 440);
   if (bmp==NULL) exit(1);

   strcpy(get_filename(path), "apollo29.dat");
   data = load_datafile(path);
   if (!data) {
      allegro_exit();
      cprintf("Error loading %s!\n\n", path);
      exit(1);
   }

  if (install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, NULL)) {
     cprintf("Error initializing sound card\n\r%s\n\r\n\r",allegro_error);
     destroy_bitmap(bmp);
     unload_datafile(data);
     exit(1);
  }
   load_midi(data[DEFAULT].dat);
   
   clear(screen);
   RLIMIT = bmp->w-SCREEN_W;
   BLIMIT = bmp->h-SCREEN_H-40;
   text_mode(0);
}

/*********************[ HANDLE KEYBOARD INPUT ]*******************/
int handle_input() {
   if (key[KEY_ESC]) return 1;   //return 1 to quit
   lander.thrust_rl=FALSE;
   lander.thrust_up=FALSE;
   if (key[KEY_UP]) {
      if (lander.fuel >= .1) {
         lander.dy-=.1;
         lander.thrust_up=TRUE;
         lander.fuel-=.1;
      }
   }
   if (key[KEY_DOWN]) {
      if (lander.fuel >= .020) {
         lander.dy-=.025;
         lander.thrust_rl=TRUE;
         lander.fuel-=.020;
      }
   }
   if (key[KEY_LEFT]) {
      if (lander.fuel >= .025) {
         lander.dx-=.025;
         lander.thrust_rl=TRUE;
         lander.fuel-=.025;
      }
   }
   if (key[KEY_RIGHT]) {
      if (lander.fuel >= .025) {
         lander.dx+=.025;
         lander.thrust_rl=TRUE;
         lander.fuel-=.025;
      }
   }
   return 0;
}

/*********************[ DISPLAY TITLE SCREEN ]*******************/
void display_title() {
   blit(data[TITLE].dat, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
   beep();
   play_midi(data[DEFAULT].dat, 1); //start music
}

/*********************[ INITIALIZE THE LANDER ]*******************/
void initialize_lander() {
   lander.x = SCREEN_W>>1;
   lander.y = 0;
   lander.dead = FALSE;
   lander.fuel = 100;
   lander.dx = .01;    //lander starts out moving to the right
   lander.dy = gravity;
   view_x=lander.x-(SCREEN_W>>1)+16;
   view_y=0;
   if (SPLUNGEMODE) lander.fuel*=10;
}

/*********************[ UPDATE THE CONSOLE ]*******************/
void update_console() {
   int fuel=lander.fuel;
   if (SPLUNGEMODE) fuel/=10;
   text_mode(0);
   rectfill(screen, 0, 200, SCREEN_W-1, SCREEN_H-1, makecol(0, 64, 128));
   textprintf(screen, font, 10, 205, makecol(0, 255, 0), "FUEL");
   rectfill(screen, 48, 206, 205+fuel, 211, makecol(255, 0, 0));
   textprintf(screen, font, 10, 215, makecol(0, 255, 0), "V. SPEED: %.2f ", lander.dx);
   textprintf(screen, font, 10, 225, makecol(0, 255, 0), "H. SPEED: %.2f ", lander.dy);
}

/*********************[ UPDATE THE SCREEN ]*******************/
void update_screen() {
   vsync();
   if (view_x>RLIMIT) {
      blit(bmp, screen, view_x, view_y, 0, 0, 1600-view_x, SCREEN_H-40);
      blit(bmp, screen, 0, view_y, 1600-view_x, 0, SCREEN_W-(1579-view_x), SCREEN_H-40);
   }
   else
      blit(bmp, screen, view_x, view_y, 0, 0, SCREEN_W, SCREEN_H-40);

   draw_rle_sprite(screen, data[LANDER].dat, lander.x, lander.y);

   if (lander.thrust_up) {
         draw_rle_sprite(screen, data[FLAME].dat, lander.x, lander.y+16);
         play_sample(data[THRUST].dat, 255, 128, 1000, 0);
   }
   if (lander.thrust_rl || lander.thrust_up)
         play_sample(data[THRUST].dat, 255, 128, 2000, 0);
   else stop_sample(data[THRUST].dat);


   update_console();

   rest(50);  //pause!!! (forgot this the first time I tested it, heheh)
}

/****************[ COLLISION DETECTION ]******************/
int collision() {
   int xoff, yoff, flag=0, c, r;
   float checkx, checky;

   checkx=view_x+lander.x;
   if (checkx > 1579) checkx-=1579;
   checky=view_y+lander.y;

   for (yoff=0; yoff<=15; yoff++) {
      for (xoff=0; xoff<=15; xoff++) {
         c = getpixel(bmp, checkx+xoff, checky+yoff);
         r = getr(c);
         if (c==pad.colour && lander.dy>=0 &&
             view_x+lander.x>=pad.x && view_x+lander.x<=pad.x+24)
         {
            stop_midi();
            stop_sample(data[THRUST].dat);
            update_screen();
            play_sample(data[LANDED].dat, 255, 128, 1000, 0);
            alert("You are safe and sound on the Moon!", NULL, NULL, "ENTER", NULL, KEY_ENTER, KEY_ESC);
            while (keypressed());
            clear_keybuf();
            if (lander.dy>.5 || lander.dx>.5 || SPLUNGEMODE) {
               hland+=1;
               alert("Only problem is you're going", "to be here a while! You landed", "too hard to ever lift off again.", "ENTER", NULL, KEY_ENTER, KEY_ESC);
               if (SPLUNGEMODE) alert("The next time maybe you'll consider", "carrying a little less fuel! :)", "(cheaters never prosper)", "ENTER", NULL, KEY_ENTER, KEY_ESC);
               while (keypressed());
               clear_keybuf();
            }
            else sland+=1;
            flag=1;
            xoff=16; yoff=16;
         }
         else if (r != 0) {
            if (lander.y<0) {
               stop_midi();
               alert("You flew too high!", "You've been lost in space, maybe they'll", "make a TV series in your honour! ;)", "ENTER", NULL, KEY_ENTER, KEY_ESC); 
               while (keypressed());
               clear_keybuf();
               lland+=1;
               flag=1;
               lander.dead=TRUE;
               xoff=16; yoff=16;
            }
            else {
               stop_midi();
               stop_sample(data[THRUST].dat);
               lander.thrust_up=FALSE;
               lander.thrust_rl=FALSE;
               update_screen();
               play_sample(data[EXPLOSION].dat, 255, 128, 1000, 0);
               alert("You have to land ON the LANDING PAD!!!", "It's a little orange thingy.", "(All lander legs have to be on the pad)", "ENTER", NULL, KEY_ENTER, KEY_ESC); 
               while (keypressed());
               clear_keybuf();
               cland+=1;
               flag=1;
               lander.dead=TRUE;
               xoff=16; yoff=16;
            }
         }
      }
   }
   return flag;
}

/****************[ MOVE LANDER, RETURN 1 IF DEAD OR WON ]******************/
int move_lander() {
   int flag;

   view_x+=lander.dx;
   if (view_x>1579) view_x-=1600;
   if (view_x<0) view_x=1600+view_x;

   lander.dy+=gravity;
   if (lander.y<(SCREEN_H>>1)-40) lander.y+=lander.dy;
   else view_y+=lander.dy;
   if (view_y > BLIMIT) {
      lander.y+=view_y-BLIMIT;
      view_y = BLIMIT;
   }
   flag=collision();

   return flag;
}

/*********************[ CLEAN UP BEFORE EXITING ]*******************/
void shutdown_game() {
   char s[40], h[40], c[40], l[40];
   sprintf(s,"You had %d safe landings.", sland);
   sprintf(h,"You landed hard %d time(s).", hland);
   sprintf(c,"You crashed %d time(s).", cland);
   sprintf(l,"You were lost in space %d time(s).", lland);
   stop_midi();
   play_midi(data[ENDMID].dat, 0);
   blit(data[ENDGAME].dat, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
   rest(3000);
   if (sland>0) {
      beep();
      alert(NULL, s, NULL, "ENTER", NULL, KEY_ENTER, KEY_ESC);
   }
   if (hland>0) {
      beep();
      alert(NULL, h, NULL, "ENTER", NULL, KEY_ENTER, KEY_ESC);
   }
   if (cland>0) {
      beep();
      alert(NULL, c, NULL, "BYE!", NULL, KEY_ENTER, KEY_ESC);
   }
   if (lland>0) {
      beep();
      alert(NULL, l, NULL, "BYE!", NULL, KEY_ENTER, KEY_ESC);
   }
   stop_midi();
   destroy_bitmap(bmp);
   unload_datafile(data);
}

/*********************[ MAIN FUNCTION ]*******************/
/* main program */
int main(int argc, char *argv[])
{
   int index, done, goodbye;

   strcpy(path, argv[0]);

   // Handle command line options here
   if (argc >= 2) {
      for (index=1; index<argc; index++) {
         if (!strcmp(argv[index], "-SPLUNGE")) SPLUNGEMODE = TRUE; // ;-)
      }
   }
   init_game();

   do {
      display_title();
      generate_landscape();
      initialize_lander();
      done=FALSE; goodbye=FALSE;
      beep();
      clear_keybuf();
      while (!keypressed());
      if (key[KEY_ESC]) goodbye=TRUE;
      if (goodbye==FALSE) {
         do {
            update_screen();
            if (handle_input()>=1) done=TRUE;
            if (move_lander()>=1) done=TRUE;
         } while(done==FALSE);
         stop_sample(data[THRUST].dat);
      }
   } while (goodbye==FALSE);

   shutdown_game();
   return 0;
}
END_OF_MAIN();
