/*  */

var Validate = {

  // three class manipulation functions from FORK.Dom
  hasClass: function(el, className) {
    //return;
    if (typeof el == 'string') { el = document.getElementById(el);}
    var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
    return re.test(el.className);
  },
  addClass: function(el, className) {
    if (typeof el == 'string') { el = document.getElementById(el);}
    if (this.hasClass(el, className)) { return; } // already present
    el.className = [el.className, className].join(' ');
  },
  removeClass: function(el, className) {
    if (typeof el == 'string') { el = document.getElementById(el);}
    if (!this.hasClass(el, className)) { return; } // not present
    var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)', 'g');
    var c = el.className;
    el.className = c.replace(re, ' ');
    if ( this.hasClass(el, className) ) { // in case of multiple adjacent
      this.removeClass(el, className);
    }
  },
  
  // three simple custom event manipulation functions 
  listeners: [],
  purgeAttached: false,
  addListener: function(el, type, fn) {
    if (window.addEventListener) {
      el.addEventListener(type, fn, false);  
    } else if (window.attachEvent) {
      el.attachEvent('on' + type, fn);
      if (!this.purgeAttached) {
        this.purgeAttached = true;
        this.addListener(window, 'unload', function(){Validate.purge();});
      }
      this.listeners.push({el:el,type:type,fn:fn}); // cache all listeners
    }
  },
  // purge events onunload in IE for memory leak protection.
  purge: function() {
    var lis = this.listeners;
    for (var i=0, len=lis; i<len; i++) {
      var li = lis[i];
      li.el.detachEvent('on'+li.type, li.fn)
    }
  },
  // used to stop the form from submiting
  preventDefault: function(e) {
    if (e.preventDefault) {
      e.preventDefault();
    }
    if (e.cancelBubble !== undefined){ // can't test returnValue directly
      e.returnValue = false;
    }
  },
  
  
  wrap: function(n) {
    var p = n.parentNode;
    if (this.hasClass(p, "errorWrap")) {
      // already wrapped in an error span
      return p;
    }
    var s = document.createElement('span');
    p.insertBefore(s, n);
    this.addClass(s, "errorWrap");
    //p.removeChild(n); // TODO don't need this? The append should remove first
    s.appendChild(n);
    return s;
  },

  unwrap: function(n) {
    var s = n.parentNode;
    if (!this.hasClass(s, "errorWrap")) {
      return;
    }
    //s.removeChild(n); // TODO will insertBefore do this for me?
    s.parentNode.insertBefore(n, s);
    s.parentNode.removeChild(s);
    //return null; // TODO why?
  },

  show: function(n, hide) {
    var t = n.type, // holds the type of the form element (eg. 'text', 'textarea')
        ua = navigator.userAgent,
        toggleClass = (hide ? 'removeClass' : 'addClass'),
        s, // hold an element being wrapped
        isSafari = ua.match(/Safari/); // should be all webkit?
        
    if ((n.tagName && n.tagName.toLowerCase() === 'textarea') ||
        (t && (t.match(/^text/i) || t.match(/^password/)))) {
      if (isSafari) {
        this[toggleClass](n,'safariErrorText');
      } else {
        this[toggleClass](n,'errorText');
      }
    } else if (t && t.match(/^file/)) {
      if (isSafari) {
        if (hide) {
          this.unwrap(n);
        } else {
          s = this.wrap(n);
          this.addClass(s, "safariErrorFile");
        }
      } else {
        this[toggleClass](n, "errorFile");
      }
    } else if (t && (t.match(/^radio/i) || t.match(/^checkbox/i))) {
      if (ua.match(/MSIE/) || ua.match(/Opera/)) {
        this[toggleClass](n, "ieErrorRC");
      } else {
        if (hide) {
          this.unwrap(n);
        } else {
          s = this.wrap(n);
          if (isSafari) {
            this.addClass(s, "safariErrorRC");
          } else {
            this.addClass(s, "errorRC");
          }
        }
      }
    } else if (t && t.match(/^select/i)) {
      if (ua.match(/firefox/i)) {
       this[toggleClass](n,"firefoxErrorSelect");
      } else {
        if (hide) {
          this.unwrap(n);
        } else {
          s = this.wrap(n);
          if (ua.match(/Safari/)) {
            this.addClass(s, "safariErrorSelect");
          } else {
            this.addClass(s, "errorSelect");
          }
        }
      }
    }
  },

  hide: function(el) {
    this.show(el, true);
  },
  
  showAlert: function() {
    alert('Sorry, errors were found. Please correct the errors indicated in red.');
  },
  
  form: function(form) {
    this.isFormValidation = true;
    
    var els = form.elements,
        flag = true,
        firstElWithError;

    for (var i=0, ilen=els.length; i<ilen; i++) {
      var el = els[i];
      flag = this.input(el) && flag;
      if (!firstElWithError && !flag) {
        // first time an error has been found. Save the element
        // so the scroll can occur.
        firstElWithError = el;
      }
    }
    if (!flag) {
      if (firstElWithError.scrollIntoView) {
        firstElWithError.scrollIntoView();
      };
      this.showAlert();
    }
    this.isFormValidation = false;
    return flag;
  },
  
  showHideErrors: function(el, msgs) {
    var errElId = el.form.name+'_'+el.name+'_'+'error',
        err = document.getElementById(errElId);
    if (msgs.length) { // there are error messages
      if (!err) {
        err = document.createElement('div');
        err.id = errElId;
        el.parentNode.insertBefore(err, el.nextSibling);
      }
      this.show(el);
      this.addClass(err, 'errorMsg');
      err.innerHTML = '<p class="head">Sorry, the item just above...</p><ul><li>' + (msgs.join('</li><li>')) + '</li></ul><p class="foot">Correct the error(s) above before continuing. Please refer to the online help for additional assistance.</p>';
    } else { // there were no error messages
      this.hide(el);
      if (err) { // but there were error messages before
        this.removeClass(err, 'errorMsg');
        err.innerHTML = '';
      }
    }
  },
  
  input: function(el) {
     // file inputs in IE have strange focus rules
     // when the "browse" button is clicked the validation
     // occurs instantly in the Fav It! form. This is because
     // the text-looking part of the input focus when the page loads
     // and clicking the browse button removes focus from that
     // text-looking part. Silly IE. So we need to delay validation
     // of file inputs until the form is submitted
    if (el.tagName.toLowerCase() == "input" &&
        el.type.toLowerCase() == "file" &&
        !this.isFormValidation) {
      return;
    }
    
    var msgs = [],
        // TODO next line
        cls = el.className.match(/(v\S*)/g); //all validation class names start with 'v'
    if (!cls) {return true;}
    // if the field is allowed to be blank then skip all other validations
    if (!(this.hasClass(el, 'vAllowBlank') && (el.value == "" || el.value.match(/^\s+$/)))) {
      for (var i=0, len=cls.length; i<len; i++) {
        var list = cls[i].split('_');
        var op = list[0];
        if (this[op]) {
          list[0] = el;
          var m = this[op].apply(this, list);
          if (m) {
            // m might be a strting or array of strings
            msgs = msgs.concat(m);
          }
        }
      }
    }

    this.showHideErrors(el, msgs);
    return (msgs.length < 1);
  },
  
  init: function() {
    var fs = document.forms;
    for (var i=0,len=fs.length; i<len; i++) {
      this.initForm(fs[i]);
    }
  },
  initForm: function(f) {
    this.addListener(f, 'submit', 
                     (function(f){
                       return function(e) {
                                if (!Validate.form(f)) {Validate.preventDefault(e);}
                              };
                      })(f));
    var els = f.elements;
    for (var i=0,len=els.length; i<len; i++) {
      this.initInput(els[i]);
    }
  },
  initInput: function(el) {
    var eventType = 'blur';
    if (el.type == 'checkbox') {
      eventType = 'click';
    }
    this.addListener(el, eventType, 
                     (function(el){
                       return function(){Validate.input(el);};
                      })(el));
  },

  // validation functions ---------------------------------------------------------
  
  // The first argument is the element being validated.
  // The following arguments are the user arguments passed in as part of the class name
  //   eg. vHasMaxLength_23
  
  vIsPresent: function(el) {
    return !!el.value ? '' : 'cannot be left blank';
  },
  
  vIsInteger: function(el) {
    return el.value.match(/^\s*\d+\s*$/) ? '' : 'must be a number';
  },

  vIsNonNegativeInteger: function(el) {
    return el.value.match(/^\s*(0|[123456789]\d*)\s*$/) ? '' : 'must be 0, 1, 2, 3, ...';
  },
  
  vHasMaxLength: function(el, max) {
    if (typeof max == 'string') {max = parseInt(max,10);}
    return (el.value.length > max) ?  ('is too long. The maximum length is ' + max) : '';
  },

  vHasMinLength: function(el, min) {
    if (typeof min == 'string') {min = parseInt(min,10);}
    return (el.value.length < min) ?  ('is too short. The minimum length is ' + min) : '';
  },

  vHasNoScripts: function(el) {
    // This regexp is very conservative but matches the Perl regexp for SiteSell.
    return (el.value.match(/<[^>]*\s*script(\s+[^>]*>|>)/i)) ?  'cannot contain scripts' : '';
  },

  vHasNoHtml: function(el) {
    // all the HTML elements listed on http://w3schools.com/tags/default.asp
    // plus "embed", "canvas"
    return (el.value.match(/<[\/]*\s*(a|abbr|acronym|address|applet|area|b|base|basefont|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|dd|del|dir|div|dfn|dl|dt|em|embed|fieldset|font|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|hr|html|i|iframe|img|input|ins|isindex|kbd|label|legend|li|link|map|menu|meta|noframes|noscript|object|ol|optgroup|option|p|param|pre|q|s|samp|script|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|ul|var|xmp)(\s+[^>]*>|>)/i)) ?  'cannot contain any HTML tags' : '';
  },

  vIsText: function(el, max) {
    if (!max) {
      if (el.tagName.toLowerCase() == 'textarea') {
        max = 600;
      } else {
        max = 500;
      }
    }

    var ms = [],
        m;

    if (m=this.vIsPresent(el)) {ms.push(m);} // yes just one equals sign
    if (m=this.vHasMaxLength(el, max)) {ms.push(m);} // yes just one equals sign
    if (m=this.vHasNoScripts(el)) {ms.push(m);} // yes just one equals sign
    return ms;
  },

  vIsEmail: function(el) {
    return (el.value.match(/^[^@]+@[^@]+$/)) ? '' : 'must be a valid email address';
  },
  
  vIsChecked: function(el) {
    return (el.checked) ? '' : 'must be checked';
  },
  
  vIsImgFile: function(el) {
    return (el.value.match(/\.(gif|jpg|jpeg|png)$/)) ?
             '' :
             'must be an image file ending with .gif, .jpg, .jpeg, or .png. Please check the name and type of your file.';
  },
  
  vIsFileType: function(el, type) {
    // type in {'img', 'html', 'xml'}
    alert('TODO implement vIsFileType');
    return '';
  }
  
};

Validate.addListener(window, 'load', function(){Validate.init();});
