/**
 * Socket constructor
 * @constructor
 * @extends EventDispatcher
 */
function Socket()
{
	// Call the prototype's constructor
	EventDispatcher.call(this);


	// Private
	var self			= this,
		socket			= null,
		requestHeaders	= {};

	// Public
	this.request			= request;
	this.getStatus			= getStatus;
	this.getStatusText		= getStatusText;
	this.getResponseText	= getResponseText;
	this.getResponseXML		= getResponseXML;
	this.setRequestHeader	= setRequestHeader;
	this.getResponseHeader	= getResponseHeader;


	/**
	 * Try XMLHttpRequest as a native object
	 * @requires XMLHttpRequest
	 */
	try
	{
		socket = new XMLHttpRequest();
	} 
	catch (exception)
	{
	}

	// Try XMLHttpRequest as an ActiveX object
	var xmlImplementations = ["MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.3.0"];

	for (var i = 0; i < xmlImplementations.length; i++) 
	{
		try
		{
			socket = new ActiveXObject(xmlImplementations[i]);
		} 
		catch (exception)
		{}
	}


	/**
	 * Perform an HTTP request, using either the GET or POST method, passing along optional parameters.
	 * @param {String} URI The URI to retrieve.
	 * @param {Object} [parameters] Parameters to send along with the request.
	 * @param {Boolean} [asynchronous] Perform the request in an asynchronous manner, dispatching events to callback functions.
	 * @param {Boolean} [post] Use the POST method instead of GET.
	 * @return {Object} SocketResponse Returns a SocketResponse object.
	 * @method
	 */
	function request(URI, parameters, asynchronous, post)
	{
		// Check arguments
		if (typeof URI != "string")
		{
			throw new Error("Invalid argument: URI is missing or not a string");
		}

		if (post == true && (parameters == undefined || parameters == null))
		{
			throw new Error("Invalid argument: parameters is missing or not an object");
		}

		if (typeof asynchronous != "boolean")
		{
			asynchronous = true;
		}

		if (typeof post != "boolean")
		{
			post = false;
		}


		// If there's a request in progress, abort it
		if (socket.readyState != 0)
		{
			socket.abort();
		}


		var requestData = "";


		// If parameters where given...
		if (parameters != undefined && parameters != null) 
		{
			// ...and if it's an object, go through its properties and build up a valid (URI encoded) string for use in the request
			if (typeof parameters == "object")
			{
				// In a GET request, the requestData string is appended to the URI. However, the URI might already contain a query string
				if (post == false) 
				{
					// There's no query string, begin with a question mark
					if (URI.indexOf("?") == -1)
					{
						requestData = "?";
					}
					// There's an existing query string, to which the requestData string will be appended. Begin with an ampersand
					else 
					{
						requestData = "&";
					}
				}

				// Loop through the parameters and URI encode both the name and the value
				for (var parameter in parameters)
				{
					requestData += encodeURIComponent(parameter) + "=" + encodeURIComponent(parameters[parameter]) + "&";
				}

				// Remove the ampsersand at the end (as a result of the loop above)
				requestData = requestData.substring(0, requestData.length - 1);
			}
			else
			{
				throw new Error("Invalid argument: parameters is not an object");
			}
		}

		// Handle the request asynchronous by dispatching events
		socket.onreadystatechange = function()
		{
			switch (socket.readyState)
			{
				case 0:
					self.dispatchEvent(new CustomEvent(Socket.UNINITIALIZED, self));
					break;
				case 1:
					self.dispatchEvent(new CustomEvent(Socket.OPEN, self));
					break;
				case 2:
					self.dispatchEvent(new CustomEvent(Socket.SENT, self));
					break;
				case 3:
					self.dispatchEvent(new CustomEvent(Socket.RECEIVING, self));
					break;
				case 4:
					self.dispatchEvent(new CustomEvent(Socket.LOADED, self));
			}
		}

		// The POST request requires some additional headers
		if (post == true)
		{
			socket.open("POST", URI, asynchronous);
			socket.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			socket.setRequestHeader("Content-Length", requestData.length);
			socket.send(requestData);
		}
		else
		{
			socket.open("GET", URI + requestData, asynchronous);

			for (var name in requestHeaders)
			{
				socket.setRequestHeader(name, requestHeaders[name]);
			}

			socket.send(null);
		}
	}


	/**
	 * Returns the response status code.
	 * @return {Number} HTTP status code of the response (e.g. 200, 404, 500).
	 * @method
	 */
	function getStatus()
	{
		return socket.status;
	}

	/**
	 * Returns the response status description.
	 * @return {String} HTTP status description of the response (e.g. OK, Not Found, Internal Server Error).
	 * @method
	 */
	function getStatusText()
	{
		return socket.statusText;
	}

	/**
	 * Returns the response as a string.
	 * @return {String} Response to a request as a string.
	 * @method
	 */
	function getResponseText()
	{
		return socket.responseText;
	}

	/**
	 * Returns the response as an XML document.
	 * @return {Object} Response to a request as an XML document.
	 * @method
	 */
	function getResponseXML()
	{
		return socket.responseXML;
	}

	/**
	 * Sets a custom request header.
	 * @param {String} name Name of the request header.
	 * @param {String} value Value of the request header.
	 * @method
	 */
	function setRequestHeader(name, value)
	{
		requestHeaders[name] = value;
	}

	/**
	 * Returns the value of a response header.
	 * @param {String} name Name of a response header.
	 * @return {String} Value of a response header.
	 * @method
	 */
	function getResponseHeader(name)
	{
		return socket.getResponseHeader(name);
	}
}

// Inheritance
Socket.prototype = new EventDispatcher();

/** @constant */
Socket.UNINITIALIZED	= "uninitialized";
/** @constant */
Socket.OPEN				= "open";
/** @constant */
Socket.SENT				= "sent";
/** @constant */
Socket.RECEIVING		= "receiving";
/** @constant */
Socket.LOADED			= "loaded";