/**
 * NotificationsManager.js
 *
 * @version: 2.1
 * @author: WGU Team (www.web-go-up.com)
 *
 * Options:
 *	id						String		<default = notification-box> id of notification box div. Used like prefix for all
 *										subelements, for example: header will have id - 'notification-box-header'
 *	cssClass				String		<default = notification_box> class of notification box div. Used like prefix for all
 *										subelements, for example: header will have class - 'notification_box_header'
 *	styles:					Object		styles of notification box's div
 *		width				String		<default = 250px> width of notification box
 *		left				String		<default = 'auto'> if not 'auto' notification box will be placed horisontaly in specified
 *										position
 *		IE6BackgroundColor	String		<default = #000> alternative background color for IE6
 *	autoHideTimeOut			int			<default = 0> if not zero notification box will hide after autoHideTimeOut milliseconds
 *	headerHTML				String		<default = ''> initial header of notification box
 *	closeButtonHTML			String		<default = 'close'> label of close button in notification box
 *
 * Events:
 *	onMouseOver				Function	dispatches when mouse over notification box
 *	onMouseOut				Function	dispatches when mouse out from notification box
 *	onHide					Function	dispatches when notification box hided
 *
 * Public methods:
 *	hide					hide notifications box and delete all notification entries
 *		return				NotificationsManager	current instance of NotificationsManager class
 *
 *	addError				add new notification with type == error
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			err				Object					see param entry of addEntry method
 *
 *	addMessage				add new notification with type == message
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			err				Object					see param entry of addEntry method
 *
 *	addEntry				add new notification entry
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			entry:			Object					object with properties of new entry. Available next properties:
 *				id					String			<default = {number of entries + 1}> default id of first entry will be
 *													'notification-box-entry-' + id
 *				type				String			<default = ''> type of new notification. Currently available 'message' and
 *													'error'
 *				title				String			<default = ''> title of new notification
 *				description			String			<default = ''> description of new notification
 *				onClick				Function		if defined, this function will be called when new entry clicked
 *				onTitleClick		Function		if defined, this function will be called when title of new entry clicked
 *				onDescriptionClick	Function		if defined, this function will be called when description of new entry clicked
 *
 *	removeEntry				remove one of entry from current notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			String or Element		id of entry or entry Element for removing
 *
 *	getCssClass				return current css class of notification box
 *		return				String					current css class of notification box
 *
 *	setCssClass				set new css class of notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			String					new css class of notification box
 *
 *	getNotificationBoxStyles	return current styles of notification box
 *		return				Object					object with current values of styles width and IE6BackgroundColor of 
 *													notification box
 *
 *	setNotificationBoxStyles	set new styles of notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			Object					object with new values of styles 'width', 'left' and 'IE6BackgroundColor' of 
 *													notification box
 *
 *	getWidth				return current width of notification box
 *		return				String					current width of notification box
 *
 *	setWidth				set new width of notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			String					new width of notification box
 *
 *	getLeft					return current left position of notification box
 *		return				String					current width of notification box
 *
 *	setLeft					set new left position of notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			String					new left position of notification box
 *
 *	getIE6BackgroundColor	return current value of IE6BackgroundColor style of notification box
 *		return				String					current value of IE6BackgroundColor style of notification box
 *
 *	setIE6BackgroundColor	set new value of IE6BackgroundColor style of notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			String					new value of IE6BackgroundColor style of notification box
 *
 *	getAutoHideTimeOut		return current autohide timeout of notification box
 *		return				String					current autohide timeout of notification box
 *
 *	setAutoHideTimeOut		set new autohide timeout of notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			String					new autohide timeout of notification box
 *
 *	getHeaderHTML			return current html of header of notification box
 *		return				String					current html of header of notification box
 *
 *	setHeaderHTML			set new html of header of notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			String					new html of header of notification box
 *
 *	getCloseButtonHTML		return current html of close button of notification box
 *		return				String					current html of header of notification box
 *
 *	setCloseButtonHTML		set new html of close button of notification box
 *		return				NotificationsManager	current instance of NotificationsManager class
 *		params:
 *			value			String					new html of close button of notification box
 *
 *	isShown				return true if modal window currently shown and false in other case
 *		return				Boolean				true if modal window currently shown and false in other case
 *
 * Known issues:
 *	if content bigger then viewport user get abillity to scroll window for infinity
 *
 * @TODO: Think about button Close! (text || graphic)
 * @TODO: Redesign without Mootools
 * @TODO: more robust testing!!!
 * @TODO: redesign with ul and lis
 */

var NotificationsManager = new Class({
	Implements: [Options, Events],

	options: {
		'id':						'notification-box',
		'cssClass':					'notification_box',
		'styles': {
			'width':				'250px',
			'left':					'auto',
			'IE6BackgroundColor':	'#000'
		},
		'autoHideTimeOut':			0,
		'headerHTML':				'',
		'closeButtonHTML':			'close'
	},

	initialize: function(options) {
		this.setOptions(options);

		// private properties

		this.numEntries							= 0;
		this.shown								= false;
		this.autoHideTimer						= null;
		this.onNotificationBoxMouseEnterBound	= this.onNotificationBoxMouseEnter.bind(this);
		this.onNotificationBoxMouseLeaveBound	= this.onNotificationBoxMouseLeave.bind(this);
		this.onShowBoxEffectStartBound			= this.onShowBoxEffectStart.bind(this);
		this.onHideBoxEffectCompleteBound		= this.onHideBoxEffectComplete.bind(this);
		this.onRemoveEntryEffectCompleteBound	= this.onRemoveEntryEffectComplete.bind(this);

		// visual components

		this.notificationBox	= new Element('div', {
			'id':			this.options.id,
			'class':		this.options.cssClass,
			'styles': 		$merge(this.options.styles,
				(Browser.Engine.trident4) ? { 'position': 'absolute', 'background-color': this.options.styles.IE6BackgroundColor } : { 'position': 'fixed' },
				{
					'display': 'none', 
					'left': (this.options.styles.left == 'auto') ? (window.getScroll().x + ((window.getSize().x - this.options.styles.width.toInt()) >> 1)) + 'px' : this.options.styles.left
				}
			),
			'events': {
				'mouseenter': this.onNotificationBoxMouseEnterBound
			}
		});

		this.headerDiv			= new Element('div', {
			'id':		this.options.id + '-header',
			'class':	this.options.cssClass + '_header'
		}).set('html', this.options.headerHTML);

		this.closeButton		= new Element('div', {
			'id':			this.options.id + '-close-button',
			'title':		'hide all notifications',
			'styles': {
				'float':	'right',
				'cursor':	'pointer'
			},
			'events': {
				'click':	this.hide.bind(this)
			}
		}).set('html', this.options.closeButtonHTML);

		var cleaner				= new Element('div', {
			'styles': {
				'clear':			'both',
				'height':			'0px',
				'font-size':		'0px',
				'line-height':		'0px'
			}
		});

		this.notificationBox.adopt([this.headerDiv, this.closeButton, cleaner]);

		$(document.body).grab(this.notificationBox);

		this.notificationBox.setStyle('top', (Browser.Engine.trident4) ? ((this.notificationBox.getSize().y != 0) ? window.getScroll().y - this.notificationBox.getSize().y : window.getScroll().y - 500) : -this.notificationBox.getSize().y);

		// effects

		this.showBoxEffect = new Fx.Tween(this.notificationBox, {
			'onStart': this.onShowBoxEffectStartBound
		});

		this.hideBoxEffect = new Fx.Tween(this.notificationBox, {
			'onComplete': this.onHideBoxEffectCompleteBound
		});

		this.removeEntryEffect = new Fx.Morph(null, {
			'onComplete': this.onRemoveEntryEffectCompleteBound
		});

		// global events

		if (Browser.Engine.trident4) {
			window.addEvent('scroll', this.onWindowScroll.bind(this));
		}

		cleaner					= null;
	},

	hide: function() {
		if (this.shown) {
			this.hideBoxEffect.start('top', -this.notificationBox.getSize().y);

			this.numEntries		= 0;

			this.autoHideTimer	= null;
		}

		return this;
	},

	addError: function(err) {
		return this.addEntry($merge(err, { 'type': 'error' }));
	},

	addMessage: function(msg) {
		return this.addEntry($merge(msg, { 'type': 'message' }));
	},

	addEntry: function(entry) {
		var id		= (entry.id ? entry.id : this.numEntries);

		if ((!document.getElementById(this.options.id + '-entry-' + id)) && (entry.title || entry.description)) {
			var container = new Element('div', {
				'id':			this.options.id + '-entry-' + id,
				'class':		this.options.cssClass + '_entry',
				'styles': {
					'width':	(Browser.Engine.trident4) ? this.options.styles.width.toInt() + 'px' : 'auto'
				},
				'events': (entry.onClick) ? { 'click': entry.onClick } : {}
			});

			if (entry.title) {
				var title = new Element('div', {
					'id':			this.options.id + '-entry-' + id + '-title',
					'class':		'entry_title' + ((entry.type) ? '_' + entry.type : ''),
					'title':		(entry.onTitleClick) ? '' : 'click to hide',
					'events': {
						'click':	(entry.onTitleClick) ? entry.onTitleClick : this.removeEntry.bind(this)
					}
				}).set('html', entry.title);

				container.grab(title);
			}

			if (entry.description) {
				var description = new Element('div', {
					'id':		this.options.id + '-entry-' + id + '-description',
					'class':	'entry_description',
					'events':	(entry.onDescriptionClick) ? { 'click': entry.onDescriptionClick } : {}
				}).set('html', entry.description);

				container.grab(description);
			}

			this.closeButton.grab(container, 'before');

			if (this.notificationBox.getPosition().y != 0.0) {
				var newTop = this.notificationBox.getPosition().y;
				newTop -= (title)		? title.getSize().y			+ title.getStyle('margin-top').toInt()			+ title.getStyle('margin-bottom').toInt()		: 0.0;
				newTop -= (description)	? description.getSize().y	+ description.getStyle('margin-top').toInt()	+ description.getStyle('margin-bottom').toInt()	: 0.0;

				this.notificationBox.setStyle('top', newTop);
			}

			if (this.numEntries == 0) {
				if (Browser.Engine.trident4) {
					(function() {
						this.showBoxEffect.start('top', window.getScroll().y);
					}).delay(500, this);
				} else {
					this.showBoxEffect.start('top', 0);
				}
			}

			if (this.options.autoHideTimeOut > 0) {
				if (this.autoHideTimer) {
					this.autoHideTimer	= $clear(this.autoHideTimer);
				}
				this.autoHideTimer		= this.hide.delay(this.options.autoHideTimeOut, this);
			}

			++this.numEntries;

			this.notificationBox.setStyle('left', (this.options.styles.left == 'auto') ? (window.getScroll().x + ((window.getSize().x - this.options.styles.width.toInt()) >> 1)) + 'px' : this.options.styles.left);
		}

		return this;
	},

	removeEntry: function(value) {
		if ($type(value) == 'event') {
			// begin of small hack ( this.removeEntryEffect.set('element', value.target.getParent()) - not work ::((( )
			this.removeEntryEffect.element = value.target.getParent();
			// end of small hack
		} else if ($type(value) == 'element') {
			// begin of small hack ( this.removeEntryEffect.set('element', value) - not work ::((( )
			this.removeEntryEffect.element = value;
			// end of small hack
		} else if ($type(value) == 'string') {
			// begin of small hack ( this.removeEntryEffect.set('element', $(value)) - not work ::((( )
			this.removeEntryEffect.element = $(value);
			// end of small hack
		}

		if (this.removeEntryEffect.element) {
			this.removeEntryEffect.start(
				$extend({
					'height': 0,
					'opacity': 0
				}, (Browser.Engine.trident4) ? { 'font-size': 0, 'line-height': 0 } : {})
			);
		}

		return this;
	},

	getCssClass: function() {
		return this.options.cssClass;
	},

	setCssClass: function(value) {
		this.notificationBox.removeClass(this.options.cssClass).addClass(value);

		this.headerDiv.removeClass(this.options.cssClass + '_header').addClass(value + '_header');

		this.notificationBox.getChildren('.' + this.options.cssClass + '_entry').each(function(el) {
			el.removeClass(this.options.cssClass + '_entry').addClass(value + '_entry');
		}.bind(this));

		this.options.cssClass = value;

		this.notificationBox.setStyle('left', (this.options.styles.left == 'auto') ? (window.getScroll().x + ((window.getSize().x - this.options.styles.width.toInt()) >> 1)) + 'px' : this.options.styles.left);

		return this;
	},

	getNotificationBoxStyles: function() {
		return this.options.styles;
	},

	setNotificationBoxStyles: function(value) {
		if (value.width) {
			this.setWidth(value.width);
		}

		if (value.left) {
			this.setLeft(value.left);
		}

		if (value.IE6BackgroundColor) {
			this.setIE6BackgroundColor(value.IE6BackgroundColor);
		}

		return this;
	},

	getWidth: function() {
		return this.options.styles.width;
	},

	setWidth: function(value) {
		this.notificationBox.setStyle('width', value);

		this.options.styles.width = value;

		this.notificationBox.setStyle('left', (this.options.styles.left == 'auto') ? (window.getScroll().x + ((window.getSize().x - this.options.styles.width.toInt()) >> 1)) + 'px' : this.options.styles.left);

		return this;
	},

	getLeft: function() {
		return this.options.styles.left;
	},

	setLeft: function(value) {
		this.notificationBox.setStyle('left', value);

		this.options.styles.left = value;

		return this;
	},

	getIE6BackgroundColor: function() {
		return this.options.styles.IE6BackgroundColor;
	},

	setIE6BackgroundColor: function(value) {
		if (Browser.Engine.trident4) {
			this.notificationBox.setStyle('background-color', value);
		}

		this.options.styles.IE6BackgroundColor = value;

		return this;
	},

	getAutoHideTimeOut: function() {
		return this.options.autoHideTimeOut;
	},

	setAutoHideTimeOut: function(value) {
		this.options.autoHideTimeOut = value;

		return this;
	},

	getHeaderHTML: function() {
		return this.options.headerHTML;
	},

	setHeaderHTML: function(value) {
		this.headerDiv.set('html', this.options.headerHTML = value);

		return this;
	},

	getCloseButtonHTML: function() {
		return this.options.closeButtonHTML;
	},

	setCloseButtonHTML: function(value) {
		this.closeButton.set('html', this.options.closeButtonHTML = value);

		return this;
	},

	isShown: function() {
		return this.shown;
	},

	/**
	 * Private function! Reacts on MouseEnter on notificationBox
	 */
	onNotificationBoxMouseEnter: function() {
		if ((this.options.autoHideTimeOut > 0) && this.autoHideTimer) {
			this.autoHideTimer	= $clear(this.autoHideTimer);
		}

		this.notificationBox.removeEvent('mouseenter', this.onNotificationBoxMouseEnterBound);
		this.notificationBox.addEvent('mouseleave', this.onNotificationBoxMouseLeaveBound);

		this.fireEvent('onMouseOver');
	},

	/**
	 * Private function! Reacts on MouseLeave on notificationBox
	 */
	onNotificationBoxMouseLeave: function() {
		if (this.options.autoHideTimeOut > 0) {
			if (this.autoHideTimer) {
				this.autoHideTimer	= $clear(this.autoHideTimer);
			}
			this.autoHideTimer		= this.hide.delay(this.options.autoHideTimeOut, this);
		}

		this.notificationBox.removeEvent('mouseleave', this.onNotificationBoxMouseLeaveBound);
		this.notificationBox.addEvent('mouseenter', this.onNotificationBoxMouseEnterBound);

		this.fireEvent('onMouseOut');
	},

	/**
	 * Private function! Reacts on starting ShowBoxEffect
	 */
	onShowBoxEffectStart: function() {
		this.shown	= true;

		this.notificationBox.setStyle('display', 'block');

		if (Browser.Engine.trident4) {
			this.notificationBox.setStyle('height', 'auto');
		}
	},

	/**
	 * Private function! Reacts on completing HideBoxEffect
	 */
	onHideBoxEffectComplete: function() {
		this.notificationBox.getChildren('.' + this.options.cssClass + '_entry').each(function(el) {
			el.destroy();
		});

		this.notificationBox.setStyles({
			'display':	'none',
			'top':		(Browser.Engine.trident4) ? ((this.notificationBox.getSize().y != 0) ? window.getScroll().y - this.notificationBox.getSize().y : window.getScroll().y - 500) : -this.notificationBox.getSize().y
		});

		this.shown = false;

		this.fireEvent('onHide');
	},

	/**
	 * Private function! Reacts on completing HideBoxEffect
	 */
	onRemoveEntryEffectComplete: function() {
		this.removeEntryEffect.element.destroy();

		this.removeEntryEffect.element = null;

		--this.numEntries;

		if (this.numEntries == 0) {
			this.hide();
		}
	},

	/**
	 * Private function! Reacts on window scrolling. Used only in IE6
	 */
	onWindowScroll: function() {
		if (this.shown) {
			this.notificationBox.setStyle('top', window.getScroll().y);
		} else {
			this.notificationBox.setStyle('top', window.getScroll().y - this.notificationBox.getSize().y - 100);
		}
		this.notificationBox.setStyle('left', (window.getScroll().x + ((this.options.styles.left == 'auto') ? ((window.getSize().x - this.options.styles.width.toInt()) >> 1) : this.options.styles.left.toInt())) + 'px');
	}
});

/**
 * Useful function but not vell look in NotficationsManager's architecture
 */
NotificationsManager.implement({
	processRes: function(res) {
		if (res) {
			if (res.errors != undefined) {
				for (var i=0; i<res.errors.length; ++i) {
					this.addError({ 'title': res.errors[i] });
				}
			}
			if (res.messages != undefined) {
				for (i=0; i<res.messages.length; ++i) {
					this.addMessage({ 'title': res.messages[i] });
				}
			}
		}
	}
});

window.addEvent('domready', function() {
	window.Notify = new NotificationsManager();
});
