#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_font.h>
#include <stdlib.h>
#include <math.h>

#include "common.c"

ALLEGRO_DEBUG_CHANNEL("main")

#define FPS 60
#define MAX_SPRITES 1024

typedef struct Sprite {
   float x, y, dx, dy;
} Sprite;

char const *text[] = {
   "H - toggle held drawing",
   "Space - toggle use of textures",
   "B - toggle alpha blending",
   "Left/Right - change bitmap size",
   "Up/Down - change bitmap count",
   "F1 - toggle help text"
};

struct Example {
   Sprite sprites[MAX_SPRITES];
   bool use_memory_bitmaps;
   int blending;
   ALLEGRO_DISPLAY *display;
   ALLEGRO_BITMAP *mysha, *bitmap;
   bool hold_bitmap_drawing;
   int bitmap_size;
   int sprite_count;
   bool show_help;
   ALLEGRO_FONT *font;

   int t;

   ALLEGRO_COLOR white;
   ALLEGRO_COLOR half_white;
   ALLEGRO_COLOR dark;
   ALLEGRO_COLOR red;

   double direct_speed_measure;

   int ftpos;
   double frame_times[FPS];
} example;

static void add_time(void)
{
   example.frame_times[example.ftpos++] = al_get_time();
   if (example.ftpos >= FPS)
      example.ftpos = 0;
}

static void get_fps(int *average, int *minmax)
{
   int i;
   int prev = FPS - 1;
   double min_dt = 1;
   double max_dt = 1 / 1000000.0;
   double av = 0;
   double d;
   for (i = 0; i < FPS; i++) {
      if (i != example.ftpos) {
         double dt = example.frame_times[i] - example.frame_times[prev];
         if (dt < min_dt)
            min_dt = dt;
         if (dt > max_dt)
            max_dt = dt;
         av += dt;
      }
      prev = i;
   }
   av /= (FPS - 1);
   *average = ceil(1 / av);
   d = 1 / min_dt - 1 / max_dt;
   *minmax = floor(d / 2);
}

static void add_sprite(void)
{
   if (example.sprite_count < MAX_SPRITES) {
      int w = al_get_display_width(example.display);
      int h = al_get_display_height(example.display);
      int i = example.sprite_count++;
      Sprite *s = example.sprites + i;
      float a = rand() % 360;
      s->x = rand() % (w - example.bitmap_size);
      s->y = rand() % (h - example.bitmap_size);
      s->dx = cos(a) * FPS * 2;
      s->dy = sin(a) * FPS * 2;
   }
}

static void add_sprites(int n)
{
    int i;
    for (i = 0; i < n; i++)
       add_sprite();
}

static void remove_sprites(int n)
{
   example.sprite_count -= n;
   if (example.sprite_count < 0)
      example.sprite_count = 0;
}

static void change_size(int size)
{
   int bw, bh;
   if (size < 1)
      size = 1;
   if (size > 1024)
      size = 1024;

   if (example.bitmap)
      al_destroy_bitmap(example.bitmap);
   al_set_new_bitmap_flags(
      example.use_memory_bitmaps ? ALLEGRO_MEMORY_BITMAP : ALLEGRO_VIDEO_BITMAP);
   example.bitmap = al_create_bitmap(size, size);
   example.bitmap_size = size;
   al_set_target_bitmap(example.bitmap);
   al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
   al_clear_to_color(al_map_rgba_f(0, 0, 0, 0));
   bw = al_get_bitmap_width(example.mysha);
   bh = al_get_bitmap_height(example.mysha);
   al_draw_scaled_bitmap(example.mysha, 0, 0, bw, bh, 0, 0,
      size, size * bh / bw, 0);
   al_set_target_backbuffer(example.display);
}

static void sprite_update(Sprite *s)
{
   int w = al_get_display_width(example.display);
   int h = al_get_display_height(example.display);

   s->x += s->dx / FPS;
   s->y += s->dy / FPS;

   if (s->x < 0) {
      s->x = -s->x;
      s->dx = -s->dx;
   }
   if (s->x + example.bitmap_size > w) {
      s->x = -s->x + 2 * (w - example.bitmap_size);
      s->dx = -s->dx;
   }
   if (s->y < 0) {
      s->y = -s->y;
      s->dy = -s->dy;
   }
   if (s->y + example.bitmap_size > h) {
      s->y = -s->y + 2 * (h - example.bitmap_size);
      s->dy = -s->dy;
   }

   if (example.bitmap_size > w) s->x = w / 2 - example.bitmap_size / 2;
   if (example.bitmap_size > h) s->y = h / 2 - example.bitmap_size / 2;
}

static void update(void)
{
   int i;
   for (i = 0; i < example.sprite_count; i++)
      sprite_update(example.sprites + i);

   example.t++;
   if (example.t == 60) {
      ALLEGRO_DEBUG("tick");
      example.t = 0;
   }
}

static void redraw(void)
{
   int w = al_get_display_width(example.display);
   int h = al_get_display_height(example.display);
   int i;
   int f1, f2;
   int fh = al_get_font_line_height(example.font);
   char const *info[] = {"textures", "memory buffers"};
   char const *binfo[] = {"alpha", "additive", "tinted", "solid", "alpha test"};
   ALLEGRO_COLOR tint = example.white;

   if (example.blending == 0) {
      al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
      tint = example.half_white;
   }
   else if (example.blending == 1) {
      al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ONE);
      tint = example.dark;
   }
   else if (example.blending == 2) {
      al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
      tint = example.red;
   }
   else if (example.blending == 3) {
      al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
   }

   if (example.blending == 4) {
      al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
      al_set_render_state(ALLEGRO_ALPHA_TEST, true);
      al_set_render_state(ALLEGRO_ALPHA_FUNCTION, ALLEGRO_RENDER_GREATER);
      al_set_render_state(ALLEGRO_ALPHA_TEST_VALUE, 128);
   }
   else {
      al_set_render_state(ALLEGRO_ALPHA_TEST, false);
   }

   if (example.hold_bitmap_drawing) {
      al_hold_bitmap_drawing(true);
   }
   for (i = 0; i < example.sprite_count; i++) {
      Sprite *s = example.sprites + i;
      al_draw_tinted_bitmap(example.bitmap, tint, s->x, s->y, 0);
   }
   if (example.hold_bitmap_drawing) {
      al_hold_bitmap_drawing(false);
   }

   al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
   if (example.show_help) {
      int dh = fh * 3.5;
      for (i = 5; i >= 0; i--) {
         al_draw_text(example.font, example.white, 0, h - dh, 0, text[i]);
         dh += fh * 6;
      }
   }

   al_draw_textf(example.font, example.white, 0, 0, 0, "count: %d",
      example.sprite_count);
   al_draw_textf(example.font, example.white, 0, fh, 0, "size: %d",
      example.bitmap_size);
   al_draw_textf(example.font, example.white, 0, fh * 2, 0, "%s",
      info[example.use_memory_bitmaps]);
   al_draw_textf(example.font, example.white, 0, fh * 3, 0, "%s",
      binfo[example.blending]);

   get_fps(&f1, &f2);
   al_draw_textf(example.font, example.white, w, 0, ALLEGRO_ALIGN_RIGHT, "FPS: %4d +- %-4d",
      f1, f2);
   al_draw_textf(example.font, example.white, w, fh, ALLEGRO_ALIGN_RIGHT, "%4d / sec",
      (int)(1.0 / example.direct_speed_measure));
   
}

int main(int argc, char **argv)
{
   ALLEGRO_TIMER *timer;
   ALLEGRO_EVENT_QUEUE *queue;
   ALLEGRO_MONITOR_INFO info;
   const char* bitmap_filename;
   int w = 640, h = 480;
   bool done = false;
   bool need_redraw = true;
   bool background = false;
   example.show_help = true;
   example.hold_bitmap_drawing = false;

   if (argc > 1) {
      bitmap_filename = argv[1];
   }
   else {
      bitmap_filename = "data/mysha256x256.png";
   }

   if (!al_init()) {
      abort_example("Failed to init Allegro.\n");
   }

   if (!al_init_image_addon()) {
      abort_example("Failed to init IIO addon.\n");
   }

   al_init_font_addon();
   init_platform_specific();

   al_get_num_video_adapters();
   
   al_get_monitor_info(0, &info);

   al_set_new_display_option(ALLEGRO_SUPPORTED_ORIENTATIONS,
                             ALLEGRO_DISPLAY_ORIENTATION_ALL, ALLEGRO_SUGGEST);
   example.display = al_create_display(w, h);
   if (!example.display) {
      abort_example("Error creating display.\n");
   }

   w = al_get_display_width(example.display);
   h = al_get_display_height(example.display);

   if (!al_install_keyboard()) {
      abort_example("Error installing keyboard.\n");
   }
  
   if (!al_install_mouse()) {
      abort_example("Error installing mouse.\n");
   }

   al_install_touch_input();

   example.font = al_create_builtin_font();
   if (!example.font) {
      abort_example("Error creating builtin font\n");
   }

   example.mysha = al_load_bitmap(bitmap_filename);
   if (!example.mysha) {
      abort_example("Error loading %s\n", bitmap_filename);
   }

   example.white = al_map_rgb_f(1, 1, 1);
   example.half_white = al_map_rgba_f(1, 1, 1, 0.5);
   example.dark = al_map_rgb(15, 15, 15);
   example.red = al_map_rgb_f(1, 0.2, 0.1);
   change_size(256);
   add_sprite();
   add_sprite();

   timer = al_create_timer(1.0 / FPS);

   queue = al_create_event_queue();
   al_register_event_source(queue, al_get_keyboard_event_source());
   al_register_event_source(queue, al_get_mouse_event_source());
   al_register_event_source(queue, al_get_timer_event_source(timer));
   
   if (al_install_touch_input())
      al_register_event_source(queue, al_get_touch_input_event_source());
   al_register_event_source(queue, al_get_display_event_source(example.display));

   al_start_timer(timer);

   while (!done) {
      float x, y;
      ALLEGRO_EVENT event;
      w = al_get_display_width(example.display);
      h = al_get_display_height(example.display);

      if (!background && need_redraw && al_is_event_queue_empty(queue)) {
         double t = -al_get_time();
         add_time();
         al_clear_to_color(al_map_rgb_f(0, 0, 0));
         redraw();
         t += al_get_time();
         example.direct_speed_measure  = t;
         al_flip_display();
         need_redraw = false;
      }

      al_wait_for_event(queue, &event);
      switch (event.type) {
         case ALLEGRO_EVENT_KEY_CHAR: /* includes repeats */
            if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
               done = true;
            else if (event.keyboard.keycode == ALLEGRO_KEY_UP) {
               add_sprites(1);
            }
            else if (event.keyboard.keycode == ALLEGRO_KEY_DOWN) {
               remove_sprites(1);
            }
            else if (event.keyboard.keycode == ALLEGRO_KEY_LEFT) {
               change_size(example.bitmap_size - 1);
            }
            else if (event.keyboard.keycode == ALLEGRO_KEY_RIGHT) {
               change_size(example.bitmap_size + 1);
            }
            else if (event.keyboard.keycode == ALLEGRO_KEY_F1) {
               example.show_help ^= 1;
            }
            else if (event.keyboard.keycode == ALLEGRO_KEY_SPACE) {
               example.use_memory_bitmaps ^= 1;
               change_size(example.bitmap_size);
            }
            else if (event.keyboard.keycode == ALLEGRO_KEY_B) {
               example.blending++;
               if (example.blending == 5)
                  example.blending = 0;
            }
            else if (event.keyboard.keycode == ALLEGRO_KEY_H) {
               example.hold_bitmap_drawing ^= 1;
            }
            break;

         case ALLEGRO_EVENT_DISPLAY_CLOSE:
            done = true;
            break;

         case ALLEGRO_EVENT_DISPLAY_HALT_DRAWING:

            background = true;
            al_acknowledge_drawing_halt(event.display.source);

            break;
         
         case ALLEGRO_EVENT_DISPLAY_RESUME_DRAWING:
            background = false;
            al_acknowledge_drawing_resume(event.display.source);
            break;
         
         case ALLEGRO_EVENT_DISPLAY_RESIZE:
            al_acknowledge_resize(event.display.source);
            break;
              
         case ALLEGRO_EVENT_TIMER:
            update();
            need_redraw = true;
            break;
         
         case ALLEGRO_EVENT_TOUCH_BEGIN:
            x = event.touch.x;
            y = event.touch.y;
            goto click;

         case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
            x = event.mouse.x;
            y = event.mouse.y;
            goto click;
            
         click:
         {
            int fh = al_get_font_line_height(example.font);
            
            if (x < fh * 12 && y >= h - fh * 30) {
               int button = (y - (h - fh * 30)) / (fh * 6);
               if (button == 0) {
                  example.use_memory_bitmaps ^= 1;
                  change_size(example.bitmap_size);
               }
               if (button == 1) {
                  example.blending++;
                  if (example.blending == 5)
                     example.blending = 0;
               }
               if (button == 3) {
                  if (x < fh * 6)
                     remove_sprites(example.sprite_count / 2);
                  else
                     add_sprites(example.sprite_count);
               }
               if (button == 2) {
                  int s = example.bitmap_size * 2;
                  if (x < fh * 6)
                     s = example.bitmap_size / 2;
                  change_size(s);
               }
               if (button == 4) {
                  example.show_help ^= 1;
               }
                
            }
            break;
         }
      }
   }

   al_destroy_bitmap(example.bitmap);

   return 0;
}

/* vim: set sts=3 sw=3 et: */