/** var $F = pulp.field.getInstance; */
pulp.Modules.field = '$F';

(function() {

  var $p = pulp.base;
  
  /**
   * Form element manipulation
   * @name  pulp.field
   * @requires base
   * @requires cls
   * @requires cls.event
   * @example
   *   var $F = pulp.field.getInstance;
   *   $F('firstName').set('Joe');
   */
  var self = pulp.field = pulp.cls.create(/** @lends pulp.field# */{
    /**
     * A wrapper for a form element or form group
     * @constructs
     * 
     * @param {HTMLElement|HTMLNodeList} field
     */
    initialize: function(field) {
      /**
       * The raw field element or nodelist
       * @type {HTMLElement|HTMLNodeList}
       */
      this.raw = field;
      this._setCache();

      if (this.raw[0]) {
        // we have a NodeList
        this.group = this.raw;
        this.getValue = this._getChecked;
        this.setValue = this._setChecked;

      } else {
        // text or select
        this.group = [this.raw];
        if (this.raw.options) {
          // select
          this.getValue = this._getSelected;
          this.setValue = this._setSelected;
      
        } else {
          // text
          this.getValue = this._getValue;
          this.setValue = this._setValue;
        }
      }
      this.type = this.group[0].type;
    },
    
    /**
     * Cache the instance of this pulp.field object on the Element or NodeList
     * (special treatment for IE6 to avoid circular references
     * 
     * @return {undefined}
     */
    _setCache: function() {
      this.raw.__pulpField = this;
    },    

    /**
     * Clear the value of the form object
     * 
     * @return {pulp.field}
     * @chainable
     */
    clear: function() {
      return this.setValue('');
    },
    
    /**
     * Return true if the form object has a value
     * 
     * @return {Boolean}
     */
    present: function() {
      return this.getValue().length > 0;
    },

    /**
     * Focus and select the form element
     * 
     * @return {pulp.field}
     * @chainable
     */
    activate: function() {
      var node = this.group[0];

      try {
        node.focus();
        node.select();
      } catch(e) {}
      return this;
    },

    /**
     * Blur and disable the form group
     * 
     * @return {pulp.field}
     * @chainable
     */
    disable: function() {      if (this.group[0].blur) { // TODO: blur all elements?
        this.group[0].blur();
      }
      for (var i = 0, len = this.group.length; i < len; i++) {
        this.group[i].disabled = true;
      }
      return this;
    },
    /**
     * Enable the element or element group
     * 
     * @return {pulp.field}
     * @chainable
     */
    enable: function() {
      for (var i = 0, len = this.group.length; i < len; i++) {
        this.group[i].disabled = false;
      }
      return this;
    },    
    
    /**
     * Get an array of checkbox/radio values that are checked
     * 
     * @return {Array|String}  Returns a string for radios and one-item checkbox groups
     */
    _getChecked: function() {
      var values = [];
      for (var i = 0, len = this.raw.length; i < len; i++) {
        if (this.raw[i].checked) {
          values.push(this.raw[i].value);
        }
      }
      return this.type == 'radio' || this.group.length == 1 ? values: values[0];
    },

    /**
     * Set the value of checkbox/radio group
     * 
     * @param {Array|String} value  The value(s) to set
     * @return {pulp.field}
     */
    _setChecked: function(value) {
      // convert the value to an array if not an array already
      var values = $p.isArray(value) ? value : [value];
      for (var i = 0, len = this.raw.length; i < len; i++) {
        this.raw[i].checked = $p.inArray(values, this.raw[i].value);
      }
      return this;
    },

    /**
     * Set the value of a select-one or select-multiple
     * 
     * @param {Array|String} value  The value(s) to set
     * @return {pulp.field}
     */
    _setSelected: function(value) {
      // convert the value to an array if not an array already
      var values = $p.isArray(value) ? value : [value];
      for (var i = 0, len = this.raw.options.length; i < len; i++) {
        this.raw.options[i].selected = $p.inArray(values, group[i][attribute]) > -1;
      }
      return this;
    },

    /**
     * Get the value of a select-one or select-multiple
     * 
     * @return {Array|String}  Returns an Array for select-multiple or a String for select-one
     */
    _getSelected: function() {
      var values = [];
      for (var i = 0, len = this.raw.options.length; i < len; i++) {
        if (this.raw.options[i].selected) {
          values.push(this.raw.options[i].value);
        }
      }
      return this.type == 'select-multiple' ? values : values[0];
    },
  
    /**
     * Get the value of a text-type field (e.g. text, password, file, textarea)
     * 
     * @return {String}
     */
    _getValue: function() {
      return this.raw.value;
    },
  
    /**
     * Set the value of a text-type field (e.g. text, password, file, textarea)
     * 
     * @param {String} value  The value to set
     * @return {String}
     */
    _setValue: function(value) {
      this.raw.value = $p.castAsString(value);
      return this;
    }  

  });
  
  // for IE6, be sure to decouple circular references
  if (pulp.isIE6) {
    // keep a list of pulp.field objects
    var wrappers = [];
    self.prototype._setCache = /** @ignore */ function() {
      this.raw.__pulpField = this;
      wrappers.push(this);
    };
    // decouple circular references to avoid memory leak
    document.attachEvent(window, 'onunload', function() {
      var i = wrappers.length;
      while (i--) {
        wrappers[i].raw = null;
      }
    });
  }  

  self.extend(/** @scope pulp.field */{
    /**
     * Return an instance of pulp.field
     * 
     * Usage 1. By Form and field
     * @param {String} formName
     * @param {String} fieldName
     * 
     * Usage 2. By Element or Element ID
     * @param {Element|NodeList|String} formElement
     * 
     * @return pulp.field 
     */
    getInstance: function() {
      var field = arguments[0].nodeType == 1 ? arguments[0] : false;
      if (!field) field = self._getFieldByName(arguments[0], arguments[1]);
      if (!field) field = self._getGroup(arguments[0]);
      if (!field) field = document.getElementById(arguments[0]);
      return (field ? field.__pulpField || new pulp.field(field) : null);
    },
  
    /**
     * Attempt to get a named field in a given form
     * 
     * @param {String} form  The name of the form
     * @param {String} field  The name of the field
     * @return {Element|Boolean}  Returns false if not found
     */
    _getFieldByName: function(form, field) {
      try {
        return document.forms[form].elements[field];
      } catch(e) {
        /*console.log([form, field, e]);*/
        return false;
      }
    },
    
    /**
     * Try to get an element group based on an element (e.g. radios, checkboxes)
     * 
     * @param {Element} group
     * @return {NodeList|Boolean}  Returns false if the element has no group
     */
     _getGroup: function(group) {
      if (group[0] && group[0].nodeType == 1) {
        return group;

      } else {
        try {
          group = document.getElementById(group);
          return group.form[group.name];
        } catch(e) {
          return false;
        }
      }
    }
  });    


})();
