/**
 * Dao() definition file
 *
 * The Data Access Object used to retrieve new data from the server
 *
 * @author   Sean McCann
 * @version  0.5.4
 * @date     2007.01.29
 *
 * Changelog
 * - 2007.01.29:  Fixed bug with handler invokation for cached data (Sean McCann)
 * - 2005.08.12:  Removed a stray unfinished comment.  No modifications were made to the codebase. (Sean McCann)
 * - 2005.07.15:  Minor updates to parameters used for displaying error output.  (Sean McCann)
 * - 2005.06.24:  Added a workaround to correct Mozilla-specific issue where the onreadystatechange handler is invoked when aborting a connection (Sean McCann)
 * - 2005.06.08:  Encapsulated XMLHttpRequestFactory() object in the Dao (Sean McCann)
 * - 2005.05.23:  Added the 'http_method' option.  Fixed NULL and false comparisons (Sean McCann)
 * - 2005.05.20:  Removed setURI() and getURI() methods.  Removed internal 'flags' object.  'enable_caching' option now turned on by default.  (Sean McCann)
 * - 2005.05.18:  Added the 'asynchronous' option (Sean McCann)
 * - 2005.05.11:  Added "DAO_" constants.  Added several data access methods.  Added options, though they're not really set up yet.  (Sean McCann)
 * - 2005.05.05:  Removed debugging output  (Sean McCann)
 * - 2005.05.03:  Added setURI() and getURI() methods  (Sean McCann)
 * - 2005.05.03:  Experiment was a failure, as the response time was noticeably slower and the script taxed the CPU on occasion.  Went back to using asynchronous communication and callbacks  (Sean McCann)
 * - 2005.05.02:  Experimented with synchronous communication and Handler() objects  (Sean McCann)
 * - 2005.04.28:  Initial release.  (Sean McCann)
 */


/**
 * See the "Remarks" section of the readyState Property (IXMLHTTPRequest) documentation on MSDN
 * @link http://msdn.microsoft.com/library/default.asp?url=/library/en-us/xmlsdk/html/xmproreadystaterequest.asp
 */
var DAO_UNINITIALIZED = 0;
var DAO_LOADING = 1;
var DAO_LOADED = 2;
var DAO_INTERACTIVE = 3;
var DAO_COMPLETED = 4;


/**
 * The DAO (data access object) used to retrieve new data from the server
 */
function Dao()
{
	/**
	 * Internal object used to query the server from the client side.
	 * Currently, this DAO implementation only supports using an XMLHttpRequest object as the internal `request_object`
	 * @access private
	 */
	this.request_object = this.XMLHttpRequestFactory();

	/**
	 * URI to the resource currently being queried
	 * @access private
	 */
	this.uri = '';

	/**
	 * Caching object used internally by the DAO to save query results
	 * 'cache' is not instantiated unless the "enable_caching" option is on
	 * @access private
	 */
	this.cache = null;

	/**
	 * Stores the last value retrieved from the data cache.
	 * @access private
	 */
	this.cached_data = false;

	/**
	 * Flag indicating whether or not the requested data was found in the local cache
	 * @access private
	 */
	this.found_cached_data = false;

	/**
	 * Flag indicating if we are attempting to abort an existing connection.  Used to work around
	 * a Firefox bug where the onreadystatechange handler is unnecessarily set
	 */
	this.aborting_request = false;


	/**
	 * Any optional settings for the DAO itself
	 * @access private
	 */
	this.options = new Object();

	// Set default values for our object
	this.options['asynchronous']         = true;
	this.options['enable_caching']       = true;
	this.options['http_method']          = 'GET';
	this.options['register_handler_dao'] = true;
}

Dao.prototype.__CLASS__ = 'Dao';
Dao.prototype.getOption = DaoGetOption;
Dao.prototype.setOption = DaoSetOption;
Dao.prototype.requestData = DaoRequestData;
Dao.prototype.requestIsComplete = DaoRequestIsComplete;
Dao.prototype.getData = DaoGetData;
Dao.prototype.getReadyState = DaoGetReadyState;
Dao.prototype.getResponseHeader = DaoGetResponseHeader;
Dao.prototype.abortRequest = DaoAbortRequest;
Dao.prototype.XMLHttpRequestFactory = DaoXMLHttpRequestFactory;


/**
 * Gets the value of a DAO configuration option
 * Throws an exception if a non-existant option is given
 * @param string option_name   the option that we want to know about
 * @return mixed
 */
function DaoGetOption( option_name )
{
	try
	{
		if (typeof this.options[option_name] == 'undefined')
		{
			throw new Error("This DAO does not have the '" + option_name + "' option ");
		}

		return this.options[option_name];
	}
	catch(e)
	{
		error_message(e, "Dao.js", 'DaoGetOption');
		return false;
	}
}

/**
 * Sets the value of a DAO configuration option
 *
 * @param string option_name   the option that we want to change
 * @param mixed  value         the new value for our option
 *
 * Option list:
 * - asynchronous
 * - enable_caching
 * - http_method
 * - register_handler_dao
 */
function DaoSetOption( option_name, value )
{
	try
	{
		if (typeof this.options[option_name] == 'undefined')
		{
			throw new Error("This DAO does not have the '" + option_name + "' option ");
		}

		this.options[option_name] = value;

		// Initialize our cache if need be
		if (option_name == "enable_caching"  &&  value  &&  !this.cache)
		{
			this.cache = new Cache();
		}
		return true;
	}
	catch(e)
	{
		error_message(e, "Dao.js", 'DaoSetOption');
		return false;
	}
}


/**
 * Requests new data from the server
 *
 * @param string uri   Specifies the location (ie. the URI) from were we can obtain our data
 * @param function handler   Performs some operation with the data obtained from the server
 * @return bool
 */
function DaoRequestData(uri, handler)
{
	try
	{
		// Initialize the flags that we might set
		this.found_cached_data = false;

		// Perform some type checking on our parameters
		if (typeof uri == 'undefined' || !uri)
		{
			return false;
		}

		// The handler is required if asynchronous mode is on (default)
		if ( this.options['asynchronous'] && (typeof handler == 'undefined' || !handler) )
		{
			return false;
		}

		// Save the location of our data resource
		// since other objects might want to know
		// where we are getting the data from
		this.uri = uri;

		// Internet Explorer does not return the
		// readyState to 'UNINITIALIZED' after
		// a successful send.  Therefore, we must
		// abort the current transaction to reset
		// the readyState before attempting to
		// send again.
		if (this.request_object.readyState != DAO_UNINITIALIZED)
		{
			this.abortRequest();
		}

		// Check for our data in the cache
		// Use our URI as the lookup key
		
		if (this.options['enable_caching'] === true)
		{ 
			if (this.cache === null)
			{
				this.cache = new Cache();
			}

			var cached_data;
			cached_data = this.cache.retrieveData(uri);
			if (cached_data)
			{
				this.found_cached_data = true;
				this.cached_data = cached_data;

				// Manually invoke our handler.  For consistent behavior
				// with cached and uncached data, we need to make sure
				// sure that the handler is not invoked as a method of
				// the DAO (ie. it's "this" object should refer to itself)
				handler.call(handler);

				return true;
			}
		}

		// Odd as this may seem, recreation of the object
		// seems to fix a number of timing-related bugs.
		// Though not documented as being necessary, most
		// other XMLHttpRequest examples perform this
		// re-instantiation
		this.request_object = this.XMLHttpRequestFactory();

		// If we do have a data handler, there are a few things
		// that we need to do before grabbing the data from
		// the server...
		if (handler)
		{
			// Our handler needs to operate on the data retrieve
			// from the server by this particular DAO instance, so
			// we need to register this instance with the handler
			if (this.options['register_handler_dao'])
			{
				handler.dao = this;
			}

			// Set the handler that will manipulate
			// the data in the server's response
			// Note that you will always need to set handler when working in asynchronous mode
			this.request_object.onreadystatechange = handler;
		}

		// Request the new data from the server
		var date = new Date();
		var timestamp = date.getTime();
		
		//adding timestamp to uri to prevent IE from caching the page info
		if (uri.indexOf('?') == -1) {
			uri = (uri + '?time=' + timestamp);
		} else {
			uri = (uri + '&time=' + timestamp);
		}
		
		this.request_object.open(this.options['http_method'], uri, this.options['asynchronous']);
		this.request_object.send(null);
		
		// If asynchronous mode is off and we have specified a handler, then we need to invoke that handler automatically.
		if ( (this.options['asynchronous'] === false) && (typeof handler != 'undefined') )
		{
			// Like before, we need to make sure that the handler's
			// "this" object actually refers to itself.  Otherwise,
			// we get some nasty bugs.
			handler.call(handler);
		}

		return true;
	}
	catch(e)
	{
		error_message(e, 'Dao.js', 'DaoRequestData');
		return false;
	}
}

/**
 * Gets the data requested by the last requestData() call
 * @return string
 */
function DaoGetData()
{ 
	try
	{
		//  First, check to see if we found our data in the cache
		if (this.found_cached_data)
		{	
			return this.cached_data;
		}

		// If we could not find the data in the cache but did find it
		// on the server *AND* we have caching enabled, then put the
		// server data in the cache
		else if (this.options['enable_caching'])
		{	
			this.cached_data = this.request_object.responseText;
			this.cache.saveData(this.uri, this.cached_data);
			this.found_cached_data = true;
		}
		
		// return the server data
		return this.request_object.responseText;
	}
	catch(e)
	{
		error_message(e, "Dao.js", 'DaoGetData');
		return false;
	}
}

/**
 * Indicates whether or not we have finished looking for the requested data
 *
 * @return bool
 */
function DaoRequestIsComplete()
{
	try
	{
		// If we are in the middle of aborting a connection, consider
		// the current request incomplete (after all, we're aborting it).
		// This should handle a Firefox issue with erroneously running
		// the onreadystatechange handler when aborting a connection.
		if (this.aborting_request)
		{
			return false;
		}

		// If we found the requested data in the cache or if the
		// server has finished sending the data to us, then
		// consider the request complete
		return (this.request_object.readyState == DAO_COMPLETED || this.found_cached_data);
	}
	catch(e)
	{
		error_message(e, "Dao.js", 'DaoRequestIsComplete');
		return false;
	}
}


/**
 * Terminates an outstanding request for data
 *
 * @return bool
 */
function DaoAbortRequest()
{
	try
	{
		// Mozilla will attempt to invoke the registered
		// handler before aborting.  This undocumented and
		// undesired behavior can lead to some strange
		// recursion issues.  We would *like* to simply remove
		// the registered handler before attempting the abort,
		// but if we do that, we end up getting a type mismatch
		// in IE.  So, we need to set a flag indicating that we
		// want to abort the outstanding connection, and test for
		// the presence of that flag in our handler

		this.aborting_request = true;
		this.request_object.abort();
		this.aborting_request = false;

		return true;
	}
	catch(e)
	{
		error_message(e, "Dao.js", 'DaoAbortRequest');
		return false;
	}
}


/**
 * Retrieves the state of current request for data.
 *
 * The state returned by getReadyState() can be any
 * of the "DAO_" constants (DAO_INITIALIZED, etc.)
 *
 * @return int
 */
function DaoGetReadyState()
{
	try
	{
		return this.request_object.readyState;
	}
	catch(e)
	{
		error_message(e, "Dao.js", 'DaoGetReadyState');
		return false;
	}
}


/**
 * Retrieves a specific header from the most recently
 * received HTTP response.  This method is really just
 * a wrapper for the request object's own equivalent
 * method.
 *
 * @return string
 */
function DaoGetResponseHeader(header_name) {
	try
	{
		return this.request_object.getResponseHeader(header_name);
	}
	catch(e)
	{
		error_message(e, "Dao.js", 'DaoGetResponseHeader');
		return false;
	}
}


/**
 * Attempts to get the browser's XMLHttpRequest (or equivalent) object
 *
 * This script will define the XMLHttpRequest() object when the user
 * is browsing with Internet Explorer, which uses its own ActiveX
 * object that functions in the same way as and the standards-based
 * XMLHttpRequest object.
 * Historical Note:  Microsoft invented the XMLHttp object, and the
 * standard is based off of their model.
 *
 * @author   Google (taken from Google Suggest beta's "ac.js")
 * @author   Sean McCann (modifications to the original code)
 */
function DaoXMLHttpRequestFactory()
{
	var xml_http_obj = null;

	// Check for different IE-specific versions of its XML module
	try
	{
		xml_http_obj = new ActiveXObject("Msxml2.XMLHTTP");
	}
	catch(e)
	{
		try
		{
			xml_http_obj = new ActiveXObject("Microsoft.XMLHTTP");
		}
		catch(oe)
		{
			xml_http_obj = null;
		}
	}

	// If nothing suitable has been found thus far, use the
	// standard XMLHttpRequest
	if (!xml_http_obj && typeof XMLHttpRequest != "undefined")
	{
		xml_http_obj = new XMLHttpRequest();
	}

	return xml_http_obj;
}
