/*
 *    Example program for the Allegro library, by Peter Wang.
 *
 *    This program tests the polygon routines in the primitives addon.
 */

#define ALLEGRO_UNSTABLE
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_primitives.h>
#include <ctype.h>
#include <math.h>

#include "common.c"

#define MAX_VERTICES 64
#define MAX_POLYGONS 9
#define RADIUS       5

enum {
   MODE_POLYLINE = 0,
   MODE_POLYGON,
   MODE_FILLED_POLYGON,
   MODE_FILLED_HOLES,
   MODE_MAX
};

enum AddHole {
   NOT_ADDING_HOLE,
   NEW_HOLE,
   GROW_HOLE
};

typedef struct Vertex Vertex;
struct Vertex {
   float x;
   float y;
};

struct Example {
   ALLEGRO_DISPLAY   *display;
   ALLEGRO_FONT      *font;
   ALLEGRO_FONT      *fontbmp;
   ALLEGRO_EVENT_QUEUE *queue;
   ALLEGRO_BITMAP    *dbuf;
   ALLEGRO_COLOR     bg;
   ALLEGRO_COLOR     fg;
   Vertex            vertices[MAX_VERTICES];
   int               vertex_polygon[MAX_VERTICES];
   int               vertex_count;
   int               cur_vertex; /* -1 = none */
   int               cur_polygon;
   int               mode;
   ALLEGRO_LINE_CAP  cap_style;
   ALLEGRO_LINE_JOIN join_style;
   float             thickness;
   float             miter_limit;
   bool              software;
   float             zoom;
   float             scroll_x, scroll_y;
   enum AddHole      add_hole;
};

static struct Example ex;

static void reset(void)
{
   ex.vertex_count = 0;
   ex.cur_vertex = -1;
   ex.cur_polygon = 0;
   ex.cap_style = ALLEGRO_LINE_CAP_NONE;
   ex.join_style = ALLEGRO_LINE_JOIN_NONE;
   ex.thickness = 1.0f;
   ex.miter_limit = 1.0f;
   ex.software = false;
   ex.zoom = 1;
   ex.scroll_x = 0;
   ex.scroll_y = 0;
   ex.add_hole = NOT_ADDING_HOLE;
}

static void transform(float *x, float *y)
{
   *x /= ex.zoom;
   *y /= ex.zoom;
   *x -= ex.scroll_x;
   *y -= ex.scroll_y;
}

static int hit_vertex(int mx, int my)
{
   int i;

   for (i = 0; i < ex.vertex_count; i++) {
      int dx = ex.vertices[i].x - mx;
      int dy = ex.vertices[i].y - my;
      int dd = dx*dx + dy*dy;
      if (dd <= RADIUS * RADIUS)
         return i;
   }

   return -1;
}

static void lclick(int mx, int my)
{
   ex.cur_vertex = hit_vertex(mx, my);

   if (ex.cur_vertex < 0 && ex.vertex_count < MAX_VERTICES) {
      int i = ex.vertex_count++;
      ex.vertices[i].x = mx;
      ex.vertices[i].y = my;
      ex.cur_vertex = i;

      if (ex.add_hole == NEW_HOLE && ex.cur_polygon < MAX_POLYGONS) {
         ex.cur_polygon++;
         ex.add_hole = GROW_HOLE;
      }

      ex.vertex_polygon[i] = ex.cur_polygon;
   }
}

static void rclick(int mx, int my)
{
   const int i = hit_vertex(mx, my);
   if (i >= 0 && ex.add_hole == NOT_ADDING_HOLE) {
      ex.vertex_count--;
      memmove(&ex.vertices[i], &ex.vertices[i + 1],
         sizeof(Vertex) * (ex.vertex_count - i));
      memmove(&ex.vertex_polygon[i], &ex.vertex_polygon[i + 1],
         sizeof(int) * (ex.vertex_count - i));
   }
   ex.cur_vertex = -1;
}

static void drag(int mx, int my)
{
   if (ex.cur_vertex >= 0) {
      ex.vertices[ex.cur_vertex].x = mx;
      ex.vertices[ex.cur_vertex].y = my;
   }
}

static void scroll(int mx, int my)
{
   ex.scroll_x += mx;
   ex.scroll_y += my;
}

static const char *join_style_to_string(ALLEGRO_LINE_JOIN x)
{
   switch (x) {
      case ALLEGRO_LINE_JOIN_NONE:
         return "NONE";
      case ALLEGRO_LINE_JOIN_BEVEL:
         return "BEVEL";
      case ALLEGRO_LINE_JOIN_ROUND:
         return "ROUND";
      case ALLEGRO_LINE_JOIN_MITER:
         return "MITER";
      default:
         return "unknown";
   }
}

static const char *cap_style_to_string(ALLEGRO_LINE_CAP x)
{
   switch (x) {
      case ALLEGRO_LINE_CAP_NONE:
         return "NONE";
      case ALLEGRO_LINE_CAP_SQUARE:
         return "SQUARE";
      case ALLEGRO_LINE_CAP_ROUND:
         return "ROUND";
      case ALLEGRO_LINE_CAP_TRIANGLE:
         return "TRIANGLE";
      case ALLEGRO_LINE_CAP_CLOSED:
         return "CLOSED";
      default:
         return "unknown";
   }
}

static ALLEGRO_FONT *choose_font(void)
{
   return (ex.software) ? ex.fontbmp : ex.font;
}

static void draw_vertices(void)
{
   ALLEGRO_FONT *f = choose_font();
   ALLEGRO_COLOR vertc = al_map_rgba_f(0.7, 0, 0, 0.7);
   ALLEGRO_COLOR textc = al_map_rgba_f(0, 0, 0, 0.7);
   int i;

   for (i = 0; i < ex.vertex_count; i++) {
      float x = ex.vertices[i].x;
      float y = ex.vertices[i].y;

      al_draw_filled_circle(x, y, RADIUS, vertc);
      al_draw_textf(f, textc, x + RADIUS, y + RADIUS, 0, "%d", i);
   }
}

static void compute_polygon_vertex_counts(
   int polygon_vertex_count[MAX_POLYGONS + 1])
{
   int i;

   /* This also implicitly terminates the array with a zero. */
   memset(polygon_vertex_count, 0, sizeof(int) * (MAX_POLYGONS + 1));
   for (i = 0; i < ex.vertex_count; i++) {
      const int poly = ex.vertex_polygon[i];
      polygon_vertex_count[poly]++;
   }
}

static void draw_all(void)
{
   ALLEGRO_FONT *f = choose_font();
   ALLEGRO_COLOR textc = al_map_rgb(0, 0, 0);
   float texth = al_get_font_line_height(f) * 1.5;
   float textx = 5;
   float texty = 5;
   ALLEGRO_TRANSFORM t;
   ALLEGRO_COLOR holec;

   al_clear_to_color(ex.bg);

   al_identity_transform(&t);
   al_translate_transform(&t, ex.scroll_x, ex.scroll_y);
   al_scale_transform(&t, ex.zoom, ex.zoom);
   al_use_transform(&t);

   if (ex.mode == MODE_POLYLINE) {
      if (ex.vertex_count >= 2) {
         al_draw_polyline(
            (float *)ex.vertices, sizeof(Vertex), ex.vertex_count,
            ex.join_style, ex.cap_style, ex.fg, ex.thickness, ex.miter_limit);
      }
   }
   else if (ex.mode == MODE_FILLED_POLYGON) {
      if (ex.vertex_count >= 2) {
         al_draw_filled_polygon(
            (float *)ex.vertices, ex.vertex_count, ex.fg);
      }
   }
   else if (ex.mode == MODE_POLYGON) {
      if (ex.vertex_count >= 2) {
         al_draw_polygon(
            (float *)ex.vertices, ex.vertex_count,
            ex.join_style, ex.fg, ex.thickness, ex.miter_limit);
      }
   }
   else if (ex.mode == MODE_FILLED_HOLES) {
      if (ex.vertex_count >= 2) {
         int polygon_vertex_count[MAX_POLYGONS + 1];
         compute_polygon_vertex_counts(polygon_vertex_count);
         al_draw_filled_polygon_with_holes(
            (float *)ex.vertices, polygon_vertex_count, ex.fg);
      }
   }

   draw_vertices();

   al_identity_transform(&t);
   al_use_transform(&t);

   if (ex.mode == MODE_POLYLINE) {
      al_draw_textf(f, textc, textx, texty, 0,
         "al_draw_polyline (SPACE)");
      texty += texth;
   }
   else if (ex.mode == MODE_FILLED_POLYGON) {
      al_draw_textf(f, textc, textx, texty, 0,
         "al_draw_filled_polygon (SPACE)");
      texty += texth;
   }
   else if (ex.mode == MODE_POLYGON) {
      al_draw_textf(f, textc, textx, texty, 0,
         "al_draw_polygon (SPACE)");
      texty += texth;
   }
   else if (ex.mode == MODE_FILLED_HOLES) {
      al_draw_textf(f, textc, textx, texty, 0,
         "al_draw_filled_polygon_with_holes (SPACE)");
      texty += texth;
   }

   al_draw_textf(f, textc, textx, texty, 0,
      "Line join style: %s (J)", join_style_to_string(ex.join_style));
   texty += texth;

   al_draw_textf(f, textc, textx, texty, 0,
      "Line cap style:  %s (C)", cap_style_to_string(ex.cap_style));
   texty += texth;

   al_draw_textf(f, textc, textx, texty, 0,
      "Line thickness:  %.2f (+/-)", ex.thickness);
   texty += texth;

   al_draw_textf(f, textc, textx, texty, 0,
      "Miter limit:     %.2f ([/])", ex.miter_limit);
   texty += texth;

   al_draw_textf(f, textc, textx, texty, 0,
      "Zoom:            %.2f (wheel)", ex.zoom);
   texty += texth;

   al_draw_textf(f, textc, textx, texty, 0,
      "%s (S)", (ex.software ? "Software rendering" : "Hardware rendering"));
   texty += texth;

   al_draw_textf(f, textc, textx, texty, 0,
      "Reset (R)");
   texty += texth;

   if (ex.add_hole == NOT_ADDING_HOLE)
      holec = textc;
   else if (ex.add_hole == GROW_HOLE)
      holec = al_map_rgb(200, 0, 0);
   else
      holec = al_map_rgb(0, 200, 0);
   al_draw_textf(f, holec, textx, texty, 0,
      "Add Hole (%d) (H)", ex.cur_polygon);
   texty += texth;
}

/* Print vertices in a format for the test suite. */
static void print_vertices(void)
{
   int i;

   for (i = 0; i < ex.vertex_count; i++) {
      log_printf("v%-2d= %.2f, %.2f\n",
         i, ex.vertices[i].x, ex.vertices[i].y);
   }
   log_printf("\n");
}

int main(int argc, char **argv)
{
   ALLEGRO_EVENT event;
   bool have_touch_input;
   bool mdown = false;

   (void)argc;
   (void)argv;

   if (!al_init()) {
      abort_example("Could not init Allegro.\n");
   }

   open_log();

   if (!al_init_primitives_addon()) {
      abort_example("Could not init primitives.\n");
   }
   al_init_font_addon();
   al_install_mouse();
   al_install_keyboard();
   have_touch_input = al_install_touch_input();

   ex.display = al_create_display(800, 600);
   if (!ex.display) {
      abort_example("Error creating display\n");
   }

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

   al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
   ex.dbuf = al_create_bitmap(800, 600);
   ex.fontbmp = al_create_builtin_font();
   if (!ex.fontbmp) {
      abort_example("Error creating builtin font\n");
   }

   ex.queue = al_create_event_queue();
   al_register_event_source(ex.queue, al_get_keyboard_event_source());
   al_register_event_source(ex.queue, al_get_mouse_event_source());
   al_register_event_source(ex.queue, al_get_display_event_source(ex.display));
   if (have_touch_input) {
      al_register_event_source(ex.queue, al_get_touch_input_event_source());
      al_register_event_source(ex.queue, al_get_touch_input_mouse_emulation_event_source());
   }

   ex.bg = al_map_rgba_f(1, 1, 0.9, 1);
   ex.fg = al_map_rgba_f(0, 0.5, 1, 1);

   reset();

   for (;;) {
      if (al_is_event_queue_empty(ex.queue)) {
         if (ex.software) {
            al_set_target_bitmap(ex.dbuf);
            draw_all();
            al_set_target_backbuffer(ex.display);
            al_draw_bitmap(ex.dbuf, 0, 0, 0);
         }
         else {
            al_set_target_backbuffer(ex.display);
            draw_all();
         }
         al_flip_display();
      }

      al_wait_for_event(ex.queue, &event);
      if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
         break;
      if (event.type == ALLEGRO_EVENT_KEY_CHAR) {
         if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
            break;
         switch (toupper(event.keyboard.unichar)) {
            case ' ':
               if (++ex.mode >= MODE_MAX)
                  ex.mode = 0;
               break;
            case 'J':
               if (++ex.join_style > ALLEGRO_LINE_JOIN_MITER)
                  ex.join_style = ALLEGRO_LINE_JOIN_NONE;
               break;
            case 'C':
               if (++ex.cap_style > ALLEGRO_LINE_CAP_CLOSED)
                  ex.cap_style = ALLEGRO_LINE_CAP_NONE;
               break;
            case '+':
               ex.thickness += 0.25f;
               break;
            case '-':
               ex.thickness -= 0.25f;
               if (ex.thickness <= 0.0f)
                  ex.thickness = 0.0f;
               break;
            case '[':
               ex.miter_limit -= 0.1f;
               if (ex.miter_limit < 0.0f)
                  ex.miter_limit = 0.0f;
               break;
            case ']':
               ex.miter_limit += 0.1f;
               if (ex.miter_limit >= 10.0f)
                  ex.miter_limit = 10.0f;
               break;
            case 'S':
               ex.software = !ex.software;
               break;
            case 'R':
               reset();
               break;
            case 'P':
               print_vertices();
               break;
            case 'H':
               ex.add_hole = NEW_HOLE;
               break;
         }
      }
      if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) {
         float x = event.mouse.x, y = event.mouse.y;
         transform(&x, &y);
         if (event.mouse.button == 1)
            lclick(x, y);
         if (event.mouse.button == 2)
            rclick(x, y);
         if (event.mouse.button == 3)
            mdown = true;
      }
      if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_UP) {
         ex.cur_vertex = -1;
         if (event.mouse.button == 3)
            mdown = false;
      }
      if (event.type == ALLEGRO_EVENT_MOUSE_AXES) {
         float x = event.mouse.x, y = event.mouse.y;
         transform(&x, &y);

         if (mdown)
            scroll(event.mouse.dx, event.mouse.dy);
         else
            drag(x, y);

         ex.zoom *= pow(0.9, event.mouse.dz);
      }
   }

   al_destroy_display(ex.display);
   close_log(true);

   return 0;
}

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