var NARDOZZI = {
	"forms" : {
		"delivery" : { },
		"margin" : { },
		"customer" : { }
	}
};

/******************************************************************************
 **********                     Customer Form                        **********
 ******************************************************************************/

NARDOZZI.forms.customer.ROW_COUNT = 3;

NARDOZZI.forms.customer.getForm = function()
{
	return document.forms.customer;
};

NARDOZZI.forms.customer.calculate = function()
{
	
	var form = NARDOZZI.forms.customer.getForm();
	
	// Sum up the gallons sold rows.
	
	var gallonsSoldTotal = 0;
	var gallonsSoldDivisor = 0;
	
	for (var i = 0; i < NARDOZZI.forms.customer.ROW_COUNT; i++)
	{
		
		var gallonsSold = form["gallonsSold_" + i].value.toInt();
		var gallonsSoldWeight = form["gallonsSoldWeight_" + i].value.toInt();
		
		// Validate the user supplied values to make sure they are numeric.
		
		if (isNaN(gallonsSold))
		{
			
			alert("The gallons sold for line " + (i + 1) + " does not appear to be a numeric value like 5,000,000.");
			
			return false;
			
		}
		
		if (isNaN(gallonsSoldWeight))
		{
			
			alert("The weight for line " + (i + 1) + " does not appear to be a numeric value like 3.");
			
			return false;
			
		}
		
		var gallonsSoldWeightedAverage = gallonsSold * gallonsSoldWeight;
		
		form["gallonsSoldWeightedAverage_" + i].value = gallonsSoldWeightedAverage.format();
		
		gallonsSoldTotal += gallonsSoldWeightedAverage;
		gallonsSoldDivisor += gallonsSoldWeight;
		
	}
	
	var gallonsSoldWeightedAverageGallons = gallonsSoldTotal / gallonsSoldDivisor;
	
	form["gallonsSoldTotal"].value = gallonsSoldTotal.format();
	form["gallonsSoldDivisor"].value = gallonsSoldDivisor.format();
	form["gallonsSoldWeightedAverageGallons"].value = gallonsSoldWeightedAverageGallons.toPrecision(0).format();
	
	// Sum up the average margin rows.
	
	var averageMarginTotal = 0;
	var averageMarginDivisor = 0;
	
	for (var i = 0; i < NARDOZZI.forms.customer.ROW_COUNT; i++)
	{
		
		var averageMargin = form["averageMargin_" + i].value.toFloat();
		var averageMarginWeight = form["averageMarginWeight_" + i].value.toInt();
		
		// Validate the user supplied values to make sure they are numeric.
		
		if (isNaN(averageMargin))
		{
			
			alert("The average margin for line " + (i + 1) + " does not appear to be a numeric value like $0.620.");
			
			return false;
			
		}
		
		if (isNaN(averageMarginWeight))
		{
			
			alert("The weight for line " + (i + 1) + " does not appear to be a numeric value like 3.");
			
			return false;
			
		}
		
		var averageMarginWeightedAverage = averageMargin * averageMarginWeight;
		
		form["averageMarginWeightedAverage_" + i].value = averageMarginWeightedAverage.formatCurrency(3);
		
		averageMarginTotal += averageMarginWeightedAverage;
		averageMarginDivisor += averageMarginWeight;
		
	}
	
	var averageMarginWeightedAverageMargin = averageMarginTotal / averageMarginDivisor;
	
	form["averageMarginTotal"].value = averageMarginTotal.formatCurrency(3);
	form["averageMarginDivisor"].value = averageMarginDivisor.format();
	form["averageMarginWeightedAverageMargin"].value = averageMarginWeightedAverageMargin.formatCurrency(3);
	
	// Update our grand totals
	
	var preliminaryValue = gallonsSoldWeightedAverageGallons * averageMarginWeightedAverageMargin;
	
	form["preliminaryValue"].value = preliminaryValue.formatCurrency(0);
	
	var intangibleMultiplier = form["intangibleMultiplier"].value.toFloat();
	
	if (isNaN(intangibleMultiplier))
	{
		
		alert("The intangible multiplier does not appear to be a numeric value like 1.50.");
		
		return false;
		
	}
	
	var customerListValue = preliminaryValue * intangibleMultiplier;
	
	form["customerListValue"].value = customerListValue.formatCurrency(0);
	
};

/******************************************************************************
 **********                      Margin Form                         **********
 ******************************************************************************/

NARDOZZI.forms.margin.getForm = function()
{
	return document.forms.margin;
};

NARDOZZI.forms.margin.calculate = function()
{
	
	var form = NARDOZZI.forms.margin.getForm();
	
	var values = { };
	
	values.gallonsSold = form.gallonsSold.value.toInt();
	values.gallonsAcquired = form.gallonsAcquired.value.toInt();
	values.newCustomerGallons = form.newCustomerGallons.value.toInt();
	values.lostCustomerGallons = form.lostCustomerGallons.value.toInt();
	values.weatherAdjustment = form.weatherAdjustment.value.toFloat();
	values.hedgingPrograms = form.hedgingPrograms.value.toFloat();
	values.pricingPrograms = form.pricingPrograms.value.toFloat();
	values.deliveryPayroll = form.deliveryPayroll.value.toFloat();
	values.ownersSalary = form.ownersSalary.value.toFloat();
	values.freeServiceContracts = form.freeServiceContracts.value.toInt();
	values.renewalFee = form.renewalFee.value.toFloat();
	values.serviceDepartment = form.serviceDepartment.value.toFloat();
	values.interestExpense = form.interestExpense.value.toFloat();
	values.loanPrincipal = form.loanPrincipal.value.toFloat();
	values.administrativeCosts = form.administrativeCosts.value.toFloat();
	values.insurance = form.insurance.value.toFloat();
	values.healthInsurance = form.healthInsurance.value.toFloat();
	values.nonOwnerPayroll = form.nonOwnerPayroll.value.toFloat();
	values.priorYearOwnersSalary = form.priorYearOwnersSalary.value.toFloat();
	values.retirementPlans = form.retirementPlans.value.toFloat();
	values.newOfficeHire = form.newOfficeHire.value.toFloat();
	values.creditCard = form.creditCard.value.toFloat();
	values.profit = form.profit.value.toFloat();
	
	if (!NARDOZZI.forms.margin.validate(values))
	{
		return false;
	}
	
	var gallonsSoldWeatherAdjustment = values.gallonsSold * values.weatherAdjustment;
	
	var totalBasic = values.gallonsSold + values.gallonsAcquired + values.newCustomerGallons
			- values.lostCustomerGallons + gallonsSoldWeatherAdjustment;
	
	if (totalBasic == 0)
	{
		// TODO: Set the fields to zero and exit.
	}
	
	var totalPayroll = values.deliveryPayroll + values.ownersSalary;
	
	var totalServiceContracts = values.freeServiceContracts * values.renewalFee;
	
	var totalPriorYearsCosts = values.administrativeCosts +	values.insurance +
			values.healthInsurance + values.nonOwnerPayroll + values.priorYearOwnersSalary
			+ values.retirementPlans + values.newOfficeHire + values.creditCard
			+ values.profit - values.ownersSalary;
	
	var totalExpenses = totalPayroll + totalServiceContracts - values.serviceDepartment
			+ values.interestExpense + values.loanPrincipal + totalPriorYearsCosts;
	
	var minimumDailyMargin = totalExpenses / totalBasic;
	
	form.minimumDailyMargin.value = minimumDailyMargin.toPrecision(3);
	
	// =IF(totalBasic=0,"",(totalPayroll+totalServiceContracts-E22+E23+E24+totalPriorYearsCosts)/(totalBasic))
	
	// =IF((E9+E10+E11-E12+(E9*E13))=0,"",(E18+E19+(E20*E21)-E22+E23+E24+((E27+E29+E30+E31+E32+E33+E34+E35+E36)-E19))/(E9+E10+E11-E12+(E9*E13)))
	
	var minimumProgramMargin = minimumDailyMargin + (values.hedgingPrograms /
			values.pricingPrograms);
	
	// =IF(E5="","",IF(E5=0,"",E5+(E16/E17)))
	
	form.minimumProgramMargin.value = minimumProgramMargin.toPrecision(3);
	
};

NARDOZZI.forms.margin.validate = function(values)
{
	
	for (var name in values)
	{
		
		var value = values[name];
		
		if (isNaN(value))
		{
			
			alert("One or more of the form fields contains a value that doesn't appear to be a number.");
			
			return false;
			
		}
		
	}
	
	return true;
	
};

/******************************************************************************
 **********                     Delivery Form                        **********
 ******************************************************************************/

NARDOZZI.forms.delivery.PAYROLL_TAX_PERCENTAGE = 0.08;
NARDOZZI.forms.delivery.EMPLOYEE_BENEFIT_PERCENTAGE = 0.11;
NARDOZZI.forms.delivery.VEHICLE_LIFETIME = 10.0;

NARDOZZI.forms.delivery.getForm = function()
{
	return document.forms.delivery;
};

NARDOZZI.forms.delivery.calculate = function()
{
	
	// Validate the delivery form.
	
	if (!NARDOZZI.forms.delivery.validate())
	{
		return false;
	}
	
	var form = NARDOZZI.forms.delivery.getForm();
	
	// Collect the form field values into local variables.
	
	var payroll = form.payroll.value.toFloat();
	var vehicles = form.vehicles.value.toInt();
	var vehicleCost = form.vehicleCost.value.toFloat();
	var vehicleExpenses = form.vehicleExpenses.value.toFloat();
	var insurance = form.insurance.value.toFloat();
	var deliveries = form.deliveries.value.toInt();
	
	// Create variables to store the results.
	
	var payrollTaxes = payroll * NARDOZZI.forms.delivery.PAYROLL_TAX_PERCENTAGE;
	var employeeBenefits = payroll * NARDOZZI.forms.delivery.EMPLOYEE_BENEFIT_PERCENTAGE;
	var newVehicle = (vehicleCost / NARDOZZI.forms.delivery.VEHICLE_LIFETIME) * vehicles;
	var annualExpenses = payroll + payrollTaxes + employeeBenefits + newVehicle + vehicleExpenses + insurance;
	var costPerDelivery = annualExpenses / deliveries;
	
	// Update the form with our totals.
	
	form.payrollTaxes.value = payrollTaxes.formatCurrency(0);
	form.employeeBenefits.value = employeeBenefits.formatCurrency(0);
	form.costPerDelivery.value = costPerDelivery.formatCurrency(0);
	
};

NARDOZZI.forms.delivery.validate = function()
{
	
	var message;
	
	var form = NARDOZZI.forms.delivery.getForm();
	
	if (isNaN(form.payroll.value.toFloat()))
	{
		message = "The driver payroll does not appear to be a numeric value such as $60,000.";
	}
	
	if (isNaN(form.vehicles.value.toInt()))
	{
		message = "The number of vehicles does not appear to be a numeric value such as 2.";
	}
	
	if (isNaN(form.vehicleCost.value.toFloat()))
	{
		message = "The new vehicle cost does not appear to be a numeric value such as $5,000.";
	}
	
	if (isNaN(form.vehicleExpenses.value.toFloat()))
	{
		message = "The annual vehicle expenses do not appear to be a numeric value such as $1,000.";
	}
	
	if (isNaN(form.insurance.value.toFloat()))
	{
		message = "The annual insurance costs do not appear to be a numeric value such as $1,800.";
	}
	
	if (isNaN(form.deliveries.value.toInt()))
	{
		message = "The deliveries per year does not appear to be a numeric value such as 300.";
	}
	
	if (message)
	{
		
		alert(message);
		
		return false;
		
	}
	
	return true;
	
};

/******************************************************************************
 **********             JavaScript Object Extensions                 **********
 ******************************************************************************/

// Formats a number as US dollars. 1000 becomes $1,000.00. Specifying a
// precision of 4 would return $1,000.0000. If the value is not a number, it
// returns NaN.

Number.prototype.formatCurrency = function(precision)
{
	
	var value = this;
	
	// If it's not a number, return NaN as a string since this function is
	// expected to return a string.
	
	if(isNaN(value))
	{
		return NaN;
	}
	
	var valueFormatted = this.format(precision);
	
	valueFormatted = "$" + valueFormatted;
	
	return valueFormatted;
	
};

// Formats a number to the specified decimal precision in the US English
// convention. A period is used for the decimal point. A comma is used to
// separate every three numeric characters. If the value is not a number, it
// returns NaN.

Number.prototype.format = function(precision)
{
	
	var value = this;
	
	// If it's not a number, return NaN as a string since this function is
	// expected to return a string.
	
	if(isNaN(value))
	{
		return NaN;
	}
	
	// If a precision was specified, then we'll apply the precision before
	// formatting the number.
	
	if (!isNaN(precision))
	{
		value = value.toPrecision(precision); 
	}
	
	var insertCommas = function()
	{
		
		var regEx = /^(.* )?([-+]?\d+)(\d{3}\b)/;
		
		var newValue = valueFormatted.replace(regEx, "$1$2,$3");
		
		if (newValue != valueFormatted)
		{
			valueFormatted = newValue;
			
			insertCommas();
				
		}
		
	};
	
	var valueFormatted = value.toString();
	
	// Check to see if we lost any zeros after the decimal place.
	
	if (!isNaN(precision) && precision > 0)
	{
		
		var decimalPosition = valueFormatted.indexOf("."); // zero-based
		
		var expectedPosition = (valueFormatted.length - precision) - 1; // one-based, subtract one.
		
		// alert(valueFormatted + ":" + precision + ":" + expectedPosition + ":" + decimalPosition);
		
		for (var i = expectedPosition;  i < decimalPosition; i++)
		{
			valueFormatted += "0";
		}
		
	}
	
	insertCommas();
	
	return valueFormatted;
	
};

// Rounds a number to the specified decimal precision. Note, however, that
// JavaScript may strip trailing 0s. So, converting 1.0001 to a precision of 3
// will result in 1. These can be added back in when creating a string by
// calling Number.format().

Number.prototype.toPrecision = function(precision)
{
	
	if (isNaN(this))
	{
		return NaN;
	}
	
	if (precision == 0)
	{
		return Math.round(this);
	}
	
	var factor = Math.pow(10, precision);
	
	return (Math.round(this * factor) / factor);
	
};

// Converts a string into a floating point number, stripping out dollar signs
// and commas. This method calls String.toNumber(). See the comments on that
// method for details of how this differs from the Number() constructor.

String.prototype.toFloat = function(s)
{
	
	s = this == window ? s : this;
	
	var number = s.toNumber();
	
	var f = parseFloat(number)
	
	return f;
	
};

// Converts a string into an integer, stripping out dollar signs and commas.
// This method calls String.toNumber(). See the comments on that method for
// details of how this differs from the Number() constructor.

String.prototype.toInt = function(s)
{
	
	s = this == window ? s : this;
	
	var number = s.toNumber();
	
	var i = parseInt(number)
	
	return i;
	
};

// Converts a string into a numeric string, stripping dollar signs and commas.
// This should be called before calling parseFloat() or parseInt().
//
// Note that, unlike the Number() constructor, this method treats a zero length
// string as NaN. The Number() constructor returns 0 when passed a zero length
// string, even though it returns NaN when passed a non-numeric string.

String.prototype.toNumber = function(s)
{
	
	s = this == window ? s : this;
	
	var number;
	
	// Check to see if we have a percentage. If so, we'll try to convert it to
	// its decimal form.
	
	s = s.replace(/\$/g, "");
	s = s.replace(/,/g, "");
	
	if (s.indexOf("%") != -1)
	{
		
		s = s.replace(/%/g, "");
		
		s = s.trim();
		
		number = new Number(s);
		
		number = number / 100;
		
	}
	else
	{
		
		s = s.trim();
		
		if (s == "")
		{
			number = NaN;
		}
		else
		{
			number = new Number(s);
		}
		
	}
	
	return number;
	
};

String.prototype.trim  = function(s)
{
	
	s = this == window ? s : this;
	
	return s.replace(/^\s+/g, '').replace(/\s+$/g, '');
	
};
