#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define ALLEGRO_UNSTABLE
#include "allegro5/allegro.h"
#include "allegro5/allegro_primitives.h"

#include "common.c"

typedef struct
{
   float x, y;
   float nx, ny, nz;
} CUSTOM_VERTEX;

#define RING_SIZE 25
#define SPHERE_RADIUS 150.1
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define NUM_RINGS (SCREEN_WIDTH / RING_SIZE + 1)
#define NUM_SEGMENTS 64
#define NUM_VERTICES (NUM_RINGS * NUM_SEGMENTS * 6)
#define FIRST_OUTSIDE_RING ((int)(SPHERE_RADIUS / RING_SIZE))

static void setup_vertex(CUSTOM_VERTEX* vtx, int ring, int segment, bool inside)
{
   float len;
   float x, y, z;
   x = ring * RING_SIZE * cosf(2 * ALLEGRO_PI * segment / NUM_SEGMENTS);
   y = ring * RING_SIZE * sinf(2 * ALLEGRO_PI * segment / NUM_SEGMENTS);
   vtx->x = x + SCREEN_WIDTH / 2;
   vtx->y = y + SCREEN_HEIGHT / 2;

   if (inside) {
      /* This comes from the definition of the normal vector as the
       * gradient of the 3D surface. */
      z = sqrtf(SPHERE_RADIUS * SPHERE_RADIUS - x * x - y * y);
      vtx->nx = x / z;
      vtx->ny = y / z;
   }
   else {
      vtx->nx = 0;
      vtx->ny = 0;
   }
   vtx->nz = 1.0;

   len = sqrtf(vtx->nx * vtx->nx + vtx->ny * vtx->ny + vtx->nz * vtx->nz);
   vtx->nx /= len;
   vtx->ny /= len;
   vtx->nz /= len;
}

int main(int argc, char **argv)
{
   ALLEGRO_DISPLAY *display;
   ALLEGRO_TIMER *timer;
   ALLEGRO_EVENT_QUEUE *queue;
   bool redraw = true;
   ALLEGRO_SHADER *shader;
   ALLEGRO_VERTEX_DECL *vertex_decl;
   ALLEGRO_VERTEX_ELEMENT vertex_elems[] = {
      {ALLEGRO_PRIM_POSITION, ALLEGRO_PRIM_FLOAT_2, offsetof(CUSTOM_VERTEX, x)},
      {ALLEGRO_PRIM_USER_ATTR, ALLEGRO_PRIM_FLOAT_3, offsetof(CUSTOM_VERTEX, nx)},
      {0, 0, 0}
   };
   CUSTOM_VERTEX vertices[NUM_VERTICES];
   bool quit = false;
   const char* vertex_shader_file;
   const char* pixel_shader_file;
   int vertex_idx = 0;
   int ring, segment;
   float diffuse_color[4] = {0.1, 0.1, 0.7, 1.0};
   float light_position[3] = {0, 0, 100};

   (void)argc;
   (void)argv;

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

   al_install_mouse();
   al_install_keyboard();
   al_install_touch_input();
   if (!al_init_primitives_addon()) {
      abort_example("Could not init primitives addon.\n");
   }
   init_platform_specific();
   al_set_new_display_flags(ALLEGRO_PROGRAMMABLE_PIPELINE);
   display = al_create_display(SCREEN_WIDTH, SCREEN_HEIGHT);
   if (!display) {
      abort_example("Error creating display.\n");
   }

   vertex_decl = al_create_vertex_decl(vertex_elems, sizeof(CUSTOM_VERTEX));
   if (!vertex_decl) {
      abort_example("Error creating vertex declaration.\n");
   }

   /* Computes a "spherical" bump ring. The z coordinate is not actually set
    * appropriately as this is a 2D example, but the normal vectors are computed
    * correctly for the light shading effect. */
   for (ring = 0; ring < NUM_RINGS; ring++) {
      for (segment = 0; segment < NUM_SEGMENTS; segment++) {
         bool inside = ring < FIRST_OUTSIDE_RING;
         setup_vertex(&vertices[vertex_idx + 0], ring + 0, segment + 0, inside);
         setup_vertex(&vertices[vertex_idx + 1], ring + 0, segment + 1, inside);
         setup_vertex(&vertices[vertex_idx + 2], ring + 1, segment + 0, inside);
         setup_vertex(&vertices[vertex_idx + 3], ring + 1, segment + 0, inside);
         setup_vertex(&vertices[vertex_idx + 4], ring + 0, segment + 1, inside);
         setup_vertex(&vertices[vertex_idx + 5], ring + 1, segment + 1, inside);
         vertex_idx += 6;
      }
   }

   shader = al_create_shader(ALLEGRO_SHADER_AUTO);

   if (!shader) {
      abort_example("Failed to create shader.");
   }

   if (al_get_shader_platform(shader) == ALLEGRO_SHADER_GLSL) {
      vertex_shader_file = "data/ex_prim_shader_vertex.glsl";
      pixel_shader_file = "data/ex_prim_shader_pixel.glsl";
   }
   else {
      vertex_shader_file = "data/ex_prim_shader_vertex.hlsl";
      pixel_shader_file = "data/ex_prim_shader_pixel.hlsl";
   }

   if (!al_attach_shader_source_file(shader, ALLEGRO_VERTEX_SHADER, vertex_shader_file)) {
      abort_example("al_attach_shader_source_file for vertex shader failed: %s\n",
         al_get_shader_log(shader));
   }
   if (!al_attach_shader_source_file(shader, ALLEGRO_PIXEL_SHADER, pixel_shader_file)) {
      abort_example("al_attach_shader_source_file for pixel shader failed: %s\n",
         al_get_shader_log(shader));
   }

   if (!al_build_shader(shader)) {
      abort_example("al_build_shader for link failed: %s\n", al_get_shader_log(shader));
   }

   al_use_shader(shader);
   al_set_shader_float_vector("diffuse_color", 4, diffuse_color, 1);
   /* alpha controls shininess, and 25 is very shiny */
   al_set_shader_float("alpha", 25);

   timer = al_create_timer(1.0 / 60);
   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());
   if (al_is_touch_input_installed()) {
      al_register_event_source(queue,
         al_get_touch_input_mouse_emulation_event_source());
   }
   al_register_event_source(queue, al_get_display_event_source(display));
   al_register_event_source(queue, al_get_timer_event_source(timer));
   al_start_timer(timer);

   while (!quit) {
      ALLEGRO_EVENT event;
      al_wait_for_event(queue, &event);

      switch (event.type) {
         case ALLEGRO_EVENT_DISPLAY_CLOSE:
            quit = true;
            break;
         case ALLEGRO_EVENT_MOUSE_AXES:
            light_position[0] = event.mouse.x;
            light_position[1] = event.mouse.y;
            break;
         case ALLEGRO_EVENT_KEY_CHAR:
            if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
               quit = true;
            break;
         case ALLEGRO_EVENT_TIMER:
            redraw = true;
            break;
      }

      if (redraw && al_is_event_queue_empty(queue)) {
         al_clear_to_color(al_map_rgb_f(0, 0, 0));

         al_set_shader_float_vector("light_position", 3, light_position, 1);
         al_draw_prim(vertices, vertex_decl, NULL, 0, NUM_VERTICES, ALLEGRO_PRIM_TRIANGLE_LIST);

         al_flip_display();
         redraw = false;
      }
   }

   al_use_shader(NULL);
   al_destroy_shader(shader);
   al_destroy_vertex_decl(vertex_decl);

   return 0;
}


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