/*
 * requires prototype.js library
*/

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

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

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) && (value.length == 5);
  }
};

Former.Validator = Class.create();
Former.Validator.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.reInput = /validation_(\w*)/;

Former.Validator.prototype = {
  initialize: function() {
  },

  elementValidationType: function(el) {
    var matches = Former.Validator.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, Former.Validator.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();
  },

  validateForm: function(element) {
    this.form = $(element);
    return this._doValidation();
  }
};

Object.extend(Former.Validator.prototype, 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;
};
*/

/* This is necessary because Safari is "special"
 * it seems that by using prototype.js's Event.stop, you cannot
 * cause a redirect by setting window.location.href :/
 * Former.Redirect.to sets up a function and uses 
 * window.setTimeout to call that function
*/
Former.Redirect = function() {};
Former.Redirect.to = function(url) {
  Former.Redirect.callback = function() {
    delete Former.Redirect.callback;
    window.location.href = url;
    return false;
  };
  window.setTimeout("Former.Redirect.callback();", 1);
};


Former.Location = Class.create();
Former.Location.props = ['hash', 'host', 'hostname', 'path', 'pathname', '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];
    }
  },

  /* there's a bug in Firefox 1.5, window.location.path is "http://host:port/path/to/resource" 
  */
  isRetardedLocation: function() {
    var re = RegExp(this.host);
    return !!(re.exec(this.path));
  },

  actualPath: function() {
    if (this.isRetardedLocation()) {
      var re = RegExp(this.protocol + "//" + this.host + "(.*)");
      return this.path.replace(re, "$1");
    }
    else {
      return (this.path || this.pathname);
    }
  },

  toString: function() {
    var bool = this.isRetardedLocation();
    Log.debug( "isRetardedLocation? " + bool + ", actualPath: " + this.actualPath() );

    var s = this.protocol + "//" + this.host + this.actualPath() + this.search + this.hash;
    Log.debug("returning: " + s);
    return s;
  }
};


Former.Cleaner = Class.create();

/* this is necessary to get around a bug in safari
*/
Former.Cleaner._defaultCallback = function(url) {

  // called by Former.Cleaner.doRedirect after a delay 
  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);
};

/* 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 = {
  // 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());
    if (isNull(this.button())) {
      rval.push("enhanced_search=1")
    }
    else {
      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() {
    this.queryPairs = new Array();

    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: " + unescape(url));
    return this.callback(url);
  },

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

  processForm: function(element) {
    this.form = $(element);
    return this.doCleanSubmission();
  }
};

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

// a dummy object in case a user clicks a submittal-link instead of clicking a button
Former.Cleaner.clickedButton = { name: "enhanced_search", value: "Enhanced Search" }

// 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;
};

/* these methods are used by the links in the drilldown form to submit
 * the form (i.e. the zipcode and freetext text boxes) when a link is clicked.
 * It performs validation and does an extra check to make sure that the user
 * has not entered both a zipcode and clicked on a location link.
*/
Former.DrillDown = {
  formSubmitLocationLink: function(event, uriString) {
    event = event || window.event;

    Log.debug("formSubmitLocationLink");

    var rval;
    var zipcode_value = $('DrillDownZipcodeInput')['value'];

    if (!(zipcode_value.length == 0)) {
      alert("Please only submit a Zip Code value or a Location, not both");
      Event.stop(event);
      rval = false;
    }
    else {
      rval = Former.DrillDown.formSubmitLink(event, uriString);
    };

    return rval;
  },

  alteredWeightHash: function() {
    var hash = $H();
    Demo.weights.alteredWeights().each(function(weight) {
      hash[weight.dimension] = weight.getValue();
    });
    return hash;
  },

  // this is a conceptual atrocity. we should be setting up the criteria as a
  // javascript object and using that for processing, but, alas...
  //
  // if a slider-related weight has been altered, then insert that weight
  // into the query string in place of the one uriString
  // returns the modified uriString
  mergeSliderWeights: function(uriString) {
    var split = unescape(uriString).split('?');
    var path = split[0], queryPairs = split[1].split('&');

    var altered = Demo.weights.alteredWeights();

    /*
    var regex = /([a-zA-Z0-9]*)\[weight\]=([0-9.]*)/;
    var newQueryPairs = queryPairs.map(function(pair) {
      var ary = pair.match(regex);
      if (ary) {
        var dim = ary[1];
        if (weightHash[dim]) {
          return dim + "[weight]=" + weightHash[dim];
        }
      }
      else {
        return pair;
      }
    });
    return path + '?' + newQueryPairs.join('&');
    */

    var newQueryPairs = altered.map(function(weight) {
      return escape(weight.dimension + "[weight]") + '=' + escape(weight.getValue());
    });
    
    return uriString + '&' + newQueryPairs.join('&');
  },

  formSubmitLink: function(event, uriString) {
    event = event || window.event;

    Log.debug("formSubmitLink");
    uriString = uriString.replace(/&amp;/g, "&");
    var queryPairs = new Array();
    var validator = new Former.Validator();
    var formValid = true;

    try {
      validator.validateForm('DrillDownForm');
      var elems = [//'DrillDownFreeTextInput', 
                   'DrillDownZipcodeInput'];
      var el;
      for (var i=0; i < elems.length; i++)
      {
        el = elems[i];
        if ($(el)['value'].length > 0) {
          queryPairs.push(Form.Element.serialize(el));
        }
      }
      
      uriString += queryPairs.join('&');
      uriString = Former.DrillDown.mergeSliderWeights(uriString);

      Log.debug("redirecting to: " + uriString);

      if (!Former.DISABLE_FORM_SUBMISSION) {
        Former.Redirect.to(uriString.unescapeHTML());
      }

      Event.stop(event);
      return false;
    }
    catch (e) {
      if (e.name) {
        switch (e.name) {
          case "ValidationError":
            formValid = false;
            Event.stop(event);
            break;
          default:
            throw e;
        };
      }
      else {
        throw e;
      }
      return false; // quiet ff-js-console
    }
  }
};



