(function($) {

  var rules = {

    'text' : {

      fields   : ['input:text','input:file','input:password','textarea'],
      nots     : ['.dirty-form-ignore', '.autocomplete_field', '.textboxlist-bit-editable-input', '.textboxlist-hidden'],
      css_rule : [],
      event    : 'focus'

    },

    'textboxlist' : {

      fields   : ['input.textboxlist-hidden'],
      nots     : ['.dirty-form-ignore'],
      css_rule : [],
      event    : 'focus'

    },

    'dirty-form-html' : {

      fields   : ['.dirty-form-html'],
      nots     : ['.dirty-form-ignore'],
      css_rule : [],
      event    : 'focus'

    },

    'autocomplete' : {

      fields       : ['div.autocomplete .autocomplete_field'],
      nots         : ['.dirty-form-ignore'],
      css_rule     : [],
      event        : 'onLoad',
      parent_rules : 'div.autocomplete'

    },

    'checkbox' : {

      fields   : ['input:checkbox'],
      nots     : ['.dirty-form-ignore'],
      css_rule : [],
      event    : 'customOver'

    },

    'radio' : {

      fields   : ['input:radio'],
      nots     : ['.dirty-form-ignore'],
      css_rule : [],
      event    : 'customOverName'

    },

    'color-swatch' : {

      fields   : ['div.color-swatch'],
      nots     : ['.dirty-form-ignore'],
      css_rule : [],
      event    : 'mousedown'

    },

    'select' : {

      fields   : ['select'],
      nots     : ['.dirty-form-ignore'],
      css_rule : [],
      event    : 'selectmenuopen'

    },

    'activatable-list' : {

      fields   : ['ul.activatable-list'],
      nots     : ['.dirty-form-ignore'],
      css_rule : [],
      event    : 'loaded'

    }



  };


  // sets up rules
  $.each ( rules , function( rules_key ) {

    // loop through fields
    $.each( this.fields, function() {

      var fullRule = this;

      // see if there are negating rules and then add the :not
      if (rules[rules_key].nots.length) {
        fullRule += ':not("';
      }

      // loop through negating rules
      $.each(rules[rules_key].nots, function() {
        fullRule += this+',';
      }); // each this.nots


      // truncate last comma and cap off the :not
      if (rules[rules_key].nots.length) {
        fullRule = fullRule.substr( 0, ( fullRule.length-1 ) );
        fullRule += '")';
      }

      rules[rules_key].css_rule.push( fullRule );

    }); // each for individual rule

    rules[rules_key].css_rule = rules[rules_key].css_rule.join(',');

  }); // each rules

  // runs on the form
  $.fn.dirtyForm = function() {

    return this.each(function() {

      var $form = $(this);

      $form.addClass('dirtyForm').data( 'dirtyForm', { 'dirty' : 0, 'fields' : [] } );

      $.each ( rules, function( rules_key ) {

        rules_key = ( rules_key == 'autocomplete_remove' ) ? 'autocomplete' : rules_key;

        var self      = this,
            configure = function() {
            
              var $this   = $(this), $parent;
              

              if ( $this.data( 'dirtyForm' ) && $this.data( 'dirtyForm' ).configured ) {
                return;
              }

              $parent = $this.parent();

              if ( typeof ( self.parent_rules ) != 'undefined' ) {
                $parent = $this.closest(self.parent_rules);
              }

              $.fn.dirtyForm.configureField($this, $form, rules_key);
              $.fn.dirtyForm.setStartingValues( $form, { $container : $parent, clean : false } );


            };

        $form.delegate( this.css_rule, this.event, configure);
        $form.delegate( this.css_rule, 'dirtyForm.forceConfig', configure);

        // sometimes you just can't wait
        if ( this.event == 'onLoad' ) {
          $form.find( this.css_rule ).trigger('onLoad');
        }


      });

    });

  };

  $.fn.dirtyForm.setStartingValues = function($form, options) {

    options    = options || {};
    $container = ( typeof( options.$container ) != 'undefined' ) ? options.$container : $form;
    clean      = ( typeof( options.clean ) != 'undefined' ) ? options.clean : true;
    configured = ( typeof( options.configured ) != 'undefined' ) ? options.configured : true;

    var preset = { dirty : false, configured : configured };

    // note that hidden is never configured, but can be used manually
    $container.find('input:hidden, input:text, input:file, input:password, textarea, select')
    .not( rules.text.nots.join(', ') )
    .each(function(i, el) {

       var $el = $(el);
       $el.data( 'dirtyForm', $.extend( {}, preset, { 'original' : $el.val() } ) );

       if (clean) {
         $.fn.dirtyForm.clean($form, $el);
       }

    }); // text inputs, selects

    $container.find('input.textboxlist-hidden').each(function(i, el) {

       var $el = $(el);
       $el.data( 'dirtyForm', $.extend( {}, preset, { 'original' : $el.val() } ) );

       if (clean) {
         $.fn.dirtyForm.clean($form, $el);
       }

    }); // textboxlist hidden

    $container.find('.autocomplete_field').each(function(i, el) {

      var $el           = $(el),
          $autocomplete = $el.closest('div.autocomplete');

      if ($autocomplete.data('dataLoaded') === true) {
        $el.data( 'dirtyForm', $.extend( {}, preset, { 'original' : $autocomplete.autocomplete('delimited') } ) );
      }
      else {
        $autocomplete.bind('dataLoaded', function() {
          $el.data( 'dirtyForm', $.extend( {}, preset, { 'original' : $autocomplete.autocomplete('delimited') } ) );
        });
      }

      if (clean) {
        $.fn.dirtyForm.clean($container, $el);
      }

    }); //autocompletes

    $container.find('ul.activatable-list').each(function(i, el) {

      var $el = $(el);

      $el.data( 'dirtyForm', $.extend( {}, preset, { 'original' : $el.find('li.active').pluck('id').join('|') } ) );


      if (clean) {
        $.fn.dirtyForm.clean($container, $el);
      }

    }); //activatable-lists

    $container.find('input:checkbox, input:radio').each(function(i, el) {

      var $el = $(el);
      $el.data( 'dirtyForm',$.extend( {}, preset, { 'original' : $el.attr('checked') } ) );

      if (clean) {
        $.fn.dirtyForm.clean($container, $el);
      }

    }); // checkboxes, radios

    $container.find('.dirty-form-html').each(function(i, el) {

       var $el = $(el);
       $el.data( 'dirtyForm', $.extend( {}, preset, { 'original' : $el.html() } ) );

       if (clean) {
         $.fn.dirtyForm.clean($container, $el);
       }

    }); // innerHTML

    $container.find('.color-swatch').each(function(i, el) {

       var $el = $(el);
       $el.data( 'dirtyForm', $.extend( {}, preset, { 'original' : $el.css('background-color') } ) );

       if (clean) {
         $.fn.dirtyForm.clean($container, $el);
       }

    }); // innerHTML

    if (clean) {
      $form.data('dirtyForm').dirty = 0;
    }

    return true;

  }; // setStartingValues

  $.fn.dirtyForm.dirty = function($container, $el) {

    if ( !$el.data('dirtyForm').dirty ) {

      $el.addClass('dirtyField').data('dirtyForm').dirty = true;

      var dirtyCount = $container.data('dirtyForm').dirty + 1;
      $container.data('dirtyForm').dirty = dirtyCount;

      $el.trigger('fieldDirty.dirtyForm')
         .trigger('fieldChange.dirtyForm', [dirtyCount, 'dirty', $el]);

      $container.trigger('formDirty.dirtyForm', [$el])
                .trigger('formChange.dirtyForm', [dirtyCount, 'dirty', $el]);

    } // element was clean

  }; // dirty

  $.fn.dirtyForm.clean = function($container, $el) {

    if ( $el.data('dirtyForm') && $el.data('dirtyForm').dirty ) {

      $el.removeClass('dirtyField').data('dirtyForm').dirty = false;

      var dirtyCount = $container.data('dirtyForm').dirty - 1;
      $container.data('dirtyForm').dirty = dirtyCount;

      $el.trigger('fieldClean.dirtyForm')
         .trigger('fieldChange.dirtyForm', [dirtyCount, 'clean', $el]);

      $container.trigger('formClean.dirtyForm', [$el])
                .trigger('formChange.dirtyForm', [dirtyCount, 'clean', $el]);

    } // element was dirty

  }; // clean

  $.fn.dirtyForm.configureField = function($el, $container, type) {

    var current_fields = $container.data('dirtyForm').fields;
    current_fields.push($el);
    $container.data('dirtyForm').fields = current_fields;

    switch (type) {

      case 'text' :

        $el.bind('keyup', function() {

          if ( $el.val() != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });
        break; // text

      case 'select' :

        $el.bind('change', function() {
          if ( $el.val() != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });

        $el.bind('keyup', function() {
          if ( $el.val() != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });

        break; // select

      case 'checkbox' :

        $el.bind('change', function() {
          if ( $el.attr('checked') != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });

        $el.bind('keyup', function() {
          if ( $el.attr('checked') != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });

        break; // checkbox

      case 'radio' :

        $el.bind('change', function() {

          $('[name=' + $el.attr('name') + ']').each(function(i, namedRadio) {
            $namedRadio = $(namedRadio);

            if ( $namedRadio.attr('checked') != $namedRadio.data('dirtyForm').original ) {
              $.fn.dirtyForm.dirty($container, $namedRadio);
            }
            else {
              $.fn.dirtyForm.clean($container, $namedRadio);
            }
          });

        }); // change

        $el.bind('keyup', function() {

          $('[name=' + $el.attr('name') + ']').each(function(i, namedRadio) {

            $namedRadio = $(namedRadio);

            if ( $namedRadio.attr('checked') != $namedRadio.data('dirtyForm').original ) {
              $.fn.dirtyForm.dirty($container, $namedRadio);
            }
            else {
              $.fn.dirtyForm.clean($container, $namedRadio);
            }


          });

        }); // keyup

        break; // radio

      case 'autocomplete' :

        var $autocomplete = $el.closest('div.autocomplete');

        $autocomplete.bind('toggleSelection', function(e) {

          if (!$autocomplete.data('dataLoaded') || !$el.data('dirtyForm')) {
            return;
          }

          if ( $autocomplete.autocomplete('delimited') != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });
        break; //autocomplete

      case 'activatable-list' :

        $el.find('li').live('click', function(e) {

          if ( $el.find('li.active').pluck('id').join('|') != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });
        break; //activatable-list

      case 'textboxlist' :

        $el.bind('textboxlist.change', function() {

          if ( $el.val() != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });

        break; // textboxlist

      case 'dirty-form-html' :

        $el.bind('html.change', function() {

          if ( $el.html() != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });

        break; //innerHTML

      case 'color-swatch' :

        $el.bind('colorpicker.close', function() {

          if ( $el.css('background-color') != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });

        $el.bind('colorpicker.revert', function() {

          if ( $el.css('background-color') != $el.data('dirtyForm').original ) {
            $.fn.dirtyForm.dirty($container, $el);
          }
          else {
            $.fn.dirtyForm.clean($container, $el);
          }

        });

      break; // colorswatch

    } // switch on type

  }; // configureField

  $.fn.dirtyForm.getValues = function($container) {

    var values = {};

    $container.find('input:text, input:file, input:password, textarea, select, input:hidden').not('.dirty-form-ignore').each(function(i, el) {

      $el = $(el);

      if ( !$el.is('.autocomplete_field, .textboxlist-bit-editable-input') ) {
        values[el.id] = $el.val();
      }

    }); // text inputs, selects

    $container.find('.textboxlist-hidden').not('.dirty-form-ignore').each(function(i, el) {

      values[el.id] = $(el).val().replace(/,/g,'|');

    }); //autocompletes

    $container.find('div.autocomplete .autocomplete_field').not('.dirty-form-ignore').each(function(i, el) {
      values[el.name] = $(el).closest('.form-item.autocomplete').autocomplete('delimited');

    }); //autocompletes

    $container.find('input:checkbox').not('.dirty-form-ignore').each(function(i, el) {

      values[el.id] = ( $(el).attr('checked') ) ? el.value : '0';

    }); // checkboxes

    $container.find('input:radio').not('.dirty-form-ignore').each(function(i, el) {

      $('[name=' + $(el).attr('name') + ']').each(function(i, namedRadio) {
        $namedRadio = $(namedRadio);

        if ( $namedRadio.attr('checked') ) {
          values[namedRadio.name] = $namedRadio.val();
        }

      });

    }); // radios

    $container.find('.dirty-form-html').not('.dirty-form-ignore').each(function(i, el) {

      $el = $(el);
      values[el.id] = $el.html();

    }); // inner HTML

    return values;

  };

})(jQuery);

