var SpinnerControl = new Class(
  {
    initialize: function(inputElement, upElement, downElement, options){

    // store the elements
    this.inputElement = $(inputElement);
    this.upElement = $(upElement);
    this.downElement = $(downElement);
    // store the options
    this.options ={
      interval: 1,
      round: 0,
      min: false,
      max: false,
      prefix: '',
      suffix: '',
      data: false,
      onIncrement: function() {},
      onDecrement: function() {},
      afterUpdate: function() {},
      onStop: function() {}
    };

    // define the rate of increasing speed
    this.speedHash = {5: 250, 10: 85, 20: 35, 30: 10};

    this.options.data = options.data;
    // set initial values
    this.reset();
    // build our update function
    this.buildUpdateFunction();
    // attach listeners
    this.observe();
  },

  clickStart: function(multiplier) {
    this.running = 'mouse';
    if (multiplier == 1) {
      this.increment();
    } else {
      this.decrement();
    }
  },

  keyStart: function(evt) {

    var event = new Event(evt);

    if (this.running == false) {

      if (event.key == 'up') {
        this.running = 'key';
        this.increment();

      } else if (event.key == 'down') {
        this.running = 'key';
        this.decrement();
      }
    }
  },

  buildUpdateFunction: function() {
    // we have a data list
    // set the position pointer to the current or first element
    var current = this.options.data.indexOf(this.inputElement.value);
    this.pos = current == -1 ? 0 : current;
    // define our function
    // set an initial value if not given

    this.updateValue = function(multiplier) {

      // advance the pointer forward or backward, wrapping between the last and first item

      if(multiplier == 0) {

// is the current value valid ?
        var valid = false;
        for (var i = 0; i < this.options.data.length; ++i) {

          if (this.inputElement.value == this.options.data[i]) {
            valid = true;
            break;
          }
        }
        if (!valid)
          this.inputElement.value = this.options.data[0];

      } else {

        this.pos = this.pos + multiplier;
        this.pos = this.pos < 0 ? this.options.data.length -1 : (
          this.pos > this.options.data.length - 1 ? 0 : this.pos
        );

        // update the value to the prefix, plus the rounded number, plus the suffix
        this.setValue(this.options.data[this.pos]);
        // call our afterUpdate function
      }

      this.options.afterUpdate(this);

    }.bind(this);

    if (this.inputElement.value === '') {
      this.inputElement.value = this.options.data[0];
    }

  },

  setValue: function(value) {
    this.inputElement.value = this.options.prefix + value + this.options.suffix;
  },

  observe: function() {

    this.inputElement.addEvent('keydown', this.keyStart.bindAsEventListener(this) );
    this.inputElement.addEvent('keyup', this.stop.bind(this) );
    this.inputElement.addEvent('blur', this.updateValue.bind(this, 0) );

    this.downElement.addEvent('mousedown', this.clickStart.bind(this,-1) );
    this.downElement.addEvent('mouseup', this.stop.bind(this) );
    this.downElement.addEvent('mouseout', this.stop.bind(this) );


    this.upElement.addEvent('mousedown', this.clickStart.bind(this,1) );
    this.upElement.addEvent('mouseup', this.stop.bind(this) );
    this.upElement.addEvent('mouseout', this.stop.bind(this) );
  },

  reset: function() {
    // blur the up/down buttons if we got started by clicking
    if (this.running == 'mouse') {
      this.upElement.blur();
      this.downElement.blur();
    }
    this.running = false;
    this.iterations = 0;
  },

  stop: function() {
    this.reset();
    window.clearTimeout(this.timeout);
    this.options.onStop(this);
  },

  increment: function() {
    this.updateValue(1);
    this.timeout = window.setTimeout(this.increment.bind(this), this.getSpeed());
    this.options.onIncrement(this);
  },

  decrement: function() {
    this.updateValue(-1);
    this.timeout = window.setTimeout(this.decrement.bind(this), this.getSpeed());
    this.options.onDecrement(this);
  },

  getSpeed: function() {
    this.iterations++;
    for (var iterations in this.speedHash) {
      if (this.iterations < iterations) {
        return this.speedHash[iterations];
      }
    }
    return this.speedHash[30];
  }

});

