/* 
  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.2 (custom DD version)

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

  Copyright (c) Stuart Rackham 2007

  Examples:
    # Open on mouse over, close on mouse out
    new Popup('foo_popup','foo_link',{position:'below', show_delay:0.0 });

    # Modal-ish
    new Popup('foo_popup',null,{position:'below', show_delay:0.0, 
              modal:true, use_overlay:false, click_anywhere_to_close:true,
              closebox:'no_popup_closebox', draghandle: 'popup_draghandle' });

    var foo_popup = $('foo_popup');
    $('your_link_here').observe('click', function(event){ 
      if (!foo_popup.visible()){
        foo_popup.popup.show(event, function(){ foo.focus(); });
      }
    } ); 
	  
    # Open on hover, click anywhere to close
	new Popup('foo_popup',null,{position:'below', position_link:'foo_link',
              modal:true, use_overlay:false, show_delay:0.0, click_anywhere_to_close:true });

	$('foo_link').observe('mouseover', function(event){ 
		var navPopup = $('foo_popup');
		if (!navPopup.visible()){
			navPopup.popup.show(event);
		}
	} );
*/

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
    }, arguments[2] || {});
    options.position = options.position || (options.modal ? 'center' : 'auto');
    options.trigger = options.trigger || (options.modal ? 'click' : 'mouseover');
    options.duration = this.first_value(options.duration, Popup.duration, 0.2);
    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, 500);
    options.hide_delay = this.first_value(options.hide_delay, Popup.hide_delay, 200);
    options.cursor_marginx = this.first_value(options.cursor_marginx, Popup.cursor_marginx, 5);
    options.cursor_marginy = this.first_value(options.cursor_marginy, Popup.cursor_marginy, 5);
    options.use_overlay = this.first_value(options.use_overlay, Popup.use_overlay, true);
    options.position_link = this.first_value(options.position_link, Popup.position_link, null);
    options.click_anywhere_to_close = this.first_value(options.click_anywhere_to_close, 
                                                       Popup.click_anywhere_to_close, false);
    options.hover_anywhere_to_close = this.first_value(options.hover_anywhere_to_close, 
                                                       Popup.hover_anywhere_to_close, false);
	this.facebook_logout_redirect_url = options.facebook_after_close_redirect_url
    this.options = options;



    if (link) 
      this.link = $(link);

    if (this.options.position_link)
      this.position_link = $(this.options.position_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 = this.popup.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));
      }
      if (!this.options.modal) {
		  Event.observe(this.popup, 'mouseover', this.stop_hide_timer.bindAsEventListener(this));
		  Event.observe(this.popup, 'mouseout', this.start_hide_timer.bindAsEventListener(this));
	  }
    }

    if (this.options.click_anywhere_to_close) {
      Event.observe(document.body, 'click', this.close_if_not_in_popup.bindAsEventListener(this));
    }

    if (this.options.modal && this.options.hover_anywhere_to_close) {
      Event.observe(document.body, 'mouseover', this.close_if_not_in_popup.bindAsEventListener(this));
    }
  },

  close_if_not_in_popup: function(event) {
    if (this.is_open){
      var evt = $(Event.element(event)); 

      if (!this.show_called_by || (event.target != this.show_called_by.target) ) {
        if (!evt.descendantOf(this.popup)){
          this.close(event);
        }
      }
    }
	
  },

  close: function(event){
    this.stop_show_timer(event);
    this.hide(event);
    this.stop_hide_timer(event);
  },

  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, after_finish_callback) {
    if (after_finish_callback === undefined)
      after_finish_callback = function(){};

    this.show_called_by = event;

    this.stop_show_timer(event);
    this.stop_hide_timer(event);
    if (this.is_open) {
      return;
    }
    if (this.options.modal) {
      this.show_overlay();
    }
    var pos;
    if (!event) {
      // We only arrive here if this.show has been called externally.
      pos = this.get_popup_position();
    }
    else if (this.is_auto_open()) {
      // Because auto-open popups calls this.show indirectly via start_show_timer.
      pos = this.get_popup_position(this.mouse_x, this.mouse_y);
    }
    else {
      pos = this.get_popup_position(Event.pointerX(event), Event.pointerY(event), event);
    }
    Element.setStyle(this.popup, { top: pos.y, left: pos.x, zIndex: Popup.zIndex++ });
    this.is_open = true;
    switch (this.options.effect) {
      case 'slide':
        Effect.SlideDown(this.popup, {duration: this.options.show_duration, afterFinish: after_finish_callback});
        break;
      case 'grow':
        Effect.Grow(this.popup, {duration: this.options.show_duration, afterFinish: after_finish_callback});
        break;
      case 'blind':
        Effect.BlindDown(this.popup, {duration: this.options.show_duration, afterFinish: after_finish_callback});
        break;
      case 'none':
		this.popup.show();
        break;
      case 'fade':
      default:
        Effect.Appear(this.popup, {duration: this.options.show_duration, afterFinish: after_finish_callback});
        break;
    }
  },
  
  hide: function(event){
    this.is_open = false;
    switch (this.options.effect) {
      case 'slide':
        Effect.SlideUp(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 'none':
		this.popup.hide();
        break;
      case 'fade':
      default:
        Effect.Fade(this.popup, {duration: this.options.hide_duration, afterFinish: function() {if (this.facebook_logout_redirect_url){FB.Connect.logoutAndRedirect(this.facebook_logout_redirect_url);}}.bind(this)});
        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 (this.options.use_overlay == false)
		return; 

    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() {
	if (this.options.use_overlay == false)
		return; 

    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'}
        });
    }
  },


  /*
    Positioning functions
  */

  // Return the top and left CSS position strings as an {x,y} object that the
  // popup should be shown at.  mouse_x and mouse_y are the mouse x,y coordinates
  // numbers when the popup was triggered.
  get_popup_position: function(mouse_x, mouse_y, event) {
    var pos;
    switch (this.options.position) {
      case 'auto':
        pos = this.get_auto_position(mouse_x, mouse_y);
        break;
      case 'center':
        pos = this.get_center_position();
        break;
      case 'below':
        pos = this.get_below_position(event);
        break;
      default:
        // Check for x,y postion format (x and y can be any valid CSS left or
        // top property value).
        if (mo = this.options.position.match(/^\s*([^\s,]+)\s*,\s*([^\s,]+)\s*$/)) {
          pos = {x: mo[1], y: mo[2]};
          // If possible convert to numbers.
          pos.x = Number(pos.x) || pos.x;
          pos.y = Number(pos.y) || pos.y;
        }
        else {
          pos = {x: 0, y: 0};
        }
        break;
    }
    if (typeof pos.x == 'number') {
      pos.x += 'px';
    }
    if (typeof pos.y == 'number') {
      pos.y += 'px';
    }
    return pos;
  },

  get_below_position: function(event) {
    var pos_link;
    if (this.position_link)
      pos_link = this.position_link;
    else if (this.link)
      pos_link = this.link;
    else
      pos_link = Event.element(event);

    if (pos_link){
      var link_height = Element.getHeight(pos_link)

      var link_pos = Position.cumulativeOffset(pos_link);
      var x = link_pos[0];
      var y = link_pos[1] + link_height;

      // If outside viewport, move inside
      var dim = pos_link.viewportOffset();
      var relative_x = dim[0];
      var relative_y = dim[1] + link_height;

      var popup_dim = Element.getDimensions(this.popup);
      var viewport_dim = this.get_viewport_dimensions();

      if (popup_dim.width >= viewport_dim.width)
        x -= relative_x; // Move to relative 0
      else if (relative_x + popup_dim.width > viewport_dim.width)
        x -= ( popup_dim.width - (viewport_dim.width - relative_x) )

      if (popup_dim.height >= viewport_dim.height)
        y -= relative_y; // Move to relative 0
      else if (relative_y + popup_dim.height > viewport_dim.height)
        y -= ( popup_dim.height - (viewport_dim.height - relative_y) )
      else if (relative_y + popup_dim.height < 0)
        y -= relative_y; // Move to relative 0

      return {x: x, y: y};
    }
    else {
      return this.get_center_position();
    }
  },

  get_center_position: function() {
    dim = Element.getDimensions(this.popup);
    var popup_width = dim.width;
    var popup_height = dim.height;
    dim = this.get_viewport_dimensions();
    var viewport_width = dim.width;
    var viewport_height = dim.height;

    relative_y = document.viewport.getScrollOffsets()[1];

    var x;
    if (popup_width >= viewport_width) {
      x = 0;
    }
    else {
      x = (viewport_width - popup_width)/2;
    }

    var y;
    if (popup_height >= viewport_height) {
      y = relative_y;
    }
    else {
      y = relative_y + ((viewport_height - popup_height)/2);
    }

    return {x: x, y: y}; 
  },

  get_auto_position: function(mouse_x, mouse_y) {
    dim = Element.getDimensions(this.popup);
    var popup_width = dim.width;
    var popup_height = dim.height;
    dim = this.get_viewport_dimensions();
    var viewport_width = dim.width;
    var viewport_height = dim.height;

    var available_right = viewport_width - (mouse_x + this.options.cursor_marginx);
    var available_left = mouse_x - this.options.cursor_marginx;
    var available_top = mouse_y - this.options.cursor_marginy;
    var available_bottom = viewport_height - (mouse_x + this.options.cursor_marginy);
    var offsetx = this.options.cursor_marginx;
    var offsety = this.options.cursor_marginy;
    var x = mouse_x;
    var y = mouse_y;

    if (popup_width >= viewport_width) {
      x = 0;
    }
    else if (popup_width <= available_right) {
      x += offsetx;
    }
    else if (popup_width <= available_left) {
      x -= popup_width + offsetx;
    }
    else if (available_right >= available_left) {
      x = viewport_width - popup_width;
    }
    else {
      x = 0;
    }

    if (popup_height >= viewport_height) {
      y = 0;
    }
    else if (popup_height <= available_bottom) {
      y += offsety;
    }
    else if (popup_height <= available_top) {
      y -= popup_height + offsety;
    }
    else if (available_bottom >= available_top) {
      y = viewport_height - popup_height;
    }
    else {
      y = 0;
    }

    return {x: x, y: y}; 
  },
  
  get_viewport_dimensions: function() {
		var dim = this.getPageSize();
    return {width: dim[2], height: dim[3]};
  },

  get_page_dimensions: function() {
		var dim = this.getPageSize();
    return {width: dim[0], height: dim[1]};
  },

  // This function from Lightbox v2.02 by Lokesh Dhakar
  // (http://www.huddletogether.com/projects/lightbox2/).
  //
  // Returns array with page width, height and window width, height
  // Core code from - quirksmode.org
  // Edit for Firefox by pHaez
  //
  getPageSize: function() {
    var xScroll, yScroll;

    if (window.innerHeight && window.scrollMaxY) {	
      xScroll = document.body.scrollWidth;
      yScroll = window.innerHeight + window.scrollMaxY;
    } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
      xScroll = document.body.scrollWidth;
      yScroll = document.body.scrollHeight;
    } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
      xScroll = document.body.offsetWidth;
      yScroll = document.body.offsetHeight;
    }

    var windowWidth, windowHeight;
    if (self.innerHeight) {	// all except Explorer
      windowWidth = self.innerWidth;
      windowHeight = self.innerHeight;
    } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
      windowWidth = document.documentElement.clientWidth;
      windowHeight = document.documentElement.clientHeight;
    } else if (document.body) { // other Explorers
      windowWidth = document.body.clientWidth;
      windowHeight = document.body.clientHeight;
    }	
    
    // for small pages with total height less then height of the viewport
    if(yScroll < windowHeight){
      pageHeight = windowHeight;
    } else { 
      pageHeight = yScroll;
    }

    // for small pages with total width less then width of the viewport
    if(xScroll < windowWidth){	
      pageWidth = windowWidth;
    } else {
      pageWidth = xScroll;
    }

    arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight);
    return arrayPageSize;
  }

}
