/*
  (c) Copyright 2006, ClickRSVP
  Javascript for all Steps
  Requires enumeraAjax.js
  Requires prototype.js (version 1.3.1 adjusted by JMH on 2005-11-04)
  Requires prototypeExt.js
  Requires validations.js
  camelize function from Effects.js, Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
*/

// TO DO: centralize all form onsubmit functions into one?

// Navigation:

var stepSections = ['sectionGetHV', 'sectionGetEquity', 'sectionGetHV2', 'sectionGetEquity2']; // changed for Wachovia
for (var i = 0; i < stepSections.length; i++) stepSections[stepSections[i]] = i; // make double-associative
var stepValidations = [null, null, validateLoanType, null]; 
var stepImages = ['imageCHVStep1', 'imageCHVStep2', 'imageCHVStep3', 'imageCHVStep4'];
var currSection = "", largestValidStep = "";

(new Image()).src = "images/loading.gif"; // pre-load loading animation gif

function startPage() {
  currSection = stepSections[0];
  largestValidStep = currSection;
  window.homeValue = null; // this will reset the progress summary
  showSection(currSection);
}

function showSection(sectionName) {
  currSection = sectionName;
  for (var i = 0; i < stepSections.length; i++) {
    var el = $(stepSections[i]), img = $(stepImages[i]);
    if (el) 
      el.style.display = stepSections[i] == sectionName ? 'block' : 'none';
  }
  showCurrStepImage();
  // JMH: fixed
  //showProgressSummary();
}

function showCurrStepImage() {
  // JMH: changed for Wachovia
  var pos = stepSections[currSection]; // get position
  if (window.homeValue && homeValue.HvbHiValue > 0) pos += 2;
  for (var i = 0; i < stepSections.length; i++) {
    var img = $(stepImages[i]);
    if (img)
      img.style.display = i == pos ? 'block' : 'none';
  }
}

function showProgressSummary() {
  var showSummary = function (html) {
    var el = document.getElementById('SummaryArea');
    if (el) el.innerHTML = html;
  }
  var url = 'AjaxSummary.aspx?'+ Math.random(1000) + (window.homeValue ? '' : '&reset=true');
  AjaxBase.getHTML(url, 'GET', null, showSummary);
}

function jumpToSection(nextSectionName) { // used for going back with alternate navigation (header menu)
  var pos = stepSections[nextSectionName];
  if (pos >= 0)
    showSection(stepSections[pos]);
  return false;
}

function goToNextSection(currSectionName, dir) {
  if (!currSectionName)
    currSectionName = currSection ? currSection : stepSections[0];
  try {
    dir = (dir < 0) ? -1 : 1; // dir is optional, default = +1
    var currPos = stepSections[currSectionName];
    var nextPos = currPos + dir; 
    // Put special navigation logic here (adjust dir accordingly) 
    if ((dir < 0) || !stepValidations[currPos] || stepValidations[currPos]()) {
      if (stepSections[nextPos]) {
        if (nextPos > stepSections[largestValidStep]) largestValidStep = stepSections[nextPos];
        showSection(stepSections[nextPos]);
        // JMH: the following lines are for Wachovia
        currSection = stepSections[nextPos];
        showCurrStepImage();
        //showProgressSummary();
      }
      return true;
    }
    else
      return false;
  }
  catch (ex) {
    alert('An error occurred: ' + ex.description);
    return false;
  }
}

function goToPrevSection(currSectionName) {
  return goToNextSection(currSectionName, -1);
}


// Step 1:

var AjaxHV = {
  url: 'AjaxHV.aspx', 
  method: 'POST', 
  anchor: 'anchorHV', // anchor to where to scroll page
  errorClass: 'ErrorMessage',
  idWait: 'sectionHVWait', // element to show loading animation
  idFill: 'sectionShowHV', // element to receive html
  idShow: 'sectionShowHV', // element to show
  getWaitElement: function () {
    return this._elWait || (this._elWait = $(this.idWait));
  },
  getFillElement: function () {
    return this._elFill || (this._elFill = $(this.idFill));
  },
  getShowElement: function () {
    return this._elShow || (this._elShow = $(this.idShow));
  }
}

function processHomeValue(form) {
  var args1 = {IsValid: false}, args2 = {IsValid: false}, args3 = {IsValid: false}, args4 = {IsValid: false};
  validateCityStateOrZipSpecified(null, args1);
  validateAddressSpecified(null, args2);
  validateHomeImproveYear(null, args3);
  validateHomeImproveAmount(null, args4);
  if (args1.IsValid && args2.IsValid && args3.IsValid && args4.IsValid) {
    try {
      var qData = Form.serialize(form);
      clearHomeValueArea(); // be sure to serialize the form first!
      showHVWait();
      with (AjaxHV) AjaxBase.getHTML(url, method, qData, showHomeValueHTML, showHomeValueError);
      handled = true;
    }
    catch (ex) {
      // TO DO: error handling
      //alert(ex.description);
    }
  }
  return false;
}

function clearHomeValueArea() {
    fillElement(AjaxHV.getFillElement(), "");
}

function clearEquityArea() {
  $('sectionShowEquity').style.visibility = 'hidden';
}

function clearEntireApplication(){
  var forms = document.getElementsByTagName('form');
  for (var i = 0; i < forms.length; i++)
    forms[i].reset();
  clearHomeValueArea();
  showSaleInfoArea(false);
  window.homeValue = null; // resets side-bar
  showProgressSummary();
  jumpToSection('sectionGetHV');
}

function clearStepTwo(){
  var form = document.getElementById('formGetEquity');
  form.reset();
  clearEquityArea();
  showProgressSummary();
}

// Address Validation
function validateAddressSpecified (source, args) {
  if (window.Hvc && window.Hvc.controls) {
    if (!source)
      source = Hvc.controls.Address1;
    args.IsValid = (source && source.value);
    // TO DO: remove if we get validators working:
    var el = Hvc.controls.Address1Validator;
    if (el)
        el.style.visibility = args.IsValid ? 'hidden' : 'visible';
  }
}

// CityStatOrZip Validation
function validateCityStateOrZipSpecified (source, args) {
  if (window.Hvc && window.Hvc.controls) {
    if (!source)
      source = Hvc.controls.CityStateOrZip;
    if (source && source.value) {
      args.IsValid = isValidUSZipCode(source.value) || isValidCityState(source.value);
    }
    else
      args.IsValid = false;
    // TO DO: remove if we get validators working:
    var el = Hvc.controls.CityStateOrZipValidator;
    if (el)
        el.style.visibility = args.IsValid ? 'hidden' : 'visible';
  }
}

// Home Improvement Year Validation
function validateHomeImproveYear (source, args) {
  if (window.Hvc && Hvc.controls) {
    if (!source)
      source = Hvc.controls.ImproveYear;
    if (!args)
      args = new Object();
    if (Hvc.controls.ImproveCost && Hvc.controls.ImproveCost.value) {
      var val = parseInt((source.value.length == 2 ? "19" : "") + source.value);
      args.IsValid = source.value.length == 2 || source.value.length == 4
        && isAllNumericCharacters(source.value)
        && val <= (new Date()).getFullYear()
        && (val >= 1967);
    }
    else
      args.IsValid = true;
    // TO DO: remove if we get validators working:
    var el = Hvc.controls.ImproveYearValidator;
    if (el)
        el.style.display = args.IsValid ? 'none' : 'inline';
  }
}

// Home Improvement Year Validation
function validateHomeImproveAmount (source, args) {
  if (window.Hvc && Hvc.controls) {
    if (!args)
      args = new Object();
    if (Hvc.controls.ImproveCost && Hvc.controls.ImproveCost.value) {
      var val = unformatDollars(Hvc.controls.ImproveCost.value);
      args.IsValid = isFinite(val) && val <= 50000;
      if (args.IsValid) Hvc.controls.ImproveCost.value = val;
    }
    else
      args.IsValid = true;
    // TO DO: remove if we get validators working:
    var el = Hvc.controls.ImproveCostValidator;
    if (el)
        el.style.display = args.IsValid ? 'none' : 'inline';
  }
}

function showHVWait() {
  AjaxHV.getWaitElement().style.display = 'block';
}

function hideHVWait() {
  AjaxHV.getWaitElement().style.display = 'none';
}

function showHomeValueHTML(html) {
  var success = true;
  homeValue = null; // clear old values!
  hideHVWait();
  AjaxHV.getFillElement().style.visibility = 'hidden';
  fillElement(AjaxHV.getFillElement(), html);
  AjaxBase.executeScripts(html); // declares Hvc.homeValue, etc.
  showSaleInfoArea(window.showSaleInfo);
  success = homeValue.HvbHiValue > 0; // successfully got a value!
  shadeInElement(AjaxHV.getShowElement());
  largestValidStep = success ? 'sectionGetEquity' : 'sectionGetHV';
  // JMH: disabled for Wachovia
  //scrollToAnchor(AjaxHV.anchor);
  if (success) {
    // JMH: custom for Wachovia.  Skip to next section immediately
    //currSection = 'sectionGetEquity';
    goToNextSection();
    $('hvbValueLow2').innerHTML = formatDollars(window.homeValue.HvbLowValue, false);
    $('hvbValueHi2').innerHTML = formatDollars(window.homeValue.HvbHiValue, false);
  }
  // JMH: the following line removed for Wachovia
  //showCurrStepImage();
  showProgressSummary();
  AjaxEquity.getShowElement().style.visibility = 'hidden'; // TO DO: Hack!!!
  if (window.homeValueError) {
    var el = $('showHVError');
    el.innerText = window.homeValueError;
    el.style.visibility = 'visible';
  }
  else {
    var el = $('showHVError');
    el.style.visibility = 'hidden';
  }
}

function showSaleInfoArea(isVisible) { // set isVisible to null to set back to default
  var elSaleInfoArea = $('SaleInfoArea');
  if (elSaleInfoArea) {
    elSaleInfoArea.style.display = isVisible == true ? 'block' : isVisible == false ? 'none' : ''; 
    if (isVisible == false) {
      var elSalePrice = $('SalePrice'), elSaleDate = $('SaleDate');
      if (elSalePrice) elSalePrice.value = '';
      if (elSaleDate) elSaleDate.value = '';
    }
  }
}

function showHomeValueError(error) {
  hideHVWait();
  injectError(error, $('showHVError'), AjaxHV.errorClass);
  // JMH: disabled for Wachovia
  //scrollToAnchor(AjaxHV.anchor); 
  //shadeInElement(AjaxHV.getShowElement());
}


// Step 2:

var AjaxEquity = {
  anchor: 'anchorEquity', // anchor to where to scroll page
  method: 'POST',
  errorClass: 'ErrorMessage',
  idShow: 'sectionShowEquity', // element to show
  getShowElement: function () {
    return this._elShow || (this._elShow = $(this.idShow));
  },
  getUrl: function () {
    return "AjaxProducts.aspx";
  }
}

function processEquity(form) {
  // show equity
  var equityHi = calcEquity(),
      equityLo = Math.max(0, equityHi - window.homeValue.HvbHiValue + window.homeValue.HvbLowValue),
      cashBackHi = equityHi - window.homeValue.Mort2Amount,
      cashBackLo = equityLo - window.homeValue.Mort2Amount;
  var elEquityCalc = document.getElementById('EquityCalculated'),
      elCashBack = document.getElementById('CashBackCalculated');
  if (elEquityCalc) elEquityCalc.value = equityHi;
  if (elCashBack) elCashBack.value = cashBackHi;
  if (equityHi > 0) {
    fillElement('sectionEquity', formatDollars(equityLo, false));
    fillElement($('sectionCashBack'), formatDollars(cashBackLo, false));
    fillElement('sectionEquity2', formatDollars(equityHi, false));
    fillElement($('sectionCashBack2'), formatDollars(cashBackHi, false));
    // display loading message in next page
    injectLoadingAnimation('sectionProductDescriptions');
    // use AJAX to get html for next page (and JSON)
    var qData = Form.serialize(form);
    with (AjaxEquity) AjaxBase.getHTML(getUrl(), method, qData, showProducts, showProductsError);
  }
  var elAmount = document.getElementById('sectionEquityAmount'),
    elError = document.getElementById('sectionEquityError');
  if (elAmount) elAmount.style.display = equityHi > 0 ? 'block' : 'none';
  if (elError) elError.style.display = equityHi <= 0 ? 'block' : 'none';
  // JMH: disabled for Wachovia
  //scrollToAnchor(AjaxEquity.anchor);
  shadeInElement(AjaxEquity.getShowElement());
  largestValidStep = 'sectionGetEquity'; // Note: change this to 'sectionGetPayment' if we don't use a default option like "(Please select a loan)"
  // JMH: fixed for Wachovia
  //showCurrStepImage();
  showProgressSummary();
  return false;
}

function calcEquity() {
  var mort = unformatDollars($('MortgageBalance').value), mort2 = unformatDollars($('SecondMortgageBalance').value);
  mort = Math.max(0, mort || 0); // fix negative values or nulls
  mort2 = Math.max(0, mort2 || 0);
  window.homeValue.Mort1Amount = mort;
  window.homeValue.Mort2Amount = mort2;
  window.homeValue.Equity = Math.floor(Math.max(0, window.homeValue.HvbHiValue - mort));
  return window.homeValue.Equity;
}

function showProducts(html) {
  // JMH: dsiabled for Wachovia
  return;
  var el = $('sectionProductDescriptions');
  fillElement(el, html);
  // this next line should declare window.products and window.locations
  AjaxBase.executeScripts(html);
  fillProductAndLocationLists();
}

function showProductsError(error) {
  injectError(error, AjaxEquity.getFillElement(), AjaxEquity.errorClass);
}

function fillProductAndLocationLists() {
  var locationCode = window.homeAddress ? window.homeAddress.StdState : null;
  fillLocationList(null, locationCode);
  var elLocation = $('selectLocation');
  locationCode = elLocation ? elLocation.value : null;
  // TO DO: preserve productCode if user already selected one?
  // TO DO: check for state selection box before proceeding to fill it
  var productCount = fillProductList(null, locationCode); 
  var elShowLoanTypes = document.getElementById('sectionShowLoanTypes'),
    elShowLoanTypesError = document.getElementById('sectionShowLoanTypesError');
  if (elShowLoanTypes) elShowLoanTypes.style.display = productCount > 0 ? 'block' : 'none';
  if (elShowLoanTypesError) elShowLoanTypesError.style.display = productCount <= 0 ? 'block' : 'none';
}

function fillLocationList(productCode, locationCode) {
  function existsProdLocCombo(productCode, locationCode) {
    for (var i = 0; i < window.products.length; i++)
      if (window.products[i].ProductCode == productCode)
        return window.products[i].Locations.indexOf(locationCode) != null;
    return false;
  }
  var el = $('selectLocation'), count = 0;
  if (el) {
    el.options.length = 0;
    for (var i = 0; i < window.locations.length; i++)
      if (!productCode || existsProdLocCombo(productCode, window.locations[i].LocationCode)) {
        el.options[el.options.length] = new Option(window.locations[i].Name, window.locations[i].LocationCode, window.locations[i].LocationCode == locationCode);
        count++;
      }
  }
  return count;
}

function fillProductList(productCode, locationCode) {
  var el = $('selectProduct'), count = 0;
  if (el) {
    el.options.length = 1; // leave first option in list
    for (var i = 0; i < window.products.length; i++)
      if (!locationCode || window.products[i].Locations.indexOf(locationCode) != null) {
        el.options[el.options.length] = new Option(window.products[i].Name, window.products[i].ProductCode, window.products[i].ProductCode == productCode);
        count++;
      }
  }
  return count;
}

function showDescriptionPopup(source, popupId) {
  var el = $(popupId);
  if (el) {
    if (!window.descriptionPopupDialog) {
      window.descriptionPopupDialog = new ProductDescriptionPopup($('divDescriptionPopupDialog'), $('divDescriptionPopupTitle'), 
        $('divDescriptionPopupTitle'), [$('imgDescriptionPopupClose')], source, $('divDescriptionPopupPlaceholder'), 
        'images/clear.gif', $('divDescriptionPopupShadow'));
      window.descriptionPopupDialog.isReusable = true;
    }
    with (window.descriptionPopupDialog) {
      srcElement = source;
      show(el.innerHTML);
    }
  }
}

// Step 3:

var AjaxLoanType = {
  getUrl: function () {
    var elLoan = $('selectProduct'); 
    var elLocation = $('selectLocation');
    if (!elLoan || !elLoan.value)
      throw ("You must select a loan type.");
    if (!elLocation || !elLocation.value)
      throw ("You must select a location.");
    if (elLoan.value == 'EO')
      return 'AjaxEORates.aspx'; //?productCode=EO&locationCode=' + elLocation.value;
    else
      return 'AjaxPayments.aspx'; //?productCode=HEL&locationCode=' + elLocation.value;
  },
  method: 'POST', 
  errorClass: 'ErrorMessage',
  idFill: 'sectionPayments', // element to receive html
  getFillElement: function () {
    return this._elFill || (this._elFill = $(this.idFill));
  }
}

function validateLoanType() {
  var msg = null, elLoanType = $('selectProduct'), elFocus = null;
  if (elLoanType && (elLoanType.value == null || elLoanType.value == '')) {
    msg = "Please select a loan.";
    elFocus = elLoanType;
  }
  if (elFocus) elFocus.focus();
  if (msg) {
    alert(msg);
    return false;
  }
  else {
    processLoanType();
    return true;
  }
}

function selectLoanType() {
  function findProduct(productCode) {
    for (var i = 0; i < window.products.length; i++) 
      if (window.products[i].ProductCode == productCode)
        return window.products[i];
    return null;
  }
  var elLoanType = $('selectProduct');
  if (elLoanType && !(elLoanType.value == null || elLoanType.value == ''))
    largestValidStep = 'sectionGetEquity2';
  else 
    largestValidStep = 'sectionGetHV2';
  window.products.selectedProduct = findProduct(elLoanType.value); // needed for slider
  // JMH: the following line removed for Wachovia
  //showCurrStepImage();
  showProgressSummary(); // TO DO: this does not show loan type since there was not post / ajax call to set it
}

function processLoanType(form) {
  if (!form) form = $('formLoanType');
  // display loading message in last page
  injectLoadingAnimation('sectionPayments');
  // use AJAX to get html for last page (and JSON)
  var qData = Form.serialize(form);
  with (AjaxLoanType) AjaxBase.getHTML(getUrl(), method, qData, showPayments, showPaymentsError);
  return false;
}

function showPayments(html) {
  showProgressSummary();
  var el = AjaxLoanType.getFillElement();
  fillElement(el, html);
  // this next line should declare window.rates, window.periods, and window.sliderParams
  AjaxBase.executeScripts(html);
  // render slider
  if (typeof window.rates == 'undefined') throw "Rates not found!";
  if (typeof window.sliderParams == 'undefined') throw "Slider Params not found!";
  if (typeof window.periods == 'undefined') throw "Periods not found!";
  if (typeof window.draws == 'undefined') window.draws = {levels: []};
  var params = window.params = new SliderParams(window.sliderParams);
  if (window.products.selectedProduct) {
    // Compute min and max amount on slider
    params.minAmount = Math.max(window.homeValue.Mort2Amount || 0, window.products.selectedProduct.AmountMin || 0);
    params.maxAmount = Math.min(window.homeValue.Equity, window.products.selectedProduct.AmountMax || Number.MAX_VALUE);
  }
  var builder = new SliderBuilder('sliderContainer', 'sliderRates', 'sliderMain', 'sliderAxisLabel',
    params, window.rates);
  builder.formatDollars = formatDollars;
  builder.formatRate = formatRate;
  builder.findRate = findOptimalRate;
  builder.render(); // Note: render() alters params.maxAmount and params.minAmount -> TO DO: change this somehow?
  // create arrow
  var slider = $('sliderMain');
  var arrow = document.createElement('DIV');
  arrow.className = 'sliderArrow';
  arrow.id = 'sliderArrow';
  var sliderMain = $('sliderMain');
  sliderMain.appendChild(arrow);
  var arrowObj = new SliderArrow(arrow, slider, params);
  arrowObj.onStopDrag = updateLoanAmountFromSliderMove;
  sliderMain.onclick = updateLoanAmountFromSliderClick.bindAsEventListener(arrowObj);
  // attach onchange event of input box
  var input = $('textLoanAmount'), inputDraw = $('textDrawAmount'),
    btnUpdate = $('btnUpdateSlider');
  input.onkeypress = updateSliderFromAmount.bindAsEventListener(arrowObj);
  //input.onchange = input.onkeypress; // JMH: removed since new button
  if (btnUpdate) btnUpdate.onclick = input.onkeypress;
  // Initialize text box and grid
  if (inputDraw) { 
    input.value = Math.round(window.draws.defValue * params.maxAmount / window.draws.precision) * window.draws.precision;
    inputDraw.value = Math.round(window.draws.levels[0] * window.draws.defValue * params.maxAmount / window.draws.precision) * window.draws.precision;
    inputDraw.onkeypress = updateGridFromDrawAmount.bindAsEventListener(arrowObj);
    //inputDraw.onchange = inputDraw.onkeypress; // JMH: removed since new button
  }
  else 
    input.value = Math.round(window.periods.defValue * params.maxAmount / window.periods.precision) * window.periods.precision;
  input.onkeypress({target: input}); // TO DO: hack alert!
}

function showPaymentsError(error) {
  injectError(error, AjaxLoanType.getFillElement(), AjaxLoanType.errorClass);
}

// TO DO: move next 2-3 functions to SliderArrow object?
function updateLoanAmountFromSliderClick(event) { // should be bound to arrowObj!
  var x = event.clientX + (document.documentElement || document.body).scrollLeft;
  var sliderMain = $('sliderMain');
  x = this._nearestTickMark(x - Floater.getClientPos(sliderMain).X - this._params.arrowOffset);
  this.slideTo(x);
  this.onStopDrag(this, new Point(x, 0));
}

function updateLoanAmountFromSliderMove(arrowObj, pos) {
  var params = arrowObj._params;
  var amount = (pos.X + 11) / params.sliderWidth * (params.maxAmount - params.minAmount) + params.minAmount; // TO DO: get width from params
  amount = Math.min(Math.max(Math.round(amount / params.dollarsPerTick, 0) * params.dollarsPerTick, params.minAmount), params.maxAmount);
  var el = $('textLoanAmount');
  if (el)
    el.value = amount;
  updateDrawAmount(amount);
  updateCashBack(amount);
  updateResultsGrid(amount);
}

function updateSliderFromAmount(event) {
  //event = event || window.event;
  var key = event.keyCode || event.charCode;
  if (key > 0 && key != 13) // can be called by keypress or change (blur)
    return true;
  else
    Event.stop(event); // stop form submittal by Enter (#13)
  var target = $('textLoanAmount');
  var params = this._params;
  var amount = unformatDollars(target.value);
  amount = isNaN(amount) 
    ? params.minAmount 
    : Math.min(Math.max(Math.round(amount / params.dollarsPerTick, 0) * params.dollarsPerTick, params.minAmount), params.maxAmount);
  target.value = amount;
  this.slideTo((amount - params.minAmount) * params.sliderWidth / (params.maxAmount - params.minAmount) - 11);
  updateDrawAmount(amount);
  updateCashBack(amount);
  updateResultsGrid(amount);
}

function updateDrawAmount(lineAmount) {
  var el = $('textDrawAmount');
  var params = window.params;
  if (el) { // may be the payments screen!!!
    var newValue = isNaN(el.value) ? 0 : el.value;
    newValue = Math.min(el.value, lineAmount); // don't exceed line amount
    el.value = Math.round(newValue / window.draws.precision) * window.draws.precision;
  }
}

function updateCashBack(lineAmount) {
  var el = $('textCashBack');
  if (el)
    el.innerHTML = formatDollars(Math.max(lineAmount - (window.homeValue.Mort2Amount || 0), 0), true);
}

function updateGridFromDrawAmount(event) {
  //event = event || window.event;
  var key = event.keyCode || event.charCode;
  if (key > 0 && key != 13) // only continue if user hit enter or tab
    return true;
  var elDraw = $('textDrawAmount'), elAmount = $('textLoanAmount');
  var draw = unformatDollars(elDraw.value), amount = unformatDollars(elAmount.value);
  if (!isNaN(draw) && !isNaN(amount) && amount != 0) {
    draw = Math.min(draw, amount);
    //window.draws.levels[0] = draw / amount; // JMH: we no longer track draw to amount by ratio
    updateDrawAmount(amount); // Yes. Amount.  // TO DO: this is a bit convoluted!
    updateCashBack(amount);
    updateLineResultsGrid(amount, draw);
  }
}

function updateResultsGrid(amount) {
  var elLoan = $('selectProduct');
  if (elLoan && elLoan.value == 'EO') { // TO DO: this is not reusable or maintainable: use inheritance/override
    var el = $('textDrawAmount'), draw = 0;
    if (el) draw = unformatDollars(el.value);
    return updateLineResultsGrid(amount, draw);
  }
  else
    return updateLoanResultsGrid(amount);
}

function updateLineResultsGrid(lineAmount, drawAmount) {
  var grid = $('tableResults');
  if (grid && window.draws && window.draws.levels) {
    for (var i = 0; i < window.draws.levels.length; i++) {
      var amount = window.draws.levels[i] * lineAmount;
      amount = i > 0 ? Math.min(Math.round(amount / window.draws.precision) * window.draws.precision, lineAmount): drawAmount;
      var rateObj = findRate(lineAmount, 0);
      var row = $('trResult' + i); // TO DO: don't hard-code this id, use string.inject and a constant
      var spans = row.getElementsByTagName('SPAN');
      for (var s = 0; s < spans.length; s++) {
        var output = 'N/A';
        var className = spans[s].className;
        if (className.indexOf('paymentTarget') >= 0) {
          if (rateObj && rateObj.PaymentPer1000[0]) {
            output = amount > 0 ? Math.max(rateObj.PaymentPer1000[0] * amount / 1000.0, 
              window.products.selectedProduct ? window.products.selectedProduct.PaymentMin : 0) : 0;
            output = formatDollars(output, true);
          }
        }
        else if (className.indexOf('currentTarget') >= 0) {
          if (rateObj) 
            output = formatPercent(rateObj.IsVariable ? rateObj.Rate + rateObj.BaseRate : rateObj.Rate, 2, true);
        }
        else if (className.indexOf('rateTarget') >= 0) {
          if (rateObj) 
            output = formatRate(rateObj);
        }
        else if (className.indexOf('amountTarget') >= 0)
          output = amount;
        spans[s].innerHTML = output;
      }
      if (Browser.isIE) 
        row.style.display = 'block';
      else
        row.style.display = 'table-row';
    } // for (var i = 0; i < window.draws.levels.length; i++)
  }
}

function updateLoanResultsGrid(amount) {
  var grid = $('tableResults');
  if (grid && window.periods && window.periods.periods) {
    for (var i = 0; i < window.periods.periods.length; i++) {
      var year = window.periods.periods[i];
      var rateObj = findRate(amount, year);
      var row = $('trResult' + i); // TO DO: don't hard-code this id, use string.inject and a constant
      var spans = row.getElementsByTagName('SPAN');
      for (var s = 0; s < spans.length; s++) {
        var output = 'N/A';
        var className = spans[s].className;
        if (className.indexOf('paymentTarget') >= 0) {
          if (rateObj && rateObj.PaymentPer1000[year]) 
            output = formatDollars(rateObj.PaymentPer1000[year] * amount / 1000.0, true);
        }
        else if (className.indexOf('rateTarget') >= 0) {
          if (rateObj) 
            output = formatPercent(rateObj.Rate, 2, true);
        }
        else if (className.indexOf('periodTarget') >= 0)
          output = year;
        spans[s].innerHTML = output;
      }
      if (Browser.isIE) 
        row.style.display = 'block';
      else
        row.style.display = 'table-row';
    } // for (var i = 0; i < window.periods.periods.length; i++)
  }
}


// TO DO: move this to a top-level class to manage rates...?
function findRate(amount, years) { // TO DO: pass in ltv?
  var value = window.homeValue.HvbHiValue; 
  // Calculate Combined LTV
  var ltv = Math.min(1.0, (amount + window.homeValue.Mort1Amount) / value);
  var rates = window.rates;
  var rate = null;
  // search for rate that satisfies requirements
  if (rates) 
    for (var i = 0; i < rates.length && rate == null; i++) {
      if ((ifNull(rates[i].TermMax, Number.MAX_VALUE) >= years && ifNull(rates[i].TermMin, -Number.MAX_VALUE) < years) 
          && (ifNull(rates[i].AmountMax, Number.MAX_VALUE) > amount && ifNull(rates[i].AmountMin, -Number.MAX_VALUE) <= amount) 
          && (ifNull(rates[i].LtvMax, Number.MAX_VALUE) >= ltv && ifNull(rates[i].LtvMin, -Number.MAX_VALUE) < ltv)
          && (!window.isValidRate || window.isValidRate(rates[i], amount, years)))
        rate = rates[i];
    }
  //$('underSlider').innerHTML += i + ' = ' + serializeObj(rate) + ' -- ';
  return rate;
}
function findOptimalRate(amount) { // TO DO: pass in ltv?
  var value = window.homeValue.HvbHiValue; 
  // Calculate Combined LTV
  var ltv = Math.min(1.0, (amount + window.homeValue.Mort1Amount) / value);
  var rates = window.rates;
  var rate = null;
  // search for best rate for given amount
  if (rates) 
    for (var i = 0; i < rates.length; i++) {
      if ((ifNull(rates[i].AmountMax, Number.MAX_VALUE) > amount && ifNull(rates[i].AmountMin, -Number.MAX_VALUE) <= amount) 
          && (ifNull(rates[i].LtvMax, Number.MAX_VALUE) >= ltv && ifNull(rates[i].LtvMin, -Number.MAX_VALUE) < ltv)
          && (!window.isValidRate || window.isValidRate(rates[i], amount)))
        if (!rate || rates[i].Rate < rate.Rate)
          rate = rates[i];
    }
  //$('underSlider').innerHTML += i + ' = ' + serializeObj(rate) + ' -- ';
  return rate;
}
function ifNull(value, defValue) {
  return (value == null) ? defValue : value;
}

function serializeObj(obj) {
  var result = '{', delim = '';
  for (var i in obj) {
    result += delim + i + ':' + obj[i];
    if (delim == '') delim = ', ';
  }
  return result;
}

// Step 4:

function processLoan(form) {
  return false; // nothing to do here
}

// Other functions

function shadeInElement(el) {
  new ShadeEffect(el);
}

function scrollToAnchor(anchor) {
//  var url = window.location.href, hashPos = url.lastIndexOf('#')
//  if (hashPos > 0) url = url.substr(0, hashPos - 1);
  window.location.href = '#' + anchor;
}

function injectError(msg, element, className) {
  var el = $(element);
  var span = document.createElement('SPAN');
  span.innerHTML = msg;
  span.className = className;
  el.appendChild(span);
}

function injectLoadingAnimation(element) {
  var img = new Image(), el = $(element);
  fillElement(el, "");
  img.src = "images/loading.gif"; // TO DO: don't hard-code
  el.appendChild(img);
}

function fillElement(element, html) {
  element = $(element);
  if (element) element.innerHTML = html;
  if (element && html == '') { // IE does not remove everything, it leaves blank lines
    for (var i = 0; i < element.childNodes.length; i++)
      element.removeChild(element.childNodes[i]);
  }
}

function formatPercent(amount, precision, forceDigits) {
  precision = precision || 2;
  var precExp = Math.pow(10, precision);
  amount = parseFloat(amount);
  if (isNaN(amount)) 
    amount = '0';
  else
    amount = '' + Math.round(amount * precExp * 100) / precExp;
  if (forceDigits) {
    var dotPos = amount.indexOf('.');
    amount += dotPos == -1 ? '.00000000000000000000' : '00000000000000000000'; // only works to precision == 20
    amount = amount.substr(0, 1 + Math.abs(dotPos) + precision);
  }
  return amount + '%';
}

function formatRate(rateObj) {
  if (rateObj.Abbrev)
    return rateObj.Abbrev + (rateObj.Rate >= 0 ? '+' : '-') + formatPercent(Math.abs(rateObj.Rate), 2, true);
  else
    return formatPercent(rateObj.Rate, 2, true);
}

function formatDollars(amount, showCents) {
  if (typeof showCents == 'undefined') showCents = 'auto';
  var result = "";
  amount = unformatDollars(amount);
  var isNeg = amount != Math.abs(amount);
  amount = isNaN(amount) ? "0.00" : "" + Math.abs(Math.round(amount * 100) / 100);
  amount = amount.split('.');
  for (var i = 1; i <= amount[0].length; i++) {
    result = amount[0].substr(amount[0].length - i, 1) + (i != 1 && i % 3 == 1 ? "," : "") + result;
  }
  if (showCents == 'auto') {
    if (amount.length > 1)
      result += '.' + (amount[1] + '00').substr(0, 2);
  }
  else if (showCents) {
    if (amount.length > 1)
      result += '.' + (amount[1] + '00').substr(0, 2);
    else 
      result += '.00';
  }
  return (isNeg ? "-$" : "$") + result;
}

function unformatDollars(text) {
  text = '' + text;
  var amount = parseFloat(text.replace(/[\$\,]/g, ''));
  return isNaN(amount) ? 0 : amount;
}

// Shade-in

/* From Effects.js */
String.prototype.camelize = function() {
  var strings = this.split('-');
  var camelizedString;
  if (strings.length == 1) 
    camelizedString = strings[0];
  else {
    strings[0] = this.charAt(0) == '-'
      ? strings[0].charAt(0).toUpperCase() + strings[0].substring(1)
      : strings[0];
    for (var i = 1; i < strings.length; i++) {
      var s = strings[i];
      strings[i] = s.charAt(0).toUpperCase() + s.substring(1);
    }
    camelizedString = strings.join('');
  }
  return camelizedString;
}
  
Object.extend(Element, {
  setStyle: function(element, style) {
    element = $(element);
    for(k in style) element.style[k.camelize()] = style[k];
  }
});

var ShadeOptions = { // defaults
  delayedStart: false,
  duration: 0.5, // seconds
  interval: 0.03, // seconds
  startStyles: {visibility: 'visible'}
};

var ShadeEffect = Class.create();
ShadeEffect.prototype.extend({
  initialize: function (element, options) {
    this._element = element;
    this.extend(ShadeOptions); // set defaults
    this.extend(options || {});
    if (!this.delayedStart) this.start();
  },
  start: function () {
    this._origHeight = this._getHeight();
    this._origTop = this._getTop(); 
    this._setTop(-this._origHeight);
    Element.setStyle(this._element, this.startStyles);
    Element.setOpacity(this._element, 0.0);
    this._startTime = new Date();
    this._thread = setInterval(this.loop.bind(this), this.interval * 1000);
  },
  finish: function () {
    this._setTop(this._origTop);
    this._setOpacity(Browser.isFF10 ? 0.9999 : 1.0); // don't use 1.0 since it causes FF 1.0 to flicker
  },
  loop: function () {
    var delta = ((new Date()).getTime() - this._startTime.getTime()) / this.duration / 1000.0;
    if (delta >= 0.9999)
      this.stop();
    else {
      this._setTop(this._origHeight * (delta - 1));
      this._setOpacity(Math.min(delta, 0.9999)); 
    }
  },
  stop: function () {
    clearInterval(this._thread);
    this.finish();
  },
  _setTop: function (top) { 
    this._element.style.top = Element.intToPx(top);
  },
  _getTop: function () { 
    return this._element.offsetTop; 
  },
  _setHeight: function (height) { 
    this._element.style.height = Element.intToPx(height);
  },
  _getHeight: function () { 
    return this._element.offsetHeight; 
  },
  _setOpacity: function (factor) {
    var opacity = factor * factor * factor; // stays transparent longer
    Element.setOpacity(this._element, opacity);
  }
});

/* class ProductDescriptionPopup */
var ProductDescriptionPopup = Class.create();
ProductDescriptionPopup.prototype.extend(new ModalDialog()).extend(new DraggableDialog()).extend(new MacZoomDialog()).extend({
  initialize: function (draggedElement, grabElement, titleElement, cancelElements, srcElement, placeholderElement, loadingImage, shadowElement) {
    this.loadingImage = loadingImage;
    this.placeholderElement = placeholderElement;
    this._maxTop = Element.pxToInt(Element.findStyleProperty(draggedElement, 'top'));
    ModalDialog.prototype.initialize.call(this, draggedElement, null, 0.2, 'silver');
    DraggableDialog.prototype.initialize.call(this, draggedElement, grabElement, titleElement, [], cancelElements, shadowElement);
    MacZoomDialog.prototype.initialize.call(this, draggedElement, titleElement, [], cancelElements, srcElement, loadingImage, shadowElement);
  },
  show: function (html) {
    MacZoomDialog.prototype.show.call(this);
    if (html) this.placeholderElement.innerHTML = html;
  },
  hide: function () {
    MacZoomDialog.prototype.hide.call(this);
    ModalDialog.prototype.hide.call(this);
  },
  showDialog: function () {
    MacZoomDialog.prototype.showDialog.call(this);
    ModalDialog.prototype.show.call(this);
  },
  _moveElementTo: function (x, y) {
    if (Browser.isIE)
      y = Math.max(y, this._maxTop); // don't go above drop-downs (IE bug)
    Floater.setOffset(this.draggedElement, x, y);
  }
});
ProductDescriptionPopup.prototype.constructor = ProductDescriptionPopup;
