/****************************************************************************/
function MakeArray(n)
{
    this.length = n;
    for(var i = 1; i <= n; i++)
	this[i] = 0;
    return this;
}

/****************************************************************************/
function IncreaseArray(Sz_, Ptr_)
{
  var i;
  var OldSz_ = Ptr_.length;
  var Size_ = Sz_ + OldSz_;
  var OldArr_ = Ptr_;
  
  Ptr_ = new MakeArray(Size_);
  for (i = 1; i <= OldSz_; ++i)
    Ptr_[i] = OldArr_[i];

  return Ptr_;
}

/****************************************************************************/
function FormatMoneyValue(amount){
  if(amount == Math.floor(amount)){
  		return amount + '.00';
  }else if(amount*10 == Math.floor(amount*10)){
  		return amount + '0';
  }else{
  		var str = amount.toString();
		return str.substr(0,str.indexOf(".")+3);
  }
}

/****************************************************************************/
function PaymentInfo()
{
  this.Payment;
  this.Principal;
  this.Interest;
  this.Balance;
};

/****************************************************************************/
function Mortgage()
{
  this.SOLVEPAYMENT;
  this.SOLVETIME;
  this.SOLVERATE;
  
  this.LONGESTPERIOD;
  this._SolveFor;
  this.TotalTime;
  this.TotalPeriods;
  this.LoopIter;
    
  this._PayInfo;
  this._ArrSz;

  this.Payment;
  this.PayPeriod;

  this.Price;
  this.Rate;
  this.Compounding;
  this.Effective;

  this.EPSILON;
  this.Min;
  this.Max;
  this.LowerBound;
  this.MinPayment;

  this.msgWindow;

  this.InitMortgage = InitMortgage;
  this.EnterButton = EnterButton;
  
  this.SolveFor = SolveFor;
  this.SetTotalTime = SetTotalTime;
  this.SetPayment = SetPayment;
  this.SetPayPeriod = SetPayPeriod;
  this.SetPrice = SetPrice;
  this.SetRate = SetRate;
  this.SetCompounding = SetCompounding;

  this.MakePaymentArray = MakePaymentArray;
  this.IncreasePaymentArray = IncreasePaymentArray;
  
  this.SetupData = SetupData;
  this.FindMinRate = FindMinRate;
  this.FindMaxRate = FindMaxRate;
      
  this.Amortize = Amortize;
  this.FindPayment = FindPayment;
  this.FindRate = FindRate;
  this.FindTime = FindTime;
  this.FindResults = FindResults;
  this.CheckArguments = CheckArguments;
    
  this.Display = Display;
}

/****************************************************************************/
function MakePaymentArray(Sz_)
{
  var i;

  this._PayInfo = new MakeArray(Sz_);
  for (i = 1; i <= Sz_; ++i)
    this._PayInfo[i] = new PaymentInfo;

  return this._PayInfo;
}

/****************************************************************************/
function IncreasePaymentArray(Sz_)
{
  var i;
  var OldSz_ = this._PayInfo.length;
  var Size_;
  
  this._PayInfo = IncreaseArray(Sz_, this._PayInfo);
  
  Size_ = Sz_ + OldSz_;
  for (i = OldSz_ + 1; i <= Size_; ++i)
    this._PayInfo[i] = new PaymentInfo;

  this._PayInfo.length = Size_;
  return this._PayInfo;
}

/****************************************************************************/
/****************************************************************************/
function InitMortgage()
{
  this._PayInfo = MakePaymentArray(50);

  this.SOLVEPAYMENT = 0;
  this.SOLVETIME = 1;
  this.SOLVERATE = 2;  

  this.LONGESTPERIOD = 6000;
  this.EPSILON = .001;
      
  this._SolveFor = 0;
  this.LoopIter = 0;
  this.TotalTime = 0;
  this.Payment = 0;
  this.PayPeriod = 0;

  this.Price = 0;
  this.Rate = 0;
  this.Compounding = 0;

  this.Effective = 0;
  this.Min = 0;
  this.Max = 0;
  this.LowerBound = 0;
  this.MinPayment = 0;  
}

/****************************************************************************/
function SetupData()
{
  var Exp_ = this.Compounding / this.PayPeriod;
  this.Effective = 1 + (this.Rate / this.Compounding);
  this.Effective = Math.pow(this.Effective, Exp_);
  
  this.TotalPeriods = Math.floor(this.TotalTime * this.PayPeriod);

  this.LowerBound = this.Price * (this.Effective - 1);
  this.Min = this.LowerBound + .01;
  this.Max = this.Price * (this.Effective - 1) + this.Price / Math.floor(this.TotalTime * this.PayPeriod);
  this.MinPayment = this.Price / this.TotalPeriods;
}

/****************************************************************************/
function FindMaxRate()
{
  var Exp_ = this.Compounding / this.PayPeriod;
  this.Rate = this.Compounding * (Math.exp(Math.log(this.Min / this.Price + 1) / Exp_) - 1);
  
  this.Effective = 1 + (this.Rate / this.Compounding);
  this.Effective = Math.pow(this.Effective, Exp_);
  this.TotalPeriods = Math.floor(this.TotalTime * this.PayPeriod);

  return this.Rate;
}

/****************************************************************************/
function FindMinRate()
{
  var Exp_ = this.Compounding / this.PayPeriod;
  this.Rate = this.Compounding * (Math.exp(Math.log((this.Payment - this.Price / Math.floor(this.TotalTime * this.PayPeriod)) / this.Price + 1) / Exp_) - 1);

  this.Effective = 1 + (this.Rate / this.Compounding);
  this.Effective = Math.pow(this.Effective, Exp_);
  this.TotalPeriods = Math.floor(this.TotalTime * this.PayPeriod);

  return this.Rate;
}

/****************************************************************************/
function Amortize(Pay_)
{
  var i;
  var Result_ = this.Price;
  var Interest_;
  var Principal_;
  var OldBalance_;
  var Limit_;

  Limit_ = (this._SolveFor == this.SOLVEPAYMENT || this._SolveFor == this.SOLVERATE) ?
                      this.TotalPeriods:
                      this.LONGESTPERIOD;

  this._PayInfo[1].Payment = 0;
  this._PayInfo[1].Principal = 0;
  this._PayInfo[1].Interest = 0;
  this._PayInfo[1].Balance = this.Price;

  this.LoopIter = 1;
  for (i = 1; i <= Limit_ && Result_ > 0; ++i)
  {
    if ((i + 1) > this._PayInfo.length)
      this._PayInfo = this.IncreasePaymentArray(50);
  
    OldBalance_ = Result_;
    Result_ *= this.Effective;

    Interest_ = Result_ - OldBalance_;
    this._PayInfo[i + 1].Interest = Interest_;
    
    Principal_ = Pay_ - Interest_;
    this._PayInfo[i + 1].Principal = Principal_;
    
    Result_ -= Pay_;
    this._PayInfo[i + 1].Payment = Pay_;

    this._PayInfo[i + 1].Balance = Result_;
    ++this.LoopIter;
  }

  return Result_;
}

/****************************************************************************/
function FindTime()
{
  var Result_ = this.Amortize(this.Payment);

  this.TotalPeriods = this.LoopIter - 1;
  this.TotalTime = this.TotalPeriods / this.PayPeriod;
    
  if (Result_ < 0)
  {
    this._PayInfo[this.LoopIter].Payment = this.Payment + Result_;
    this._PayInfo[this.LoopIter].Balance = 0;
  }

  return Result_;
}

/****************************************************************************/
function FindPayment(Min_, Max_)
{
  var Midpoint_;
  var Result_;

  while (1)
  {
    if (this.Rate <= 0)
      Midpoint_ = this.Min;
    else
      Midpoint_ = (Min_ + Max_) / 2;
      
    Result_ = this.Amortize(Midpoint_);
  
    if (Result_ < 0 - this.EPSILON || this.LoopIter < this.TotalPeriods + 1)
      Max_ = Midpoint_;
    else if (Result_ > 0 + this.EPSILON)
      Min_ = Midpoint_;
    else
      break;
  }

  this._PayInfo[this.TotalPeriods + 1].Balance = 0;
  return Midpoint_;  
}

/****************************************************************************/
function FindRate()
{
  var Min_ = this.FindMinRate();
  var Max_ = this.FindMaxRate();

  var Midpoint_;
  var Result_;

  while (1)
  {
    if (this.Payment < this.MinPayment + .01)
      Midpoint_ = Min_;
    else    
      Midpoint_ = (Min_ + Max_) / 2;
    
    this.Rate = Midpoint_;
    this.SetupData();
    
    Result_ = this.Amortize(this.Payment);

    if (this.Payment < this.MinPayment + .01)
      break;
    
    if (Result_ < 0 - this.EPSILON || this.LoopIter < this.TotalPeriods + 1)
      Min_ = Midpoint_;    
    else if (Result_ > 0 + this.EPSILON)
      Max_ = Midpoint_;    
    else
      break;
  }

  this._PayInfo[this.TotalPeriods + 1].Balance = 0;
  return this.Rate;
}

/****************************************************************************/
function CheckArguments(Flag_)
{
  if (Flag_ == this.SOLVEPAYMENT)
  {
    if (this.Price < .01)
    {
      alert("Error: Price must be at least $0.01");
      return false;
    }
    if (this.TotalTime <= 0)
    {
      alert("Error: Time must be at greater than 0");
      return false;
    }
  }

  if (Flag_ == this.SOLVETIME)
  {
    if (this.Price < .01)
    {
      alert("Error: Price must be at least $0.01");
      return false;
    }
    if (this.Payment <= this.LowerBound)
    {
      alert("Error: Payment value must be greater than $" +
            this.LowerBound +
            " to make it possible to pay off the debt");
      return false;
    }    
  }

  if (Flag_ == this.SOLVERATE)
  {
    if (this.Payment < this.MinPayment)
    {
      alert("Error: The current payment value cannot be used to pay off debt" +
            "even for no-interest loans. Please enter a value which is at least" +
            this.MinPayment + " for the payment.");
      return false;
    }
  }

  return true;
}

/****************************************************************************/
function FindResults()
{
  var Years_;
  var Result_ = this.CheckArguments(this._SolveFor);

  if (Result_)
    if (this._SolveFor == this.SOLVEPAYMENT)
      this.Payment = this.FindPayment(this.Min, this.Max);
    else if (this._SolveFor == this.SOLVERATE)
      this.FindRate();
    else
    {
      Years_ = this.LONGESTPERIOD / this.PayPeriod;
      Result_ = this.FindTime() <= 0;
      
      if (!Result_)
        alert("Error: Debt cannot be amortized for " +
              Years_ + " Years!\n" +
              "Giving Up Calculation!");
    }

  return Result_;
}

/****************************************************************************/
function Display(){
  var i;
  var Value_;
  var Limit_ = this.TotalPeriods + 1;
  
  var html = "";
  html += "<br/><br/><h2>Loan Calculation Summary</h2>";
  
  html += "<table class='stripeMe rental' width='50%'><tr><th>Event</th><th>Amount</th><th>Term</th><th>Period</th></tr>";
  html += "<tr><td>Loan</td><td>"+FormatMoneyValue(this._PayInfo[1].Balance)+"</td><td>1</td><td></td></tr>";
 
  html += "<tr><td>Payment</td><td>"+FormatMoneyValue(this._PayInfo[2].Payment)+"</td><td>"+(this.TotalPeriods-1)+"</td><td>Monthly</td></tr>";
  if(this._PayInfo[Limit_].Payment < this._PayInfo[2].Payment){
	    Value_ = FormatMoneyValue(this._PayInfo[Limit_].Payment);
	    html += "<tr><td>Payment</td><td>"+Value_+"</td><td>1</td><td>Month</td></tr>";
  }
	
 

  
  var tabl ="<h2>Loan Repayment Schedule</h2><table class='stripeMe rental' width='100%'><thead>";
  tabl += "<tr><th>Month</th><th>Payment</th><th>Interest</th><th>Principal</th><th>Balance</th></tr></thead>";
  var tot = 0;
  for (i = 1; i <= Limit_; ++i){
	   tabl += "<tr>";
	   tabl += "<td>#" + (i - 1) +"</td>";	   
	  
	   Value_ = FormatMoneyValue(this._PayInfo[i].Payment);
	   tabl += "<td>$" + Value_+"</td>";	   
	  
	   Value_ = FormatMoneyValue(this._PayInfo[i].Interest);
	   tabl += "<td>$" + Value_+"</td>"; 
	   
	   Value_ = FormatMoneyValue(this._PayInfo[i].Principal);
	   tabl += "<td>$" + Value_+"</td>";	  
	     	
	   Value_ = FormatMoneyValue(this._PayInfo[i].Balance);
	   tabl += "<td>$" + Value_+"</td>";
	   
	   tabl += "</tr>";
	   tot += this._PayInfo[i].Interest;
  }
  
  tabl += "</table>";
  if (this._SolveFor == this.SOLVETIME){
  
   html += "<tr><td colspan='4' style='text-align:right;'><strong>Total Time Required = " + formatDate(this.TotalTime) + "</strong></td>";
   html += "<tr><td colspan='4' style='text-align:right;'><strong>Interest Paid During This Period = $" + FormatMoneyValue(tot) + "</strong></td>";
  }
	
  html += "</table>";

  if (this._SolveFor == this.SOLVEPAYMENT)  {
   
    Value_ = FormatMoneyValue(this.Payment);
    html += "<P>Payment Value Required = $" + Value_ + "</P>";
  }  
  
  if (this._SolveFor == this.SOLVERATE) {  
    if (Rate == 0)
     html += "<P>Interest Rate Required = 0%</P>";
    else
     html += "<P>Interest Rate Required = " + (this.Rate * 100) + "% or less</P>";
  }

  html +="";
  html += tabl;
  var el = document.getElementById("results").innerHTML = html;
  	//$(".stripeMe tr").mouseover(function() {$(this).addClass("over");}).mouseout(function() {$(this).removeClass("over");});
	//$(".stripeMe tr:even").addClass("alt");
 
}
function formatDate(num){
	var yrs = Math.floor(num);
	var mos = Math.ceil(12 * (num - yrs));
	var str = "";
	if(yrs > 1){
		str += yrs+" years";
	}else if(yrs == 1){
		str += yrs+" year";
	}
	if(yrs != 0 && mos != 0){
		str += ", "
	}
	if(mos > 1){
		str += mos + " months";
	}else if(mos == 1){
		str += mos + " month";
	}
	return str;	
	
}
/****************************************************************************/
function SolveFor(Flag_)
{
  this._SolveFor = Flag_;
  return this;
}

/****************************************************************************/
function SetTotalTime(Value_)
{
  this.TotalTime = Value_;
  return this;
}

/****************************************************************************/
function SetPayment(Pay_)
{
  this.Payment = Pay_;
  return this;
}

/****************************************************************************/
function SetPayPeriod(Period_)
{
  this.PayPeriod = Period_;
  return this;
}

/****************************************************************************/
function SetPrice(Value_)
{
  this.Price = Value_;
  return this;
}

/****************************************************************************/
function SetRate(Value_)
{
  this.Rate = Value_ / 100;
  return this;
}

/****************************************************************************/
function SetCompounding(Value_)
{
  this.Compounding = Value_;
  return this;
}

/****************************************************************************/
function EnterButton(BtnPressed)
{
  this.SetTotalTime(0);
  this.SetPayment(parseFloat(document.DataForm.Payment.value));
  this.SetPayPeriod(12);
  this.SetPrice(parseFloat(document.DataForm.Price.value));
  this.SetRate(parseFloat(document.DataForm.Rate.value));
  this.SetCompounding(12);

  if (BtnPressed == "BtnSolvePay")
  {
    this.SolveFor(this.SOLVEPAYMENT);
    this.SetPayment(0.0);
    this.SetupData();
  }
  else if (BtnPressed == "BtnSolveRate")
  {
    this.SolveFor(this.SOLVERATE);
    this.SetRate(0.0);
    this.SetupData();  
  }
  else if (BtnPressed == "BtnSolveTime")
  {
    this.SolveFor(this.SOLVETIME);
    this.SetTotalTime(0);    
    this.SetupData();
  }

  if (this.FindResults())
  {
    //alert("Preparing To Display");
    this.Display();
  }
  else
    alert("Error In Calculating Results");
}

/****************************************************************************/
MortgageFinder = new Mortgage();
MortgageFinder.InitMortgage();
