﻿window.location.isSameDomain = function(uri) {
	return !/^http/.test(uri)
		|| new RegExp('^' + this.protocol + '//' + this.hostname + '/').test(uri);
};

var notificationDispatcher = $.init(function() {
	var 
	freeze,
	controllers = {},
	endpoint = '/{controllers}/{attendee}/',
	dataType,
	interval,
	attendee,
	predicate,
	timeoutID,

	// events
	onAttendeeChanged = [],

	// methods
	timeoutInit = function(int) {
		timeoutID = window.setTimeout(launchNotificationChecking, -(-int) || interval);
	},
	launchNotificationChecking = function() {
		if (timeoutID)
			window.clearTimeout(timeoutID);

		if (predicate && !predicate() && (timeoutInit() || true))
			return;

		freeze ? timeoutInit(2000) : checkNotifications(timeoutInit);
	},
	checkNotifications = function() {
		var c = '', timestamps = {}, callback, names, force;

		for (var i = 0; i < arguments.length; i++)
			$.isFunction(arguments[i])
				? callback = arguments[i]
				: $.isArray(arguments[i])
					? names = arguments[i]
					: force = arguments[i];

		$.each(names || controllers, function(name) {
			name = this.attempts ? name : this;

			var receiver = controllers[name];

			if ((receiver.attendeeDepended && !attendee) ||
				(!force && (receiver.frozen || (receiver.attempt = (receiver.attempt % receiver.attempts) + 1) != 1)))
				return;

			c += '+' + name;

			var timestamp;

			if (receiver.timestamp) {
				try {
					timestamp = receiver.timestamp.toEtag();
				}
				catch (ex) {
					/*
					$.browser.safari && console && console.log("timestamp is " + typeof receiver.timestamp + ": " + receiver.timestamp);
					$.browser.safari && console && console.log("toEtag is " + typeof receiver.timestamp.toEtag);
					*/
				}
			}

			timestamps[name] = receiver.revision || timestamp;
		});

		if (!c)
			return callback && callback();

		var url = endpoint
			.replace('{controllers}', c.replace(/^\+/, ''))
			.replace('{attendee}', (attendee || 'everyone'))
			.replace(/([^:])\/\//, '$1/')
		;

		$.ajax({
			url: url,
			data: timestamps,
			dataType: dataType,
			success: function(data) {
				processNotifications(data);
			},
			error: function(xhr, st, e) {
				if (xhr.status == 403)
					return window.location = '/login/?returnUrl=' + escape(window.location);

				callback && callback(e);

				$.browser.safari && console && console.error('NotificationDispatcher error');
			},
			complete: function(xhr, st) {
				callback && callback(st);
			}
		});
	},

	changeAttendee = function(_attendee) {
		attendee = _attendee;

		$.each(controllers, function() {
			$.isFunction(this.changeAttendee) && this.changeAttendee(attendee);
		});

		for (var i in onAttendeeChanged)
			onAttendeeChanged[i](attendee);
	},
	processNotifications = function(data) {
		var notifications = {};

		$(data).each(function() {
			notifications[this.type] = this;
		});

		$.each(controllers, function(type) {
			if (!notifications[type])
				return;

			this.revision = notifications[type].revision;

			this.timestamp = notifications[type].timestamp;

			this.process(notifications[type].updates, attendee);
		});
	}
	;

	// publics
	this.changeAttendee = function(_attendee) {
		$.isFunction(_attendee)
			? onAttendeeChanged.push(_attendee)
			: changeAttendee(_attendee);
	};

	this.getAttendee = function() {
		return attendee;
	};

	this.checkNotifications = function(callback, predicate) {
		if (!dataType)
			return $.browser.safari && console.error('Notification dispatcher is not initialized.');

		checkNotifications.apply(this, Array.prototype.slice.call(arguments, 0, 2).concat(true));
	};

	this.freeze = function(grep) {
		freeze = true;

		$.each(controllers, function(name) {
			freeze &= (this.frozen = !grep || grep(name));
		});
	};

	this.unfreeze = function() {
		$.each(controllers, function() {
			this.frozen = false;
		});

		freeze = false;
	};

	this.init = function() {
		var e, a, c;

		for (var i = 0; i < arguments.length; i++)
			switch (typeof arguments[i]) {
			case 'string':
				e = arguments[i]; /*endpoint*/
				break;
			case 'number':
				a = arguments[i]; /*attendee*/
				break;
			case 'object':
				c = arguments[i]; /*controllers*/
				break;
			case 'function':
				predicate = arguments[i];
				break;
		}

		endpoint = e + endpoint;
		dataType =
			/^http/.test(endpoint) &&
			!new RegExp('^((' + window.location.protocol + '//' + window.location.hostname + '/)|(/))').test(endpoint)
				? 'jsonp' : 'json';

		var r, k, ints = {};

		$.each(c, function(name) {
			this && $.isFunction(this.process)
				? ints[name] = (controllers[name] = this).interval : null;
		});

		$.each(ints, function() {
			if (interval || (interval = this) != this)
				for (k = this; r = k % interval; k = interval, interval = r);
		});

		$.each(controllers, function(name) {
			this.attempt = (this.attempts = (ints[name] || interval) / interval) && 0;
		});

		changeAttendee(a);
		launchNotificationChecking();
	};
});

var serviceChannel = $.init(function() {
	var 
	baseUri,
	dataType,
	me = this,
	call = function() {
		if (!dataType)
			$.browser.safari && console.error('Service channel is not initialized.');

		var endpoint, data, callback, errorCallback;

		for (var i = 0; i < arguments.length; i++)
			!endpoint
				? endpoint = arguments[i]
				: $.isFunction(arguments[i])
					? (!callback && (callback = arguments[i])) || (errorCallback = arguments[i])
					: data = arguments[i];

		$.ajax({
			url: baseUri + endpoint,
			type: data ? 'POST' : 'GET',
			data: data,
			cache: !data ? false : undefined,
			dataType: dataType,
			success: function(data) {
				if (data && data[0] &&
						data[0].type == 'send-message-callback' &&
						!data[0]['is-success']) {
					errorCallback && errorCallback('error');
				} else {
					callback && callback();
				}

				data && processNotifications(data);
			},
			error: function(xhr, st, e) {
				if (xhr.status == 403)
					window.location = '/login/?returnUrl=' + escape(window.location);

				if (xhr.status == 402)
					$.notify('checkout-details', $.extend(
						$.parseJSON(xhr.responseText),
						{ 'via-service-channel': true }
					));

				if (Math.floor(xhr.status / 100) === 5)
					$.browser.safari && console.error('Service channel error.');

				errorCallback && errorCallback(xhr, st, e);
			}
		});
	};

	this.init = function() {
		baseUri = ((arguments[0] || '') + '/').replace(/\/\/$/, '/');

		dataType = window.location.isSameDomain(baseUri) ? 'json' : 'jsonp';
	};

	var callCtor = function(endpoint) {
		return function(attendee) {
			var args = Array.prototype.slice.call(arguments);
			args[0] = endpoint + attendee;

			call.apply(me, args);
		};
	};

	this.sendMessage = callCtor('send-message/');
	this.endConversation = callCtor('end-conversation/');
	this.sendBusy = callCtor('send-busy/');
	this.blockContact = callCtor('send-block/');
	this.addContact = callCtor('add-contact/');
	this.removeContact = callCtor('remove-contact/');
	this.sendTypingState = callCtor('typing-state/');
	this.orderGift = callCtor('order-gift/');
});
