/* Handles extracting and replacing data in the calculator form */
/* copyright(c) 2009 Robert E Walker */

var DEBUGGING = false;
var DEPLOYED = true;		// determines file path syntax
var TESTING = false;
var TRACE = false;
var TESTING_DATA = false; 	// if true runs the calculation with test data input
							// by the user, if false uses data from the client
							// data file

var ERR_UNDEFINED = 0;
var ERR_BAD_DATA = 1;
var ERR_USER_INPUT = 2;
var WARNING_NO_PROVIDER = 3;
var ERR_BAD_PROVIDER = 4;
var ERR_PROVIDER_NOT_FOUND = 5;
var ERR_BAD_TRIGGER = 6;
var WARNING = 7;
var WARNING_PROVIDER_SHORT = 8;
var WARNING_INVALID_DATA = 9;
var WARNING_NO_BEDCOUNT = 10;
var WARNING_PROVIDER_LONG = 12;
var WARNING_NO_DATA = 13;
var ERR_UNABLE_TO_RUN_SCRIPT = 11;
var NOTICE_USER_DATA_SENT = 14;
var NOTICE_USER_DATA_FAILED = 15;
var ERR_SEND_MAIL = 16;

// used to branch based on user activity
var TRIGGER_PROVIDERNAME = 0;
var TRIGGER_ESTANNUALMSC = 1;
var TRIGGER_USERDATA = 2;

var genericErrorMsg = 'Sorry for the inconvenience.';

// spec the path name differently for testing and deployment
if (DEPLOYED)
{
	if (TESTING)
	{
		var SCRIPT = 'http://www.medsphere.com/roi_calc_test/calc_roi.php';
	}
	else // not testing - ie: live
	{
		var SCRIPT = 'http://www.medsphere.com/roi_calc/calc_roi.php';
	}
}
else // not deployed
{
	if (TESTING)
	{
		var SCRIPT = '/roi_calc_test/calc_roi.php';
	}
	else //not testing
	{
		var SCRIPT = '/roi_calc/calc_roi.php';
	}
}

// store data for making real time alterations to the field labels for user hints
var fieldDisplay = new Object ();
fieldDisplay.hintClass = 'hint';
fieldDisplay.noteElementClass = 'note';
fieldDisplay.defaultValues = new Object();

fieldDisplay.defaultValues.providerNumber = new Object ();
fieldDisplay.defaultValues.providerNumber.baseName = 'providerNumber';
fieldDisplay.defaultValues.providerNumber.groupClass = '';
fieldDisplay.defaultValues.providerNumber.noteText = '';

fieldDisplay.defaultValues.estAnnualMSC = new Object ();
fieldDisplay.defaultValues.estAnnualMSC.baseName = 'estAnnualMSC';
fieldDisplay.defaultValues.estAnnualMSC.groupClass = '';
fieldDisplay.defaultValues.estAnnualMSC.noteText = '';

function initAjaxRequest(trigger)
{
	if (DEBUGGING && TRACE) {alert ('initAjaxRequest');}
	// if any user hints were displayed clear them now
	clearUserHints();
	
	var request = null;
	var dataReturn = new Array();
	dataReturn['error'] = '';
	dataReturn['errorType'] = -1;
	
	if (window.XMLHttpRequest)
	{
		// browser: Safari, Firefox, Chrome, Opera and IE7+
		request = new window.XMLHttpRequest;
	}
	else if (window.ActiveXObject)
	{
		// browser: IE6 and IE5
		request = new ActiveXObject("Microsoft.XMLHTTP");
	}
	else
	{
		reportError ('Unable to complete server request: initAjaxRequest()');
		return false;
	}
	
	// set the function to handle server return
	request.onreadystatechange = function()
	{
		if (DEBUGGING && TRACE) {alert ('onreadystatechange');}
		if (request.readyState==4)
		{
			if (DEBUGGING && TRACE) {alert ('request ready: '+request.responseText);}
			if (DEBUGGING) {document.getElementById('txtDump').innerHTML = 'Raw Data Dump:\n' + request.responseText;}
			
			// convert the json string returned into an object for ease of access
			// try/catch is use because a browser without JSON support (*cough* IE) will throw
			// an error if JSON is referenced, we catch the error to branch program flow
			try 
			{
				if (JSON.parse != null)
				{
					// use the native parse function
					if (DEBUGGING && TRACE) {alert ('JSON.parse');}
					dataReturn = JSON.parse(request.responseText);
				}
				else
				{
					// theoretically this will never happen because the 
					// browser which doesn't support JSON will throw an
					// error and jump directly to the catch below
					dataReturn = JSONtoObject(request.responseText);
					
				}
			}
			catch (error)
			{
				// browser does not support JSON object so parse the response
				// with the function in this script
				dataReturn = JSONtoObject(request.responseText);
			}
			
			// dataReturn as been processed but may be null or contain error msgs
			if (dataReturn == null)
			{
				if (DEBUGGING && TRACE) {alert ('failed to parse jason');}
				reportError('dataReturn is null', ERR_DATA);
			}
			else if (dataReturn['error'] != '')
			{
				if (DEBUGGING && TRACE) {alert('dataReturn has error');}
				// not all errors are fatal, some are expected
				// they will be sorted and handled in reportError()
				var errType = parseInt(dataReturn['errorType']);
				reportError(dataReturn['error'], errType, dataReturn['providerName']);
			}
			else
			{
				// dataReturn is clean, no errors, no warnings so display it
				if (DEBUGGING && TRACE) {alert('dataReturn is clean');}
				putCalculations(dataReturn);
			}
		}
	}
	
	// get the user input from the form inputs
	if (DEBUGGING && TRACE) {alert ('getFormInput - trigger: '+trigger);}
	var input;
	switch (trigger)
	{
		case TRIGGER_PROVIDERNAME:
			input = getCalculatorData(trigger);
			break;
		case TRIGGER_ESTANNUALMSC:
			input = getCalculatorData(trigger);
			break;
		case TRIGGER_USERDATA :
			input = getUserData(trigger);
			break;
		default:
			reportError ('Script called with invalid trigger', ERR_BAD_TRIGGER);
			input = false;
			break;
	}
	
	// run the calculations
	if (input)
	{
		if (DEBUGGING && TRACE) {alert ('sending input to: '+SCRIPT);}
		request.open("GET", SCRIPT + "?" +input, true);
		request.send(null);
	}
	else
	{
		// there was no user input, i don't think this should be triggered
		reportError ('Unable to process user input, there is no input.', ERR_BAD_DATA);
	}
	
}// initAjaxRequest

function getCalculatorData (trigger)
{
	if (DEBUGGING && TRACE) {alert ('getCalculatorData');}
	var providerNumber = '';
	var estAnnualMSC = '';
	var totalInitialAmount = '';
	
	// used for testing only
	var medicareDischarges = '';
	var medicaidDischarges = '';
	var otherDischarges = '';
	
	// get the provider number from the form
	providerNumber = document.getElementById('providerNumber').value;
	if (!providerNumber && TESTING_DATA)
	{
		if (DEBUGGING && TRACE) {alert ('Using test data');}
		// get discharge data from form
		medicareDischarges = document.getElementById('medicareDischarges').value
		medicaidDischarges = document.getElementById('medicaidDischarges').value
		otherDischarges = document.getElementById('otherDischarges').value
		
		if (!(medicareDischarges && medicaidDischarges && otherDischarges))
		{
			// wait for more input
			return '';
		}
	}

	if (DEBUGGING && TRACE) {alert ('Using form data');}
	// get estimated annual MSC from the beds selection
	var selectObject = document.getElementById('estAnnualMSC');
	if (!selectObject || !(estAnnualMSC = selectObject.options[selectObject.selectedIndex].value))
	{
		// failed to get estimated annual MSC
		reportError ('Unable to get Estimated Annual MSC from the number of beds.');
		return '';
	}

	// get the total initial amount which is stored in a hidden field
	if (!(totalInitialAmount = document.getElementById('totalInitialAmount').value))
	{
		// failed to get total initial amount
		reportError ('Unable to get the Total Initial Amount');
		return '';
	}

	// all necessary data has been retrieved from the form
	// send it along
	// note: medicareDischarges, medicaidDischarges and otherDischarges will be empty unless
	// the following two conditions are true: providerNumber is empty and testing is set to true

	// format the values as a query string
	return 'providerNumber=' + providerNumber + '&estAnnualMSC=' + estAnnualMSC + '&totalInitialAmount=' + totalInitialAmount + '&medicareDischarges=' + medicareDischarges + '&medicaidDischarges=' + medicaidDischarges + '&otherDischarges=' + otherDischarges + '&useTestData=' + TESTING_DATA + '&trigger=' + trigger;

}// getCalculatorData

function getUserData (trigger)
{
	if (DEBUGGING && TRACE) {alert ('getUserData');}
	// Any of these fields may be empty
	firstName = document.getElementById('firstName').value;
	lastName = document.getElementById('lastName').value;
	phone = document.getElementById('phone').value;
	email = document.getElementById('email').value;
	organization = document.getElementById('providerName').value;
	// format the values as a query string
	return 'firstName=' + firstName + '&lastName=' + lastName + '&phone=' + phone + '&email=' + email + '&organization=' + organization + '&trigger=' + trigger;

}// getUserData

function putCalculations (output)
{
	if (DEBUGGING && TRACE) {alert('putCalculations');}
	setField('providerName', output['providerName']);
	setField('totalMedicareDischarges', output['medicareDischarges']);
	setField('totalMedicaidDischarges', output['medicaidDischarges']);
	setField('totalOtherDischarges', output['otherDischarges']);
	setField('totalDischarges', output['totalDischarges']);
	setField('firstLvlDischargeAdj', output['firstLvlDischargeAdj']);
	setField('initAndDischargeAdj', output['initAndDischargeAdjustment']);
	setField('curMedicare', output['curMedicare']);
	setField('curMedicaid', output['curMedicaid']);
	setField('CMSadjTotal', output['CMSadjTotal']);
	setField('ARRA1', output['ARRA1']);
	setField('ARRA2', output['ARRA2']);
	setField('ARRA3', output['ARRA3']);
	setField('ARRA4', output['ARRA4']);
	setField('maxHIT', output['maxHIT']);
	// setField('estAnnualMSCdisplay', output['estAnnualMSC']);
	setField('atRiskPerfIncentiveDisplay', output['atRiskPerfIncentive']);
	setField('MSC5yrSubscription', output['MSC5yrSubscription']);
	setField('netMSCvARRA', output['netMSCvARRA']);
	setField('estTimeToRecoup', output['estTimeToRecoup']);
	return true;
}// putCalculations

function clearOutputFields ()
{
	if (DEBUGGING && TRACE) {alert('clearOuputFields');}
	setField('providerName', '');
	setField('totalMedicareDischarges', '');
	setField('totalMedicaidDischarges', '');
	setField('totalOtherDischarges', '');
	setField('totalDischarges', '');
	setField('firstLvlDischargeAdj', '');
	setField('initAndDischargeAdj', '');
	setField('curMedicare', '');
	setField('curMedicaid', '');
	setField('CMSadjTotal', '');
	setField('ARRA1', '');
	setField('ARRA2', '');
	setField('ARRA3', '');
	setField('ARRA4', '');
	setField('maxHIT', '');
	setField('atRiskPerfIncentiveDisplay', '');
	setField('MSC5yrSubscription', '');
	setField('netMSCvARRA', '');
	setField('estTimeToRecoup', '');
	return true;
}// clearOutputFields

function setField (fieldName, value)
{
	//if (DEBUGGING && TRACE) {alert('setField: ' + fieldName + ' to ' + value);}
	try
	{
		document.getElementById(fieldName).value = value;
		return true;
	}
	catch (err)
	{
		reportError ('Unable to set the value of field "' + fieldName + '": error - ' + err.message);
		alert (msg);
		return false;
	}
}// setField

function setInnerHTML (elementName, string)
{
	if (DEBUGGING && TRACE) {alert('setInnerHTML');}
	var element;
	try
	{
		document.getElementById(elementName).innerHTML = string;
		return true;
	}
	catch (err)
	{
		reportError ('Unable to set the innerHTML of element "' + elementName + '": error - ' + err.message);
		alert (msg);
		return false;
	}
}// setInnerHTML

function displayUserHint (baseName, hintMsg)
{
	groupParagraph = document.getElementById(baseName + 'Group');
	if (!groupParagraph)
	{
		reportError ('Unable to get HTML element ' + baseName + 'Group');
		return false;
	}
	
	if (fieldDisplay.hintClass)
	{
		hintClass = fieldDisplay.hintClass;
	}
	else
	{
		// default value
		hintClass = 'hint';
	}
	
	groupParagraph.className = hintClass;
	
	// display any hint message
	if (hintMsg)
	{
		if (groupParagraph.hasChildNodes)
		{
			// look for a child node with class 'note'
			children = groupParagraph.childNodes;
			msg = '';
			noteElementClass = fieldDisplay.noteElementClass;
			for (i=0; i < children.length; i++)
			{
				if (noteElementClass && children.item(i).className == noteElementClass)
				{
					children.item(i).innerHTML = hintMsg;
				}
			}
		}
	}
	return true;
}// displayUserHint

function clearUserHints ()
{
	if (fieldDisplay.defaultValues)
	{
		for (inputGroup in fieldDisplay.defaultValues)
		{
			var baseName = fieldDisplay.defaultValues[inputGroup].baseName;
			var groupClass = fieldDisplay.defaultValues[inputGroup].groupClass;
			var noteText = fieldDisplay.defaultValues[inputGroup].noteText;
			var note = null;
			var groupParagraph = document.getElementById(baseName + 'Group');
			
			if (!groupParagraph)
			{
				reportError ('Unable to get groupParagraph "' + baseName + 'Group"', WARNING)
				return false;
			}
			groupParagraph.className = groupClass;
			
			if (!(note = findChildByClass(groupParagraph, fieldDisplay.noteElementClass)))
			{
				reportError ('Unable to get note element for "' + baseName + 'Group"', WARNING)
				return false;
			}
			note.innerHTML = noteText;
		}
		return true;
	}
}// clearUserHints

function setFocus (fieldName)
{
	try
	{
		document.getElementById(fieldName).focus();
		return true;
	}
	catch (err)
	{
		msg = 'Unable to set focus to field "' + fieldName + '": error - ' + err.message;
		alert (msg);
		return false;
	}
}// setFocus

function findChildByClass (parent, className)
{
	// only returns the first child element with class 'class'
	var foundChild = null;
	var child = null;
	
	if (!parent.hasChildNodes)
	{
		return null;
	}
	
	child = parent.firstChild;
	while (child)
	{
		if (child.className == className)
		{
			foundChild = child;
			child = null;
		}
		else
		{
			child = child.nextSibling;
		}
	}
	return foundChild;

}// findChildByClass

function JSONtoObject (json)
{
	if (DEBUGGING && TRACE) {alert('JSONtoObject');}
	// this is a very minimal function to convert json formatted string into an object
	// it is assumed that the json string is properly formatted, if it is not
	// the results are not predicable
	var returnObject = new Object();
	var parseObject = new Object();
	
	if (!json)
	{
		reportError ('JSONtoObject was passed a null value in json.', ERR_DATA);
		return null;
	}
	// this is a crude method of removing enclosing '{' and '}'
	// this regex is  not robust, but is only intended to process
	// data returned from calc_roi.php in json format
	try
	{
		json = json.match(/[^\{]+[^\}]/);
	}
	catch (err)
	{
		reportError ('Failed to remove bounding {} from json. ' +  err.message, ERR_DATA);
		return null;
	}

	// assuming that match will return only one array
	try
	{
		// this is sloppy but return values can contain commas so to split json between
		// between comma delimited element include the " which is assumed to occur
		// only between elements
		// note: both keys and values must be contained by double quotes ("")
		parseObject = json[0].split('",');
	}
	catch (err)
	{
		reportError ('Failed to split json. ' +  err.message, ERR_DATA);
		return null;
	}
	// each element of the array holds a string formatted "key":"value"
	// extract the key and value from the string and make them into an object
	// property and value
	// note: both keys and values must be contained by double quotes ("")
	var temp;
	try
	{
		for (var index = 0; index < parseObject.length; index++)
		{
			// very crude but it will do the job for now
			try
			{
				temp = parseObject[index].split(':');
			}
			catch (err)
			{
				reportError ('Failed to split parseObject at index ' + index + '. ' +  err.message, ERR_DATA);
				return null;
			}
			// strip " marks
			try
			{
				temp[0] = temp[0].match(/[^"]+/);
				// temp[0] is an array, the first index contains the string value.
				// collecting the string value now will simplify handling 
				temp[0] = temp[0][0];
			}
			catch (err)
			{
				reportError ('Failed to match proper keyword. ' +  err.message, ERR_DATA);
				return null;
			}
			// assuming values are only letters 
			try
			{
				temp[1] = temp[1].match(/[^"]+/);
				// temp[1] is an array, the first index contains the string value.
				// collecting the string value now will simplify handling 
				if (temp[1])
				{
					temp[1] = temp[1][0];
				}
				else
				{
					temp[1] = null;
				}
			}
			catch (err)
			{
				reportError ('Failed to match proper value. temp[1] = ' +  temp[1] +  err.message, ERR_DATA);
				return null;
			}
			returnObject[temp[0]] = temp[1];
		}
	}
	catch (err)
	{
		reportError ('Failed to parse json in key:value pairs. ' +  err.message, ERR_DATA);
		return null;
	}
	return returnObject;
}// JSONtoObject

function reportError (msg, errType, extraData)
{
	// coerce errType to integer because some browsers (*cough* IE7) think it's an object
	errType = parseInt(errType);
	switch (errType)
	{
		// specifically handle any errors you don't want displayed
		
		// Check for invalid provider number and update the html to
		// indicate correction to user.
		case WARNING_NO_PROVIDER:
			displayUserHint ('providerNumber', 'Please enter your Provider Number.');
			clearOutputFields();
			break;
			
		case ERR_BAD_PROVIDER:
			displayUserHint ('providerNumber', 'Provider not found.');
			clearOutputFields();
			// alert (msg);
			break;
			
		case ERR_PROVIDER_NOT_FOUND:
			displayUserHint ('providerNumber', 'Provider not found.  Please verify number.');
			clearOutputFields();
			// alert (msg);
			break;
			
		case WARNING:
			if (DEBUGGING)
			{
				alert (msg);
			}
			break;
		
		case WARNING_PROVIDER_SHORT:
			clearOutputFields();
			break;
			
		case WARNING_NO_BEDCOUNT:
			displayUserHint ('estAnnualMSC', 'Choose correct range for your hospital.');
			clearOutputFields();
			// display the provider name as feedback that the provider number is valid
			setField('providerName', extraData);
			break;
			
		case NOTICE_USER_DATA_SENT:
			setInnerHTML('user_data_message', 'Thank you, we will have a representative contact you shortly.');
			break;
						
		default:
			if (!DEBUGGING)
			{
				msg = "We are unable to complete your request due to " + errCodeToText(errType) + "\n" + genericErrorMsg;
			}
			else
			{
				msg = msg + "\n" +  " due to " + errCodeToText(errType);
			}
			clearOutputFields();
			alert (msg);
	}
}// reportError

function errCodeToText (errType)
{

	switch (errType) 
	{
		case ERR_UNDEFINED:
			return 'an undefined error';
			break;
		case ERR_BAD_DATA:
			return 'bad data';
			break;
		case ERR_USER_INPUT:
			return 'bad user input';
			break;
		case WARNING_NO_PROVIDER:
			return 'no provider number';
			break;
		case ERR_BAD_PROVIDER:
			return 'invalid provider number';
			break;
		case ERR_PROVIDER_NOT_FOUND:
			return 'provider not found';
			break;
		case ERR_BAD_TRIGGER:
			return 'an invalid trigger';
			break;
		case WARNING_NO_BEDCOUNT:
			return 'no bed count';
			break;
		case WARNING:
			return 'an undefined warning';
			break;
		case WARNING_PROVIDER_SHORT:
			return 'provider number is too short';
			break;
		case WARNING_PROVIDER_LONG:
			return 'provider number is too long';
			break;
		case WARNING_INVALID_DATA:
			return 'invalid data';
			break;
		case ERR_SEND_MAIL:
			return 'a failure of the mail server'; 
			break;
		default:
			return errType;
	}
}// errCodeToText

