

#include <cmath>
#include <cstdio>

#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>

#include <vector>
#include <utility>

using std::vector;
using std::pair;



vector<pair<int,int> > point_buf;

void First(uintptr_t state , int x1 , int y1 ,  ALLEGRO_VERTEX* v1 ,  ALLEGRO_VERTEX* v2);
void Step(uintptr_t state , int step);
void Draw(uintptr_t state , int x1 , int y1);

void DivideByZero(const char* expr , const char* file , int line , const char* func);

double GetAngleFromAtoB(int ax , int ay , int bx , int by);



enum STAR_ERROR {
   STAR_NEGATIVE_RADIUS = 0,
   STAR_OUT_OF_MEMORY = 1,
   STAR_NEGATIVE_THICKNESS = 2,
   STAR_LOCK_ERROR = 3
};

class StarTester {

   double cx,cy;
   double inner_radius , outer_radius;
   double thickness;

   float pts[22];
   ALLEGRO_COLOR line_color;

   ALLEGRO_BITMAP* starbuf;

public :

   StarTester() :
         cx(0.0),
         cy(0.0),
         inner_radius(0.0),
         outer_radius(0.0),
         thickness(1.0),
         pts(),
         line_color(),
         starbuf(0)
   {}


   void Free();
   void AllocateStarBuffer(int size);
   void SetupStar(double radius_inner , double radius_outer , double star_thickness , ALLEGRO_COLOR color);


   void SetupPoints();
   void RedrawStarBuffer();
   void DrawStar();
   void SetPos(double centerx , double centery);

   bool DetectMouseCollision(int mouse_start_x , int mouse_start_y , int mouse_stop_x , int mouse_stop_y);

   pair<int,int> GetStartingPoint();
   pair<int,int> GetCenterPoint();
};






int main(int argc , char** argv) {

   if (!al_init()) {return 1;}
   if (!al_install_keyboard()) {return 2;}
   if (!al_install_mouse()) {return 3;}
   if (!al_install_audio()) {return 4;}

   if (!al_init_primitives_addon()) {return 5;}
   if (!al_init_font_addon()) {return 6;}
   if (!al_init_ttf_addon()) {return 7;}

   if (!al_init_acodec_addon()) {return 8;}

   if (!al_reserve_samples(4)) {return 9;}


//   al_restore_default_mixer();

   printf("Default mixer gain = %f\n" , al_get_mixer_gain(al_get_default_mixer()));

//void al_register_assert_handler(void (*handler)(char const *expr,
//   char const *file, int line, char const *func))

   al_register_assert_handler(DivideByZero);

   al_set_new_display_flags(ALLEGRO_OPENGL | ALLEGRO_WINDOWED);

   ALLEGRO_DISPLAY* display = al_create_display(800,600);

   ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();

   ALLEGRO_TIMER* timer = al_create_timer(1.0/60.0);

   ALLEGRO_FONT* font = al_load_ttf_font("consola.ttf" , 20 , 0);

//ALLEGRO_SAMPLE *al_create_sample(void *buf, unsigned int samples,
//   unsigned int freq, ALLEGRO_AUDIO_DEPTH depth,
//   ALLEGRO_CHANNEL_CONF chan_conf, bool free_buf)

   ALLEGRO_SAMPLE* idle_sound = al_load_sample("Annoy1.ogg");

   ALLEGRO_SAMPLE_INSTANCE* sample_instance = al_create_sample_instance(idle_sound);


   if (!display || !queue || !timer || !font || !idle_sound || !sample_instance) {
      printf("Setup error.\n");
      return 1;
   }

   ALLEGRO_SAMPLE_ID sample_id;
//   al_play_sample_instance(sample_instance);
   if (!al_play_sample(idle_sound , 1.0 , 0.0 , 1.0 , ALLEGRO_PLAYMODE_ONCE , &sample_id)) {
      printf("Failed to play sample.\n");
   }

   al_register_event_source(queue , al_get_display_event_source(display));
   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));


   StarTester star;

   star.SetupStar(150.0 , 250.0 , 50.0 , al_map_rgba(0,0,255,255));
   star.SetPos(400.0 , 300.0);

   bool quit = false;
   bool redraw = true;
   bool running = false;

   bool on_star = false;

   bool mouse_idle = false;
   double mouse_idle_time = 0.0;

   double mouse_idle_limit = 3.0;

   bool sound_playing = false;


   int cmx = 0;
   int cmy = 0;
   int lmx = 0;
   int lmy = 0;

   double starting_angle_to_mouse = 0.0;
   double current_angle_to_mouse = 0.0;
   double previous_angle_to_mouse = 0.0;
   double mouse_angle_delta = 0.0;
   double total_mouse_angle = 0.0;


   al_start_timer(timer);

   al_set_target_backbuffer(display);

   al_hide_mouse_cursor(display);

   int score = 0;

   while (!quit) {

      if (redraw) {



         al_clear_to_color(al_map_rgb(0,0,0));
         star.DrawStar();

         if (running) {
            // draw mouse;
            al_draw_filled_circle(cmx , cmy , 10 , al_map_rgba(127,127,127,127));
//            al_draw_pixel(cmx , cmy , al_map_rgba(127,127,127,127));

   //void al_draw_textf(const ALLEGRO_FONT *font, ALLEGRO_COLOR color,
   //   float x, float y, int flags,
   //   const char *format, ...)
            al_draw_textf(font , al_map_rgb(255,255,255) , 10 , 10 , 0 , on_star?"On Star":"Off Star");
            al_draw_textf(font , al_map_rgb(255,255,255) , 630 , 10 , ALLEGRO_ALIGN_RIGHT , mouse_idle_time>1.0?"Mouse idle":"Mouse active");
            al_draw_textf(font , al_map_rgb(255,255,255) , 630 , 30 , ALLEGRO_ALIGN_RIGHT , sound_playing?"Sound playing":"Sound off");
            al_draw_textf(font , al_map_rgb(255,255,255) , 10 , 580 , ALLEGRO_ALIGN_LEFT , "Score = %d" , score);
         }


         al_flip_display();
         redraw = false;
      }

      do {
         ALLEGRO_EVENT ev;
         al_wait_for_event(queue , &ev);

         if (ev.type == ALLEGRO_EVENT_TIMER) {
            if (mouse_idle) {
               mouse_idle_time += al_get_timer_speed(ev.timer.source);
               if (mouse_idle_time >= mouse_idle_limit) {
//bool al_play_sample(ALLEGRO_SAMPLE *spl, float gain, float pan, float speed,
//   ALLEGRO_PLAYMODE loop, ALLEGRO_SAMPLE_ID *ret_id)
                  if (!sound_playing) {
                     if (!al_play_sample_instance(sample_instance)) {
                        printf("Failed to play sample.\n");
                     }
                     else {
                        sound_playing = true;
                     }
                  }
               }
            }
            mouse_idle = true;
            redraw = true;
            if (sound_playing) {
               if (!al_get_sample_instance_playing(sample_instance)) {
                  sound_playing = false;
               }
            }
         }

         if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
            quit = true;
         }

         if (ev.type == ALLEGRO_EVENT_KEY_DOWN) {
            if (ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
               quit = true;
            }

            if (ev.keyboard.keycode == ALLEGRO_KEY_SPACE) {
               // start over

               running = true;

               pair<int,int> start = star.GetStartingPoint();
               pair<int,int> center = star.GetCenterPoint();
               
               starting_angle_to_mouse = GetAngleFromAtoB(center.first , center.second , start.first , start.second );
               total_mouse_angle = 0.0;
               previous_angle_to_mouse = current_angle_to_mouse = starting_angle_to_mouse;
               mouse_angle_delta = 0.0;

               lmx = cmx = start.first;
               lmy = cmy = start.second;
               al_set_mouse_xy(display , cmx , cmy);

               on_star = true;

            }

         }

         if (running) {
            if (ev.type == ALLEGRO_EVENT_MOUSE_AXES) {

               mouse_idle = false;
               mouse_idle_time = 0.0;
               
               if (sound_playing) {
                  al_stop_sample_instance(sample_instance);
                  sound_playing = false;
               }

               
               int tempx = cmx;
               int tempy = cmy;

               cmx -= ev.mouse.dx;
               cmy -= ev.mouse.dy;
               
               al_set_mouse_xy(display , al_get_display_width(display)/2 , al_get_display_height(display)/2);
               
               lmx = tempx;
               lmy = tempy;

               pair<int,int> center = star.GetCenterPoint();
               
               previous_angle_to_mouse = current_angle_to_mouse;
               current_angle_to_mouse = GetAngleFromAtoB(center.first , center.second , cmx , cmy);
               mouse_angle_delta = current_angle_to_mouse - previous_angle_to_mouse;
               if (mouse_angle_delta > M_PI) {
                  mouse_angle_delta -= 2.0*M_PI;
               }
               if (mouse_angle_delta < -M_PI) {
                  mouse_angle_delta += 2.0*M_PI;
               }
               total_mouse_angle += mouse_angle_delta;

               on_star = star.DetectMouseCollision(cmx , cmy , lmx , lmy);
               
               bool reset = false;

               if (on_star && ((total_mouse_angle >= 2.0*M_PI) || (total_mouse_angle <= -2.0*M_PI))) {
                  /// Increase score
                  score += 1;
                  reset = true;
               }
               else if (!on_star) {
                  /// Play error sound here
                  
                  reset = true;
               }
               
               if (reset) {
                  /// reset mouse position
                  pair<int,int> start = star.GetStartingPoint();
                  
                  starting_angle_to_mouse = GetAngleFromAtoB(center.first , center.second , start.first , start.second );
                  total_mouse_angle = 0.0;
                  previous_angle_to_mouse = current_angle_to_mouse = starting_angle_to_mouse;
                  mouse_angle_delta = 0.0;

                  lmx = cmx = start.first;
                  lmy = cmy = start.second;
                  al_set_mouse_xy(display , cmx , cmy);
                  on_star = true;
               }
            }
         }

      } while (!al_is_event_queue_empty(queue));


   }

   al_destroy_sample_instance(sample_instance);
   al_destroy_sample(idle_sound);


   return 0;
}




void First(uintptr_t state , int x1 , int y1 ,  ALLEGRO_VERTEX* v1 ,  ALLEGRO_VERTEX* v2) {
   (void)state;
   point_buf.clear();
   (void)v1;
   (void)v2;
}



void Step(uintptr_t state , int step) {
   (void)state;
   (void)step;
}



void Draw(uintptr_t state , int x1 , int y1) {
   (void)state;
   point_buf.push_back(pair<int,int>(x1,y1));
}



void DivideByZero(const char* expr , const char* file , int line , const char* func) {
   int fail = 1/0;
   (void)fail;
}



double GetAngleFromAtoB(int ax , int ay , int bx , int by) {
   return atan2(by-ay,bx-ax);
}




void StarTester::Free() {
   if (starbuf) {
      al_destroy_bitmap(starbuf);
      starbuf = 0;
   }
}
void StarTester::AllocateStarBuffer(int size) {
   Free();
   starbuf = al_create_bitmap(size,size);
   if (!starbuf) {
      throw STAR_OUT_OF_MEMORY;
   }
}

void StarTester::SetupStar(double radius_inner , double radius_outer , double star_thickness , ALLEGRO_COLOR color) {

   line_color = color;

   if (radius_inner > radius_outer) {
         double temp = radius_inner;
         radius_inner = radius_outer;
         radius_outer = temp;
   }

   if (radius_inner < 0.0 || radius_outer <= 0.0) {
      throw STAR_NEGATIVE_RADIUS;
   }
   if (star_thickness <= 0.0) {
      throw STAR_NEGATIVE_THICKNESS;
   }
   thickness = star_thickness;


   inner_radius = radius_inner;
   outer_radius = radius_outer;

   double total_size = (int)ceil(2.0*outer_radius + 2.0*thickness);
   AllocateStarBuffer(total_size);

   SetupPoints();

   RedrawStarBuffer();
}



void StarTester::SetupPoints() {
   double angle = -M_PI/2.0;
   double angle_delta = (2.0*M_PI)/10.0;
   const double mx = outer_radius + thickness;
   const double my = mx;


   for (unsigned int i = 0 ; i < 11 ; ++i) {
      double radius = (i %2 == 0)?outer_radius:inner_radius;
      pts[2*i] = mx + radius*cos(angle);
      pts[2*i + 1] = my + radius*sin(angle);
      angle += angle_delta;
   }
}

void StarTester::RedrawStarBuffer() {
   al_set_target_bitmap(starbuf);
   al_clear_to_color(al_map_rgba(0,0,0,0));

//void al_draw_polygon(const float *vertices, int vertex_count,
//   int join_style, ALLEGRO_COLOR color, float thickness, float miter_limit)

   al_draw_polygon(pts , 11 , ALLEGRO_LINE_JOIN_ROUND , line_color , thickness , 0.0);

//   al_draw_prim(pts , NULL , NULL , 0 , 11 , ALLEGRO_PRIM_LINE_STRIP);
}

void StarTester::DrawStar() {
   double tlx = cx - (outer_radius + thickness);
   double tly = cy - (outer_radius + thickness);
   al_draw_bitmap(starbuf , tlx , tly , 0);
}

void StarTester::SetPos(double centerx , double centery) {
   cx = centerx;
   cy = centery;
}



bool StarTester::DetectMouseCollision(int mouse_start_x , int mouse_start_y , int mouse_stop_x , int mouse_stop_y) {

   int start_x = (mouse_start_x < mouse_stop_x)?mouse_start_x:mouse_stop_x;
   int stop_x = (mouse_start_x > mouse_stop_x)?mouse_start_x:mouse_stop_x;
   int start_y = (mouse_start_y < mouse_stop_y)?mouse_start_y:mouse_stop_y;
   int stop_y = (mouse_start_y > mouse_stop_y)?mouse_start_y:mouse_stop_y;

   double tlx = cx - (outer_radius + thickness);
   double tly = cy - (outer_radius + thickness);
   double mx1 = start_x - tlx;
   double my1 = start_y - tly;
   double mx2 = stop_x - tlx;
   double my2 = stop_y - tly;

   ALLEGRO_VERTEX v1,v2;
   v1.x = mx1;
   v1.y = my1;
   v2.x = mx2;
   v2.y = my2;

//void al_draw_soft_line(ALLEGRO_VERTEX* v1, ALLEGRO_VERTEX* v2, uintptr_t state,
//   void (*first)(uintptr_t, int, int, ALLEGRO_VERTEX*, ALLEGRO_VERTEX*),
//   void (*step)(uintptr_t, int),
//   void (*draw)(uintptr_t, int, int))

   al_draw_soft_line(&v1 , &v2 , 0 , First , Step , Draw);

   int w = al_get_bitmap_width(starbuf);
   int h = al_get_bitmap_height(starbuf);
   int minx = w;
   int miny = h;
   int maxx = -1;
   int maxy = -1;
   int start_index = point_buf.size();
   int finish_index = 0;

   bool start_found = false;
   bool finish_found = false;

   bool hit = false;

   for (unsigned int i = 0 ; i < point_buf.size() ; ++i) {
      int x1 = point_buf[i].first;
      int y1 = point_buf[i].second;

      if (x1 < 0 || y1 < 0) {
         continue;
      }
      else {
         if (!start_found) {
            start_index = i;
            start_found = true;
         }
      }
      if ((x1 >=  w) || (y1 >= h)) {
         continue;
      }
      else {
         if (!finish_found) {
            finish_index = i + 1;
            finish_found = true;
         }
      }

      if (x1 > maxx) {maxx = x1;}
      if (y1 > maxy) {maxy = y1;}
      if (x1 < minx) {minx = x1;}
      if (y1 < miny) {miny = y1;}
   }

//ALLEGRO_LOCKED_REGION *al_lock_bitmap_region(ALLEGRO_BITMAP *bitmap,
//int x, int y, int width, int height, int format, int flags)

   if (minx >= w) {return false;}
   if (miny >= h) {return false;}
   if (maxx < 0) {return false;}
   if (maxy < 0) {return false;}

   w = maxx - minx + 1;
   h = maxy - miny + 1;

//   printf("WxH = %i x %i\n" , w , h);

ALLEGRO_LOCKED_REGION* lock = al_lock_bitmap_region(starbuf , minx , miny , w , h , ALLEGRO_PIXEL_FORMAT_ARGB_8888 , ALLEGRO_LOCK_READONLY);

   if (!lock) {
      throw STAR_LOCK_ERROR;
      return false;
   }

   unsigned char r2 = 0;
   unsigned char g2 = 0;
   unsigned char b2 = 0;
   unsigned char a2 = 0;

   al_unmap_rgba(line_color , &r2 , &g2 , &b2 , &a2);

//   printf("Line color = %i , %i , %i , %i\n" , r2 , g2 , b2 , a2);

   for (int i = start_index ; i < finish_index ; ++i) {
      int x = point_buf[i].first - minx;
      int y = point_buf[i].second - miny;

      int color = ((int*)lock->data)[y*lock->pitch + x];
//      ALLEGRO_COLOR test_color = al_map_rgba((color & 0xff000000) >> 24 , (color & 0x00ff0000) >> 16 , (color & 0x0000ff00) >> 8 , (color & 0xfff));
      unsigned char a1 = (color & 0xff000000) >> 24;
      unsigned char r1 = (color & 0x00ff0000) >> 16;
      unsigned char g1 = (color & 0x0000ff00) >> 8;
      unsigned char b1 = (color & 0xfff);

//      printf("Test color = %i , %i , %i , %i\n" , r1 , g1 , b1 , a1);

      if (r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2) {
//      if (test_color == line_color) {
         hit = true;
         break;
      }

   }


   al_unlock_bitmap(starbuf);

   if (hit) {
      return true;
   }

   return false;

}



pair<int,int> StarTester::GetStartingPoint() {

   double start_angle = M_PI/2.0;

   int x = cx + inner_radius*cos(start_angle);
   int y = cy + inner_radius*sin(start_angle);

   return pair<int,int>(x,y);
}



pair<int,int> StarTester::GetCenterPoint() {
   return pair<int,int>(cx,cy);
}





