/*
  popup.js

  A lightweight general purpose JavaScript DOM element popup class.

  Webpage:
	http;//www.methods.co.nz/popup/popup.html

  Inspired by:
	Lightbox2: http://www.huddletogether.com/projects/lightbox2/
	Lightbox Gone Wild: http://particletree.com/features/lightbox-gone-wild/
	Tooltip: http://blog.innerewut.de/pages/tooltip
	Prototype library: http://www.prototypejs.org/
	Scriptaculous library: http://script.aculo.us/

  Attributions:
	- Uses the getPageSize() function from Lightbox v2.02 by Lokesh Dhakar
	  (http://www.huddletogether.com/projects/lightbox2/).
	- Adapted the the modal overlay technique used in Lightbox v2.02 by Lokesh
	  Dhakar (http://www.huddletogether.com/projects/lightbox2/).

  Version: 1.0.1

  Author:    Stuart Rackham <srackham@methods.co.nz>
  License:   This source code is released under the MIT license.

  Copyright (c) Stuart Rackham 2007

*/

var Popup = Class.create();
Popup.zIndex = 1000;  // z-index of first popup.

Popup.prototype = {

  /*
	Popup creation
  */
  initialize: function(popup, link) {
	var options = Object.extend({
	  modal: false,
	  effect: 'fade',
	  hidden: true,
	  closebox: 'popup_closebox',       // CSS class name of click-to-close elements.
	  draghandle: 'popup_draghandle'    // CSS class name of drag handle elements.
	}, arguments[2] || {});
	options.trigger = options.trigger || (options.modal ? 'click' : 'mouseover');
	options.duration = this.first_value(options.duration, Popup.duration, 0.5);
	options.show_duration = this.first_value(options.show_duration, options.duration);
	options.hide_duration = this.first_value(options.hide_duration, options.duration);
	options.opacity = this.first_value(options.opacity, Popup.opacity, 0.5);
	options.show_delay = this.first_value(options.show_delay, Popup.show_delay, 0);
	options.hide_delay = this.first_value(options.hide_delay, Popup.hide_delay, 200);
	options.cursor_margin = this.first_value(options.cursor_margin, Popup.cursor_margin, 5);
	this.options = options;
	if (link) {
	  this.link = $(link);
	}
	this.popup = $(popup);
	this.popup.popup = this;  // Make the popup object a property of the DOM popup element.
	if (options.hidden) {
	  this.popup.hide();
	}
	if (options.closebox) {
	  this.closeboxes = document.getElementsByClassName(options.closebox, this.popup);
	  if (this.popup.hasClassName(options.closebox)) {
		this.closeboxes[this.closeboxes.length] = this.popup;
	  }
	}
	else {
	  this.closeboxes = [];
	}
	if (options.draghandle) {
	  var draghandles = document.getElementsByClassName(options.draghandle, this.popup);
	  for (i = 0; i < draghandles.length; i++) {
		new Draggable(this.popup, { handle: draghandles[i] });
	  }
	  if (this.popup.hasClassName(options.draghandle)) {
		new Draggable(this.popup, { handle: this.popup });
	  }
	}
	this.register_events();
  },


  /*
	Event functions
  */

  register_events: function() {
	var trigger_function;
	if (this.is_auto_open()) {
	  trigger_function = this.start_show_timer;
	  if (this.link) {
		Event.observe(this.link, 'mouseout', this.stop_show_timer.bindAsEventListener(this));
	  }
	}
	else {
	  trigger_function = this.show;
	}
	if (this.link) {
	  Event.observe(this.link, this.options.trigger, trigger_function.bindAsEventListener(this));
	}
	if (!this.options.modal) {
	  Event.observe(this.popup, 'click', this.bring_to_front.bindAsEventListener(this));
	}
	if (this.closeboxes.length > 0) {
	  for (var i = 0; i < this.closeboxes.length; i++) {
		Event.observe(this.closeboxes[i], 'click', this.hide.bindAsEventListener(this));
	  }
	}
	else {
	  if (this.link) {
		Event.observe(this.link, 'mouseout', this.start_hide_timer.bindAsEventListener(this));
	  }
	  Event.observe(this.popup, 'mouseover', this.stop_hide_timer.bindAsEventListener(this));
	  Event.observe(this.popup, 'mouseout', this.start_hide_timer.bindAsEventListener(this));
	}
  },

  bring_to_front: function(event) {
	// Bring to front if not already there.
	if (Number(this.popup.style.zIndex) < Popup.zIndex - 1) {
	  this.popup.style.zIndex = Popup.zIndex++;
	}
  },

  start_show_timer: function(event) {
	// NOTE: event is bound to this.show but it's state changes between being
	// bound here and arriving at this.show -- specifically, the mouse
	// coordinates are reset to zero). I've no idea why. Anyway, this is the
	// reason for passing the event mouse coordinates as properties of this.
	this.stop_show_timer(event);
	this.mouse_x = Event.pointerX(event);
	this.mouse_y = Event.pointerY(event);
	this.show_timer = setTimeout(this.show.bind(this, event), this.options.show_delay);
  },

  stop_show_timer: function(event) {
	if (this.show_timer) {
	  clearTimeout(this.show_timer);
	  this.show_timer = null;
	}
  },

  start_hide_timer: function(event) {
	this.stop_hide_timer(event);
	this.hide_timer = setTimeout(this.hide.bind(this, event), this.options.hide_delay);
  },

  stop_hide_timer: function(event) {
	if (this.hide_timer) {
	  clearTimeout(this.hide_timer);
	  this.hide_timer = null;
	}
  },

  show: function(event) {
	this.stop_show_timer(event);
	this.stop_hide_timer(event);
	if (this.is_open) {
	  return;
	}
	if (this.options.modal) {
	  this.show_overlay();
	}

	Element.setStyle(this.popup, { zIndex: Popup.zIndex++ });

	this.is_open = true;
	switch (this.options.effect) {
	  case 'slide':
		Effect.SlideUp(this.popup, {duration: this.options.show_duration});
		break;
	  case 'grow':
		Effect.Grow(this.popup, {duration: this.options.show_duration});
		break;
	  case 'blind':
		Effect.BlindDown(this.popup, {duration: this.options.show_duration});
		break;
	  case 'fade':
	  default:
		Effect.Appear(this.popup, {duration: this.options.show_duration});
		break;
	}
  },

  hide: function(event){
	this.is_open = false;
	switch (this.options.effect) {
	  case 'slide':
		Effect.SlideDown(this.popup, {duration: this.options.hide_duration});
		break;
	  case 'grow':
		Effect.Shrink(this.popup, {duration: this.options.hide_duration});
		break;
	  case 'blind':
		Effect.BlindUp(this.popup, {duration: this.options.hide_duration});
		break;
	  case 'fade':
	  default:
		Effect.Fade(this.popup, {duration: this.options.hide_duration});
		break;
	}
	if (this.options.modal) {
	  this.hide_overlay();
	}
  },


  /*
	Helper functions
  */

  // Return the first function argument that is not undefined.
  // Because when zero numerical value are possible you can't use || chains.
  first_value: function() {
	for (var i = 0; i < arguments.length; i++) {
	  if (arguments[i] !== undefined) {
		return arguments[i];
	  }
	}
	return undefined;
  },

  is_auto_open: function() {
	return this.options.trigger == 'mouseover';
  },

  show_overlay: function() {
	if (!Popup.overlay) {
	  var overlay = document.createElement('div');
	  overlay.setAttribute('id','popup_overlay');
	  overlay.style.display = 'none';
	  document.body.appendChild(overlay);
	  Popup.overlay = overlay;
	  Popup.overlay_levels = [];
	}
	Popup.overlay.style.height = this.get_page_dimensions().height + 'px';
	var z = Popup.zIndex++;
	Popup.overlay.style.zIndex = z;
	Popup.overlay_levels.push(z);
	if ( Popup.overlay_levels.length == 1) { // Opening the first modal popup.
	  // Queue the global overlay effect to ensure correct execution order.
	  new Effect.Appear(Popup.overlay,
		{ duration: this.options.show_duration,
		  to: this.options.opacity,
		  queue: {position: 'end', scope: 'popup_overlay'}
		});
	}
	else { // There is another modal popup at a lower level so move the overlay forward.
	  Popup.overlay.style.zIndex = z;
	}
  },

  hide_overlay: function() {
	Popup.overlay_levels.pop();
	var z = Popup.overlay_levels.pop();
	if (z) { // There is another modal popup at a lower level so move the overlay back.
	  Popup.overlay_levels.push(z);
	  Popup.overlay.style.zIndex = z;
	}
	else { // The last modal popup is being closed so hide the overlay
	  // Queue the global overlay effect to ensure correct execution order.
	  new Effect.Fade(Popup.overlay,
		{ duration: this.options.hide_duration,
		  queue: {position: 'end', scope: 'popup_overlay'}
		});
	}
  }


}
