/**
 * @class Spinner Control Widget
 *
 * @author Ken Snyder (kendsnyder at gmail dot com)
 * @date 2007-07-15
 * @version 1.0
 * @license Creative Commons Attribution 3.0 (http://creativecommons.org/licenses/by/3.0/)
 * @requires Prototype version 1.5.0 or newer
 * @tested in FF2, IE7, Safari 3, Opera 9
 */
var SpinnerControl = Class.create();

SpinnerControl.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: Prototype.emptyFunction,      
      onDecrement: Prototype.emptyFunction,      
      afterUpdate: Prototype.emptyFunction,      
      onStop: Prototype.emptyFunction      
    }, 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};
    }
    // attach listeners
    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 = parseFloat(this.inputElement.value.replace(/^(.*?)([\-\d\.]+)(.*)$/, '$2'));
        if (isNaN(value)) value = this.options.min || 0;
        // what are we adding
        if (multiplier == 1) {
          value = (value + this.options.interval).toFixed(this.options.round);
        } else if (multiplier == -1) {
          value = (value - this.options.interval).toFixed(this.options.optionsround);
        }
        // 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
        this.options.afterUpdate(this);
      }.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
        this.options.afterUpdate(this);
      }.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(): invalid value for options.data');
    }  
  },
  setValue: function(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 clickbuscarNo
      .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);
    this.options.onStop(this);
  },
  /**
   * Increment the value
   *
   * @return void
   */
  increment: function() {
    this.updateValue(1);
    this.timeout = window.setTimeout(this.increment.bind(this), this.getSpeed());
    this.options.onIncrement(this);
  },
  /**
   * Decrement the value
   *
   * @return void
   */  
  decrement: function() {
    this.updateValue(-1);
    this.timeout = window.setTimeout(this.decrement.bind(this), this.getSpeed());
    this.options.onDecrement(this);
  },
  /**
   * 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];
  } 
};

  /**
   * Extiende la clase SpinnerControl para generar los elementos de control.
   * @param string/Elemento container Objeto contenedor donde desplegar
   * @param string/Elemento inputName Nombre del input a generar
   * @param map/opciones
   *
   * Opciones disponibles:
   *   label        Etiqueta descriptiva del spinner
   *   imgUpSrc     Ruta de la imagen del botón de incremento
   *   imgDownSrc   Ruta de la imagen del botón de decremento
   *   inputId      Identificador del input a generar (p.d "id" + upper(N) + ame)
   *   inputClass   Estilo CSS del input de entrada (p.d. "embedSpinnerControl")
   *   imgClass     Estilo CSS de las imágenes (p.d. 
   *   initValue    Valor inicial del input de entrada
   *   interval     Cantidad de incremento
   *   round        Cantidad de decimales de redondeo
   *   min          Menor valor permitido, 'false' para ilimitado
   *   max          Mayor valor permitido, 'false' para ilimitado
   *   prefix       Cadena prefija cuando se actualize
   *   suffix       Cadena sufija cuando se actualize
   *   data         Array con los items para iterar
   *   onIncrement  Función a invocar tras incrementar
   *   onDecrement  Función a invocar tras decrementar
   *   afterUpdate  Función a invocar tras actualizar el valor
   *   onStop       Función a invocar cuando evento de ratón onClick/mouseUp
   * @return void
   */
var EmbedSpinnerControl = Class.create(SpinnerControl,
    {
      initialize: function ($super, container, inputName, options) {
        var opciones = Object.extend({ 
                              imgUpSrc: "js/spinnerControl-1.0/hotelmecum-up.png", 
                              imgDownSrc: "js/spinnerControl-1.0/hotelmecum-dn.png",
                              inputClass: "embedSpinnerControl",
                              imgClass: "botonImg",
                              initValue: 0 }, options);

        this.contenedor = $(container);
        this.inputName  = inputName;
        this.inputId    = (opciones.inputId)? opciones.inputId : "id" + this.inputName.capitalize().substr(0,1) + this.inputName.substr(1);
        var html = "<table class=\"embedSpinnerControl\" cellspacing=\"0\" cellpadding=\"0\">\n";

        html +="<tr>";
        if (opciones.label)
        	html += "<td class=\"label\">&nbsp;" + opciones.label + "&nbsp;</span></td>";
        html += "<td><input type=\"text\" id=\"" + this.inputId + "\" ";

        html += "name=\"" + this.inputName + "\" class=\"" + opciones.inputClass + "\" ";
        html += "value=\"" + opciones.initValue + "\" autocomplete=\"off\"/></td>"
        html += "<td>\n<table cellspacing=\"0\" cellpadding=\"0\"><tr><td><img src=\"" + opciones.imgUpSrc + "\" class=\"" + opciones.imgClass + "\" id=\"" + this.inputId + "BotonMas\" style=\"margin-top: 3px\" /></td></tr>\n"
        html += "<tr><td><img src=\"" + opciones.imgDownSrc + "\" class=\"" + opciones.imgClass + "\" id=\"" + this.inputId + "BotonMenos\" style=\"margin-bottom: 3px\"/></td></tr>\n</table></td></table>"

        this.contenedor.update(html);
        $super(this.inputId, this.inputId + "BotonMas", this.inputId + "BotonMenos", opciones);
      }
});
