var ProWaveSpinner = Class.create();

ProWaveSpinner.prototype = {
  /**
   * @constructor  Create the spinner using an input, an up button, and a down button
   *
   * @param string/Element  inputElement
   * @param string/Element  upElement
   * @param string/Element  downElement
   * @param object          options
   * Available Options:
   *   interval     The amount to increment (default=1)
   *   round        The number of decimal points to which to round (default=0)
   *   min          The lowest allowed value, false for no min (default=false)
   *   max          The highest allowed value, false for no max (default=false)
   *   prefix       String to prepend when updating (default='')
   *   suffix       String to append when updating (default='')
   *   data         An array giving a list of items through which to iterate (default=false)
   *   onIncrement  Function to call after incrementing
   *   onDecrement  Function to call after decrementing
   *   afterUpdate  Function to call after update of the value
   *   onStop       Function to call on click or mouseup
   * @return void
   */
  initialize: function(inputElement, upElement, downElement, options) {
    // store the elements
    this.inputElement = $(inputElement);
    this.upElement = $(upElement);
    this.downElement = $(downElement);
    // store the options
    this.options = Object.extend({
      interval: 1,
      round: 0,
      min: false,
      max: false,
      prefix: '',
      suffix: '',
      data: false,
      onIncrement: false,      
      onDecrement: false,      
      afterUpdate: false,      
      onStop: false,
      groupingUsed: false,
      disabled:false      
    }, options);
    // set initial values
    this.reset();
    // build our update function
    this.buildUpdateFunction();
    // define the rate of increasing speed
    if (Prototype.Browser.IE) {
      this.speedHash = {5: 300, 10: 175, 20: 90, 30: 17};
    } else {
      this.speedHash = {5: 250, 10: 85, 20: 35, 30: 10};
    }
    //no line break
    try{
	    this.tableElement = $(inputElement+":table");
	    if(Prototype.Browser.IE ){//ie
	    	this.tableElement.setStyle({display: "inline"});
	    }else{
	    	this.tableElement.setStyle({display: "inline-table"});
	    }
	}catch(e){}
	// attach listeners
    this.setValue(this.inputElement.value);
    if(!this.options.disabled)
   		this.observe();
  },
  /**
   * Helper function to define the update function
   *
   * @return void
   */
  buildUpdateFunction: function() {
    // do we have a data list?
    if (this.options.data == false) {
      // no, we are an integer or decimal
      this.updateValue = function(multiplier) {
        // parse the value ignoring the substring
        var value;
       	value = this.inputElement.value.replace(/,/g, "").replace(/^(.*?)([\-\d\.]+)(.*)$/, '$2');
       	value = parseFloat(value);
       
        if (isNaN(value)) value = this.options.min || 0;
        // what are we adding
        var T=Number('1e'+12);
        if (multiplier == 1) {
          value = Math.round((value + this.options.interval) * T)/T;
        } else if (multiplier == -1) {
          value = Math.round((value - this.options.interval) * T)/T;
        }
        if(this.options.round)
        	value = value.toFixed(this.options.round);
        // ensure value falls between the min and max
        if (this.options.min !== false)
          value = Math.max(this.options.min, value);
        if (this.options.max !== false)
          value = Math.min(this.options.max, value);            
        this.setValue(value);
        // call our afterUpdate function
        if(this.options.afterUpdate)
        	eval(this.options.afterUpdate);
      }.bind(this);
      // set an initial value if not given
      if (this.inputElement.value === '') {
        this.inputElement.value = this.options.min || 0;
      }
    } else if (this.options.data.constructor == Array && this.options.data.length) {
      // 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
      this.updateValue = function(multiplier) {
        // advance the pointer forward or backward, wrapping between the last and first item
        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
        if(this.options.afterUpdate)
        	eval(this.options.afterUpdate);
      }.bind(this);
      // set an initial value if not given
      if (this.inputElement.value === '') {
        this.inputElement.value = this.options.data[0];
      }
    } else {
      // we have an invalid data option
      throw new Error('SpinnerControl.initialize(): invlalid value for options.data');
    }  
  },
  setValue: function(value) {
  	if(this.options.groupingUsed)
  		value=this.getGroupingFormat(value);
    this.inputElement.value = this.options.prefix + value + this.options.suffix;
  },
  /**
   * Helper function to attach listeners
   */
  observe: function() {
    // define a pre-bound stop function
    var stop = this.stop.bind(this);
    // observe the input
    this.inputElement
      // begin incrementing at start of a keypress
      .observe('keydown', this.keyStart.bindAsEventListener(this))
      // stop incrementing at the end of a keypress
      .observe('keyup', stop)
      // reformat and enforce min-max for typed values
      .observe('blur', this.updateValue.bind(this, 0));
    // observe the up element
    this.upElement
      // begin incrementing at start of click
      .observe('mousedown', this.clickStart.bind(this, 1))
      // stop incrementing at end of click
      .observe('mouseup', stop)
      // in the case of a click and drag, also stop
      .observe('mouseout', stop);
    // observe the down element
    this.downElement
      // begin decrementing at start of click
      .observe('mousedown', this.clickStart.bind(this, -1))
      // stop decrementing at end of click
      .observe('mouseup', stop)
      // in the case of a click and drag, also stop
      .observe('mouseout', stop);
  },
  /**
   * Start incrementing or decrementing based on a pressed key
   *
   * @event keydown on this.inputElement
   * @param object evt
   * @return void
   */
  keyStart: function(evt) {
    if (this.running == false) {
      if (evt.keyCode == Event.KEY_UP) {
        this.running = 'key';
        this.increment();
      } else if (evt.keyCode == Event.KEY_DOWN) {
        this.running = 'key';
        this.decrement();
      }
    }
  },
  /**
   * Start incrementing or decrementing based on a mousedown action
   *
   * @param boolean multiplier  If multipler is 1, increment
   * @return void
   */  
  clickStart: function(multiplier) {
    this.running = 'mouse';
    if (multiplier == 1) {
      this.increment();
    } else {
      this.decrement();
    }
  },
  /**
   * Set to resting state
   *
   * return @void
   */
  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;
  },
  /**
   * Reset and clear timeout
   *
   * @return void
   */
  stop: function() {
    this.reset();
    window.clearTimeout(this.timeout);
    if(this.options.onStop)
    	eval(this.options.onStop);
  },
  /**
   * Increment the value
   *
   * @return void
   */
  increment: function() {
  	this.updateValue(1);
    this.timeout = window.setTimeout(this.increment.bind(this), this.getSpeed());
    if(this.options.onIncrement)
    	eval(this.options.onIncrement);
  },
  /**
   * Decrement the value
   *
   * @return void
   */  
  decrement: function() {
    this.updateValue(-1);
    this.timeout = window.setTimeout(this.decrement.bind(this), this.getSpeed());
    if(this.options.onDecrement)
    	eval(this.options.onDecrement);
  },
  /**
   * Get the delay for the next timeout
   * Overwrite this function for custom speed schemes
   *
   * @return integer
   */  
  getSpeed: function() {
    this.iterations++;
    for (var iterations in this.speedHash) {
      if (this.iterations < iterations) {
        return this.speedHash[iterations];
      }
    }
    return this.speedHash[30];
  },
  
  getGroupingFormat: function(n) {
      var reg = /(^[+-]?\d+)(\d{3})/;
  	  n += '';                         
      while (reg.test(n))
        n = n.replace(reg, '$1' + ',' + '$2');
      return n;
	}  
};
