var Watch = function () {

  return {
watchForm: function ( formId, callback ) {

             var els = Form.getElements( $(formId) );
             _watch( els, callback );

           },

watch: function ( elements, callback ) {
         var els = new Array();
         for ( var i=0; i < elements.length; ++i ) {
           els.push( $(elements[i]) );
         }

         _watch( els, callback );
       }

  }
  function _watch ( elements, callback ) {

    // Initialize search elements
    for ( var i=0; i < elements.length; ++i ) {
      var thisEl = elements[i];
      thisEl.setAttribute( 'oldValue', _getValueOrStatus($(elements[i])) );

      // Select the appropriate event for each element type
      // select -> onChange
      // text   -> onKeyUp
      // radio, checkbox -> onClick
      // other elements are not considered
      var tagName = thisEl.nodeName;
      var evName;
      if ( tagName == 'SELECT' ) {
        evName = 'change';

      } if ( tagName == 'INPUT' ) {

        var type = thisEl.getAttribute( 'type' );
        type = type.toUpperCase();

        if ( type == 'TEXT' ) {
          evName = 'keyup';
        } else if ( type == 'RADIO' || type == 'CHECKBOX' ) {
          evName = 'click';
        }
      }

      if ( evName != undefined ) {
        Event.observe( thisEl, evName, 
            function(e, noCheck) {
              return function() {
                if ( _hasChanged( e ) ) {
                  // call the callback only if the content has changed
                  callback( e );
                }
              }
            }(thisEl) );
      }
    }
  }

  function _hasChanged ( el ) {
    // return true if the value of the element has changed
    // since last time

    var value = _getValueOrStatus( el );
    var oldValue = el.getAttribute('oldValue');
    if ( value != oldValue ) {

      _setOldValue( el, value );
      return true;

    } else {

      return false;
    }
  }

  function _getValueOrStatus ( el ) {
    // returns the value of an input or select element
    // for radiobuttons and checkboxes returns 1 or 0
    // depending of checked status

    var tagName = el.nodeName;
    var value;
    if ( tagName == 'INPUT' ) {
      var type = el.getAttribute( 'type' );
      type = type.toUpperCase();
      if ( type == 'TEXT' ) {

        value = $F(el);

      } else if ( type == 'RADIO' ) {

        value = el.checked ? 1 : 0;

      } else {
        // Don't return boolean because we can't do 
        // equality checks with it
        value = el.checked ? 1 : 0;
      }
    } else {

      value = $F(el);
    }

    return value;
  }

  function _setOldValue ( el, value ) {
    // Set the oldValue attribute of an element
    // to its current value.

    var tagName = el.nodeName;
    var value;
    if ( tagName == 'INPUT' ) {
      var type = el.getAttribute( 'type' );
      type = type.toUpperCase();
      if ( type == 'RADIO' ) {
        _updateRadioGroupOldValue( el );
      } else {
        el.setAttribute( 'oldValue', value );
      }
    } else {
      el.setAttribute( 'oldValue', value );
    }
  }

  function _updateRadioGroupOldValue ( el ) {
    // Updates the oldValue attribute for all the
    // radio buttons in the same group al el
    // (i.e. those with the same name)
    //
    // NB: this works only if there are no other 
    // radio groups with the same name in other forms
    // on the same page.
    // 
    // TODO: make it form-specific
    var name = el.name;

    var group = document.getElementsByName( name );
    for ( var i=0; i < group.length; ++i ) {

      var thisEl = group[i];
      var tagName = thisEl.nodeName;
      var value;
      if ( tagName == 'INPUT' ) {
        var type = thisEl.getAttribute( 'type' );
        type = type.toUpperCase();
        if ( type == 'RADIO' ) {
          thisEl.setAttribute( 'oldValue', thisEl.checked ? 1 :0 );
        }
      }
    }
  }

}();
