var Cathmhaol = window.Cathmhaol || {};

/**
* Financial calculators.
*
* @author	Robert King (hrobertking@cathmhaol.com)
*
* @example	var oSchedule = Cathmhaol.Financial.amortize(100000.00, 6.5, 30, true);
*/
Cathmhaol.Financial = {
	/**
	* @method	Returns an multi-dimensional array containing an amortization schedule when given the principal, the apr, the number of years before the loan is due, and whether or not there is to be a balloon payment.
	* @returns	{float[][]}
	* @argument	{float} principal
	* @argument	{float} apr
	* @argument	{integer} years
	* @argument	{boolean} balloon
	*/
	amortize: function (principal, apr, years, balloon) {
		// While the balance is greater than zero and the balloon payment is not set, calculate the payment against principal
		// and the interest payment for the period. Adjust the balance due and check to make sure we haven't set the balloon
		// flag on each iteration and add the data to the array.

		apr = parseFloat(apr);
		principal = parseFloat(principal);
		years = parseFloat(years);

		var Schedule = new Array() ;

		var fBalance = (isNaN(apr) || isNaN(principal) || isNaN(years)) ? 0 : principal;
		Schedule[0] = {payment: 0, principal: 0, interest: 0, balance: fBalance} ;
		if (fBalance > 0) {
			var iPeriodIndex = 1;
			var iPeriods = years * 12;
			var fPeriodicRate = apr / 1200;
			var fPaymentPrincipal = Math.abs(Math.round((fBalance*(fPeriodicRate/(1-Math.pow((1+fPeriodicRate),iPeriods)))*-1)*100)/100) ;
			var fPaymentInterest = Math.abs(Math.round((fBalance*fPeriodicRate)*100)/100) ;
			var fPaymentTotal = fPaymentPrincipal + fPaymentInterest ;
			var bPaymentsLeft = true
			var bIsBalloon = (iPeriodIndex == (iPeriods+1));
			while (!(balloon ? bIsBalloon : (fBalance <= 0))) {
				var bIsBalloon = (iPeriodIndex == (iPeriods + 1));
				fPaymentInterest = fBalance * fPeriodicRate;
				fPaymentPrincipal = ((balloon && bIsBalloon) || (fPaymentTotal > fBalance)) ? fBalance : (fPaymentTotal - fPaymentInterest);
				fPaymentTotal = ((balloon && bIsBalloon) || (fPaymentTotal > fBalance)) ? (fPaymentInterest + fPaymentPrincipal) : fPaymentTotal;
				fBalance = ((balloon && bIsBalloon) || (fPaymentTotal >= fBalance)) ? 0 : (fBalance - fPaymentPrincipal);
				Schedule[iPeriodIndex] = {payment: (Math.round(fPaymentTotal * 100)/100), principal: (Math.round(fPaymentPrincipal * 100)/100), interest: (Math.round(fPaymentInterest * 100)/100), balance: (Math.round(fBalance * 100)/100)} ;
				iPeriodIndex++ ;
				if (iPeriodIndex > iPeriods) { break; }
			}
		}
		return Schedule ;
	},

	/**
	* @method	Returns the sales to assets ratio
	* @returns	{float}
	* @argument	{float} sales
	* @argument	{float} assets
	*/
	assetTurnover: function(sales, assets) {
		sales = parseFloat(sales);
		assets = parseFloat(assets);
		if (isNaN(sales) || isNaN(assets) || assets == 0) { return 0; }
		return (sales / assets);
	},

	/**
	* @method	Returns a break-even point
	* @returns	{float}
	* @argument	{float} fixedCost
	* @argument	{float} sellingPrice
	* @argument	{float} variableCost
	*/
	breakEvenPoint: function(fixedCost, sellingPrice, variableCost) {
		// Calculate the point at which you start making a profit given the fixed costs (e.g. rent) and variable costs (e.g. material cost)
		// associated with production and the selling price of the item.

		fixedCost = parseFloat(fixedCost);
		sellingPrice = parseFloat(sellingPrice);
		variableCost = parseFloat(variableCost);
		if (isNaN(fixedCost) || isNaN(sellingPrice) || isNaN(variableCost) || (sellingPrice - variableCost) == 0) { return 0; }
		return (fixedCost/(sellingPrice - variableCost)) ;
	},

	/**
	* @method	Returns the ratio of working capital to assets
	* @returns	{float}
	* @argument	{float} capital
	* @argument	{float} assets
	*/
	capitalLiquidity: function(capital, assets) {
		capital = parseFloat(capital)
		assets = parseFloat(assets);
		if (isNaN(capital) || isNaN(assets) || assets == 0) { return 0; }
		return (capital / assets);
	},

	/**
	* @method	Returns the interest compounded for the specified number of periods
	* @returns	{float}
	* @argument	{float} principal  Amount invested
	* @argument	{float} apr        The annual percentage rate
	* @argument	{integer} periods  Number of periods investment is held
	*/
	compoundInterest: function(principal, apr, periods) {
		principal = parseFloat(principal);
		apr = parseFloat(apr);
		periods = parseFloat(periods);
		if (isNaN(principal) || isNaN(apr) || isNaN(periods)) { return 0; }
		return (principal * Math.pow((1 + apr), periods));
	},

	/**
	* @method	Returns the debt equity ratio
	* @returns	{float}
	* @argument	{float} liabilities
	* @argument	{float} equity
	*/
	debtEquity: function(liabilities, equity) {
		liabilities = parseFloat(liabilities);
		equity = parseFloat(equity);
		if (isNaN(liabilities) || isNaN(equity) || equity == 0) { return 0; }
		return (parseFloat(liabilities)/parseFloat(equity));
	},

	/**
	* @method	Returns the inverse of the debt equity ratio
	* @returns	{float}
	* @argument	{float} liabilities
	* @argument	{float} equity
	*/
	debtEquityInverse: function(liabilities, equity) {
		equity = parseFloat(equity);
		liabilities = parseFloat(liabilities);
		if (isNaN(equity) || isNaN(liabilities) || liabilities == 0) { return 0; }
		return (equity / liabilities);
	},

	/**
	* @method	Returns the owner's equity or net worth
	* @returns	{float}
	* @argument	{float} assets
	* @argument	{float} liabilities
	*/
	equity: function(assets, liabilities) {
		assets = parseFloat(assets);
		liabilities = parseFloat(liabilities);
		if (isNaN(assets) || isNaN(liabilities)) { return 0; }
		return (assets - liabilities);
	},

	/**
	* @method	Formats a float to the specified precision and returns a string
	* @returns	{string}
	* @argument	{float} amount
	* @argument	{integer} precision
	*/
	formatDecimal: function(amount, precision) {
		var sAmount;
		if (!isNaN(amount)) {
			var iMultiplier = Math.pow(10, precision);
			amount = (Math.round(amount * iMultiplier)/iMultiplier);
			sAmount = amount.toString();
			sAmount = sAmount.indexOf(".") < 0 ? sAmount + "." : sAmount;
			while (sAmount.substr(sAmount.indexOf(".")+1, precision).length < precision) {
				sAmount = sAmount + "0";
			}
		}
		return sAmount;
	},

	/**
	* @method	Returns the future value of an annuity
	* @returns	{float}
	* @argument	{float} payment
	* @argument	{float} apr
	* @argument	{integer} periods
	*/
	futureValue: function(payment, apr, periods) {
		payment = parseFloat(payment);
		apr = parseFloat(apr);
		periods = parseFloat(periods);
		if (isNaN(payment) || isNaN(apr) || isNaN(periods) || apr == 0) { return 0; }
		return payment * ((Math.pow((1 + apr), periods) - 1)/apr);
	},

	/**
	* @method	Returns the statistical mean for the specified data set
	* @returns	{float}
	* @argument	{float[]} data
	*/
	mean: function(data) {
		var fSum = 0 ;
		var iCount = 0;
		for (c = 0; c < data.length; c++) {
			var f = parseFloat(data[c]);
			if (!isNaN(f)) { fSum += f; iCount++; }
		}
		return (iCount == 0) ? 0 : (fSum/iCount);
	},

	/**
	* @method	Returns the median for the specified data set
	* @returns	{variant}
	* @argument	{variant[]} data
	*/
	median: function(data) {
		// Because the 'median' is the middle element, it doesn't matter whether the data is numeric or not.

		data.sort();
		return data[(Math.floor(data.length/2)-1)];
	},

	/**
	* @method	Returns the profit margin
	* @returns	{float}
	* @argument	{float} profit
	* @argument	{float} sales
	*/
	profitMargin: function(profit, sales) {
		profit = parseFloat(profit);
		sales = parseFloat(sales);
		if (isNaN(profit) || isNaN(sales) || sales == 0) { return 0; }
		return (profit/sales);
	},

	/**
	* @method	Returns the ratio of retained earnings to assets. A ratio >= 1 indicates growth is sustainable (growth financed by profits).
	* @returns	{float}
	* @argument	{float} earnings
	* @argument	{float} assets
	*/
	retainedEarnings: function(retainedEarnings, assets) {
		retainedEarnings = parseFloat(retainedEarnings);
		assets = parseFloat(assets)
		if (isNaN(retainedEarnings) || isNaN(assets) || assets == 0) { return 0; }
		return (retainedEarnings/assets);
	},

	/**
	* @method	Returns the Return on Assets (ROA)
	* @returns	{float}
	* @argument	{float} earnings
	* @argument	{float} assets
	*/
	returnOnAssets: function(earnings, assets) {
		earnings = parseFloat(earnings);
		assets = parseFloat(assets)
		if (isNaN(earnings) || isNaN(assets) || assets == 0) { return 0; }
		return (earnings/assets);
	},

	
	/**
	* @method	Returns the Return on Equity (ROE)
	* @returns	{float}
	* @argument	{float} profit
	* @argument	{float} equity
	*/
	returnOnEquity: function(profit, equity) {
		profit = parseFloat(profit);
		equity = parseFloat(equity)
		if (isNaN(profit) || isNaN(equity) || equity == 0) { return 0; }
		return (profit/equity);
	},

	/**
	* @method	Returns the amount of time or the APR required to double an investment
	* @returns	{string}
	* @argument	{float} amount  Amount to invest
	* @argument	{boolean} bAPR  Return the APR required
	*/
	ruleOfSeventyTwo: function(amount, bAPR) {
		bAPR = bAPR === true ? true : false;
		amount = parseFloat(amount) ;
		if (amount <= 0) { return 0 ; }
		var fRate = (72/amount);
		if (bAPR) {
			return fRate.toString()+"%" ;
		} else {
			var yrs = (Math.floor(fRate)==1 ? d=Math.floor(fRate)+" year" : d=Math.floor(fRate)+" years") ;
			var mos = Math.floor(Math.round((fRate-Math.floor(fRate))*12)) ;
			if (mos>0) { yrs = yrs+" "+((mos==1)? mos.toString()+" month" : mos.toString()+" months") ; }
			return yrs;
		}
	},

	/**
	* @method	Returns the standard deviation for the specified data set
	* @returns	{float}
	* @argument	{float[]} data
	*/
	standardDeviation: function(data) {
		var fMean = this.mean(data);
		var fSsum = 0;
		var iCount = 0;
		for (var c = 0; c < data.length; c++) {
			var f = parseFloat(data[c]);
			if (!isNaN(f)) {
				fSsum += Math.pow((f - fMean), 2);
				iCount++;
			}
		}
		var fVariance = (iCount < 2) ? 0: (fSum/(iCount - 1));
		return Math.sqrt(fVariance);
	},

	/**
	* @method	Returns the Altman Z Score for a company.
	* @returns	{float}
	* @argument	{float} earnings     Earnings for the period
	* @argument	{float} sales        Sales for the period
	* @argument	{float} equity       Owner's equity
	* @argument	{float} capital      Working capital
	* @argument	{float} retained     Retained earnings
	* @argument	{float} assets       Assets held by the organization
	* @argument	{float} liabilities  Liabilities held by the organization
	*/
	zScore: function(earnings, sales, equity, capital, retained, assets, liabilities) {
		// Calculate the Altman Z-Score using the earnings and sales for a period, the owner's equity, working capital, retained earnings, and the assets and liabilities held by the organization.
		// According to the theory, by calculating how liquid a company is and how strong their cash flow, we can determine whether or not the company is likely to fail.
		// In the author's opinion, this is less likely to be an accurate predictor for companies that are not manufacturing/sales based (e.g. service companies).
		// In the theory, a score less than 1.8 indicates a high probability of failure; a score between 1.8 and 2.6 indicates a moderate/high probability of failure; 
		// a score between 2.7 and 2.9 indicates a  moderate probability of failure; and a score greater than 2.9 indicates a low probability of failure.

		if (isNaN(earnings) || isNaN(sales) || isNaN(equity) || isNaN(capital) || isNaN(retained) || isNaN(assets) || isNaN(liabilities)) { return; }
		return (this.returnOnAssets(earnings, assets) * 3.3) + (this.assetTurnover(sales, assets) * 0.999) + (this.debtEquityInverse(liabilities, equity) * .6) + (this.capitalLiquidity(capital, assets) * 1.2) + (this.retainedEarnings(retained, assets) * 1.4);
	}
};

