/*
 * EventSource polyfill version 0.9.7
 * Supported by sc AmvTek srl
 * :email: devel@amvtek.com
 *
 * Modified by interfaSys sàrl
 * 	* Supports multiple parallel EventSource
 * 	* Use XMLHttpRequest for IE8/9 - That means no streaming,
 * 		but no need to make the endpoint accept requests from all origins
 */
(function (global) {

	if (global.EventSource && !global._eventSourceImportPrefix) {
		return;
	}

	var evsImportName = (global._eventSourceImportPrefix || '') + "EventSource";

	var EventSource = function (url, options) {

		if (!url || typeof url != 'string') {
			throw new SyntaxError('Not enough arguments');
		}

		this.URL = url;
		this.setOptions(options);
		var evs = this;
		setTimeout(function () {
			evs.poll();
		}, 0);
	};

	EventSource.prototype = {

		CONNECTING: 0,

		OPEN: 1,

		CLOSED: 2,

		defaultOptions: {

			loggingEnabled: false,

			loggingPrefix: "eventsource",

			interval: 500, // milliseconds

			bufferSizeLimit: 256 * 1024, // bytes

			silentTimeout: 300000, // milliseconds

			getArgs: {
				'evs_buffer_size_limit': 256 * 1024
			},

			xhrHeaders: {
				'Accept': 'text/event-stream',
				'Cache-Control': 'no-cache',
				'X-Requested-With': 'XMLHttpRequest'
			}
		},

		setOptions: function (options) {

			var defaults = this.defaultOptions;
			var option;

			// set all default options...
			for (option in defaults) {

				if (defaults.hasOwnProperty(option)) {
					this[option] = defaults[option];
				}
			}

			// override with what is in options
			for (option in options) {

				if (option in defaults && options.hasOwnProperty(option)) {
					this[option] = options[option];
				}
			}

			// if getArgs option is enabled
			// ensure evs_buffer_size_limit corresponds to bufferSizeLimit
			if (this.getArgs && this.bufferSizeLimit) {

				this.getArgs['evs_buffer_size_limit'] = this.bufferSizeLimit;
			}

			// if console is not available, force loggingEnabled to false
			if (typeof console === "undefined" || typeof console.log === "undefined") {

				this.loggingEnabled = false;
			}
		},

		log: function (message) {

			if (this.loggingEnabled) {

				console.log("[" + this.loggingPrefix + "]:" + message);
			}
		},

		poll: function () {

			try {

				if (this.readyState == this.CLOSED) {
					return;
				}
				this.cleanup();
				this.readyState = this.CONNECTING;
				this.cursor = 0;
				this.cache = '';
				this._xhr = new this.XHR(this);
				this.resetNoActivityTimer();

			}
			catch (e) {

				// in an attempt to silence the errors
				this.log('There were errors inside the pool try-catch');
				this.dispatchEvent('error', {
					type: 'error',
					data: e.message
				});
			}
		},

		pollAgain: function (interval) {

			// schedule poll to be called after interval milliseconds
			var evs = this;
			evs.readyState = evs.CONNECTING;
			evs.dispatchEvent('error', {
				type: 'error',
				data: "Reconnecting "
			});
			this._pollTimer = setTimeout(function () {
				evs.poll();
			}, interval || 0);
		},


		cleanup: function () {

			this.log('evs cleaning up');

			if (this._pollTimer) {
				clearInterval(this._pollTimer);
				this._pollTimer = null;
			}

			if (this._noActivityTimer) {
				clearInterval(this._noActivityTimer);
				this._noActivityTimer = null;
			}

			if (this._xhr) {
				this._xhr.abort();
				this._xhr = null;
			}
		},

		resetNoActivityTimer: function () {

			if (this.silentTimeout) {

				if (this._noActivityTimer) {
					clearInterval(this._noActivityTimer);
				}
				var evs = this;
				this._noActivityTimer = setTimeout(
					function () {
						evs.log('Timeout! silentTImeout:' + evs.silentTimeout);
						evs.pollAgain();
					},
					this.silentTimeout
				);
			}
		},

		close: function () {

			this.readyState = this.CLOSED;
			this.log('Closing connection. readyState: ' + this.readyState);
			this.cleanup();
		},

		_onxhrdata: function () {

			var request = this._xhr;
			if (request.isReady() && !request.hasError()) {
				// reset the timer, as we have activity
				this.resetNoActivityTimer();

				// move this EventSource to OPEN state...
				if (this.readyState == this.CONNECTING) {
					this.readyState = this.OPEN;
					this.dispatchEvent('open', {type: 'open'});
				}

				var buffer = request.getBuffer();

				if (buffer.length > this.bufferSizeLimit) {
					this.log('buffer.length > this.bufferSizeLimit');
					this.pollAgain();
				}

				if (this.cursor == 0 && buffer.length > 0) {

					// skip byte order mark \uFEFF character if it starts the stream
					if (buffer.substring(0, 1) == '\uFEFF') {
						this.cursor = 1;
					}
				}

				var lastMessageIndex = this.lastMessageIndex(buffer);
				if (lastMessageIndex[0] >= this.cursor) {

					var newcursor = lastMessageIndex[1];
					var toparse = buffer.substring(this.cursor, newcursor);
					this.parseStream(toparse);
					this.cursor = newcursor;
				}

				// if request is finished, reopen the connection
				if (request.isDone()) {
					this.log('request.isDone(). reopening the connection');
					this.pollAgain(this.interval);
				}
			}
			else if (this.readyState !== this.CLOSED) {

				this.log('this.readyState !== this.CLOSED');
				this.pollAgain(this.interval);

				//MV: Unsure why an error was previously dispatched
			}
		},

		parseStream: function (chunk) {

			// normalize line separators (\r\n,\r,\n) to \n
			// remove white spaces that may precede \n
			chunk = this.cache + this.normalizeToLF(chunk);

			var events = chunk.split('\n\n');

			var i, j, eventType, datas, line, retry;

			for (i = 0; i < (events.length - 1); i++) {

				eventType = 'message';
				datas = [];
				var parts = events[i].split('\n');

				for (j = 0; j < parts.length; j++) {

					line = this.trimWhiteSpace(parts[j]);

					if (line.indexOf('event') == 0) {

						eventType = line.replace(/event:?\s*/, '');
					}
					else if (line.indexOf('retry') == 0) {

						retry = parseInt(line.replace(/retry:?\s*/, ''));
						if (!isNaN(retry)) {
							this.interval = retry;
						}
					}
					else if (line.indexOf('data') == 0) {

						datas.push(line.replace(/data:?\s*/, ''));
					}
					else if (line.indexOf('id:') == 0) {

						this.lastEventId = line.replace(/id:?\s*/, '');
					}
					else if (line.indexOf('id') == 0) { // this resets the id

						this.lastEventId = null;
					}
				}

				if (datas.length) {
					// dispatch a new event
					var event = new MessageEvent(eventType, datas.join('\n'),
						window.location.origin, this.lastEventId);
					this.dispatchEvent(eventType, event);
				}
			}

			this.cache = events[events.length - 1];
		},

		dispatchEvent: function (type, event) {
			var handlers = this['_' + type + 'Handlers'];

			if (handlers) {

				for (var i = 0; i < handlers.length; i++) {
					handlers[i].call(this, event);
				}
			}

			if (this['on' + type]) {
				this['on' + type].call(this, event);
			}

		},

		addEventListener: function (type, handler) {
			if (!this['_' + type + 'Handlers']) {
				this['_' + type + 'Handlers'] = [];
			}

			this['_' + type + 'Handlers'].push(handler);
		},

		removeEventListener: function (type, handler) {
			var handlers = this['_' + type + 'Handlers'];
			if (!handlers) {
				return;
			}
			for (var i = handlers.length - 1; i >= 0; --i) {
				if (handlers[i] === handler) {
					handlers.splice(i, 1);
					break;
				}
			}
		},

		_pollTimer: null,

		_noActivityTimer: null,

		_xhr: null,

		lastEventId: null,

		cache: '',

		cursor: 0,

		onerror: null,

		onmessage: null,

		onopen: null,

		readyState: 0,

		// ===================================================================
		// helpers functions
		// those are attached to prototype to ease reuse and testing...

		urlWithParams: function (baseURL, params) {

			var encodedArgs = [];

			if (params) {

				var key, urlarg;
				var urlize = encodeURIComponent;

				for (key in params) {
					if (params.hasOwnProperty(key)) {
						urlarg = urlize(key) + '=' + urlize(params[key]);
						encodedArgs.push(urlarg);
					}
				}
			}

			if (encodedArgs.length > 0) {

				if (baseURL.indexOf('?') == -1) {
					return baseURL + '?' + encodedArgs.join('&');
				}
				return baseURL + '&' + encodedArgs.join('&');
			}
			return baseURL;
		},

		lastMessageIndex: function (text) {

			var ln2 = text.lastIndexOf('\n\n');
			var lr2 = text.lastIndexOf('\r\r');
			var lrln2 = text.lastIndexOf('\r\n\r\n');

			if (lrln2 > Math.max(ln2, lr2)) {
				return [lrln2, lrln2 + 4];
			}
			return [Math.max(ln2, lr2), Math.max(ln2, lr2) + 2];
		},

		trimWhiteSpace: function (str) {
			// to remove whitespaces left and right of string

			var reTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
			return str.replace(reTrim, '');
		},

		normalizeToLF: function (str) {

			// replace \r and \r\n with \n
			return str.replace(/\r\n|\r/g, '\n');
		}

	};

	EventSource.isPolyfill = "XHR";

	// EventSource will send request using XMLHttpRequest
	EventSource.prototype.XHR = function (evs) {

		var request = new XMLHttpRequest();
		this._request = request;
		evs._xhr = this;

		// set handlers
		request.onreadystatechange = function () {
			// On old IE, we can only process the data once the request has been completed
			if ((request.readyState > 3 || (!isOldIE() && request.readyState > 1 )) &&
				evs.readyState != evs.CLOSED) {

				if (request.status == 200
					|| (request.status >= 300
					&& request.status < 400)) {
					evs._onxhrdata();
				} else {
					request._failed = true;
					evs.readyState = evs.CLOSED;
					evs.dispatchEvent('error', {
						type: 'error',
						data: "The server responded with " + request.status
					});
					evs.close();
				}
			}
		};

		request.onprogress = function () {
		};

		request.open('GET', evs.urlWithParams(evs.URL, evs.getArgs), true);

		var headers = evs.xhrHeaders; // maybe null
		for (var header in headers) {
			if (headers.hasOwnProperty(header)) {
				request.setRequestHeader(header, headers[header]);
			}
		}
		if (evs.lastEventId) {
			request.setRequestHeader('Last-Event-Id', evs.lastEventId);
		}

		request.send();
	};

	EventSource.prototype.XHR.prototype = {

		useXDomainRequest: false,

		_request: null,

		_failed: false, // true if we have had errors...

		isReady: function () {


			return this._request.readyState >= 2;
		},

		isDone: function () {

			return (this._request.readyState == 4);
		},

		hasError: function () {

			return (this._failed || (this._request.status >= 400));
		},

		getBuffer: function () {

			var rv = '';
			try {
				rv = this._request.responseText || '';
			}
			catch (e) {
			}
			return rv;
		},

		abort: function () {

			if (this._request) {
				this._request.abort();
			}
		}
	};

	function MessageEvent (type, data, origin, lastEventId) {

		this.bubbles = false;
		this.cancelBubble = false;
		this.cancelable = false;
		this.data = data || null;
		this.origin = origin || '';
		this.lastEventId = lastEventId || '';
		this.type = type || 'message';
	}

	function isOldIE () {

		// return true if we are in IE8 or IE9
		return (window.XDomainRequest &&
		(window.XMLHttpRequest && new XMLHttpRequest().responseType === undefined)) ? true : false;
	}

	global[evsImportName] = EventSource;
})(this);
