var Guzi = new Object();
Guzi.RealtorCalculators = new Object();

Guzi.RealtorCalculators.ValidatingFormListener = Class.create();
Guzi.RealtorCalculators.ValidatingFormListener.prototype = {
  initialize: function(opts) {
    this.listeners = new Array();
    Object.extend(this, opts);
  },
  
  bindNumericInput: function(sourceElement, targetObject, targetField) {
    this.listeners.push({
      sourceElement: sourceElement,
      targetObject:  targetObject,
      targetField:   targetField
    });
    targetObject[targetField] = this.getNumberFromInput(sourceElement);
    Event.observe(sourceElement, 'change', function(e) {
      if(!this.validateNumericInput(sourceElement))
        this.displayValidationError(sourceElement, "This field must contain only numeric information");
      else {
        this.clearValidationError(sourceElement);
        targetObject[targetField] = this.getNumberFromInput(sourceElement);
      }
    }.bind(this));
  },
  
  validateNumericInput: function(element) {
    try {
      var val = this.getNumberFromInput(element);
      if(isNaN(val)) return false;
      return true;
    } catch(e) {
      return false;
    }
  },
  
  getNumberFromInput: function(element) {
    var val;
    if(element.tagName == "SELECT") {
      val = element.options[element.selectedIndex].value;
    } else {
      val = element.value;
    }
    //return parseFloat(val.replace(",", ""));
  },
  
  displayValidationError: function(element, message) {
    this.clearValidationError(element);
    
    var errorDiv = document.createElement("div");
    errorDiv.className = 'validationError';
    errorDiv.id = this.idForErrorElement(element);
    errorDiv.appendChild(document.createTextNode(message));
    element.parentNode.insertBefore(errorDiv, element.nextSibling);
  },
  
  clearValidationError: function(element) {
    if($(this.idForErrorElement(element))) 
      $(this.idForErrorElement(element)).parentNode.removeChild($(this.idForErrorElement(element)));
  },
  
  idForErrorElement: function(element) {
    return 'validation_error_for_' + element.id;
  }
};

/**
 * CalculatorResult
 *
 * Contains the result of a calculation operation
 */
Guzi.RealtorCalculators.CalculatorResult = Class.create();
Guzi.RealtorCalculators.CalculatorResult.prototype = {
  initialize: function(opts) {
    Object.extend(this,
      Object.extend({
        items: new Array()
      }, opts)
    );
  },
  
  addResult: function(key, caption, value, formatted) {
    this.items.push( {
      key:        key,
      caption:    caption,
      value:      value,
      formatted:  formatted
    } );
  },
  
  addDollarResult: function(key, caption, value) {
    this.addResult(key, caption, value, this.formatDollars(value));
  },
  
  addRateResult: function(key, caption, value) {
    this.addResult(key, caption, value, this.formatRate(value));
  },
  
  formatDollars: function(value) {
    return '$' + this.formatNumber(Math.floor(value)).toString();
  },
  
  formatRate: function(value) {
    // TODO: Not Yet Implemented
    return value;
  },
  
  elementForResult: function(result) {
    var container = document.createElement("div");
    container.className = 'calculatorResult';
    var label = document.createElement("label");
    label.appendChild(document.createTextNode(result.caption));
    container.appendChild(label);
    var value = document.createElement("p");
    value.className = "data";
    value.appendChild(document.createTextNode(result.formatted));
    container.appendChild(value);
    var clearDiv = document.createElement("div");
    clearDiv.className = "clear";
    container.appendChild(clearDiv);
    return container;
  },
  
  clearAndDisplay: function(containerElement) {
    while(containerElement.hasChildNodes())
      containerElement.removeChild(containerElement.lastChild);
    this.display(containerElement);
  },
  
  display: function(containerElement) {
    for(var i = 0; i < this.items.length; i++) {
      containerElement.appendChild(this.elementForResult(this.items[i]));
    }
  },
  
  formatNumber: function(value) {
    var tempString = "";
    
    if(!value.indexOf) value = value.toString();
    //MAKE SURE THAT THE LAST 3 DIGITS = ".00"
    if ((value.indexOf('.') == (value.length - 2))&&(value.length != 1))
    {
      value += "0";
    }
    else if ((value.indexOf('.') == (value.length - 1))&&(value.length != 1))
    {
      value += "00";
    }
    else if (value.indexOf('.') == -1 )
    {
      value += ".00";
    }
    
    //Now, Add commas
    if (value.substring(value.indexOf('.'),0).length > 3)
    {
      //Add first comma, based on length of number from decimal
      tempString = value.substring(value.indexOf('.')-3,0) + "," + value.substring(value.length,value.indexOf('.')-3);
      value = tempString;
    
      //Now add more commas while necessary, based on length of number from first visible comma in the string
      while (value.indexOf(",") > 3)
      {
        tempString = value.substring(value.indexOf(',')-3,0) + "," + value.substring(value.length,value.indexOf(',')-3);
        value = tempString;
      }
    }
    
    //remove potential error "-," for negative numbers a multiple of 3 digits in length
    if ((value.indexOf('-') == 0)&&(value.indexOf(',') == 1))
    {
      tempString = "-" + (value.substring(value.length,value.indexOf(',')+1));
      value = tempString;
    }
    
    //Fix requested by Jennifer Kohl - remove everything decimal and after - this is what is done in the application
    tempString = value.substring(0,value.indexOf('.'));
    value = tempString;
    
    return value;
  }
};


/**
 * MonthlyPaymentCalculator
 *
 * Calculates the monthly payment of a mortgage
 */
Guzi.RealtorCalculators.MonthlyPaymentCalculator = Class.create();
Guzi.RealtorCalculators.MonthlyPaymentCalculator.prototype = {
  /*
   * Constructor
   *
   * @param {Object} an object containing the loanTerm, interestRate, 
   *   homeValue, loanAmount, annualPropertyTax, annualHazardInsurance,
   *   annualHomeownersAssociation
   */
  initialize: function(opts) {
    Object.extend(this,
      Object.extend({
        loanTerm:                     30,
        interestRate:                 7.00,
        homeValue:                    300000,
        loanAmount:                   290000, 
        annualPropertyTax:            2800,
        annualHazardInsurance:        100,
        annualHomeownersAssociation:  100
      }, opts)
    );
  },
  
  /*
   * Compute the mortage payments
   *
   * @return {CalculatorResult} the results 
   */
  calculate: function() {
    var down              = this.homeValue - this.loanAmount;
    var loanToValueRatio  = this.loanAmount / this.homeValue;
    // Determine the rate of primary mortgage insurance
    var pmiRate           = 0.00;
    if(loanToValueRatio > 0.8) {
      if(loanToValueRatio <= 0.85) pmiRate = 0.0032;
      else if(loanToValueRatio <= 0.90) pmiRate = 0.0052;
      else if(loanToValueRatio <= 0.95) pmiRate = 0.0078;
      else pmiRate = 0.0090;
    }
    // Amortize
    var monthlyInterestRate = this.interestRate / 1200;
    var base = 1;
    var mbase = 1 + monthlyInterestRate;
    for(var i = 0; i < this.loanTerm * 12; i++) base = base * mbase;
    // Sum the monthly payment
    var sum = this.loanAmount * monthlyInterestRate / (1 - (1 / base)) 
            + this.annualPropertyTax / 12 
            + this.annualHomeownersAssociation / 12 
            + this.loanAmount * (pmiRate / 12) 
            + this.annualHazardInsurance / 12;
    var results = new Guzi.RealtorCalculators.CalculatorResult();
    results.addDollarResult(
      "monthlyLoanPayment",
      "Your estimated monthly principal and interest payment is",
      this.loanAmount * monthlyInterestRate / ( 1 - (1 / base)) );
    results.addDollarResult(
      "monthlyPrivateMortageInsurance",
      "Your estimated monthly private mortgage insurance payment is",
      this.loanAmount * (pmiRate / 12) );
    results.addDollarResult(
      "monthlyPropertyTax",
      "Your estimated monthly property tax payment is",
      this.annualPropertyTax / 12 );
    results.addDollarResult(
      "monthlyHazardInsurance",
      "Monthly hazard insurance",
      this.annualHazardInsurance / 12 );
    results.addDollarResult(
      "monthlyHomeownersAssociation",
      "Monthly Homeowners Association dues",
      this.annualHomeownersAssociation / 12 );
    results.addDollarResult(
      "monthlyTotalPayment",
      "Your total estimated monthly payment is",
      sum );
    return results;
  },
  
  test: function() {
    var c = new Guzi.RealtorCalculators.MonthlyPaymentCalculator({
      loanTerm:                     30,
      interestRate:                 7.00,
      homeValue:                    300000,
      loanAmount:                   290000, 
      annualPropertyTax:            2800,
      annualHazardInsurance:        100,
      annualHomeownersAssociation:  100
    });
    var r = c.calculate();
    if(r.items[0].formatted !== '$1,929') 
      console.log("Expected $1,929 from formatted %s but instead found %s",   r.items[0].key, r.items[0].formatted);
    if(r.items[1].formatted !== '$217')
      console.log("Expected $217 from formatted %s but instead found %s",     r.items[0].key, r.items[0].formatted);
    if(r.items[2].formatted !== '$233')
      console.log("Expected $233 from formatted %s but instead found %s",     r.items[0].key, r.items[0].formatted);
    if(r.items[5].formatted !== '$2,396')
      console.log("Expected $2,396 from formatted %s but instead found %s",   r.items[0].key, r.items[0].formatted);
  }
};



/**
 * AffordabilityCalculator
 *
 * Calculates the monthly payment of a mortgage
 */
Guzi.RealtorCalculators.AffordabilityCalculator = Class.create();
Guzi.RealtorCalculators.AffordabilityCalculator.prototype = {
  /*
   * Constructor
   *
   * @param {Object} an object containing the annualIncome, 
   *   monthlyDebts
   */
  initialize: function(opts) {
    Object.extend(this,
      Object.extend({
        annualIncome:                 100000,
        monthlyDebts:                 1000
      }, opts)
    );
  },
  
  /*
   * Compute the affordability
   *
   * @return {CalculatorResult} the results 
   */
  calculate: function() {
    var monthlyIncome = this.annualIncome / 12;
    var usableIncome = monthlyIncome * 0.35;
    var usableIncome = usableIncome - this.monthlyDebts;
            
    var results = new Guzi.RealtorCalculators.CalculatorResult();
    results.addDollarResult(
      "monthlyPayment",
      "You can afford a payment of",
      usableIncome );
    return results;
  }
};


/**
 * RentVsBuyCalculator
 *
 */
Guzi.RealtorCalculators.RentVsBuyCalculator = Class.create();
Guzi.RealtorCalculators.RentVsBuyCalculator.prototype = {
  /*
   * Constructor
   *
   * @param {Object} an object 
   */
  initialize: function(opts) {
    Object.extend(this,
      Object.extend({
        monthlyRent:                1000,
        yearlyRentIncrease:         60,
        incomeTaxBracket:           0.31,
        loanTerm:                   30,
        interestRate:               7.00,
        loanAmount:                 160000,
        homeValue:                  200000,
        appreciation:               0.01,
        annualPropertyTax:          2000,
        yearsInHome:                10,
        discountPoints:             0.01,
        annualMiscOwnershipFees:    0.0,
        annualMiscRentalFees:       0.0
      }, opts)
    );
  },
  
  /*
   * Compute the affordability
   *
   * @return {CalculatorResult} the results 
   */
  calculate: function() {
    var downPayment = this.homeValue - this.loanAmount;
    
    var rbCalc1 = new RentVsBuyCalculator();
    var rbCalc = rbCalc1.calculate(this.homeValue, this.interestRate / 100, this.loanAmount, this.loanTerm * 12, PMICalculator(this.loanAmount, downPayment), this.incomeTaxBracket, this.appreciation, this.discountPoints, this.yearsInHome, this.monthlyRent, this.yearlyRentIncrease / 100, this.annualPropertyTax, this.annualMiscOwnershipFees, this.annualMiscRentalFees);
    
            
    var results = new Guzi.RealtorCalculators.CalculatorResult();
    results.addDollarResult(
      "costsOfRenting",
      "Estimated costs of renting",
      rbCalc.getRentingCost() );
    results.addDollarResult(
      "grossCostOfBuying",
      "Estimated gross cost of buying",
      rbCalc.getGrossCosts() );
    results.addDollarResult(
      "taxSavings",
      "Estimated amount of tax related savings",
      rbCalc.taxSavings );
    results.addDollarResult(
      "amountOfIncreasedEquity",
      "Estimated amount in increased equity",
      rbCalc.getEquityEarned() );
    results.addDollarResult(
      "netCostOfBuying",
      "Estimated net cost of buying",
      rbCalc.getGrossCosts() - rbCalc.taxSavings - rbCalc.getEquityEarned() );
    results.addDollarResult(
      "totalSavings",
      "Estimated total savings (not including investment related savings)",
      rbCalc.getOwnershipBenefit() );
    results.addDollarResult(
      "investmentSavings",
      "Estimated investment related savings",
      rbCalc.getPotentialSavings() );
    return results;
  }
};
