var FloatingPanel = Class.create({
  // PREREQUISITES: Prototype 1.6+, Scriptaculous 1.8+ (effects.js module only)
  // REQUIRED PARAMETERS:
  //   trigger: id of link (or other triggering element)
  //  panel: id of panel to open (MUST be set to display:none inline, not in linked CSS)
  // AVAILABLE OPTIONS:
  //   triggerEvent - possible values are click, focus, and mouseover. default is mouseover.
  //   closeButton - set to id of a link to close the panel (link must be within the panel element).
  //               if set, panel will stay open until link is clicked, canceling default auto-close.
  //  allowDefault - to allow the default action of the trigger to happen, set allowDefault to true (does not apply to 'click' triggerEvents)
  //   target - set to make the panel appear next to an element other than the trigger
  //  closeDelay - set to change how long (in seconds) before a panel is closed on mouseout
  //  leftOffset, topOffset - adjust position of panel relative to default (10px right of trigger or target)
  //   effects - turn off open/close effects by setting this to 'off'
  //  effectDuration - set length of effect (in seconds)
  //  callbacks - fire a function before a panel opens or after one closes by setting beforeOpen or afterClose options
  //   new callback - beforeClose
  //  ajaxUrl and loading - fill panel with ajax request
  //  correctPosition - set to false to prevent default adjustment of position of panel near bottom of viewport
  //  new callback - afterOpen: will fire after any ajax loading happens
  togglePanel: function(e) {
    if ($(this.panel).visible()) {
      this.closePanel();
    } else {
      this.event("beforeOpen");
      var target_pos = $(this.panelTarget).cumulativeOffset();
      var target_viewpos = $(this.panelTarget).viewportOffset();
      var doc_height = document.viewport.getHeight();
      var doc_width = document.viewport.getWidth();
      var panel_height = $(this.panel).getHeight();
      var panel_width = $(this.panel).getWidth();
      var px_from_bottom = (doc_height - target_viewpos[1] - 10);
      var px_from_right = (doc_width - target_viewpos[0] - 10);
      var need_vert_px = (this.correctPosition) ? panel_height - px_from_bottom : 0;
      var need_horiz_px = (this.correctPosition) ? panel_width - px_from_right : 0;
      var panel_top = (need_vert_px > 0) ? (target_pos[1] - need_vert_px) : target_pos[1] + parseInt(this.topOffset);
      var panel_left = (need_horiz_px > 0) ? (target_pos[0] - need_horiz_px) : target_pos[0] + $(this.panelTarget).getWidth() + parseInt(this.rightOffset);
      $(this.panel).setStyle({ left: panel_left+'px', top: panel_top+'px' });
      if (window.external && typeof window.XMLHttpRequest == "undefined") {
        // if IE6, do iframe shim hack to preserve panel's z-index
        this.insertIframe(panel_top,panel_left,panel_width,panel_height); 
      }
      if (this.effects) {
        new Effect.Appear($(this.panel), { duration:this.effectDuration });
      } else {
        $(this.panel).show();
      }
      if (this.ajaxUrl) {
        new Ajax.Updater($(this.panel), this.ajaxUrl, {method:'get', onComplete:this.event("afterOpen")});
      } else {
        this.event("afterOpen");
      }
    }
    if (!this.allowDefault) Event.stop(e);
  },
  insertIframe: function(ptop, pleft, pwidth, pheight) {
    this.myFrame = document.createElement('IFRAME'); 
    this.myFrame.frameBorder = 0;
    this.myFrame.scrolling = 'no';
    this.myFrame.style.zIndex = 1;
    this.myFrame.style.position = 'absolute';
    Element.extend(this.myFrame);
    this.myFrame.setStyle({ top: ptop+'px', left: pleft+'px', width: pwidth+'px', height: pheight+'px' });
    document.body.appendChild(this.myFrame);
  },
  closePanel: function(e) {
    this.event("beforeClose");
    if (this.myFrame) {
     Element.remove(this.myFrame);
    }
    if (this.effects) {
      new Effect.Fade($(this.panel), { duration:this.effectDuration });
    } else {
      $(this.panel).hide();
    }
    if (e) Event.stop(e);
    this.event("afterClose", this.effectDuration);
  },
  doClosePanel: function() {
    this.fp_delay = this.closePanel.bind(this).delay(this.closeDelay);
  },
  stopClosePanel: function() {
    window.clearTimeout(this.fp_delay);
  },
  startObserving: function() {
    Event.observe(this.trigger, this.triggerEvent, this.togglePanel.bindAsEventListener(this));
    if (this.triggerEvent == 'mouseover' && !this.sticky) {
      Event.observe(this.trigger, 'mouseout', this.doClosePanel.bindAsEventListener(this));
      Event.observe(this.panel, 'mouseover', this.stopClosePanel.bindAsEventListener(this));
      Event.observe(this.panel, 'mouseout', this.doClosePanel.bindAsEventListener(this));
    } else if (this.triggerEvent == 'focus' && !this.sticky) {
      Event.observe(this.trigger, 'blur', this.closePanel.bindAsEventListener(this));
    }
    if (this.sticky) {
      Event.observe(this.closeButton, 'click', this.closePanel.bindAsEventListener(this));
    }
    if (!this.allowDefault && this.triggerEvent != 'click') Event.observe(this.trigger, 'click', function(e) { Event.stop(e); });
  },
  event: function(eventName,delay) {
    if(this.events[eventName]) {
      var cbDelay = (delay != undefined) ? delay : 0;
      this.events[eventName].delay(cbDelay);
    }
  },
  initialize: function(trigger, panel, opts) {
    // check that necessary libraries and elements are present
    // if (!checkLibraryVersion('Prototype','1.6') || !checkLibraryVersion('Scriptaculous','1.8')) return;
    if (!trigger || !panel || !$(trigger) || !$(panel)) return;
    // set defaults
    this.trigger = trigger;
    this.panel = panel;
    this.triggerEvent = 'mouseover';
    this.sticky = false;
    this.closeButton = null;
    this.allowDefault = false;
    this.correctPosition = true;
    this.panelTarget = this.trigger;
    this.closeDelay = .45;
    this.rightOffset = 10;
    this.topOffset = 0;
    this.fp_delay = '';
    this.effectDuration = .3;
    this.effects = true;
    this.events = {};
    this.ajaxUrl = null;
    this.loading = '/images/loading/spinner-trans.gif';
    // set options
    if (opts) {
      this.opts = opts;
      if (this.opts['triggerEvent']) this.triggerEvent = this.opts['triggerEvent'];
      if (this.opts['closeButton'] && $$('#'+this.panel+' #'+this.opts['closeButton']+'').length > 0) this.closeButton = this.opts['closeButton'];
      if (this.closeButton) this.sticky = true;
      if (this.opts['allowDefault'] == true) this.allowDefault = true;
      if (this.opts['correctPosition'] == false) this.correctPosition = false;
      if (this.opts['target']) this.panelTarget = this.opts['target'];
      if (this.opts['closeDelay']) this.closeDelay = this.opts['closeDelay'];
      if (this.opts['rightOffset']) this.rightOffset = this.opts['rightOffset'];
      if (this.opts['topOffset']) this.topOffset = this.opts['topOffset'];
      if (this.opts['effects'] == 'off') this.effects = false;
      if (this.opts['effectDuration']) this.effectDuration = this.opts['effectDuration'];
      if (this.opts['beforeClose'] && typeof this.opts['beforeClose'] == 'function') this.events["beforeClose"] = this.opts['beforeClose'];
      if (this.opts['afterClose'] && typeof this.opts['afterClose'] == 'function') this.events["afterClose"] = this.opts['afterClose'];
      if (this.opts['beforeOpen'] && typeof this.opts['beforeOpen'] == 'function') this.events["beforeOpen"] = this.opts['beforeOpen'];
      if (this.opts['afterOpen'] && typeof this.opts['afterOpen'] == 'function') this.events["afterOpen"] = this.opts['afterOpen'];
      if (this.opts['ajaxUrl']) this.ajaxUrl = this.opts['ajaxUrl'];
      if (this.opts['loading']) this.loading = this.opts['loading'];
    }
    this.startObserving();
  }
});