//Constructor
function DeliveryEstimates(){
	
	this.productID = "";
	this.coundownTimerInterval = 0;
	this.serverTimeStamp = "";
	this.startTimeStampOfLocalMachine = "";
	this.currentlySelectedShipping = "";
	
	this.shippingOptionsArr = new Array();
	
	this.deliveryEstResponseEventsFunctionsArr = new Array();
	this.deliveryEstResponseEventsObjectsArr = new Array();
	
	this.deliveryEstCriticalErrorEventsFunctionsArr = new Array();
	this.deliveryEstCriticalErrorEventsObjectsArr = new Array();
	
	
	this.updateCountdownTimerEventsFunctionsArr = new Array();
	this.updateCountdownTimerEventsObjectsArr = new Array();
	
}

// The selected shipping method may change with the user selecting a new value in the drop down menu.
// The currently selected method affects the cut-off time (because every shipping method is different), and therefore the  countdown timer.
DeliveryEstimates.prototype.selectNewShippingMethod = function(shippingMethodName)
{
	this.currentlySelectedShipping = shippingMethodName;
	
	// Don't wait for the interval to update the countdown timer
	this.coundownTimerInterval_private();
	
}

// The event that you attach should accept 3 parameters (hours, minutes, seconds)
DeliveryEstimates.prototype.attachCountdownTimerEvent = function(functionRef, objectRef)
{
	this.updateCountdownTimerEventsFunctionsArr.push(functionRef);
	this.updateCountdownTimerEventsObjectsArr.push(objectRef);
	
}

// The event that you attach doesn't need parameters.
DeliveryEstimates.prototype.attachResponseEvent = function(functionRef, objectRef)
{
	this.deliveryEstResponseEventsFunctionsArr.push(functionRef);
	this.deliveryEstResponseEventsObjectsArr.push(objectRef);
	
}
// The event that you attach should accept 2 parameter (errorCode, errorDescription).
DeliveryEstimates.prototype.attachErrorEvent = function(functionRef, objectRef)
{
	this.deliveryEstCriticalErrorEventsFunctionsArr.push(functionRef);
	this.deliveryEstCriticalErrorEventsObjectsArr.push(objectRef);
}

// Set the Product ID before calling the method fireRequest()
DeliveryEstimates.prototype.setProductID = function(productID)
{
	this.productID = productID;
}

DeliveryEstimates.prototype.getProductID = function()
{
	return this.productID;
}


// Set the UserName and Password before calling this method.
DeliveryEstimates.prototype.fireRequest = function()
{
	
	if(this.productID == ""){
		alert("You must set a Product ID first");
		return;
	}

	// So we can scope the callback.
	var instance = this;
	
	var dateObj = new Date();
	var apiRequestURL = "/time_estimates_xml.php?productid=" + this.productID;
	
	// Get a free/new connection object by reference. Abort method call if an HTTP object cant be created. The method getXmlHttpObj will alert an error message too.
	var xmlHttpObjFromGlobalQueue = getXmlHttpObj("DeliveryEstXML");
	if(xmlHttpObjFromGlobalQueue == null)
		return;
	
	xmlHttpObjFromGlobalQueue.open('GET', apiRequestURL, true);


	// Micosoft does not like you setting the Ready State before calling "open".
	xmlHttpObjFromGlobalQueue.onreadystatechange = function() 
	{

		// Now the Callback happened from the XMLHttpRequest communication
		// We have to find out which one was used from our Global Array of connection pools.
		// There is no way to guarantee the order in which these are processed.
		// We are basically looking for the first connection object which is Not Free... Having a matching CallBack function "identifier string".
		for(var t=0; t<xmlReqsObjArr.length; t++){
	
			if(xmlReqsObjArr[t].reqObjFree || xmlReqsObjArr[t].reqObjParsing || xmlReqsObjArr[t].xmlHttpObj.readyState != 4 || xmlReqsObjArr[t].callBackFunctionName != "DeliveryEstXML")
				continue;
			if(xmlReqsObjArr[t].xmlHttpObj.status == "200"){
				xmlReqsObjArr[t].reqObjParsing = true;
				instance.parseXMLresponse(xmlReqsObjArr[t].xmlHttpObj.responseText);
			}
			else{
				instance.errorEvent_private(xmlReqsObjArr[t].xmlHttpObj.status, xmlReqsObjArr[t].xmlHttpObj.statusText);
			}

			// Now that we are done dealing with our Error, or parsing the document... 
			// ... we can mark our XMLhttpRequest Object as free to be used by another connection.
			xmlReqsObjArr[t].reqObjFree = true;
			xmlReqsObjArr[t].reqObjParsing = false;
		}
	}
	
	// GET requests don't have any data to send in the body.
	xmlHttpObjFromGlobalQueue.send(null);	
}

// If you make any changes to the quantity or the options/choices, call this method to save to the database.
// Make sure to define an event handler to listen for the response from the server.
DeliveryEstimates.prototype.parseXMLresponse = function(xmlhttpresponseText)
{

	// Find out if the document was not what we expected to receive.
	// If Not, try to get an Error message and send that through the error event.
	var matchArr = xmlhttpresponseText.match(/\<now\>(\d+)\<\/now\>/);
	if(!matchArr){
		this.errorEvent_private("5921", "No response from server.");

		return;
	}
	
	this.serverTimeStamp = matchArr[1];
	this.startTimeStampOfLocalMachine = Math.round(new Date().getTime()/1000.0);


	var shippingOptionsCounter = -1;
	
	// The server should return each XML tag on its own line.
	var xmlLinesArr = xmlhttpresponseText.split("\n");
	
	for(var lineNo = 0; lineNo < xmlLinesArr.length; lineNo++)
	{
		
		if(xmlLinesArr[lineNo].toString().match(/\<shipping\>/)){
			shippingOptionsCounter++;
			this.shippingOptionsArr[shippingOptionsCounter] = new ShippingOption();
			
		}
		else if(xmlLinesArr[lineNo].toString().match(/\<method\>/)){
			this.shippingOptionsArr[shippingOptionsCounter].shippingOptionName = getXMLdata(xmlLinesArr[lineNo]);
			
			// Make the first shipping method the default
			if(this.currentlySelectedShipping == "")
				this.currentlySelectedShipping = this.shippingOptionsArr[shippingOptionsCounter].shippingOptionName;
		}
		else if(xmlLinesArr[lineNo].toString().match(/\<arrival\>/)){
			this.shippingOptionsArr[shippingOptionsCounter].arrivalDesc = getXMLdata(xmlLinesArr[lineNo]);
		}
		else if(xmlLinesArr[lineNo].toString().match(/\<arrival_date\>/)){
			this.shippingOptionsArr[shippingOptionsCounter].arrivalDate = getXMLdata(xmlLinesArr[lineNo]);
		}
		else if(xmlLinesArr[lineNo].toString().match(/\<ship\>/)){
			this.shippingOptionsArr[shippingOptionsCounter].shipDate = getXMLdata(xmlLinesArr[lineNo]);
		}
		else if(xmlLinesArr[lineNo].toString().match(/\<cutoff\>/)){
			this.shippingOptionsArr[shippingOptionsCounter].cutOffTimeStamp = getXMLdata(xmlLinesArr[lineNo]);
		}
		
	}





	// See if the user has subscribed to the sign-in event.
	// If it returns TRUE there will be no error description... otherwise it will give a reason why the login didn't sucdeed.
	for(var i=0; i < this.deliveryEstResponseEventsFunctionsArr.length; i++){
		this.deliveryEstResponseEventsFunctionsArr[i].call(this.deliveryEstResponseEventsObjectsArr[i]);
	}
	
	// Fire off the countdown timer as soon as possible.  Don't wait for the first interval to be triggered.
	this.coundownTimerInterval_private();

	var instanceForThis = this;
	this.coundownTimerInterval = setInterval ( function() { instanceForThis.coundownTimerInterval_private(); }, 1000 );
	

}

// Goes through all available shipping methods
// Returns a string such as "Jan 1, 2008"
DeliveryEstimates.prototype.getEarliestDeliveryDate = function()
{
	var earliestDate = "";
	for(var i=0; i<this.shippingOptionsArr.length; i++){
			
		// The first element in the array always starts out as the earliest
		if(earliestDate == ""){
			earliestDate = this.shippingOptionsArr[i].arrivalDate;
			continue;
		}
		
		var dateOfThisMethod = Date.parse(this.shippingOptionsArr[i].arrivalDate);
		var dateOfEarliest = Date.parse(earliestDate);

		if(dateOfThisMethod < dateOfEarliest){
			earliestDate = this.shippingOptionsArr[i].arrivalDate;
		}
	}
	return earliestDate;
}



DeliveryEstimates.prototype.errorEvent_private = function(responseCode, responseError)
{

	// See if the user has subscribed to the critical error event.
	for(var i=0; i < this.deliveryEstCriticalErrorEventsFunctionsArr.length; i++){
		this.deliveryEstCriticalErrorEventsFunctionsArr[i].call(this.deliveryEstCriticalErrorEventsObjectsArr[i], responseCode, responseError);
	}


}


// this private method should be called on an interval every 1 second.
// It will callculate the number of hours, minutes, and seconds left.
// The 3 parametmers returned will be strings.  The minutes an seconds will always have 2 digits, with a possible zero in front.
// It will fire off events with this info to anyone who has subscribed to the updateCoundownTimer event.
// If the cutoff time passes... this method will return 00, 00, 00
DeliveryEstimates.prototype.coundownTimerInterval_private = function()
{
	// Get the Cutoff Timestamp of the selected shipping method
	var currentCutoffTimestamp = 0;
	for(var i=0; i<this.shippingOptionsArr.length; i++){
			if(this.shippingOptionsArr[i].shippingOptionName == this.currentlySelectedShipping){
				currentCutoffTimestamp = this.shippingOptionsArr[i].cutOffTimeStamp;
				break;
			}
	}
	if(currentCutoffTimestamp == 0)
	{
		alert("Error getting the shipping cutoff timestamp");
	}
	
	
	// The only way to know what the current unix timestamp is on the server... is by adding the (server/local) difference to our most-recent local timestamp.
	// We know the difference between the local machine and server time because we recorded each of them at the time the XML was parsed.
	var timeDifferenceBetweenLocalAndRemote = this.serverTimeStamp - this.startTimeStampOfLocalMachine;
	var currentTimeStampOfServer = Math.round(new Date().getTime()/1000.0) + timeDifferenceBetweenLocalAndRemote;
	
	// The cutoff timestamp (to place the order by) of the shipping method should be in the future (reative to the server unix timestamp)
	// The only exception is if the page was sitting idle for too long.
	var secondsLeftUntilDeadline = (currentCutoffTimestamp - currentTimeStampOfServer);
	
	if(secondsLeftUntilDeadline < 0)
	{
		var hours = "0";
		var minutes = "00";
		var seconds = "00";
	}
	else{
		var hours = Math.floor((secondsLeftUntilDeadline / (60 * 60)));
		var numberOfSecondsWithoutHours = secondsLeftUntilDeadline - (60 * 60 * hours);
		
		var minutes = Math.floor(numberOfSecondsWithoutHours / 60);
		var seconds = secondsLeftUntilDeadline - (60 * 60 * hours) - (60 * minutes);

		// Convert integers to strings
		hours = hours + "";
		minutes = minutes + "";
		seconds = seconds + "";
		
		// Make sure there are always 2 digits over seconds and minutes;
		if(minutes.length == 1)
			minutes = "0" + minutes;
		if(seconds.length == 1)
			seconds = "0" + seconds;
			
	}
	
	// Loop through all of the Event Subscriptions and let them know about the new coundown values.
	for(var i=0; i < this.updateCountdownTimerEventsFunctionsArr.length; i++){	
		this.updateCountdownTimerEventsFunctionsArr[i].call(this.updateCountdownTimerEventsObjectsArr[i], hours, minutes, seconds);
	}
}

// Pass in a javascript date object.  This will return the name of the shipping method which will make the delivery date.
// It will return the lowest shipping method in the stack.  For example... 1 day shipping will always make it on time... but if someone wants their cards in 1 month, this method should return "ground" shipping
DeliveryEstimates.prototype.getLowestPriorityMethodWhichArrivesByDate = function(arrivalDateObj)
{
	
	// The methods returned from the API call are organized with the highest priority methods on top
	var lastAvailableShippingMethod = "";
	for(var i=0; i<this.shippingOptionsArr.length; i++){
			
		
		var dateOfThisMethod = Date.parse(this.shippingOptionsArr[i].arrivalDate);
		var requiredArrivalDate = arrivalDateObj.getTime();
		
		// Take off a few seconds to make sure we fall back before midnight slightly.
		dateOfThisMethod -= 5000;

		if(dateOfThisMethod < requiredArrivalDate){
			lastAvailableShippingMethod = this.shippingOptionsArr[i].shippingOptionName;
		}
	}
	return lastAvailableShippingMethod;
}



// A simple class to hold the data stucture of each shipping option detail
function ShippingOption(){
	this.shippingOptionName = "";
	this.cutOffTimeStamp = "";
	this.arrivalDesc = "";
	this.arrivalDate = "";
	this.shipDate = "";
}













