/*
 * requires prototype.js library
*/

if (!Array.prototype.each) {
  Array.prototype.each = function(f) {
    for (var i=0; i < this.length; i++) {
      f(this[i]);
    }
    return null;
  };
}

// Hack to fix IE's retardedness
if (!window['Log']) {
  Log = {
    debug: function(msg) { ; },
    info: function(msg) { ; },
    error: function(msg) { ; },
    fatal: function(msg) { ; }
  };
}

/*
Array.prototype.toString = function() {
  return "[" + this.join(", ") + "]";
};
*/

if (!String.prototype.test) {
  String.prototype.test = function(regexp) {
    var re = new RegExp(regexp);
    return(this.match(re) !== null);
  };
}

var Enumerable = {
  map: function(func) {
    var ary = new Array();
    this.each( function(item) { ary.push(func(item)); } );
    return ary;
  },

  find: function(func) {
    var found = null;
    this.each( function(item) {
      if (func(item) && found !== null) {
        found = item;
      }
    } );
    return found;
  }
};


var Former = function() {};

Former.DISABLE_FORM_SUBMISSION = false;

Former.Validator = function() {};

Former.IsMethods = {
  isEmpty: function(str) {
    return (str === "" || str === null);
  },

  isString: function(obj) {
    return ((typeof obj == "string") || (obj.constructor == String));
  },

  isT11eRange: function(str) {
    return (!!str.match(/([\[\(])([\d.]*),([\d.]*)([\]\)])?/));
  },

  isNumber: function(str) {
    return (!str.match(/[^0-9.]/) && !!(str.match(/[0-9](\.[0-9]+)?/)));
  },

  isValidText: function(value) {
    var invalid_chars = /[=]/;
    return (!invalid_chars.test(value));
  },
  
  isValidZipcode: function(value) {
    var re = /\d{5}/;
    return re.test(value);
  }
};

Former.Validator.prototype = {
  reInput: /validation_(\w*)/,

  elementValidationType: function(el) {
    var matches = this.reInput.exec(el.className);
    if (matches !== null) {
      return matches[1];
    }
    return null;
  },

  checkElement: function(el) {
    var vType = this.elementValidationType(el);
    var value = $F(el);
    if (!this.isEmpty(value) && vType !== null)
    {
      var isValid;

      switch (vType) {
        case 'number':
          isValid = this.isT11eRange(value) || this.isNumber(value);
          break;
        case 'text':
          isValid = this.isValidText(value);
          break;
        case 'zipcode':
          isValid = this.isValidZipcode(value);
          break;
        default:
          throw "unknown validation type '" + vType + "'";
      }

      Log.info("element: " + el.name + (isValid ? " is " : " isn't ") + "valid");
      
      if (!isValid) {
        var err = new Former.ValidationError(el, this.errorMessage[vType]);
        throw err;
      }
    }
    return true;
  },

  _delayedReFocus: function(el) {
    Former._erroredElement = el;
    var cmd = [
      "Former._erroredElement.focus()",
      "Former._erroredElement.select()",
      "delete Former._erroredElement" ];
    Log.debug("calling cmd.join(';')");
    window.setTimeout(cmd.join(';'), 50);
  },

  _doValidation: function() {
    var inputs = Form.getInputs(this.form);
    var rval = true;
    if (inputs !== null) {
      try {
        for (var i=0; i < inputs.length; i++) {
          this.checkElement(inputs[i]);
        }
      }
      catch (e) {
        if (e.name && e.name === "ValidationError") {
          alert(e.message);
          this._delayedReFocus(e.element);
        }
        // rethow exception, catch it later
        throw e;
      }
    }
    return rval;
  },

  handleEvent: function(evt) {
    this.evt = evt;
    this.form = Event.element(evt);
    return this._doValidation();
  },

  errorMessage: {
    number: "Sorry, please only enter digits 0-9 and the period ('.') character",
    text: "Sorry, please do not use the '=' character",
    zipcode: "Sorry, please enter a valid, 5-digit US zipcode"
  }
};

Former.Validator.prototype.extend(Former.IsMethods);

Former.ValidationError = function(element, message) {
  this.message = message;
  this.element = element;
  this.name = "ValidationError";
};


/* sets up this validator to respond to the onclick event of a form 
*/
/*
Validator.observeForm = function(element) {
  var v = new Validator();
  var lambda = function(evt) {
    return v.handleEvent(evt);
  };
  Event.observe(element, 'submit', lambda, true);
  return v;
};
*/

Former.Location = Class.create();
Former.Location.props = ['hash', 'host', 'hostname', 'path', 'port', 'protocol', 'search'];

Former.Location.prototype = {
  initialize: function() {
    var loc = window.location;
    var props = Former.Location.props;
    var key, i;
    for (i=0; i < props.length; i++) {
      key = props[i];
      this[key] = loc[key];
    }
  },

  toString: function() {
    return this.protocol + "//" + this.host + this.path + this.search + this.hash;
  }
};


Former.Cleaner = Class.create();

Former.Cleaner._defaultCallback = function(url) {
  Former.Cleaner._doRedirect = function() {
    window.location.href = url;
    return false;
  };
  if (!Former.DISABLE_FORM_SUBMISSION) {
    window.setTimeout("Former.Cleaner.doRedirect();", 50);
  }
  Event.stop(this.evt);
};

Former.Cleaner.prototype = {
  // callback is a method that should be called
  // with the cleaned form args, or the string "redirect"
  // do do a regular form submittal
  initialize: function(callback) {
    if (callback) {
      this.callback = callback;
    }
    else {
      this.callback = Former.Cleaner._defaultCallback;
    }
  },

  button: function() {
    return Former.Cleaner.clickedButton;
  },

  buttonArgs: function() {
    var b = this.button();
    var pair = [encodeURIComponent(b.name), encodeURIComponent(b.value)];
    Log.debug("Former.Cleaner.buttonArgs: calling pair.join('-')");
    return pair.join("=");
  },

  isFuzzy: function() {
    Log.info("isFuzzy");
    var b = this.button();
    Log.debug("b: " + b);
    return b.name.test(/enhanced/);
  },
  
  ensureCorrectButtonArg: function(newargs) {
    var rval = new Array();
    var qstr;
    while (newargs.length > 0) {
      qstr = newargs.pop();
      // remove all button arguments from newargs
      if (qstr.match(/legacy/) || qstr.match(/enhanced/)) {
        Log.debug("removing button " + qstr);
        continue;
      }
      else {
        rval.push(qstr);
      }
    }
    // add correct argument
    Log.info("adding button argument " + this.buttonArgs());
    rval.push(this.buttonArgs());
    return rval;
  },

  searchWithCulledEmpties: function() {
    var serialized = Form.serialize(this.form);
    Log.debug("serialized form: " + serialized);
    var args = serialized.split('&');
    Log.debug("args: " + args);
    var newargs = new Array();
    var fuzzy = this.isFuzzy();
    var qstr, i;
    Log.debug("args.length: " + args.length);
    var reEmptyArg = /[^=]*=(%5B%5D)?$/;

    while (args.length > 0) {
      qstr = args.pop();
      if (!reEmptyArg.test(qstr)) {
        Log.debug("nonempty qstr: " + qstr);
        newargs.push(qstr);
      }
    }
    newargs = this.ensureCorrectButtonArg(newargs);
    var rval = "?" + newargs.join("&");
    Log.debug("searchWithCulledEmpties, returning: " + rval);
    return rval;
  },

  HTMLCollectionToArray: function(col) {
    var a = new Array();
    for(var i = 0; i < col.length; i++) {
      a.push(col[i]);
    }
    return a;
  },

  removeBlankMinMaxInclusives: function() {
    Log.info("Cleaner.removeBlankMinMaxInclusives");
    var inputs = this.HTMLCollectionToArray(Form.getInputs(this.form));
    var el, mobj, dim, minOrMax, relatedValue;
    while (inputs.length > 0) {
      el = inputs.pop();
      mobj = el.id.match(/(\w*)_(min|max)Inclusive/);
      if (mobj) {
        dim = mobj[1]; minOrMax = mobj[2];
        relatedValue = $F(dim + "_" + minOrMax);
        if (isEmpty(relatedValue)) {
          Element.remove(el);
        }
      }
    }
  },

  doCleanSubmission: function() {
    Log.info("Cleaner.doCleanSubmission");
    this.removeBlankMinMaxInclusives();
    Log.debug("removed blank elements");
    var newloc = new Former.Location();
    Log.debug("created newloc");
    newloc.search = this.searchWithCulledEmpties();
    newloc.path = this.form.action;
    var url = newloc.toString();
    Log.info("new location: " + url);
    return this.callback(url);
  },

  handleEvent: function(evt) {
    Log.info("Cleaner.handleEvent called");
    this.evt = evt;
    this.form = Event.element(evt);
    this.queryPairs = new Array();
    return this.doCleanSubmission();
  }
};

/* to be called by a setTimeout eval string.
 * runs the function Fomer.Cleaner._doRedirect after
 * deleting it from Former.Cleaner
*/
Former.Cleaner.doRedirect = function() {
  var cmd = Former.Cleaner._doRedirect;
  delete Former.Cleaner._doRedirect;
  cmd();
};

Former.Cleaner.prototype.extend(Former.IsMethods);

// watches a button for it's onclick and saves an element reference
// of the clicked button as the property Former.Cleaner.clickedButton
Former.Cleaner.createButtonWatcher = function(el) {
  return function(evt) {
    Former.Cleaner.clickedButton = el;
    return true;
  };
};

Former.Handler = function () {};
Former.Handler.observer = function(evt) {
  Log.info("Former.Handler.observer called: " + evt);
  var validator = new Former.Validator();
  var cleaner = new Former.Cleaner();

  try {
    validator.handleEvent(evt);
    cleaner.handleEvent(evt);
  }
  catch (e) { 
    switch (e.name) {
      case  "ValidationError":
        Event.stop(evt);
        break;
      default:
        Log.error("caught error " + e);
        throw e;
    }
  }
  finally {
    if (Former.DISABLE_FORM_SUBMISSION) {
      Event.stop(evt);
    }
  }
  return false;
};


