#include <stdio.h> 
#include <stdlib.h> 
#include <allegro5/allegro.h> 

const int FPS = 60;
const int SCREEN_W = 640;
const int SCREEN_H = 480;
const int BOUNCER_SIZE = 32;

const int SPRITELIST_START_SIZE = 1000;
const int SPRITELIST_INCREMENT  =  100;

/* Enum for different statuses for return values of functions that can fail. */
enum GameStatus {
  GameStatusOK	 	= 0,
  GameStatusNoMemory	= -1,
  GameStatusBadArgument	= -2,
  GameStatusAllegroError= -3,
};


/* A set of x and Y coordinaes. */
struct Point {
  float x;
  float y;
};

struct Point point_make(float x, float y) {
  struct Point point;
  point.x = x;
  point.y = y;
  return point;
}

struct Point point_add(struct Point self, struct Point other) {
  return point_make(self.x + other.x, self.y + other.y);
}

/* A sprite keeps thack of the visualisation, position and speed of an in-game entity. */
struct Sprite {
  ALLEGRO_BITMAP * bitmap;
  struct Point     position;
  struct Point     speed;
};

void sprite_init(struct Sprite * self, ALLEGRO_BITMAP * bitmap,
                 int x, int y, int vx, int vy) {
  self->bitmap   = bitmap;
  self->position = point_make(x, y);
  self->speed    = point_make(vx, vy);
}

void sprite_done(struct Sprite * self) {
  al_destroy_bitmap(self->bitmap);
  self->bitmap = NULL;
}


/* 
  We don't need a generic vector. We need a list of sprites to draw. 
*/
struct SpriteList {
  struct Sprite * sprites;
  int      size;
  int      in_use;
};


/* Initialize the sprite list. */
int spritelist_init(struct SpriteList * self) {
  self->in_use  = 0;
  self->size    = SPRITELIST_START_SIZE;
  self->sprites = calloc(self->size, sizeof(struct Sprite));
  if (!self->sprites) {
    self->size = 0;
    return GameStatusNoMemory;
  }
  return GameStatusOK;
}



/* Grows the sprite list */
int spritelist_grow(struct SpriteList * self, int grow_by) {
  struct Sprite * new_list;
  if (grow_by < 1) return GameStatusBadArgument;
  new_list = realloc(self->sprites, self->size + grow_by);
  if(!new_list) return GameStatusNoMemory;
  self->size   += grow_by;
  self->sprites = new_list;
  return GameStatusOK;
}

/* Gets a pointer to a sprite in the list. */
struct Sprite * spritelist_get(struct SpriteList * self, int index) {
  if (index < 0) return NULL;
  if (index > self->size) return NULL;
  return self->sprites+index;
}

/* Cleans up the sprite list. */
void spritelist_done(struct SpriteList *self) {
  int index;
  for (index = 0 ; index < self->in_use; index++) {
    struct Sprite * sprite = spritelist_get(self, index);
    sprite_done(sprite);
  }
  free(self->sprites);
  self->in_use = 0;
  self->size = 0;
}


/* Adds a sprite to the list. returns the index at which it is , or a negative value if it failed to ad the item to the list.  */
int spritelist_add_sprite(struct SpriteList * self, ALLEGRO_BITMAP * bitmap, 
                         int x, int y, int vx, int vy) {
  int result;
  struct Sprite * sprite;
  if(self->in_use >= self->size) {
    int res = spritelist_grow(self, SPRITELIST_INCREMENT);
    if (res != GameStatusOK) return res;
  }
  sprite = spritelist_get(self, self->in_use);
  sprite_init(sprite, bitmap, x, y, vx, vy);
  result = self->in_use;
  self->in_use++;
  return result;
}

/* Adds a sprite to the list and also allocate a bitmap for it with the given sizes. */
int spritelist_add_sprite_with_size(struct SpriteList * self, int bitmap_w, int bitmap_h, int x, int y, int vx, int vy) {
  ALLEGRO_BITMAP * bitmap;
  bitmap = al_create_bitmap(bitmap_w, bitmap_h);
  if (!bitmap) return GameStatusAllegroError;
  return spritelist_add_sprite(self, bitmap, x, y, vx, vy);
}

/* Adds a sprite to the list and also allocate a bitmap for it with the given size and color. */
int spritelist_add_sprite_with_size_and_color(struct SpriteList * self, int bitmap_w, int bitmap_h, int r, int g, int b, int x, int y, int vx, int vy) {
  ALLEGRO_BITMAP * bitmap;
  bitmap = al_create_bitmap(bitmap_w, bitmap_h);
  if (!bitmap) return GameStatusAllegroError;
  al_set_target_bitmap(bitmap); 
  al_clear_to_color(al_map_rgb(r, g, b)); 
  al_set_target_bitmap(al_get_backbuffer(al_get_current_display())); 
  return spritelist_add_sprite(self, bitmap, x, y, vx, vy);
}

/* Draws all sprites in the list. */
void spritelist_draw(struct SpriteList * self) {
  int index;
  for (index = 0; index < self->in_use; index++) {
    struct Sprite * sprite = spritelist_get(self, index);
    if (sprite && sprite->bitmap) {
      al_draw_bitmap(sprite->bitmap, sprite->position.x, sprite->position.y, 0);
    }
  }
}

/* Updates the position of all sprites in the list based on their speed. */
void spritelist_update(struct SpriteList * self) {
  int index;
  for (index = 0; index < self->in_use; index++) {
    struct Sprite * sprite = spritelist_get(self, index);
    if (sprite) {
      sprite->position = point_add(sprite->position, sprite->speed);
    }
  }
}




/* A struct to kep all the game state in. */
struct GameState {
  ALLEGRO_DISPLAY     * display; 
  ALLEGRO_EVENT_QUEUE * event_queue; 
  ALLEGRO_TIMER       * timer; 
  struct SpriteList     spritelist;
  int                   busy;
  int                   redraw;
};



/* Initializes the game state. */
int gamestate_init(struct GameState * self, int screen_w, int screen_h) {
  int res;

  if(!al_init()) {
    fprintf(stderr, "Failed to initialize Allegro!\n");
    return GameStatusAllegroError;
  }
  if(!al_install_mouse()) {
     fprintf(stderr, "Failed to initialize the mouse!\n");
     return GameStatusAllegroError;
  }
  self->timer = al_create_timer(1.0 / FPS);
  if(!self->timer) {
    fprintf(stderr, "Failed to create timer!\n");
    return GameStatusAllegroError;
  }
  
  self->display = al_create_display(screen_w, screen_h);
  if(!self->display) {
     fprintf(stderr, "Failed to create display!\n");
     al_destroy_timer(self->timer);
     return GameStatusAllegroError;
  }

  self->event_queue = al_create_event_queue();
  if(!self->event_queue) {
    fprintf(stderr, "Failed to create event_queue!\n");
    al_destroy_display(self->display);
    al_destroy_timer(self->timer);
    return GameStatusAllegroError;
  }

  al_register_event_source(self->event_queue,
                           al_get_display_event_source(self->display));
  al_register_event_source(self->event_queue,
                           al_get_timer_event_source(self->timer));
  al_register_event_source(self->event_queue,
                           al_get_mouse_event_source());


  res = spritelist_init(&self->spritelist);
  if(res != GameStatusOK) {
    al_destroy_timer(self->timer);
    al_destroy_display(self->display);
    al_destroy_event_queue(self->event_queue);
    return GameStatusAllegroError;
    return res;
  }
  
  al_start_timer(self->timer);
  
  self->busy   = 1;
  self->redraw = 0;

  return GameStatusOK;
}


/* Cleans up the game state. */
void gamestate_done(struct GameState * self) {
  al_destroy_timer(self->timer);
  al_destroy_display(self->display);
  al_destroy_event_queue(self->event_queue);
  spritelist_done(&self->spritelist);
  self->timer   = NULL;
  self->display = NULL;
  self->event_queue = NULL;
  self->busy    = 0;
  self->redraw  = 0;
};



/* Handles a single loop when running the game state. Here all the real action happens. */
void gamestate_runloop(struct GameState * self) {
   struct Sprite * bouncer;
   ALLEGRO_EVENT ev;
   al_wait_for_event(self->event_queue, &ev);
   switch(ev.type) { 
     case ALLEGRO_EVENT_TIMER:
       self->redraw = 1;  break;

     case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
     case ALLEGRO_EVENT_DISPLAY_CLOSE:  
       self->busy = 0; break; 

     case ALLEGRO_EVENT_MOUSE_AXES:
     case ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY:  
       bouncer = spritelist_get(&self->spritelist, 0);
       bouncer->position.x = ev.mouse.x; 
       bouncer->position.y = ev.mouse.y; 
       break;
     default: 
     break;
   }
  /* Update and redraw if needed. */
  if(self->redraw && al_is_event_queue_empty(self->event_queue)) {
    spritelist_update(&self->spritelist);
    self->redraw = 0;
    al_clear_to_color(al_map_rgb(0,0,0));
    spritelist_draw(&self->spritelist);
    al_flip_display();
  }
}


/* Runs the game state by looping it using gamestate_runloop */
void gamestate_run(struct GameState * self) {
  while(self->busy) {
    gamestate_runloop(self);
  }
}


/* Adds a few sprites to the game state. */
int gamestate_addsprites(struct GameState * gamestate) {
  int res;
  float bouncer_x = SCREEN_W / 2.0 - BOUNCER_SIZE / 2.0;
  float bouncer_y = SCREEN_H / 2.0 - BOUNCER_SIZE / 2.0;

  res = spritelist_add_sprite_with_size_and_color(
          &gamestate->spritelist, 32, 32, 255, 0, 255, 
          bouncer_x, bouncer_y, 0, 0);
  if (res < 0) return res;
  res = spritelist_add_sprite_with_size_and_color(
          &gamestate->spritelist, 32, 64, 255, 255, 
          0, 0, 0, 1, 1);
  if (res < 0) return res;
  /* Etc, add more sprites here if needed.... */
  return GameStatusOK;
}

/* The main function is now much simplified. */
int main(int argc, char **argv) {
  struct GameState gamestate;

  if (gamestate_init(&gamestate, SCREEN_W, SCREEN_H) != GameStatusOK) {
    return 1;
  }
  if (gamestate_addsprites(&gamestate) != GameStatusOK) {
    gamestate_done(&gamestate);
    return 1;
  }
  gamestate_run(&gamestate);
  gamestate_done(&gamestate);
  return 0;
}
