(function()
{
  /*
   *  File: moxi_forms.js
   *  Version: 2
   *  NameSpace: Moxi
   *  Description: 
   *  Created: December 8, 2009
   *  Last Modified: 
   *  Design Document: 
   *  Description:
   *    Processes each form on a page and based on field attributes determines the validation method.
   *    Form Attributes:
   *      enablecookies - Boolean: Stores each field value/selectedIndex in a cookie.
   *      silentsubmit - Boolean: Submits the form using an iFrame.
   *      
   *    Field Attributes by type:
   *      TEXT, PASSWORD
   *        fieldname - String: Value representing the friendly name of the field.
   *        format - String: email, phone, zipcode
   *        length - String: nameFull, namePart, userName, password, csrPassword, address, city, zip, phone
   *        method - String: alpha, email, format, length, required
   *        defaultvalue - String: The initial value of the field.
   *        forceformat - String: Forces the field value to conform to a given format ( i.e. (###)###-#### )
   *
   *      SELECT, RADIO, CHECKBOX, TEXTAREA
   *        datasource - String: NameSpace ID for the object containing the field option text and values.
   *        datafields - String: Pipe delimited pair of object keys. Limited to 2.
   *        fieldname - String: Value representing the friendly name of the field.
   *        startindex - Number: The index to start validation, default is 0 (SELECT-ONE FIELDS ONLY).
   *        method - String: required
   */

  var thisFunc = this;
  var validationMethod = 'submit'; // Tells the system when to validate. A value of submit will validate
                                   // the form when the user submits the form. A value of inline will
                                   // validate the current field on a blur or change event.
  
  thisFunc.fieldLenMap = { // Predefined list of maximum and minimum field lengths.
    nameFull: { min: 7, max: 64 },
    namePart: { min: 1, max: 64 },
    userName: { min: 4, max: 30 },
    password: { min: 6, max: 64 },
    csrPassword: { min: 6, max: 30 },
    address: { min: 0, max: 100 },
    city: { min: 0, max: 100 },
    zip: { min: 5, max: 9 },
    phone: { min: 0, max: 10 }
  }
  thisFunc.validationData = {}; // A container for the form/field data to be validated.
  
  // Place holders for custom form events.
  jQuery.extend( window, {
    onBeforeValidate: function(){return true;}, // Fires before the form is validated.
    onAfterValidate: function(){return true;},  // Fires after the form is validated but before the form is submitted.
                                                // onAfterValidate will fire even if the form did not pass validation.
    onFormProcessed: function(){}   // Fires after all of the forms on the page have been initialized.
  });
  
  function addMethod( a_FieldjQueryElement, a_Method )
  {
    ///	<summary>
    ///   Adds the passed method name to the fields method attribute.
    ///	</summary>
    ///	<param name="a_FieldjQueryElement" type="jQuery" />
    ///	<param name="a_Method" type="String" />
    var fieldMethods = a_FieldjQueryElement.attr( 'method' );
    if( fieldMethods )
    {
      var fieldMethodList = fieldMethods.split( '|' );
      if( !fieldMethodList.isMember( a_Method ) )
        fieldMethodList.push( a_Method );
    }
    a_FieldjQueryElement.attr( 'method', fieldMethodList.join( '|' ) );
  }

  function getFieldType( a_Field )
  {
    ///	<summary>
    ///   Returns a generic type of input control represented by the object.
    ///	</summary>
    ///	<param name="a_Field" type="HTMLElement" />
    ///	<returns type="String/Null">
    ///   1: The generic object type.
    ///   2: Null if object does not have a type attribute.
    /// </return>
    switch( a_Field.type )
    {
      case 'text':
      case 'textarea':
      case 'file':
      case 'password':
        return 'text';
        break;
      case 'select-one':
      case 'select-multiple':
        return 'select';
        break;
      case 'radio':
      case 'checkbox':
        return 'multiple';
        break;
    }
    return null;
  }
  
  function setFieldValueFromCookie( a_FieldElement, a_FormObject )
  {
    ///	<summary>
    ///   Retrieves the field value from cookies and sets it value.
    ///	</summary>
    ///	<param name="a_FieldElement" type="HTMLElement" />
    var CookieValue = $.cookie( a_FieldElement.name );
    switch( a_FieldElement.type )
    {
      case 'text':
      case 'textarea':
        if( CookieValue ){a_FieldElement.value = CookieValue;}
        break;
      case 'select-one':
        if( CookieValue ){a_FieldElement.options[new Number( CookieValue )].selected = true;}
        break;
      case 'select-multiple':
        if( CookieValue )
        {
          CookieValue = CookieValue.split( ',' );
          for( var c = 0; c < CookieValue.length; c++ )
          {
            var FieldIndex = new Number( CookieValue[c] );
            a_FieldElement.options[FieldIndex].selected = true;
          }
        }
        break;
      case 'radio':
      case 'checkbox':
        if( CookieValue )
        {
          if( a_FormObject[a_FieldElement.name].length )
          {
            CookieValue = CookieValue.split( ',' );
            var ValueIndex = 0;
            for( fld = 0; fld < a_FormObject[a_FieldElement.name].length; fld++ )
            {
              a_FormObject[a_FieldElement.name][fld].checked = ( CookieValue[ValueIndex] == 'checked' )?true:false;
              ValueIndex++;
            }
          }
          else
          {
            a_FieldElement.checked = ( CookieValue == 'checked' )?true:false;
          }
        }
        break;
    }
  }
  
  thisFunc.errorMessage = function( a_Field, a_Message )
  {
    var SourceIndex = 0;
    var Field = a_Field;
    if( Field[0] && ( ( 'radio' ).Eq( Field[0].type ) || ( 'checkbox' ).Eq( Field[0].type ) ) )
    {
      SourceIndex = $( Field[0] ).attr( 'sourceIndex' );
      Field[0].focus();
    }
    else
    {
      SourceIndex = $( Field ).attr( 'sourceIndex' );
      Field.focus();
    }
    var ErrorMessageElement = null;
    $( '.form_error' ).each( function()
    {
      if( $( this ).attr( 'sourceIndex' ) < SourceIndex )
      {
        ErrorMessageElement = this;
      }
    });
    $( ErrorMessageElement ).css( 'color', '#ff0000' )
    $( ErrorMessageElement ).text( a_Message );
    $( ErrorMessageElement ).show();
    var IsInView = ( $( ErrorMessageElement ).getAbsolutePosition().y > $( document ).scrollTop() && $( ErrorMessageElement ).getAbsolutePosition().y < ( $( document ).scrollTop() + $( window ).height() ) );
    if( !IsInView )
    {
      ErrorMessageElement.scrollIntoView();
      $( document ).scrollTop( parseInt( $( document ).scrollTop() ) - 10 );
    }
  }
  
  thisFunc.getFieldName = function( a_Field )
  {
    ///	<summary>
    ///   1. Gets the friendly name of a field using the field's label or the fieldname attribute.
    ///	</summary>
    ///	<param name="a_Event" type="EventObject">
    ///   1. This is only passed by non IE browsers.
    /// </param>
    ///	<returns type="Boolean" />
    var fieldLabel = $( 'label[for=' + a_Field.name + ']' );
    var fieldLabelText = ( fieldLabel.length )? fieldLabel.text() : '';
    var ValidationName = a_Field.getAttribute( 'fieldname' );
    return ( ValidationName )? ValidationName : fieldLabelText;
  }
  
  thisFunc.handleKeyEvent = function( a_Event )
  {
    ///	<summary>
    ///   1. If an error messages is visible, hides the message when the user presses a key within a form field.
    ///   2. Validates the field length against the maximum character limit if specified.
    ///	</summary>
    ///	<param name="a_Event" type="EventObject">
    ///   1. This is only passed by non IE browsers.
    /// </param>
    ///	<returns type="Boolean" />
    var Event = ( a_Event )? a_Event : window.event;
    var KeyCode = Event.keyCode;
    var EventSource = Event.srcElement || Event.target;
    if( EventSource.type && EventSource.type == 'text' && EventSource.value == EventSource.getAttribute( 'default' ) )
    {
      this.value = '';
      $( this ).css( 'color', '#ffffff' );
    }
    $( '.form_error' ).hide();
    $( '.form_error' ).text( '' );
    
    // Validate field value length.
    var fieldMax = EventSource.getAttribute( 'max' );
    if( fieldMax )
    {
      if( KeyCode < 47 ){return true;}
      else{return ( EventSource.value.length + 1 < fieldMax );}
    }
  }

  thisFunc.Validation = {};

  $( 'FORM' ).ready( function()
  {
    var formName = '', formCount = 0, formObject = formElements = null, cookiesEnabled = false;
    var fieldData = null;
    $( 'FORM' ).each( function()
    {
      formObject = document.forms[formCount];
      formElements = formObject.elements;
      
      formjQueryObject = $( formObject );
      formjQueryObject.attr( 'index', formCount );

      formName = formjQueryObject.attr( 'name' )? formjQueryObject.attr( 'name' ) : formjQueryObject.attr( 'id' );
      if( !formName )
      {
        formName = 'form' + Math.floor( Math.random() * 99999 );
        formObject.id = formName;
      }

      var enableCookieAttribute = formjQueryObject.attr( 'enablecookies' );
      cookiesEnabled = ( enableCookieAttribute && enableCookieAttribute.toLowerCase() == 'true' )?true:false;
      
      var silentSubmit = formjQueryObject.attr( 'silentsubmit' ) == 'true'?true:false;
      if( silentSubmit )
        formjQueryObject.silentSubmit();
      
      fieldData = []; // Stores the names of each field to be validated.
      for( var i = 0; i < formElements.length; i++ )
      {
        var formField = formElements[i];
        var formjQueryField = $( formElements[i] );

        // Populate Select Elements
        var fieldDataSource = formjQueryField.attr( 'datasource' );
        if( fieldDataSource && getFieldType( formField ) == 'select' )
        {
          var fieldDataRecord = getObject( fieldDataSource );
          var fieldDataFields = formjQueryField.attr( 'datafields' ).split( '|' );
          var OptionText = OptionValue = '';
          for( var d = 0; d < fieldDataRecord.length; d++ )
          {
            fieldOptionText = fieldDataRecord[d][fieldDataFields[0]];
            fieldOptionValue = ( DataField[1] )? fieldDataRecord[d][fieldDataFields[1]] : fieldOptionText;
            formField.options[formElements[i].options.length] = new Option( fieldOptionText, fieldOptionValue );
          }
        }
        // Setup fields for cookie storage.
        if( cookiesEnabled )
        {
          if( isDefined( $.cookie ) )
          {
            setFieldValueFromCookie( formField, formObject );
          }
          switch( formField.type )
          {
            case 'text':
            case 'textarea':
              formjQueryField.blur( function()
              {
                $.cookie( this.name, this.value );
              });
              break;
            case 'select-one':
              formjQueryField.change( function()
              {
                $.cookie( this.name, this.selectedIndex );
              });
              break;
            case 'select-multiple':
              formjQueryField.change( function()
              {
                var Selected = [];
                var ThisOptions = this.options;
                for( var o = 0; o < ThisOptions.length; o++ )
                {
                  if( ThisOptions[o].selected ){Selected.push( o );}
                }
                $.cookie( this.name, Selected.join( ',' ) );
              });
              break;
            case 'radio':
            case 'checkbox':
              if( formObject[formField.name].length )
              {
                for( var fld = 0; fld < formObject[formField.name].length; fld++ )
                {
                  formObject[formField.name][fld].onclick = function()
                  {
                    var Selected = [];
                    var ItemCount = 0;
                    var fieldCollection = formObject[this.name];
                    for( var fld = 0; fld < fieldCollection.length; fld++ )
                    {
                      if( fieldCollection[fld].checked ){Selected.push( 'checked' );}
                      else{Selected.push( 'unchecked' );}
                      ItemCount++;
                    }
                    $.cookie( this.name, Selected.join( ',' ) );
                  }
                }
              }
              else
              {
                formField.onclick = function()
                {
                  $.cookie( this.name, ( this.checked? 'checked': 'unchecked' ) );
                }
              }
              break;
          }
        }
        
        // Check for field validation.
        var fieldValidationMethods = formjQueryField.attr( 'method' );
        if( fieldValidationMethods )
        {
          var fieldValidationMethodList = fieldValidationMethods.split( '|' );
          fieldData.push( formField.name );
          
          // Adding functionality to clear and hide the error message when value changes.
          switch( formField.type )
          {
            case 'text':
            case 'textarea':
              formjQueryField.keydown( thisFunc.handleKeyEvent );
              break;
            case 'select-one':
            case 'select-multiple':
              formjQueryField.change = function()
              {
                $( '.form_error' ).hide();
                $( '.form_error' ).text( '' );
              }
              break;
            case 'radio':
            case 'checkbox':
              var clickableElement = formObject[formField.name];
              for( var zed = 0; zed < clickableElement.length; zed++ )
              {
                clickableElement[zed].onclick = function()
                {
                  $( '.form_error' ).hide();
                  $( '.form_error' ).text( '' );
                }
              };
              break;
          }
        }
        
        var fieldLengthAttribute = formjQueryField.attr( 'length' );
        if( fieldLengthAttribute && getFieldType( formField ) == 'text' )
        {
          formjQueryField.attr( 'min', thisFunc.fieldLenMap[fieldLengthAttribute].min );
          formjQueryField.attr( 'max', thisFunc.fieldLenMap[fieldLengthAttribute].max );
          formjQueryField.attr( 'maxlength', thisFunc.fieldLenMap[fieldLengthAttribute].max );
          addMethod( formjQueryField, 'length' );
          formElements[i].onkeydown = thisFunc.handleKeyEvent;
        }
        else if( formjQueryField.attr( 'max' ) )
        {
          formjQueryField.keydown( thisFunc.handleKeyEvent );
        }
        
        // Set field default value.
        var fieldDefault = formjQueryField.attr( 'defaultvalue' );
        if( fieldDefault && getFieldType( formField ) == 'text' )
        {
          formjQueryField.val( ( formjQueryField.val()? formjQueryField.val() : fieldDefault ) );
          formjQueryField.bind( 'focus', function()
          {
            if( $( this ).val() == $( this ).attr( 'defaultvalue' ) )
            {
              this.select();
            }
          });
          formjQueryField.bind( 'blur', function()
          {
            if( !$( this ).val() )
            {
              $( this ).val( $( this ).attr( 'defaultvalue' ) );
              $( this ).css( 'color', '#909090' );
            }
          });
        }

        // Set format conforming event.
        var fieldForceFormat = formjQueryField.attr( 'forceformat' );
        if( fieldForceFormat && getFieldType( formField ) == 'text' )
        {
          formjQueryField.bind( 'blur', function(){$( this ).formatNumber()} );
        }
      }

      if( fieldData.length )
      {
        thisFunc.validationData[formName] = {
          fields: fieldData
        };
        require( 'validate' );
        if( !$.browser.msie )
        {
          var sourceCount = 0;
          $( '*' ).each( function()
          {
            $( this ).attr( 'sourceIndex', sourceCount++ );
          });
        }
        formObject.onsubmit = function()
        {
          return thisFunc.Validate( this );
        }
      }
    });
    window.onFormProcessed();
  });
  
  $.fn.formatNumber = function()
  {
    ///	<summary>
    ///   1. Formats numbers with a given format template.
    ///	</summary>
    var definedFormat = $( this ).attr( 'forceformat' );
    if( definedFormat )
    {
      var formatRegExp = '[^\\d]*';
      var numberGroups = definedFormat.match( /(\#+)/g );
      for( var i = 0; i < numberGroups.length; i++ )
      {
        var numberCount = numberGroups[i].split( '' ).length;
        var RegExpReplace = '(\\d{' + numberCount + '})[^\\d]*';
        formatRegExp += RegExpReplace;
        definedFormat = definedFormat.replace( new RegExp( numberGroups[i], 'i' ), '$' + ( i + 1 ) );
      }
      formatRegExp += '';
      var fieldValue = this.val();
      var matchRegExp = new RegExp( formatRegExp, 'i' );
      if( matchRegExp.test( fieldValue ) )
        this.val( fieldValue.replace( matchRegExp, definedFormat ) );
    }
  };
  
  $.fn.silentSubmit = function( a_Options )
  {
    ///	<summary>
    ///   1. Uses a hidden iFrame to submit forms.
    ///	</summary>
    ///	<param name="a_Options" type="Object">
    ///   1. 
    /// </param>
    return this.each( function()
    {
      var IFrameName = 'frame_' + Math.floor( Math.random() * 99999 );
      var IFrameDiv = document.createElement( 'DIV' );
      $( 'body' ).append( $( '<div><iframe style="display:none" src="about:blank" id="' + IFrameName + '" name="' + IFrameName + '"></iframe></div>' ) );
      this.setAttribute( 'target', IFrameName );
      if( a_Options && typeof ( a_Options.onComplete ) == 'function' )
      {
        $( '#' + IFrameName ).bind( 'readystatechange', a_Options.onComplete );
      }
    } );
  };
  
}).add( 'Moxi.Forms' );