/**
 * Hilfesystem
 *
 * @version   0.3
 * @author    Dragan Espenschied <drx@a-blast.org>
 * @requires  jQuery 1.3.2
 */

(function($) {

  $.helpBubbles = {
    id:      0, // Zähler für UIDs für Hilfblasen
    margin: 16, // genereller abstand der blasen zum fensterrand
    runs:    0, // wie oft wurde schon versucht, eine passende position zu finden?

    clawSize: 16,
    cornerDistance: 4,
    clawCorrection : 3
  };
  $.bubbleTimer = 10000; //Wartezeit bis bubble wieder schiesst, 0 fuer unendlich
  
  /**
   * findPosition -- gibt "optimale" Koordinaten für eine Blase zurück
   *
   * @param   targetElement   node    An dieses Element soll angehängt werden
   * @param   bubbleElement   node    Diese Blase soll daran angehängt werden
   * @param   position        string  Optionale Position nach Himmelsrichtung: 'n','ne','sw'...
   *
   * @type    object
   */

  $.helpBubbles.findPosition = function(targetElement, bubbleElement, position, force) {

      $.helpBubbles.runs ++;

      var result = {
        bubble: {},    // absolute position der Blase
        claw:   {}     // position des 'dreieckchens'
      };

      var bubbleHeight = $(bubbleElement).outerHeight();  // Größe der Blase feststellen
      var bubbleWidth  = $(bubbleElement).outerWidth();

      var windowWidth  = $(window).width();
      var windowHeight = $(window).height();

      var documentWidth = $(document).width();

      var scrollTop  = $(document).scrollTop();
      var scrollLeft = $(document).scrollLeft();

      var targetPosition = $(targetElement).offset();
      var targetWidth    = $(targetElement).outerWidth();
      var targetHeight   = $(targetElement).outerHeight();

      // Position aussuchen, default ist 'n'
      if(!position) position = $(bubbleElement).data('position') || 'n';
      switch(position) {
        case 'n':
          result.claw.top = bubbleHeight-$.helpBubbles.clawCorrection+1;
          result.bubble.top = targetPosition.top - bubbleHeight - $.helpBubbles.clawSize;
          result.bubble.left = Math.max(
            targetPosition.left + targetWidth/2 - bubbleWidth/2,
            scrollLeft
          );
          result.bubble.left = Math.min(
            result.bubble.left,
            scrollLeft+windowWidth-bubbleWidth
          );
          result.claw.left = targetPosition.left+targetWidth/2
                             - result.bubble.left
                             - $.helpBubbles.clawSize/2;

          // position prüfen, außer es wird geforce'd
          if(!force && $.helpBubbles.runs<4) {
            if(result.bubble.top < scrollTop
            || result.bubble.left < scrollLeft
            || result.claw.left < $.helpBubbles.cornerDistance
            || result.claw.left > result.bubble.width+$.helpBubbles.clawSize-$.helpBubbles.cornerDistance)
              return $.helpBubbles.findPosition(targetElement, bubbleElement, 'e', false);
          }
          $.helpBubbles.runs = 0;
          break;

        case 'e':
          result.bubble.top = Math.max(
            targetPosition.top + targetHeight/2 - bubbleHeight/2,
            scrollTop
          );
          result.bubble.top = Math.min(
            result.bubble.top,
            scrollTop+windowHeight-bubbleHeight
          );
          result.bubble.left = targetPosition.left + targetWidth + $.helpBubbles.clawSize;
          result.claw.left = -$.helpBubbles.clawSize + $.helpBubbles.clawCorrection;
          result.claw.top = targetPosition.top+targetHeight/2
                            - result.bubble.top
                            - $.helpBubbles.clawSize/2;
          // position prüfen, außer es wird geforce'd
          if(!force && $.helpBubbles.runs<4) {
            if(result.bubble.left + bubbleWidth > scrollLeft + windowWidth
               || result.claw.top < $.helpBubbles.cornerDistance
               || result.claw.top > bubbleHeight - $.helpBubbles.clawSize - $.helpBubbles.cornerDistance)
              return $.helpBubbles.findPosition(targetElement, bubbleElement, 's', false);
          }
          $.helpBubbles.runs = 0;
          break;

        case 's':
          result.claw.top = -$.helpBubbles.clawSize + $.helpBubbles.clawCorrection;
          result.bubble.top = targetPosition.top + targetHeight + $.helpBubbles.clawSize;
          result.bubble.left = Math.max(
            targetPosition.left + targetWidth/2 - bubbleWidth/2,
            scrollLeft
          );
          result.bubble.left = Math.min(
            result.bubble.left,
            scrollLeft+windowWidth-bubbleWidth
          );
          result.claw.left = targetPosition.left+targetWidth/2
                             - result.bubble.left
                             - $.helpBubbles.clawSize/2;

          // position prüfen, außer es wird geforce'd
          if(!force && $.helpBubbles.runs<4) {
            if(result.bubble.top + bubbleHeight + $.helpBubbles.clawSize > scrollTop + windowHeight
               || result.claw.left < $.helpBubbles.cornerDistance
               || result.claw.left > result.bubble.width+$.helpBubbles.clawSize-$.helpBubbles.cornerDistance)
              return $.helpBubbles.findPosition(targetElement, bubbleElement, 'w', false);
          }
          $.helpBubbles.runs = 0
          break;

        case 'w':
          result.bubble.top = Math.max(
            targetPosition.top + targetHeight/2 - bubbleHeight/2,
            scrollTop
          );
          result.bubble.top = Math.min(
            result.bubble.top,
            scrollTop+windowHeight-bubbleHeight
          );
          result.bubble.left = targetPosition.left - bubbleWidth - $.helpBubbles.clawSize;
          result.claw.left = bubbleWidth - $.helpBubbles.clawCorrection +1;
          result.claw.top = targetPosition.top+targetHeight/2
                            - result.bubble.top
                            - $.helpBubbles.clawSize/2;
          // position prüfen, außer es wird geforce'd
          if(!force && $.helpBubbles.runs<4) {
            // steht links raus? mal nach norden versuchen
            if(result.bubble.left < scrollLeft
               || result.claw.top < $.helpBubbles.cornerDistance
               || result.claw.top > bubbleHeight - $.helpBubbles.clawSize - $.helpBubbles.cornerDistance)
              return $.helpBubbles.findPosition(targetElement, bubbleElement, 'n', false);
          }
          $.helpBubbles.runs = 0;
          break;


      }

      result.bubble['class'] = position;
      result.bubble.height = bubbleHeight;
      $(bubbleElement).data('position', position);

      return result;
  }

  // wenn das die fenstergröße verändert wird, müssen die
  // blasen evtl. neu an ihren "hooks" positioniert werden

  $(document).ready( function() {
    $(window).bind('resize', function() {
      $('div.helpBubble').each( function() {
        var coords = $.helpBubbles.findPosition( $(this).data('hook'), this );
        $(this)
          .removeClass('helpBubble-n')
          .removeClass('helpBubble-e')
          .removeClass('helpBubble-s')
          .removeClass('helpBubble-w')
          .css({
            left: coords.bubble.left,
            top: coords.bubble.top
            })
          .addClass('helpBubble-'+coords.bubble['class'])
          .find("div.claw").css(coords.claw).end()
      });
    });
  });

  /**
   * createBubble -- show a help bubble
   *
   * @param  string/array  text     HTML als String oder in einem Array für den
   *                                Inhalt der Blase oder Blasen
   * @param  settings      object   Folgende Switches:
   *                                position: 'n|e|s|w'  -- Himmelsrichtung
   *                                force:    true|false -- Blase auf jeden Fall so anzeigen
   *                                width: int           -- Breite in Pixeln
   *                                type: 'dismiss'      -- mit Closer rechts oben
   *                                      'sticky'       -- geht nie weg
   *                                      'confirm'      -- Button in der Blase
   *                                confirmation: string -- Text für die Blase
   *                                class                -- zusätzliche CSS-Klasse
   *                                callback: function   -- Beim Verschwinden der Blase aufrufen
   */

  $.fn.createBubble = function(text, settings) {

    // defaults
    var config = {
      force:    false,              // blase wird auf jeden fall angezeigt
      position: false,              // ein String mit einer Himmelsrichtung
      width:    200,                // Breite in px
      type:     'dismiss',          // 'dismiss' : mit closer
                                    // 'sticky': bleibt stehen
                                    // 'confirm': ok-button
      confirmation: 'OK',           // Das steht auf dem Button
      'class' : '',                 // extra-Stylesheet
      callback: function(){}        // nach confirm wird das aufgerufen
    };
    if (settings) $.extend(config, settings);

    // alle elemente abklappern
    this.each( function() {

      var targetElement = this;

      // prüfen, an dem element bereits eine blase hängt.
      // wenn ja, wird die operation abgebrochen.
      // bei aktiviertem "force" wird die blase immer gezeigt

      if(!config.force) {
        if( $(targetElement).data('helpBubble') ) return false;
      }

      // eine ID für die Blase ... wer's braucht :)
      var newId = "helpBubble"+$.helpBubbles.id;
      $.helpBubbles.id++;

      var content = text;
      if($.isArray(text)) var content = text.shift();

      // Neue Bubble außerhalb des sichtbaren Bereichs erzeugen
      var newBubble = $('<div id="'+newId+'" class="helpBubble '+config['class']+'"><div><div class="claw"><div></div></div></div></div>')
      .css(
        {
        top: "-1000px",
        left: "-1000px",
        width: config.width
        }
      )[0];
      $(newBubble)
        .appendTo('body') // ins DOM einfügen
        .data('hook', targetElement)
        .data('callback', config.callback);

      content = $("<div>"+content+"</div>")[0];

      $(content).appendTo( newBubble.firstChild );

      // Closer einbauen??
      if( config.type == 'dismiss' ) {
        $('<div class="closer">[X]</div>')
          .data('helpBubble', newBubble)
          .one('click', function() {
            $(newBubble).removeBubble();
          })
          .prependTo( newBubble.firstChild );
      } else if(config.type == 'confirm') {
        $('<button><span>'+config.confirmation+'</span></button>')
          .one('click', function() {
            $(newBubble).removeBubble();
          })
          .appendTo( newBubble.firstChild );
      }

      // dem targetElement verweis auf blase geben
      $(targetElement).data('helpBubble', newBubble).addClass('helpBubble-attached');

      // positionsdaten für die blasen rausfinden
      var coords = $.helpBubbles.findPosition(targetElement, newBubble, config.position);

      // alles auf die Blase übertragen
      $(newBubble).css({
        left: coords.bubble.left,
        top: coords.bubble.top
      })
        .hide()
        .addClass('helpBubble-'+coords.bubble['class'])
        .find("div.claw").css(coords.claw).end()
        .fadeIn(300);
     
     //closetimer
     if ($.bubbleTimer>0){
       $(newBubble).data('timer',window.setTimeout(function(bubble){
          if ($(bubble).data('timer')) $(bubble).removeBubble();
        },$.bubbleTimer,[newBubble]));
     }

    });

    return this;

  }

  /**
   * removeBubble -- Hilfsblase(n) entfernen. Funktioniert mit Hilfsblasen
   *                 oder den Elementen, an denen sie hängen.
   */

  $.fn.removeBubble = function() {
    this.each( function() {

      var bubble = false;
      
      // ist das selbst eine Hilfsblase???
      if($(this).hasClass('helpBubble') ) {
        var bubble = this;
      } else if( $(this).hasClass('helpBubble-attached') ) {
        var bubble = $(this).data('helpBubble');
      }
      if(bubble) {
        $(bubble).data('timer','');
        $( $(bubble).data('hook') )
            .removeClass('helpBubble-attached')
            .removeData('helpBubble');
        $(bubble).fadeOut(300, function(){
          $(bubble).data('callback')();
          $(bubble).remove();
        });
      }
    });
  }

})(jQuery);

