(function ($, undef) {
	var global = this;

	// Namespace.
	global.FS = global.FS || {};

	global.FS.PostMessage = function ()
	{
		var
			_is_child = false,
			_postman = new NoJQueryPostMessageMixin('postMessage', 'receiveMessage'),
			_callbacks = {},
			_base_url,
			_parent_url = decodeURIComponent(document.location.hash.replace(/^#/, '')),
			_parent_subdomain = _parent_url.substring(0, _parent_url.indexOf('/', ('https://' === _parent_url.substring(0, ('https://').length)) ? 8 : 7)),
			_init = function () {
				_postman.receiveMessage(function (e) {
					var data = JSON.parse(e.data);

					if (_callbacks[data.type]) {
						for (var i = 0; i < _callbacks[data.type].length; i++) {
							// Execute type callbacks.
							_callbacks[data.type][i](data.data);
						}
					}
				}, _base_url);
			},
			_hasParent = ('' !== _parent_url),
			$window = $(window),
			$html = $('html');

		return {
			init : function (url, iframes)
			{
				_base_url = url;
				_init();

				// Automatically receive forward messages.
				FS.PostMessage.receiveOnce('forward', function (data){
					window.location = data.url;
				});

				iframes = iframes || [];

				if (iframes.length > 0) {
					$window.on('scroll', function () {
						for (var i = 0; i < iframes.length; i++) {
							FS.PostMessage.postScroll(iframes[i]);
						}
					});
				}
			},
			init_child : function ()
			{
				this.init(_parent_subdomain);

				_is_child = true;

				// Post height of a child right after window is loaded.
				$(window).bind('load', function () {
					FS.PostMessage.postHeight();

					// Post message that window was loaded.
					FS.PostMessage.post('loaded');
				});
			},
			hasParent : function ()
			{
				return _hasParent;
			},
			postHeight : function (diff, wrapper) {
				diff = diff || 0;
				wrapper = wrapper || '#wrap_section';
				this.post('height', {
					height: diff + $(wrapper).outerHeight(true)
				});
			},
			postScroll : function (iframe) {
				this.post('scroll', {
					top: $window.scrollTop(),
					height: ($window.height() - parseFloat($html.css('paddingTop')) - parseFloat($html.css('marginTop')))
				}, iframe);
			},
			post : function (type, data, iframe)
			{
				console.debug('PostMessage.post', type);

				if (iframe)
				{
					// Post to iframe.
					_postman.postMessage(JSON.stringify({
						type: type,
						data: data
					}), iframe.src, iframe.contentWindow);
				}
				else {
					// Post to parent.
					_postman.postMessage(JSON.stringify({
						type: type,
						data: data
					}), _parent_url, window.parent);
				}
			},
			receive: function (type, callback)
			{
				console.debug('PostMessage.receive', type);

				if (undef === _callbacks[type])
					_callbacks[type] = [];

				_callbacks[type].push(callback);
			},
			receiveOnce: function (type, callback)
			{
				if (this.is_set(type))
					return;

				this.receive(type, callback);
			},
			// Check if any callbacks assigned to a specified message type.
			is_set: function (type)
			{
				return (undef != _callbacks[type]);
			},
			parent_url: function ()
			{
				return _parent_url;
			},
			parent_subdomain: function ()
			{
				return _parent_subdomain;
			}
		};
	}();
})(jQuery);