/* A simple Javascript class to communicate with the user various warnings,
errors and other info in a consistant and non-intrusive way */
var Notification = Class.create();

/* Instance Methods */
Object.extend(Notification.prototype, {
	/* Constructor. You probably want to use one of the class methods
	that provide an easier interface but for low-level work you can use
	the constructor directly:

	* messages - An array of messages to display
	* options - Any options on how to display this notification. Options are:
	  - zIndex: The z-order of the notification. Defaults to 1000
	  - class_name: An extra class name to append to the notication area
	  - duration: How long to display the notication. If not specified it
	  will display until closed. This is the default */
	initialize: function(messages, options) {
		this.messages = messages;
		this.options = $H({
			zIndex: 1000
		}).merge(options);
		function serialize_show() {
			// Race condition. It doesn't matter too much
			if(Notification.showing_message)
				return setTimeout(serialize_show.bind(this), 100);
			Notification.showing_message = true;
			this.show();
		}
		serialize_show.bind(this)();
	},

	/* Will show the given notification. Only one notification should
	show at a time to avoid them showing on top of each other. The
	constructor tries to make this happen */
	show: function() {
		var msg_box = $(document.createElement('div'));
		this.bottom = 0;
		if( Notification.active_notifications.length > 0 ) {
			var last_notification = Notification.active_notifications.last();
			this.bottom = last_notification.bottom +
				last_notification.options.height;
		}
		msg_box.setStyle({
			position: 'absolute',
			width: '100%',
			left: '0px',
			zIndex: this.options.zIndex,
			display: 'none',
			bottom: this.bottom + 'px'
		});

		var msg = $(document.createElement('div'));
		msg_box.appendChild(msg);
		msg.addClassName('notification');
		if(this.options.class_name)
			msg.addClassName(this.options.class_name);
		msg.setStyle({
			position: 'relative',
			width: '100%'
		});

		var close = $(document.createElement('a'));
		msg.appendChild(close);
		close.setAttribute('href', '#');
		close.onclick = this.close.bindAsEventListener(this);
		close.addClassName('close');
		close.appendChild(document.createTextNode('Close'));

		var msgs = this.messages.collect(function(msg) {
			msg = msg.gsub(/\n/, '<br />');
			var li = document.createElement('li');
			Element.update(li, msg);
			return li;
		});
		var ul = document.createElement('ul');
		msgs.each(function(li) {
			ul.appendChild(li);
		});
		msg.appendChild(ul);

		document.body.appendChild(msg_box);
		this.options.height = msg_box.getDimensions().height;
		msg_box.style.height = this.options.height + 'px';

		// Not sure why this is necessary. Maybe another style?
		msg_box.style.bottom = '15px';

		Effect.Appear(msg_box, {afterFinish: (function() {
			if(this.options.duration)
				setTimeout(this.close.bind(this),
				this.options.duration * 1000);

			this.message_box = msg_box;
			Notification.active_notifications.push(this);
			Notification.showing_message = false;
		}).bind(this)});
	},

	/* Will close the given notification. Is designed so that it can
	be called from an event handler but it does not have to be */
	close: function(event) {
		Notification.active_notifications =
			Notification.active_notifications.without(this);
		var msg_box = this.message_box;
		Effect.Fade(msg_box, {afterFinish: function() {msg_box.remove()}});
		return false;
	}
});
Object.extend(Notification, {
	/* List of all notifications actively displayed */
	active_notifications: [],

	/* List of notifications waiting to be displayed */
	delayed_notifications: [],

	/* Indicates if a message is currently being displayed */
	showing_message: false,

	/* Will run all delayed notifications. Add a delayed notification while
	loading the page then call this once the page is loaded. The various
	class wrapper methods (warning, message, notice) all take a delay
	option that will indicate if the notification should be immediately
	displayed or delayed. */
	run: function() {
		this.delayed_notifications.each(function(notification) {
			notification();
		});
		this.delayed_notifications = [];
	},

	// Negative Feedback: ex. errors
	warning: function(/*warnings, ...*/) {
		var messages = $A(arguments);
		var options = this._process_options(messages, {
			class_name: 'warning'
		});
		this._exec_message(messages, options);
	},
	// Neutral Feedback: ex. reminders
	message: function(/*messages, ...*/) {
		var messages = $A(arguments);
		var options = this._process_options(messages, {
			class_name: 'message',
			duration: 5
		});
		this._exec_message(messages, options);
	},
	// Positive Feedback: ex. successful action
	notice: function(/*notice, ...*/) {
		var messages = $A(arguments);
		var options = this._process_options(messages, {
			class_name: 'notice',
			duration: 5
		});
		this._exec_message(messages, options);
	},

	/* Will carry out the actual display or delaying of a message */
	_exec_message: function(messages, options) {
		var run = function() {
			new Notification(messages, options);
		}
		if( options.delay ) {
			this.delayed_notifications.push(run);
		} else {
			run();
		}
	},

	/* Make the options easier to deal with */
	_process_options: function(args, additional_options) {
		var options = $H();
		if(typeof(args.last()) != 'string') options = args.pop();
		return $H(additional_options).merge(options);
	}
});