(function($) {
    
    /**
     * SexyForms global namespace. Contains utility classes and global configuration options.
     * @module SexyForms
     *
     */
    var SexyForms = {
        /**
         * Utility methods.
         *
         * @class Util
         * @namespace SexyForms
         */
        Util: {},
        
        Widgets: {
            /**
             * Class for converting a <select> element to a functioning <ol>.
             * @class SelectBox
             * @constructor
             * @param {Object} element The DOM element to convert. Must be a <select>.
             */
            SelectBox: function(element)
            {
                
                // Only allow <SELECT> elements
                if (!$(element).is("select")) { return; }
                
                var selectObj = this;
                
                $.extend(this, {
                    element: element,
                    selected: null,
                    list: $('<ol class="select-box"></ol>'),
                    container: $('<div class="select-box-container"></div>'),
                    bg: $('<div class="select-box-bg"></div>')
                });
                
                this.doReplace();
                
                // Copy over the ID if there is one (prefixed)
                var id = $(element).attr('id');
                
                if (id) 
                {
                    this.container.attr('id', 'select-box-container-' + id)
                }
                
                this.items = this.list.children("li");
                
                // Ensure something is selected
                if (!this.selected) 
                {
                    $(this.items[0]).addClass("selected");
                    this.selected = this.items[0];
                }
                
                // Open when clicked
                $(this.selected).click(function()
                {
                    selectObj.open();
                });
                
                // Hide on mouseout
                this.list.mouseout(function(e)
                {
                    
                    if (!$.relatedTarget(e).isChildOf(this)) 
                    {
                        selectObj.close();
                    }
                });
                
                return this;
            },
            
            /*
             * Uses a swapping technique to overlay a file input with a text box
             *  and button image
             *
             * @class FileInput
             * @constructor
             */
            FileInput: function(element)
            {
                var decoy = $(element).parent().prev();
                // copy the value into the real input field
                $(element).change(function()
                {
                    decoy.val($(element).val());
                });
            },
            
              // Abstract constructor for elements that have on/off states      
              Toggleable: function(element)
              {
                this.element = element;
                var $element = $(this.element);
                
                $.extend(this, {
                  label:   $element.parent(),
                  checked: $element.attr("checked"),
                  klass:   this.checked,
                  
                  // radio mode: only 0 or 1 item can be selected
                  // checkbox mode: any number can be selected
                  mode:     ( $element.is("input[type=checkbox]") ? "checkbox" : $element.is("input[type=radio]") ? "radio" : null)
                });
                
                $.extend(this, {
                  onClass: this.mode + "-on",
                  offClass: this.mode + "-off"
                });
                
                // Return the appropriate css class name
                this.cssClass = function(on) { if (on) { return this.onClass; } return this.offClass; };
                    
                return this;
              },
                
              // RadioButton inherits from toggleable
              RadioButton: function(element)
              { 
                return new SexyForms.Widgets.Toggleable(element); 
              },
              
                RadioGroup: function(element)
                {
                    var group    = this,
                        $element = $(element);
                    
                    
                    $.extend(this, {
                      inputs : [],
                      current: null 
                    });
                        
                    // Save to element for global access
                    $element
                      .find("input").each(function()
                      {
                        var radio = new SexyForms.Widgets.RadioButton($(this));
                        
                        if (radio.checked) { group.current = radio; }
                        group.inputs.push(radio);
                        radio.set(radio.checked);
                        radio.label.click(function() { group.select(radio); });
                        
                    });
                }

        }
    };
    
    /*
     * Widget object prototypes
     */
    SexyForms.Widgets.SelectBox.prototype = {
        
        /**
         * Hides the <select> element and inserts an <ol>.
         * @method doReplace
         */
        doReplace: function()
        {
            var selectBoxObj = this;
            $(this.element)        // Create <li> elements from <option> elements 
            
            .children("option").each(function()
            {
                var li = $("<li>" + $(this).text() + "</li>");
                
                if ($(this).attr("selected")) 
                {
                    selectBoxObj.selected = li;
                    li.addClass("selected");
                }
                
                selectBoxObj.list.append(li);
            })
            .end()
            
            .after(selectBoxObj.container)        // Hide the actual <SELECT>
            
            .css({
                position: "absolute",
                visibility: "hidden",
                left: "-100000px"
            });
            // Insert the elements
            this.container.append(this.bg).append(this.list);
        },
        
        /**
         * Select an item in the list. Focuses the list on the selected item,
         * and updates the hidden <select> element with the same value.
         * @method setSelected
         * @param {Object} item
         */
        setSelected: function(item)
        {
            
            if (this.selected) 
            {
                this.selected.removeClass("selected");
            }
            
            this.selected = $(item);
            this.selected.addClass("selected");
            this.close();
            
            // Set value in the hidden <SELECT>
            this.element.selectedIndex = this.items.index(item);
        },
        
        /**
         * Open the option list.
         * @method open
         */
        open: function()
        {
            var selectObj = this;
            
            selectObj.list.addClass("select-box-open");
            
            // Bind click events
            $(selectObj.items).each(function()
            {
                var item = $(this);
                item.unbind().click(function()
                {
                    selectObj.setSelected(item);
                });
            });
        },
        close: function()
        {
            var selectObj = this;
            
            selectObj.list.removeClass("select-box-open");
            
            // Update click events
            $(selectObj.items).each(function()
            {
                var item = $(this);
                item.unbind();
                
                if (item[0] === selectObj.selected[0]) 
                {
                    item.click(function()
                    {
                        selectObj.open();
                    });
                }
            });
        }
    };
  
   $.extend(SexyForms.Widgets.Toggleable.prototype, {
       
     check: function(checked) {
       this.checked = checked;
       this.element.attr("checked", this.checked);
     },
     
     uncheck: function() { this.check(false); },
     
     setClass: function(state)
     {
        this.label.removeClass(this.cssClass(!state));
        this.label.addClass(this.cssClass(state));
     },
     
      set: function(state)
      {
        // Check/uncheck it 
        this.check(state);

        // Swap classnames
        this.setClass(state);   
        
      },
      toggle: function()
      {       
        // New state of component
        this.set(!this.element.attr("checked"));
      }
   });

   // RadioButton inherits from toggleable
   SexyForms.Widgets.RadioButton.prototype = $.extend(new SexyForms.Widgets.Toggleable(), {});

  SexyForms.Widgets.RadioGroup.prototype = {
    select: function(radio)
    {

      this.current = radio;

      for (var i=0,l=this.inputs.length; i<l; i++)
      {
        if (this.inputs[i].element[0] == this.current.element[0])
        {
          this.inputs[i].set(true);
        }
        else
        {
          this.inputs[i].setClass(false);
        }
      }
    }
  };
    
	$.fn.extend({
	  // Styled <select> (converted to <ol>)
	  selectBox: function()
	  {
	    return this.each(function() {
	      new SexyForms.Widgets.SelectBox(this);
	    });
	  },
	
	  // Styled <input type="file" />
	  fileInput: function() {
	    return this.each(function() {
            new SexyForms.Widgets.FileInput();
        });
	  },
	
	  // Block element with class="radio-group",
	  //  containing one or more <input type="radio" />
	  radioGroup: function() {
	     return this.each(function() {
	       new SexyForms.Widgets.RadioGroup(this);
	     });
	  },
	  
		// Abstract element with on/off functionality
	  toggleable: function()
	  {
	    return this.each(function() {
	      
	      var $this = $(this),
	          tog   = new SexyForms.Widgets.Toggleable($this);
	
	      tog.set(tog.checked);
	      tog.label.click(function() { tog.toggle(); });
	    }); 
	  },
      
      // Return true if el is a child of this, or the same element
      isChildOf: function(el)
      {
       var target = this[0];

       if (target === el) { 
           return true; 
       }
      
       return $(el).children()
                   .filter(function()
                   {
                       return this === target;
                   })
                   .length > 0;
       

      }
	});

    // jQuery aliases for simple usage
    $.fn.checkbox = $.fn.toggleable; // Generic toggleable === checkbox 
      
    // Return the related target for a mouse event
    $.relatedTarget = function(e)
    {
      var relatedTarget;
    
      if (e.type != 'mouseover' && e.type != 'mouseout')
      {
          return $;
      }
      
      // Which element we're going to or coming from
      if (e.relatedTarget)
      {
          relatedTarget = e.relatedTarget;
      }
      else
      {
          // IE versions of relatedTarget
          if (e.type == 'mouseout')
          {
              relatedTarget = e.toElement;
          }
          else
          {
              relatedTarget = e.fromElement;
          }
      }
    
      return $(relatedTarget);            
    };
  
})(jQuery);