/*
 *    Example program for the Allegro library, by Beoran.
 *
 *    Exhaustive haptics example.
 */

#include <string>

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

#include "nihgui.hpp"

#include "common.c"

struct Haptic
{
   ALLEGRO_HAPTIC *haptic;
   ALLEGRO_HAPTIC_EFFECT effect;
   ALLEGRO_HAPTIC_EFFECT_ID id;
   const char *name;
   bool playing;
};

#define EX_MAX_HAPTICS 8

static Haptic haptics[EX_MAX_HAPTICS];
static int num_haptics = 0;
static ALLEGRO_EVENT_QUEUE * joystick_queue;
static ALLEGRO_TIMER * update_timer = NULL;

static void release_all_haptics() {
   for (int i = 0; i < num_haptics; i++) {    
      al_release_haptic(haptics[i].haptic);
      haptics[i].haptic = NULL;
   }  
}

static void get_all_haptics() {
   num_haptics = 0;
   ALLEGRO_DISPLAY * display = al_get_current_display();

   if (al_is_display_haptic(display)) {
      haptics[num_haptics].haptic = al_get_haptic_from_display(display);
      if (haptics[num_haptics].haptic) {
         haptics[num_haptics].name = (const char *)"display";
         haptics[num_haptics].playing = false;
         num_haptics++;
      }
   }

   for (int i = 0;
      num_haptics < EX_MAX_HAPTICS && i < al_get_num_joysticks();
      i++)
   {
      ALLEGRO_JOYSTICK *joy = al_get_joystick(i);
      if (al_is_joystick_haptic(joy)) {
         haptics[num_haptics].haptic = al_get_haptic_from_joystick(joy);
         if (haptics[num_haptics].haptic) {
            const char *name = al_get_joystick_name(joy);
            haptics[num_haptics].name = (const char *)name;
            haptics[num_haptics].playing = false;
            num_haptics++;
         }
      }
   }

}

struct CapacityName
{
   int value;
   const char *name;
};

static const CapacityName capname[] =
{
   {ALLEGRO_HAPTIC_RUMBLE, "ALLEGRO_HAPTIC_RUMBLE"},
   {ALLEGRO_HAPTIC_PERIODIC, "ALLEGRO_HAPTIC_PERIODIC"},
   {ALLEGRO_HAPTIC_CONSTANT, "ALLEGRO_HAPTIC_CONSTANT"},
   {ALLEGRO_HAPTIC_SPRING, "ALLEGRO_HAPTIC_SPRING"},
   {ALLEGRO_HAPTIC_FRICTION, "ALLEGRO_HAPTIC_FRICTION"},
   {ALLEGRO_HAPTIC_DAMPER, "ALLEGRO_HAPTIC_DAMPER"},
   {ALLEGRO_HAPTIC_INERTIA, "ALLEGRO_HAPTIC_INERTIA"},
   {ALLEGRO_HAPTIC_RAMP, "ALLEGRO_HAPTIC_RAMP"},
   {ALLEGRO_HAPTIC_SQUARE, "ALLEGRO_HAPTIC_SQUARE"},
   {ALLEGRO_HAPTIC_TRIANGLE, "ALLEGRO_HAPTIC_TRIANGLE"},
   {ALLEGRO_HAPTIC_SINE, "ALLEGRO_HAPTIC_SINE"},
   {ALLEGRO_HAPTIC_SAW_UP, "ALLEGRO_HAPTIC_SAW_UP"},
   {ALLEGRO_HAPTIC_SAW_DOWN, "ALLEGRO_HAPTIC_SAW_DOWN"},
   {ALLEGRO_HAPTIC_CUSTOM, "ALLEGRO_HAPTIC_CUSTOM"},
   {ALLEGRO_HAPTIC_GAIN, "ALLEGRO_HAPTIC_GAIN"},
   {ALLEGRO_HAPTIC_ANGLE, "ALLEGRO_HAPTIC_ANGLE"},
   {ALLEGRO_HAPTIC_RADIUS, "ALLEGRO_HAPTIC_RADIUS"},
   {ALLEGRO_HAPTIC_AZIMUTH, "ALLEGRO_HAPTIC_AZIMUTH"}
};

#define EX_START_TYPES   0
#define EX_MAX_TYPES     8
#define EX_END_TYPES     8

#define EX_START_WAVES   8
#define EX_END_WAVES    13
#define EX_MAX_WAVES     5
/* Ignore custom waveforms for now... */

#define EX_START_COORDS 15
#define EX_MAX_COORDS    3
#define EX_END_COORDS   18

class CanStopAndPlay
{
 public:
   virtual void on_play() = 0;
   virtual void on_stop() = 0;
};

class PlayButton: public Button
{
 protected:
   CanStopAndPlay *stop_and_play;
 public:
   PlayButton(CanStopAndPlay *snp) :
      Button("Play"),
      stop_and_play(snp)
   {
   }
   void on_click(int mx, int my);
};

void PlayButton::on_click(int, int)
{
   if (is_disabled())
      return;
   log_printf("Start playing...\n");
   if (stop_and_play) {
      stop_and_play->on_play();
   }
}

class StopButton: public Button
{
 protected:
   CanStopAndPlay *stop_and_play;
 public:
   StopButton(CanStopAndPlay *snp) :
      Button("Stop"),
      stop_and_play(snp)
   {
   }
   void on_click(int mx, int my);
};

void StopButton::on_click(int, int)
{
   if (is_disabled())
      return;
   log_printf("Stop playing...\n");
   if (stop_and_play) {
      stop_and_play->on_stop();
   }
}

class Prog: public CanStopAndPlay
{
 private:
   Dialog d;

   List device_list;
   List type_list;
   List waveform_list;

   Label device_label;
   Label type_label;
   Label waveform_label;

   HSlider length_slider;
   HSlider delay_slider;
   HSlider loops_slider;
   Label replay_label;
   Label length_label;
   Label delay_label;
   Label loops_label;

   HSlider attack_length_slider;
   HSlider attack_level_slider;
   HSlider fade_length_slider;
   HSlider fade_level_slider;
   Label envelope_label;
   Label attack_length_label;
   Label attack_level_label;
   Label fade_length_label;
   Label fade_level_label;

   HSlider angle_slider;
   HSlider radius_slider;
   HSlider azimuth_slider;
   Label coordinates_label;
   Label angle_label;
   Label radius_label;
   Label azimuth_label;

   HSlider level_slider;
   Label constant_effect_label;
   Label level_label;

   HSlider start_level_slider;
   HSlider end_level_slider;
   Label ramp_effect_label;
   Label start_level_label;
   Label end_level_label;

   HSlider right_saturation_slider;
   HSlider right_coeff_slider;
   HSlider left_saturation_slider;
   HSlider left_coeff_slider;
   HSlider deadband_slider;
   HSlider center_slider;

   Label right_saturation_label;
   Label right_coeff_label;
   Label left_saturation_label;
   Label left_coeff_label;
   Label condition_effect_label;
   Label deadband_label;
   Label center_label;

   HSlider period_slider;
   HSlider magnitude_slider;
   HSlider offset_slider;
   HSlider phase_slider;
   Label periodic_effect_label;
   Label period_label;
   Label magnitude_label;
   Label offset_label;
   Label phase_label;

   HSlider strong_magnitude_slider;
   HSlider weak_magnitude_slider;
   Label rumble_effect_label;
   Label strong_magnitude_label;
   Label weak_magnitude_label;

   HSlider gain_slider;
   Label gain_label;
   
   HSlider autocenter_slider;
   Label autocenter_label;

   Label message_label;
   Label message_label_label;

   PlayButton play_button;
   StopButton stop_button;

   Haptic *last_haptic;
   Haptic *show_haptic;

 public:
   Prog(const Theme & theme, ALLEGRO_DISPLAY *display);
   void run();
   void update();
   void update_controls(Haptic *dev);
   virtual void on_play();
   virtual void on_stop();
   void get_envelope(ALLEGRO_HAPTIC_ENVELOPE *envelope);
};

Prog::Prog(const Theme & theme, ALLEGRO_DISPLAY *display) :
   d(Dialog(theme, display, 20, 40)),
   device_list(0),
   type_list(0),
   waveform_list(0),
   device_label("Haptic Device"),
   type_label("Haptic Effect Type"),
   waveform_label("Wave Form Periodic Effect"),
   length_slider(1, 10),
   delay_slider(1, 10),
   loops_slider(1, 10),
   replay_label("Replay"),
   length_label("Length", false),
   delay_label("Delay", false),
   loops_label("Loops"),
   attack_length_slider(2, 10),
   attack_level_slider(4, 10),
   fade_length_slider(2, 10),
   fade_level_slider(4, 10),
   envelope_label("Envelope"),
   attack_length_label("Attack Length", false),
   attack_level_label("Attack Level", false),
   fade_length_label("Fade Length", false),
   fade_level_label("Fade Level", false),
   angle_slider(0, 360),
   radius_slider(0, 10),
   azimuth_slider(180, 360),
   coordinates_label("Coordinates"),
   angle_label("Angle", false),
   radius_label("Radius", false),
   azimuth_label("Azimuth", false),
   level_slider(5, 10),
   constant_effect_label("Constant Effect"),
   level_label("Level", false),
   start_level_slider(3, 10),
   end_level_slider(7, 10),
   ramp_effect_label("Ramp Effect"),
   start_level_label("Start Level", false),
   end_level_label("End Level", false),
   right_saturation_slider(5, 10),
   right_coeff_slider(5, 10),
   left_saturation_slider(5, 10),
   left_coeff_slider(5, 10),
   deadband_slider(1, 10),
   center_slider(5, 10),
   right_saturation_label("Right Saturation", false),
   right_coeff_label("Right Coefficient", false),
   left_saturation_label("Left Saturation", false),
   left_coeff_label("Left Coefficient", false),
   condition_effect_label("Condition Effect"),
   deadband_label("Deadband", false),
   center_label("Center", false),
   period_slider(1, 10),
   magnitude_slider(5, 10),
   offset_slider(0, 10),
   phase_slider(0, 10),
   periodic_effect_label("Periodic Effect"),
   period_label("Period", false),
   magnitude_label("Magnitude", false),
   offset_label("Offset", false),
   phase_label("Phase", false),
   strong_magnitude_slider(5, 10),
   weak_magnitude_slider(5, 10),
   rumble_effect_label("Rumble effect"),
   strong_magnitude_label("Strong Magnitude", false),
   weak_magnitude_label("Weak Magnitude", false),
   gain_slider(10, 10),
   gain_label("Gain"),
   autocenter_slider(0, 10),
   autocenter_label("Autocenter"),
   message_label("Ready.", false),
   message_label_label("Status", false),
   play_button(this),
   stop_button(this),
   last_haptic(NULL),
   show_haptic(NULL)
{
   for (int i = 0; i < num_haptics; i++) {
      device_list.append_item(haptics[i].name);
   }
   d.add(device_label, 0, 1, 7, 1);
   d.add(device_list, 0, 2, 7, 8);

   for (int i = EX_START_TYPES; i < EX_END_TYPES; i++) {
      type_list.append_item(capname[i].name);
   }
   d.add(type_label, 7, 1, 6, 1);
   d.add(type_list, 7, 2, 6, 8);

   for (int i = EX_START_WAVES; i < EX_END_WAVES; i++) {
      waveform_list.append_item(capname[i].name);
   }
   d.add(waveform_label, 13, 1, 7, 1);
   d.add(waveform_list, 13, 2, 7, 8);

   d.add(replay_label, 0, 11, 7, 1);
   d.add(length_label, 0, 12, 2, 1);
   d.add(length_slider, 2, 12, 5, 1);
   d.add(delay_label, 0, 13, 2, 1);
   d.add(delay_slider, 2, 13, 5, 1);

   d.add(loops_label, 7, 11, 7, 1);
   d.add(loops_slider, 7, 12, 6, 1);
   d.add(gain_label, 13, 11, 7, 1);
   d.add(gain_slider, 13, 12, 7, 1);
   
   d.add(autocenter_label, 13, 13, 7, 1);
   d.add(autocenter_slider, 13, 14, 7, 1);


   d.add(envelope_label, 0, 15, 9, 1);
   d.add(attack_length_label, 0, 16, 3, 1);
   d.add(attack_length_slider, 4, 16, 6, 1);
   d.add(attack_level_label, 0, 17, 3, 1);
   d.add(attack_level_slider, 4, 17, 6, 1);
   d.add(fade_length_label, 0, 18, 3, 1);
   d.add(fade_length_slider, 4, 18, 6, 1);
   d.add(fade_level_label, 0, 19, 3, 1);
   d.add(fade_level_slider, 4, 19, 6, 1);

   d.add(coordinates_label, 11, 15, 9, 1);
   d.add(angle_label, 11, 16, 2, 1);
   d.add(angle_slider, 13, 16, 7, 1);
   d.add(radius_label, 11, 17, 2, 1);
   d.add(radius_slider, 13, 17, 7, 1);
   d.add(azimuth_label, 11, 18, 2, 1);
   d.add(azimuth_slider, 13, 18, 7, 1);

   d.add(condition_effect_label, 0, 21, 9, 1);
   d.add(right_coeff_label, 0, 22, 4, 1);
   d.add(right_coeff_slider, 4, 22, 6, 1);
   d.add(right_saturation_label, 0, 23, 4, 1);
   d.add(right_saturation_slider, 4, 23, 6, 1);
   d.add(left_coeff_label, 0, 24, 4, 1);
   d.add(left_coeff_slider, 4, 24, 6, 1);
   d.add(left_saturation_label, 0, 25, 4, 1);
   d.add(left_saturation_slider, 4, 25, 6, 1);
   d.add(deadband_label, 0, 26, 4, 1);
   d.add(deadband_slider, 4, 26, 6, 1);
   d.add(center_label, 0, 27, 4, 1);
   d.add(center_slider, 4, 27, 6, 1);

   d.add(periodic_effect_label, 11, 21, 9, 1);
   d.add(period_label, 11, 22, 2, 1);
   d.add(period_slider, 13, 22, 7, 1);
   d.add(magnitude_label, 11, 23, 2, 1);
   d.add(magnitude_slider, 13, 23, 7, 1);
   d.add(offset_label, 11, 24, 2, 1);
   d.add(offset_slider, 13, 24, 7, 1);
   d.add(phase_label, 11, 25, 2, 1);
   d.add(phase_slider, 13, 25, 7, 1);

   d.add(ramp_effect_label, 11, 29, 9, 1);
   d.add(start_level_label, 11, 30, 2, 1);
   d.add(start_level_slider, 13, 30, 7, 1);
   d.add(end_level_label, 11, 31, 2, 1);
   d.add(end_level_slider, 13, 31, 7, 1);

   d.add(rumble_effect_label, 0, 29, 9, 1);
   d.add(strong_magnitude_label, 0, 30, 4, 1);
   d.add(strong_magnitude_slider, 4, 30, 6, 1);
   d.add(weak_magnitude_label, 0, 31, 4, 1);
   d.add(weak_magnitude_slider, 4, 31, 6, 1);

   d.add(constant_effect_label, 0, 33, 9, 1);
   d.add(level_label, 0, 34, 3, 1);
   d.add(level_slider, 4, 34, 6, 1);

   d.add(message_label_label, 0, 36, 2, 1);
   d.add(message_label, 2, 36, 12, 1);

   d.add(play_button, 6, 38, 3, 2);
   d.add(stop_button, 12, 38, 3, 2);
   
   d.register_event_source(al_get_timer_event_source(update_timer));

}

void Prog::update()
{
   /* Update playing state and display. */
   if (last_haptic && last_haptic->playing) {
      if (!al_is_haptic_effect_playing(&last_haptic->id)) {
         last_haptic->playing = false;
         al_release_haptic_effect(&last_haptic->id);
         message_label.set_text("Done.");
         play_button.set_disabled(false);
         d.request_draw();
         log_printf("Play done on %s\n", last_haptic->name);
      }
   }
   
   ALLEGRO_EVENT e;
   
   /* Check for hot plugging*/
   while(al_get_next_event(joystick_queue, &e)) {
     /* clear, reconfigure and fetch haptics again. */
     if (e.type == ALLEGRO_EVENT_JOYSTICK_CONFIGURATION) {
       al_reconfigure_joysticks(); 
       release_all_haptics();
       get_all_haptics();
       device_list.clear_items();
       for (int i = 0; i < num_haptics; i++) {
          device_list.append_item(haptics[i].name);
       }
       log_printf("Hot plugging detected...\n");  
       message_label.set_text("Hot Plugging...");
       play_button.set_disabled(false);
       d.request_draw();
     }
   }

   /* Update availability of controls based on capabilities. */
   int devno = device_list.get_cur_value();
   Haptic *dev = haptics + devno;
   if (dev && dev->haptic) {
      if (dev != show_haptic) {
         play_button.set_disabled(false);
         update_controls(dev);
         show_haptic = dev;
         message_label.set_text("Haptic Device Found.");
         d.request_draw();
      }     
   } else {
      play_button.set_disabled(true);
      message_label.set_text("No Haptic Device.");
      d.request_draw();
   }
}

void Prog::run()
{
   d.prepare();

   while (!d.is_quit_requested()) {
      update();
      if (d.is_draw_requested()) {
         al_clear_to_color(al_map_rgb(128, 148, 168));
         d.draw();
         al_flip_display();
      }

      d.run_step(true);
   }

   /* Stop playing anything we were still playing. */
   on_stop();
}

#define TEST_CAP(CAP, FLAG) (((CAP) & (FLAG)) == (FLAG))

void Prog::update_controls(Haptic *dev)
{
   /* Take a deep breath, here we go... */
   bool condition, envelope, periodic;      
   int cap = 0;
   if (dev) { 
     cap = al_get_haptic_capabilities(dev->haptic);
   }

   /* Gain capability */
   gain_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_GAIN));
   gain_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_GAIN));
   
   /* Autocenter capability */
   autocenter_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_AUTOCENTER));
   autocenter_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_AUTOCENTER));
   

   /* Envelope related capabilities and sliders. */
   envelope = TEST_CAP(cap, ALLEGRO_HAPTIC_PERIODIC) ||
      TEST_CAP(cap, ALLEGRO_HAPTIC_CONSTANT) ||
      TEST_CAP(cap, ALLEGRO_HAPTIC_RAMP);

   envelope_label.set_disabled(!envelope);
   attack_level_slider.set_disabled(!envelope);
   attack_length_slider.set_disabled(!envelope);
   fade_level_slider.set_disabled(!envelope);
   fade_length_slider.set_disabled(!envelope);
   attack_level_label.set_disabled(!envelope);
   attack_length_label.set_disabled(!envelope);
   fade_level_label.set_disabled(!envelope);
   fade_length_label.set_disabled(!envelope);

   /* Coordinate related capabilities. */
   angle_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_ANGLE));
   angle_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_ANGLE));
   radius_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_RADIUS));
   radius_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_RADIUS));
   azimuth_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_AZIMUTH));
   azimuth_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_AZIMUTH));

   /* Condition effect related capabilities. */
   condition = TEST_CAP(cap, ALLEGRO_HAPTIC_DAMPER) ||
      TEST_CAP(cap, ALLEGRO_HAPTIC_FRICTION) ||
      TEST_CAP(cap, ALLEGRO_HAPTIC_INERTIA) ||
      TEST_CAP(cap, ALLEGRO_HAPTIC_SPRING);

   condition_effect_label.set_disabled(!condition);
   right_coeff_slider.set_disabled(!condition);
   left_coeff_slider.set_disabled(!condition);
   right_saturation_slider.set_disabled(!condition);
   left_saturation_slider.set_disabled(!condition);
   center_slider.set_disabled(!condition);
   deadband_slider.set_disabled(!condition);
   right_coeff_label.set_disabled(!condition);
   left_coeff_label.set_disabled(!condition);
   right_saturation_label.set_disabled(!condition);
   left_saturation_label.set_disabled(!condition);
   center_label.set_disabled(!condition);
   deadband_label.set_disabled(!condition);

   /* Constant effect related capabilities. */
   constant_effect_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_CONSTANT));
   level_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_CONSTANT));
   level_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_CONSTANT));

   /* Ramp effect capabilities. */
   ramp_effect_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_RAMP));
   start_level_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_RAMP));
   start_level_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_RAMP));
   end_level_slider.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_RAMP));
   end_level_label.set_disabled(!TEST_CAP(cap, ALLEGRO_HAPTIC_RAMP));

   /* Period effect capabilities. */
   periodic = TEST_CAP(cap, ALLEGRO_HAPTIC_PERIODIC);
   waveform_label.set_disabled(!periodic);
   waveform_list.set_disabled(!periodic);
   periodic_effect_label.set_disabled(!periodic);
   period_slider.set_disabled(!periodic);
   magnitude_slider.set_disabled(!periodic);
   offset_slider.set_disabled(!periodic);
   phase_slider.set_disabled(!periodic);
   period_label.set_disabled(!periodic);
   magnitude_label.set_disabled(!periodic);
   offset_label.set_disabled(!periodic);
   phase_label.set_disabled(!periodic);

   /* Change list of supported effect types. */
   type_list.clear_items();
   for (int i = EX_START_TYPES; i < EX_END_TYPES; i++) {
      const CapacityName *cn = &capname[i];
      if (TEST_CAP(cap, cn->value)) {
         type_list.append_item(cn->name);
      }
   }

   /* Change list of supported wave form types. */
   waveform_list.clear_items();
   for (int i = EX_START_WAVES; i < EX_END_WAVES; i++) {
      const CapacityName *cn = &capname[i];
      if (TEST_CAP(cap, cn->value)) {
         waveform_list.append_item(cn->name);
      }
   }
}

static int name_to_cap(const std::string & name)
{
   for (int i = 0; i < EX_END_WAVES; i++) {
      if (name == capname[i].name) {
         return capname[i].value;
      }
   }
   return -1;
}

static const char *cap_to_name(int cap)
{
   for (int i = 0; i < EX_END_WAVES; i++) {
      if (cap == capname[i].value) {
         return capname[i].name;
      }
   }
   return "unknown";
}

static double slider_to_magnitude(const HSlider & slider)
{
   double value = (double)slider.get_cur_value();
   double max = (double)slider.get_max_value();
   return value / max;
}

static double slider_to_duration(const HSlider & slider)
{
   double value = (double)slider.get_cur_value();
   double max = 1.0;
   return value / max;
}

static double slider_to_angle(const HSlider & slider)
{
   double value = (double)slider.get_cur_value();
   double max = (double)slider.get_max_value();
   return value / max;
}

void Prog::get_envelope(ALLEGRO_HAPTIC_ENVELOPE *envelope)
{
   if (!envelope)
      return;
   envelope->attack_length = slider_to_duration(attack_length_slider);
   envelope->fade_length = slider_to_duration(fade_length_slider);
   envelope->attack_level = slider_to_magnitude(attack_level_slider);
   envelope->fade_level = slider_to_magnitude(fade_level_slider);
}

void Prog::on_play()
{
   int devno = device_list.get_cur_value();
   if (devno < 0 || devno >= num_haptics) {
      message_label.set_text("No Haptic Device!");
      log_printf("No such device: %d\n", devno);
      return;
   }

   Haptic *haptic = haptics + devno;
   if (!haptic || !haptic->haptic) {
      log_printf("Device is NULL: %d\n", devno);
      message_label.set_text("Device Is NULL!");
      return;
   }

   if (!al_is_haptic_active(haptic->haptic)) {
      message_label.set_text("Device Not Active!");
      log_printf("Device is not active: %d\n", devno);
      return;
   }

   /* Stop playing previous effect. */
   if (haptic->playing) {
      al_stop_haptic_effect(&haptic->id);
      haptic->playing = false;
      al_release_haptic_effect(&haptic->id);
   }

   /* First set gain. */
   double gain = slider_to_magnitude(gain_slider);
   al_set_haptic_gain(haptic->haptic, gain);
   
   /* Set autocentering. */
   double autocenter = slider_to_magnitude(autocenter_slider);
   al_set_haptic_autocenter(haptic->haptic, autocenter);


   /* Now fill in the effect struct. */
   int type = name_to_cap(type_list.get_selected_item_text());
   int wavetype = name_to_cap(waveform_list.get_selected_item_text());

   if (type < 0) {
      message_label.set_text("Unknown Effect Type!");
      log_printf("Unknown effect type: %d on %s\n", type, haptic->name);
      return;
   }

   if ((wavetype < 0) && (type == ALLEGRO_HAPTIC_PERIODIC)) {
      message_label.set_text("Unknown Wave Form!");
      log_printf("Unknown wave type: %d on %s\n", wavetype, haptic->name);
      return;
   }

   haptic->effect.type = type;
   haptic->effect.replay.delay = slider_to_duration(delay_slider);
   haptic->effect.replay.length = slider_to_duration(length_slider);
   int loops = loops_slider.get_cur_value();
   haptic->effect.direction.angle = slider_to_angle(angle_slider);
   haptic->effect.direction.radius = slider_to_magnitude(angle_slider);
   haptic->effect.direction.azimuth = slider_to_angle(angle_slider);

   switch (type) {
      case ALLEGRO_HAPTIC_RUMBLE:
         haptic->effect.data.rumble.strong_magnitude = slider_to_magnitude(strong_magnitude_slider);
         haptic->effect.data.rumble.weak_magnitude = slider_to_magnitude(weak_magnitude_slider);
         break;
      case ALLEGRO_HAPTIC_PERIODIC:
         get_envelope(&haptic->effect.data.periodic.envelope);
         haptic->effect.data.periodic.waveform = wavetype;
         haptic->effect.data.periodic.magnitude = slider_to_magnitude(magnitude_slider);
         haptic->effect.data.periodic.period = slider_to_duration(period_slider);
         haptic->effect.data.periodic.offset = slider_to_duration(offset_slider);
         haptic->effect.data.periodic.phase = slider_to_duration(phase_slider);
         haptic->effect.data.periodic.custom_len = 0;
         haptic->effect.data.periodic.custom_data = NULL;
         break;
      case ALLEGRO_HAPTIC_CONSTANT:
         get_envelope(&haptic->effect.data.constant.envelope);
         haptic->effect.data.constant.level = slider_to_magnitude(level_slider);
         break;
      case ALLEGRO_HAPTIC_RAMP:
         get_envelope(&haptic->effect.data.ramp.envelope);
         haptic->effect.data.ramp.start_level = slider_to_magnitude(start_level_slider);
         haptic->effect.data.ramp.end_level = slider_to_magnitude(end_level_slider);
         break;

      case ALLEGRO_HAPTIC_SPRING:
      case ALLEGRO_HAPTIC_FRICTION:
      case ALLEGRO_HAPTIC_DAMPER:
      case ALLEGRO_HAPTIC_INERTIA:  /* fall through. */
         haptic->effect.data.condition.right_saturation = slider_to_magnitude(right_saturation_slider);
         haptic->effect.data.condition.left_saturation = slider_to_magnitude(left_saturation_slider);
         haptic->effect.data.condition.right_coeff = slider_to_magnitude(right_coeff_slider);
         haptic->effect.data.condition.left_coeff = slider_to_magnitude(left_coeff_slider);
         haptic->effect.data.condition.deadband = slider_to_magnitude(deadband_slider);
         haptic->effect.data.condition.center = slider_to_magnitude(center_slider);
         /* XXX need a different conversion function here, but I don't have a
          * controller that supports condition effects anyway... :p
          */
         break;
      default:
         message_label.set_text("Unknown Effect Type!");
         log_printf("Unknown effect type %d %d\n", devno, type);
         return;
   }

   if (!al_is_haptic_effect_ok(haptic->haptic, &haptic->effect)) {
      message_label.set_text("Effect Not Supported!");
      log_printf("Playing of effect type %s on %s not supported\n",
         cap_to_name(type), haptic->name);
      return;
   }

   haptic->playing = al_upload_and_play_haptic_effect(haptic->haptic,
      &haptic->effect, &haptic->id, loops);
   if (haptic->playing) {
      message_label.set_text("Playing...");
      log_printf("Started playing %d loops of effect type %s on %s\n", 
                  loops, cap_to_name(type),   haptic->name);
      last_haptic = haptic;
   }
   else {
      message_label.set_text("Playing of effect failed!");
      log_printf("Playing of effect type %s on %s failed\n", cap_to_name(type),
         haptic->name);
   }

   play_button.set_disabled(true);
}

void Prog::on_stop()
{
   int devno = device_list.get_cur_value();
   if (devno < 0 || devno >= num_haptics) {
      log_printf("No such device %d\n", devno);
      return;
   }

   Haptic *haptic = haptics + devno;
   if (haptic->playing && al_is_haptic_effect_playing(&haptic->id)) {
      al_stop_haptic_effect(&haptic->id);
      haptic->playing = false;
      al_release_haptic_effect(&haptic->id);
      log_printf("Stopped device %d: %s\n", devno, haptic->name);
   }
   message_label.set_text("Stopped.");
   play_button.set_disabled(false);
}

int main(int argc, char *argv[])
{
   ALLEGRO_DISPLAY *display;
   ALLEGRO_FONT *font;

   (void)argc;
   (void)argv;

   if (!al_init()) {
      abort_example("Could not init Allegro\n");
   }
   al_init_primitives_addon();
   al_install_keyboard();
   al_install_mouse();
   al_install_joystick();
   if (!al_install_haptic()) {
      abort_example("Could not init haptics\n");
   }

   al_init_font_addon();
   al_init_ttf_addon();

   al_set_new_display_flags(ALLEGRO_GENERATE_EXPOSE_EVENTS);
   display = al_create_display(800, 600);
   if (!display) {
      abort_example("Unable to create display\n");
   }

   open_log();

   font = al_load_font("data/DejaVuSans.ttf", 11, 0);
   if (!font) {
      log_printf("Failed to load data/DejaVuSans.ttf\n");
      font = al_create_builtin_font();
      if (!font) {
         abort_example("Could not create builtin font.\n");
      }
   }
   
  
   joystick_queue = al_create_event_queue();
   if (!joystick_queue) {
      abort_example("Could not create joystick event queue font.\n");
   }
   
   al_register_event_source(joystick_queue, al_get_joystick_event_source());
  
   
   get_all_haptics();

   /* Don't remove these braces. */
   {
      /* Set up a timer so the display of playback state is polled regularly. */
      update_timer = al_create_timer(1.0);
      Theme theme(font);
      Prog prog(theme, display);
      al_start_timer(update_timer);
      prog.run();
      al_destroy_timer(update_timer);
   }
   
   release_all_haptics();
   al_destroy_event_queue(joystick_queue); 

   close_log(false);
   
   al_destroy_font(font);

   return 0;
}

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