/**
 * WebSocket with graceful degradation - jQuery plugin
 * @author David Lindkvist
 * @version 0.1
 * 
 * Returns an object implementing the WebSocket API. 
 * 
 * If browser supports WebSockets a native WebSocket instance is returned. 
 * If not, a simulated half-duplex implementation is returned which uses polling 
 * over HTTP to retrieve new messages
 * 
 * OPTIONS
 * -----------------------------------------------------------------------------
 * 
 * {Number}		fallbackOpenDelay		number of ms to delay simulated open 
 * 										event for fallback
 * {Number}		fallbackPollInterval	number of ms between requests for 
 * 										fallback polling
 * {Object}		fallbackPollParams		optional params to pass with each poll 
 * 										requests
 * 
 * EXAMPLES
 * -----------------------------------------------------------------------------
 * 
 * 	var websocket = $.gracefulWebSocket("ws://127.0.0.1:8080/");
 * 
 * 	var websocket = $.gracefulWebSocket({
 * 		fallbackPollParams:  {
 * 			"latestMessageID": function () {
 * 				return latestMessageID;
 * 			}
 *  	} 
 * 	});
 * 
 */

(function ($) {

	$.extend({
	
		gracefulWebSocket: function (url, options) {
		
			// Default properties 
			this.defaults = {
				keepAlive: false,		// not implemented - should ping server to keep socket open
				autoReconnect: false,	// not implemented - should try to reconnect silently if socket is closed
				fallback: true,			// not implemented - always use HTTP fallback if native browser support is missing
				fallbackSendURL: url.replace('ws:', 'http:').replace('wss:', 'https:'),
				fallbackSendMethod: 'POST',
				fallbackPollURL: url.replace('ws:', 'http:').replace('wss:', 'https:'),
				fallbackPollMethod: 'GET',
				fallbackOpenDelay: 100,	// number of ms to delay simulated open event
				fallbackPollInterval: 3000,	// number of ms between poll requests
				fallbackPollParams: {}		// optional params to pass with poll requests
			};
			
			// Override defaults with user properties
			var opts = $.extend({}, this.defaults, options);
			
			/**
			 * Creates a fallback object implementing the WebSocket interface
			 */
			function FallbackSocket() {
				
				// WebSocket interface constants
				const CONNECTING = 0;
				const OPEN = 1;
				const CLOSING = 2;
				const CLOSED = 3;
				
				var pollInterval;
				var openTimout;
				
				// create WebSocket object
				var fws = {
					// ready state
					readyState: CONNECTING,
					bufferedAmount: 0,
					send: function (data) {
						var success = true;
						$.ajax({
							async: false, // send synchronously
							type: opts.fallbackSendMethod,
							url: opts.fallbackSendURL + '?' + $.param( getFallbackParams() ),
							data: data,
							dataType: 'text',
							contentType : "application/x-www-form-urlencoded; charset=utf-8",
							success: pollSuccess,
							error: function (xhr) {
								success = false;
								$(fws).triggerHandler('error');
							}
						});
						return success;
					},
					close: function () {
						clearTimeout(openTimout);
						clearInterval(pollInterval);
						this.readyState = CLOSED;
						$(fws).triggerHandler('close');
					},
					onopen: function () {},
					onmessage: function () {},
					onerror: function () {},
					onclose: function () {},
					previousRequest: null,
					currentRequest: null
				};
				
				function getFallbackParams() {
					
					// update timestamp of previous and current poll request
					fws.previousRequest = fws.currentRequest;					
					fws.currentRequest = new Date().getTime();
					
					// extend default params with plugin options
					return $.extend({"previousRequest": fws.previousRequest, "currentRequest": fws.currentRequest}, opts.fallbackPollParams);
				}
				
				/**
				 * @param {Object} data
				 */
				function pollSuccess(data) {
					
					// trigger onmessage
					var messageEvent = {"data" : data};
					fws.onmessage(messageEvent);	
				}
				
				function poll() {
					
					$.ajax({
						type: opts.fallbackPollMethod,
						url: opts.fallbackPollURL,
						dataType: 'text',
						data: getFallbackParams(),
						success: pollSuccess,
						error: function (xhr) {			
							$(fws).triggerHandler('error');
						}
					});		
				}
				
				// simulate open event and start polling
				openTimout = setTimeout(function () { 
					fws.readyState = OPEN;
					//fws.currentRequest = new Date().getTime();
					$(fws).triggerHandler('open');
					poll();
					pollInterval = setInterval(poll, opts.fallbackPollInterval);
					
				}, opts.fallbackOpenDelay);
				
				// return socket impl
				return fws;
			}
			
			// create a new websocket or fallback
			var ws = window.WebSocket ? new WebSocket(url) : new FallbackSocket();
	 		$(window).unload(function () { ws.close(); ws = null });
			return ws;
		}
	});
	
})(jQuery);