// By       : John Borchert  jborchert@email.com
// Date     : March 14, 1999
// Language : JavaScript 
// Program  : Mortgage Amortization Schedule
// Version  : V1.1

// ******************************************
// amortization schedule form functions
// ******************************************

// eliminate commas from string to allow math
function stripOutCommas( strold )
{  var strnew = "";
   var i;
   for ( i = 0; i < strold.length; i++)
   {  var chr = strold.substring(i, i+1);
      if (chr != ",")
         { strnew = strnew + chr; }
   }
   return strnew;
}

// Ensure that form field is a valid number.
// If 2nd parameter is true, then number is allowed to end with %
function isValidNumber(str, percentSignOk )
{   var flag = 0;
    var i;
    for ( i = 0; i < str.length; i++)
    {  var chr = str.substring(i, i+1);
       if ( !( (percentSignOk) && (i==str.length-1) && (str.length!=1) && (chr=="%") ) )
       {  if (((chr < "0") || (chr > "9")) && (chr != "."))
             {flag = 1;}
       }
    }

    if ((flag == 1) || (str.length == 0))
    {  return false;}
    else {return true;}
}

// if invalid entry then write invalid in field and highlight
function setFieldError(infield)
{  infield.value = 'invalid';
   infield.focus();
   infield.select();
   return true;
}

// called by field validation functions if successful entry
function writeOkWindowStatus()
{  window.status = "Entry ok.";
   return true;
}

// check if principal amount is valid
function isValidPrincipal(principal)
{  principal.value = stripOutCommas( principal.value );

   if ( !isValidNumber( principal.value, false ) )
   {  window.status = "Entry Error: Please enter a number for principal amount.";
      setFieldError( principal );
      return false;
   }
   else
   { if ( principal.value == "0" )
     {  window.status = "Entry Error: Please enter a principal amount greater than zero.";
        setFieldError( principal );
        return false;
     }
     else {writeOkWindowStatus();}
   }
   return true;
}

// check if years is valid
function isValidYears(years)
{  years.value = stripOutCommas( years.value );

   if ( !isValidNumber(years.value, false) || (parseFloat(years.value) < 1) || (parseFloat(years.value) > 50) )
   {  window.status = "Entry Error: Please enter a number from 1 to 50 for amortization years.";
      setFieldError( years );
      return false;
   }
   else {writeOkWindowStatus();}
   return true;
}

// check if nominal rate is valid
function isValidNominalRate(nominalRate)
{  nominalRate.value = stripOutCommas( nominalRate.value );

   if ( !isValidNumber(nominalRate.value, true) || (parseFloat(nominalRate.value) < 1) || (parseFloat(nominalRate.value) > 100) )
   {  window.status = "Entry Error: Please enter a number from 1 to 100 for nominal rate.";
      setFieldError( nominalRate );
      return false;
   }
   else {writeOkWindowStatus();}
   return true;
}

// check if first payment year is valid
function isValidFirstYear(firstYear)
{  firstYear.value = stripOutCommas( firstYear.value );

   if ( !isValidNumber(firstYear.value, false) || (parseFloat(firstYear.value) < 1950) || (parseFloat(firstYear.value) > 2010) )
   {  window.status = "Entry Error: Please enter a starting year from 1950 to 2010.";
      setFieldError( firstYear );
      return false;
   }
   else
   // eliminate any decimal points
   {  firstYear.value = parseInt(firstYear.value);
      writeOkWindowStatus();    
   }
   return true;
}

// Top level routine called by Calculate button
function runCalc(form)
{ if ( (isValidPrincipal(form.principal)) && (isValidYears(form.years)) && (isValidNominalRate(form.nominalRate)) && (isValidFirstYear(form.firstYear)))
  { principal= eval(form.principal.value);
    years    = eval(form.years.value);
    
    var tempRate = form.nominalRate.value
    if (tempRate.substring(tempRate.length-1, tempRate.length) == "%")
       {nominalRate = eval(tempRate.substring(0,tempRate.length-1))/100.0;} 
    else {nominalRate = eval(tempRate)/100.0;}

    compoundedDesc = form.compounded.options[form.compounded.selectedIndex].text;
    termDesc       = form.term.options[form.term.selectedIndex].text;
    firstMonth     = form.firstMonth.selectedIndex;
    firstYear      = eval(form.firstYear.value);
    monthsInTerm   = eval(form.term.options[form.term.selectedIndex].value);

    if ( monthsInTerm > (years*12) )
    { window.status = "Entry Error: Amortization years must be greater than or equal to term.";
      setFieldError( form.years );
    }
    else 
    { monthlyAmortization() }
  }
  return true;
}

// ******************************************
// amortization schedule generation functions
// ******************************************

// format number to dollar and cents and convert to string
function roundTwoDecimal(n)
{ var str1 = "" + Math.round(n * 100);
  var len = str1.length;

  // if positive number that is 1 digit long
  if (len == 1)
  {  str1 = "0" + str1;
     len = 2;
  }
  else
  {  // if negative number that is 1 digit long
     if ((len == 2) && (str1.substring(0,1)=="-"))
     {  str1 = "-0" + str1.substring(1,2);
        len = 3;
     }
  }
  return str1.substring(0,len-2) + "." + str1.substring(len-2,len);
}

// Calculates the equivalent monthly periodic rate of interest based
// on a given nominal rate and compounding frequency 
function monthlyIntRateCalc()
{  var imo;

   if (compoundedDesc=="Annual")
      {imo = Math.pow(1+nominalRate, 1/12) - 1;}
   else
      {if (compoundedDesc=="Semi-Annual")
         {imo = Math.pow(1+(nominalRate/2), 2/12) - 1;}
       else
          {if (compoundedDesc=="Quarterly")
             {imo = Math.pow(1+(nominalRate/4), 4/12) - 1;}
           else
             {imo = nominalRate/12;}
          }
      }
  return imo;
}

// Calulates the monthly payment amount
function monthly(monthlyInterestRate)
{
  var payments = years * 12;
  var monthlyAmt = (principal * monthlyInterestRate) / (1 - (1 / Math.pow(1 + monthlyInterestRate, payments)));

  if (Math.round(monthlyAmt * 100) < (monthlyAmt*100))
    {
    roundedMonthlyAmt = roundTwoDecimal( monthlyAmt+.01 );
    }
  else
    {
    roundedMonthlyAmt = roundTwoDecimal( monthlyAmt );
    }

  return roundedMonthlyAmt;
}



// Main routine that calculates principal and interest payments, and balances,
// for the mortgage amortization schedule desired.  It presents this info
// in a table on a separate window frame, ready for printing.
function monthlyAmortization()
{ 
  var monthsInAmortization=years*12;
  var monthlyIntRate=monthlyIntRateCalc();
  var monthlyPayment=monthly(monthlyIntRate);
 
  var writeToDoc = '';
  writeToDoc += "<input type='button' value='<<' onClick='showHide();'>&nbsp;<input type='button' value='Print' onClick='window.print();'>";

  // top table containing input values
  writeToDoc += "<table border='1' width='100%' cellpadding='4' cellspacing='0'>";
  writeToDoc += "<tr>";
  writeToDoc += "<td width='20%'><b>Amount</b></td>";
  writeToDoc += "<td width='20%'>$" + roundTwoDecimal(principal) + "</td>";
  writeToDoc += "<td width='30%'><b>Compounded</b></td>";
  writeToDoc += "<td width='30%'>" + compoundedDesc +"</td>";
  writeToDoc += "</tr>";

  writeToDoc += "<tr>";
  writeToDoc += "<td><b>Years</b></td>";
  writeToDoc += "<td>" + years + "</td>";
  writeToDoc += "<td><b>Interest Rate</b></td>";
  writeToDoc += "<td>" + roundTwoDecimal(nominalRate*100.0)+"%</td>";
  writeToDoc += "</tr>";

  writeToDoc += "<tr>";
  writeToDoc += "<td><b>Term</b></td>";
  writeToDoc += "<td>" + termDesc +"</td>";
  writeToDoc += "<td><b>Monthly Payment</b></td>";
  writeToDoc += "<td>$" + monthlyPayment + "</td>";
  writeToDoc += "</tr>";

  writeToDoc += "</table>";
  writeToDoc += "<br>";

  // bottom table containing computed values
  writeToDoc += "<table border='1' width='100%' cellpadding='4' cellspacing='0'>";
  writeToDoc += "<tr>";
  writeToDoc += "<td width='5%'>#</th>";
  writeToDoc += "<td width='25%'>Payment</td>";
  writeToDoc += "<td width='20%'>Principal</th>";
  writeToDoc += "<td width='20%'>Interest</th>";
  writeToDoc += "<td width='25%'>Balance</th>";
  writeToDoc += "</tr>";
  
  var interestPayment;
  var principalPayment;
  var balance = principal;
  var interestPaymentSum = 0;
  var principalPaymentSum = 0;
  var monthNum = firstMonth;
  var yearNum = firstYear;
  var i;

  for (i=1; i<=monthsInTerm; i++)
  { interestPayment=roundTwoDecimal(balance*monthlyIntRate);
    principalPayment=roundTwoDecimal(monthlyPayment - interestPayment);
    balance = roundTwoDecimal(balance - principalPayment);

    if (eval(balance) < 0)
    {  principalPayment = roundTwoDecimal(eval(principalPayment) + eval(balance));
       monthlyPayment   = roundTwoDecimal(eval(monthlyPayment) + eval(balance));
       balance = ".00";
    }

    interestPaymentSum = interestPaymentSum + eval(interestPayment);
    principalPaymentSum = principalPaymentSum + eval(principalPayment)

    writeToDoc += "<tr>";
    writeToDoc += "<td align='center'><b>"+i+"</b></td>";
    writeToDoc += "<td align='center'>"+monthlyPayment+"</td>";
    writeToDoc += "<td align='center'>"+principalPayment+"</td>";
    writeToDoc += "<td align='center'>"+interestPayment+"</td>";
    writeToDoc += "<td align='center'>"+balance+"</td>";
    writeToDoc += "</tr>";

    if ((monthNum == 11) || (i==monthsInTerm))
      {  writeToDoc += "<tr>";
         writeToDoc += "<td colspan='2' align='center'><b>Totals for " + yearNum + "</b></td>";
         writeToDoc += "<td align='center'><b>" + roundTwoDecimal(principalPaymentSum) + "</b></td>";
         writeToDoc += "<td align='center'><b>" + roundTwoDecimal(interestPaymentSum) + "</b></td>";
         writeToDoc += "<td></td>";
         writeToDoc += "</tr>";

         monthNum = 0;
         yearNum++;
         interestPaymentSum = 0;
         principalPaymentSum = 0;
      } 
    else {monthNum++;}
  }

  writeToDoc += "</table>"; // end of table containing computed values
  
  document.getElementById('results').innerHTML = writeToDoc;
  document.getElementById('results').style.visibility = 'visible';
  document.getElementById('results').style.display = 'inline';
  document.getElementById('calculator').style.visibility = 'hidden';
  document.getElementById('calculator').style.display = 'none';
  return true;
}


// ***********************************************
// help functions for amortization schedule form.
// Important to be first functions to load, 
// to reduce chance of functions not being found
// if option clicked while page is loading
// ***********************************************
function helpHowToUse()
{ alert("HOW TO USE\n\n1. Fill in the fields.\n\n2. Press the Calculate button (may take up to a minute).\n\n3. To print the schedule, click the Print Icon on your browser's toolbar or select Print from your browser's File menu.\n\n4. To try another amortization, just repeat steps 1 through 3.");

  return;
}

function helpOnField( fld )
{ if (fld == "amount")
  { alert( "AMOUNT: The mortgage loan dollar amount. Valid values are positive and non-zero." ); }

  if (fld == "years")
  { alert( "YEARS: This is the amortization period, which is the no. of years to fully pay off the mortgage. Residential mortgages are typically offered with maximum amortization periods of 25 years.  In Canada, it is generally longer that the term (length of interest guarantee). In the US, it is often the same period of time as the term. Valid values are from 1 to 50." ); }

  if (fld == "term")
  { alert( "TERM: This is the length of time the mortgage is guaranteed at a particular rate.  At the end of the term the lender can demand payment of the balance owing on the loan. In Canada, borrowers on average take a 5 year term. The term value also determines how many calculation lines are displayed / printed. Valid values are available on the dropdown box and must be equal to, or less than the amortization period (years)." ); }

  if (fld == "rate")
  { alert( "RATE(%): The interest rate available from the lender. The rate should correspond to the rate offered for the length of term. Valid values are from 1 to 100." ); }

  if (fld == "compounded")
  { alert( "COMPOUNDED: This is the frequency per year that interest is to be calculated on the mortgage. In Canada the standard is 'semi-annual'.  In the US the standard is 'monthly'." ); }

  if (fld == "firstmonth")
  { alert( "1st MONTH: This is the month that the funds from the mortgage are advanced. The program only handles monthly 'payment' frequencies at this time, and assumes receiving funds at the 1st of the month, and calculates the payment for the end of the month. Valid values are available on the dropdown box." ); }

  if (fld == "firstyear")
  { alert( "1st YEAR: This is the year that the funds from the mortgage are advanced. Valid values are from 1950 to 2010." ); }

  return;
}

function showHide() {
  document.getElementById('results').style.visibility = 'hidden';
  document.getElementById('results').style.display = 'none';
  document.getElementById('calculator').style.visibility = 'visible';
  document.getElementById('calculator').style.display = 'inline';
}

