/**
 * A general purpose tooltip claas that popups a hint when hovering
 * a corresponding HTML element.
 *
 * Author: Ralf Obmann <ralf@obmann.de>
 *         with kind support by dmc digital media center GmbH
 *
 * License: This software is in the public domain.
 *          Code can be used and modified for all purposes without any
 *          restrictions.
 */
(function() {
  // the popup HTML element (if openend, otherwise null) -
  // variable is shared between all ToolTip instances.
  var popupElem = null;

  ToolTip = function()
  {
    // corresponding dom element that has to be hovered to show its tooltip
    this._elem = null;

    // text on tooltip
    this._tipText  = '';

    // title on tooltip
    this._tipTitle = '';

    // flag that indicated whether tooltip text is HTML or not
    this._tipTextIsHtml = false;

    // list of listners to be called when
    // popup is opened
    this._onPopupListeners = [];

    // option to set if tooltip shall follow mouse movement
    this._followMouse = false;

    // time (in milliseconds) to wait until tooltip is shown on hovering element
    this._delay = 0;

    // CSS class of the tooltip HTML elemen
    this._cssClass = 'ui_toolTip';

    // preferred position of the tooltip (default is 'northEast')
    this._preferredPosition = 'northEast';

    // reference to this tooltip instance (to be used by closures)
    var self = this;

    // flag to show that popup has really been triggered (used to interrupt delayed
    // popup opening)
    var triggeredOpenPopup = false;

    // mouse coordinates when mouse event was triggered
    var mouseX = 0;
    var mouseY = 0;

    // time when mouseover event was triggered (used for popup delay)
    var timeEntered = 0;

    // current position of the popup
    // (possible values are northEast, southEast, southWest, northWest)
    var position = '';

    // opens the popup (at least if desired/required)
    var openPopup = function()
    {
      if (!triggeredOpenPopup
          || (this._delay > 0)
          && (new Date().getTime() - timeEntered < self._delay - 20))
      {
        return;
      }

      triggeredOpenPopup = false;
      var text = trim(self._tipText);
      var title = trim(self._tipTitle);

      for (var i = 0; i < self._onPopupListeners.length; ++i)
      {
        var func = self._onPopupListeners[i];

        if (typeof func == 'function')
        {
          if (func() === false)
          {
            return;
          }
        }
      }

      if (!popupElem && document.body && text !== '')
      {
        var titleElem = document.createElement('div');
        titleElem.className = 'title-';
        titleElem.appendChild(document.createTextNode(title));

        var textElem = document.createElement('div');
        textElem.className = 'text-';

        if (!self._tipTextIsHtml)
        {
          textElem.appendChild(document.createTextNode(text));
        }
        else
        {
          textElem.innerHTML = text;
        }

        var colNames = ['a', 'b', 'c', 'd', 'e'];

        var tbody = document.createElement('tbody');

        for (var rowNum = 1; rowNum <= 5; ++rowNum)
        {
          if (title === '' && (rowNum == 2 || rowNum == 3))
          {
            continue;
          }

          var row = document.createElement('tr');
          var colCount = (rowNum % 2 == 0) ? 3 : 5;

          for (var colIdx = 0; colIdx < colCount; ++colIdx)
          {
            var cell = document.createElement('td');
            cell.className = 'cell_' + rowNum + colNames[colIdx] + '-';

            if (colCount == 3 && colIdx == 1)
            {
              cell.setAttribute('colSpan', 3);
              cell.appendChild(rowNum == 2 ?  titleElem : textElem);
            }
            else
            {
              var blank = document.createElement('span');
              cell.appendChild(blank);
              blank.style.fontSize = '1px';
              blank.style.width = '1px';
              blank.style.height= '1px';
              blank.innerHTML = '&nbsp';
              blank.style.overflow = 'hidden';
            }

            row.appendChild(cell);
          }

          tbody.appendChild(row);
        }

        popupElem = document.createElement('table');
        popupElem.setAttribute('cellSpacing', 0);
        popupElem.setAttribute('cellPadding', 0);
        popupElem.style.visibility = 'hidden';
        popupElem.style.position = 'absolute';
        popupElem.style.left = 0;
        popupElem.style.top = 0;
        popupElem.style.display = 'inline';
        popupElem.className = self._cssClass;

        addEventHandler(popupElem, 'mouseout', self._doOnMouseOut);
        popupElem.appendChild(tbody);
        document.body.appendChild(popupElem);

        // find the best poup position (to show all or at least most of the popup)
        var availPositions = ['northEast', 'southEast',
            'northWest', 'southWest'];
        var positions = [self._preferredPosition];

        for (var i = 0; i < availPositions.length; ++i)
        {
          if (availPositions[i] != self._preferredPosition)
          {
            positions[positions.length] = availPositions[i];
          }
        }

        var bestPosition = '';
        var bestPositionHiddenSize = 0;

        for (var i = 0; i < positions.length; ++i)
        {
          position = positions[i];
          self._resizePopup(popupElem, textElem, titleElem, position);
          self._positionPopup(popupElem, position, mouseX, mouseY);
          var overflowSize = 0;

          var left = parseInt(popupElem.style.left);
          var top = parseInt(popupElem.style.top);
          var width = popupElem.offsetWidth;
          var height = popupElem.offsetHeight;
          var right = left + width;
          var bottom = top + height;
          var totalSize = width * height;
          var shownWidth = width;
          var shownHeight = height;


          var availWidth = document.body.clientWidth;
          var availHeight = document.body.clientHeight;
          var scrollX = document.body.scrollLeft;
          var scrollY = document.body.scrollTop;

          if (left - scrollX < 0)
          {
            shownWidth += (left - scrollX);
          }

          if (right > availWidth + scrollX)
          {
             shownWidth -= (right - availWidth - scrollX);
          }

          if (top - scrollY < 0)
          {
            shownHeight += (top - scrollY);
          }

          if (bottom > availHeight + scrollY)
          {
            shownHeight -= (bottom - availHeight - scrollY)
          }

          var shownSize = shownWidth * shownHeight;
          var hiddenSize = totalSize - shownSize;

          if (hiddenSize == 0)
          {
            bestPosition = position;
            bestPositionHiddenSize = 0;
            break;
          }
          else if (bestPositionHiddenSize == 0
              || bestPositionHiddenSize > hiddenSize)
          {
            bestPosition = position;
            bestPositionHiddenSize = hiddenSize;
          }
        }

        if (bestPosition != position)
        {
          position = bestPosition;
          self._positionPopup(popupElem, position, mouseX, mouseY);
        }
      }

      if (popupElem)
      {
        popupElem.style.visibility = 'visible';
        self._positionPopup(popupElem, position, mouseX, mouseY);
      }
    }

    // Callback function for mouseover or mousemove event.
    // Initiates the popup (maybe delayed).
    this._showPopup = function(evt)
    {
      if (!evt)
      {
        evt = window.evt;
      }

      mouseX = evt.clientX;
      mouseY = evt.clientY;

      if (timeEntered = 0)
      {
        timeEntered  = new Date().getTime();
      }

      if (!triggeredOpenPopup)
      {
        triggeredOpenPopup = true;

        if (!popupElem && self._delay > 0)
        {
          setTimeout(openPopup, self._delay);
        }
        else
        {
          openPopup();
        }
      }
    }

    // callback function for mouseout event
    this._doOnMouseOut = function(evt)
    {
      if (!popupElem)
      {
        return;
      }

      if (!evt) {
        evt = window.evt;
      }

      var mouseX = evt.clientX;
      var mouseY = evt.clientY;

      // IE versions <=6 behave very strange when mouse is both over element AND popup.
      // So check whether mouse is still over element or popup and quit if yes
      var popupLeft = getElemPos(popupElem, 'left');
      var popupTop = getElemPos(popupElem, 'top');
      var popupWidth = popupElem.offsetWidth;
      var popupHeight = popupElem.offsetHeight;
      var elemLeft = getElemPos(self._elem, 'left');
      var elemTop = getElemPos(self._elem, 'top')
      var elemWidth = self._elem.offsetWidth;
      var elemHeight = self._elem.offsetHeight;

      var scrollLeft = document.body.scrollLeft;
      var scrollTop = document.body.scrollTop;

       // some IE6 specialities
      if (scrollLeft == 0 && scrollTop == 0)
      {
        scrollLeft = document.documentElement.scrollLeft;
        scrollTop = document.documentElement.scrollTop;
      }

      var mouseX = evt.clientX + scrollLeft;
      var mouseY = evt.clientY + scrollTop;

      if (!(mouseX >= popupLeft && mouseX < popupLeft + popupWidth
            && mouseY >= popupTop && mouseY < popupTop + popupHeight)
          &&
          !(mouseX >= elemLeft && mouseX < elemLeft + elemWidth
            && mouseY >= elemTop && mouseY < elemTop + elemHeight))
      {
        triggeredOpenPopup = false;
        clearTimeout(openPopup);
        timeEntered = 0;

        if (popupElem)
        {
          document.body.removeChild(popupElem);
          popupElem = null;
        }
      }
    }
  }


  // ---- public methods ---------------------------------------------------

  /**
   * Assigns the tool tip an html element.
   * The html element can be passed as DOM object or as id.
   * Return whether the assignment was successful or not.
   *
   * @param     mixed      elem    DOM element or its ID
   * @return    boolean            true on success, false on failure
   */
  ToolTip.prototype.connect = function(elem)
  {
    var retVal = false;
    this.disconnect();

    if (typeof elem == 'string')
    {
      elem = document.getElementById(elem);
    }

    if (elem)
    {
      if (this._followMouse)
      {
        addEventHandler(elem, 'mousemove', this._showPopup);
      }
      else
      {
        addEventHandler(elem, 'mouseover', this._showPopup);
      }

      addEventHandler(elem, 'mouseout', this._doOnMouseOut);
      this._elem = elem;
      retVal = true;
    }

    return retVal;
  };


  /**
   * Brakes the assignment of tooltip instance and HTML element.
   */
  ToolTip.prototype.disconnect = function()
  {
    if (this._elem)
    {
      if (this._followMouse)
      {
        removeEventHandler(elem, 'mousemove', this._showPopup);
      }
      else
      {
        removeEventHandler(elem, 'mouseover', this._showPopup);
      }

      removeEventHandler(elem, 'mouseout', this._doOnMouseOut);
      this._elem = null;
    }
  };


  /**
   * Sets the text for the tool tip.
   * @param string    text    the hint
   * @param string    title   the title of tooltip (optional)
   */
  ToolTip.prototype.setTipText = function(text, title, textIsHtml)
  {
    this._tipText = trim(text);
    this._tipTitle = trim(title);
    this._tipTextIsHtml = !!textIsHtml;
  };

  /**
   * Sets time delay when showing the popup.
   *
   * @param integer   value   time delay (in milliseconds)
   */
  ToolTip.prototype.setDelay = function(value)
  {
    this._delay = value;
  }


  /**
   * Sets the option whether the tooltip shall follow mouse movements
   *
   * @param boolean   value   switch for this feature
   */
  ToolTip.prototype.setFollowMouse = function(value)
  {
    this._followMouse = !!value;
  };


  /**
   * Sets the tooltips CSS class to choose an other style
   *
   * @param string   className   name of the CSS class(es)
   */
  ToolTip.prototype.setCssClass = function(className)
  {
    this._cssClass = className;
  };


  /**
   * Sets the preferred position for the popup.
   * Allowed values are: northEast, southEast, northWest, southWest
   *
   * @param string   position   preference
   */
  ToolTip.prototype.setPreferredPosition = function(position)
  {
    if (position == 'northEast' || position == 'southEast'
        || position == 'northWest'  || position == 'southWest')
    {
      this._preferredPosition = position;
    }
  }


  /**
   * Registers a new callback function that is invoked right before the
   * tooltip is popped up.
   * This callback function can return <false> to prevent the tooltip to
   * be popped up.
   *
   * @param string   position   position preference
   */
  ToolTip.prototype.addOnPopupListener = function(func)
  {
    this._onPopupListeners[this._onPopupListeners.length] = func;
  }


  /**
   * Converts automatically the title attributes of all HTML elements where
   * set to a tooltip (title attribute itself will be cleared)
   *
   * @static
   */
  ToolTip.automatize = function()
  {
     var _automatizeLocal = function(node)
     {
       if (node)
       {
         var children = node.childNodes;

         for (var i = 0; i < children.length; ++i)
         {
           _automatizeLocal(children[i]);
         }

         var title = node.title;

         if (typeof title !== 'string')
         {
           title = '';
         }
         else
         {
           title = title.replace(/^\s+|\s+$/, '');
         }

         if (title !== '' && !trim(node.className).match(/(^|\s)dontShowTitleAsToolTip(\s|$)/))
         {
           node.setAttribute('title', '');
           var toolTip = new ToolTip();
           toolTip.setTipText(title, '', true);
           toolTip.connect(node);
         }
       }
     }

     _automatizeLocal(document.body);
  }


  // ---- private methods --------------------------------------------------

  // resizes the popup to a decent extend
  ToolTip.prototype._resizePopup = function(popupElem, textElem, titleElem, position)
  {
    var oldLeft = popupElem.style.left;
    var oldTop = popupElem.style.top;
    popupElem.style.left = 0;
    popupElem.style.top = 0;

    var minWidth = 80;
    popupElem.style.width = '';

    if (position !== 'northEast' && position !== 'southEast'
        && position !== 'northWest' && position !== 'southWest')
    {
      position = 'southEast';
    }

    popupElem.firstChild.className = position + '-';

    if (textElem.offsetWidth > minWidth)
    {
      var width = textElem.offsetWidth;
      var height = textElem.offsetHeight;
      var oldWidth = 0;
      textElem.style.width = '' + width + 'px';
      var min = minWidth;
      var max = width;
      var i = 0;

      while (min + 1 < max)
      {
        var width = min + Math.floor((max - min) / 2);
        textElem.style.width = width + 'px';

       if ((width / textElem.offsetHeight) > 7)
        {
          max = width;
        }
        else
        {
          min = width;
        }
      }

      width = min;
      textElem.style.width = width + 'px';

      var oldHeight = textElem.offsetHeight;

      min = 0;
      max = width;

      while (min + 1 < max)
      {
        width = min + Math.floor((max - min) / 2);
        textElem.style.width = width + 'px';

        if (oldHeight == textElem.offsetHeight)
        {
          max = width;
        }
        else
        {
          min = width;
        }
      }

      width = Math.max(titleElem.offsetHeight, max);
      textElem.style.width = width + 'px';
      popupElem.style.width = popupElem.offsetWidth + 'px';
      textElem.style.width = '';
    }

    popupElem.style.left = oldLeft;
    popupElem.style.top = oldTop;
  };

  // positions the popup
  ToolTip.prototype._positionPopup = function(popupElem, position, mouseX, mouseY)
  {
    popupElem.firstChild.className = position + '-';
    var left = 0;
    var top = 0;
    var offsetX = 0;
    var offsetY = 0;
    var width = popupElem.offsetWidth;
    var height = popupElem.offsetHeight;
    popupElem.style.position = 'absolute';

    if (this._followMouse)
    {
      left = mouseX + document.body.scrollLeft;
      top = mouseY + document.body.scrollTop;

      if (typeof window.scrollX != 'undefined')
  	  {
          left += window.scrollX;
          top += window.scrollY;
        }
        else if (document.documentElement) {
        	 left += document.documentElement.scrollLeft;
        	 top += document.documentElement.scrollTop;
        	 window.status = left + ' | ' + top;
  	  }
      else if (typeof document.body.scrollTop != 'undefined')
  	  {
        left += document.body.scrollLeft;
        top += document.body.scrollTop;
      }

      var offsetX = 0;
      var offsetY = 10;

      if (position == 'northEast')
      {
        popupElem.style.top = (top - height - offsetY) + 'px';
        popupElem.style.left = (left + offsetX) + 'px';
      }
      else if (position == 'southEast')
      {
        popupElem.style.top = (top + offsetY) + 'px';
        popupElem.style.left = (left + offsetX) + 'px';
      }
      else if (position == 'southWest')
      {
        popupElem.style.top = (top + offsetY) + 'px';
        popupElem.style.left = (left - width - offsetX) + 'px';
      }
      else if (position == 'northWest')
      {
        popupElem.style.top = top - height - offsetY;
        popupElem.style.left = left - width - offsetX;
      }
    }
    else
    {
      left = getElemPos(this._elem, 'left') + this._elem.offsetWidth;
      top = getElemPos(this._elem, 'top');
      var offsetX = -Math.floor(this._elem.offsetWidth / 2);
      var offsetY = -Math.floor(getElemHeight(this._elem) / 2);

       // Move tooltip position a bit so the tooltip itself does not cover the
       // related HTML element too much.
      if (position == 'northEast' || position == 'northWest')
      {
        offsetY += 10;
      }
      else
      {
        offsetY -= 10;
      }
      
      if (position == 'northEast')
      {
        popupElem.style.top = (top - height - offsetY) + 'px';
        popupElem.style.left = (left + offsetX) + 'px';
      }
      else if (position == 'southEast')
      {
        popupElem.style.top = (top + getElemHeight(this._elem) + offsetY) + 'px';
        popupElem.style.left = (left + offsetX) + 'px';
      }
      else if (position == 'southWest')
      {
        popupElem.style.top = (top + getElemHeight(this._elem) + offsetY) + 'px';
        popupElem.style.left = (left - this._elem.offsetWidth - width - offsetX) + 'px';
      }
      else if (position == 'northWest')
      {
        popupElem.style.top = (top - height - offsetY) + 'px';
        popupElem.style.left = (left - this._elem.offsetWidth - width - offsetX) + 'px';
      }
    }
  };

  // ---- static functions --------------------------------------------------

  // simple 'trim string' function
  function trim(text)
  {
    var retVal = '';

    if (text !== null && typeof text !== 'undefined')
    {
      retVal = ('' + text).replace(/^\s+|\s+$/g, '');
    }

    return retVal;
  }

  // registers a callback function for an event on a HTML element
  function addEventHandler(obj, eventName, func, capture)
  {
    var retVal = false;

    if (obj)
    {
      if (obj.addEventListener)
      {
        obj.addEventListener(eventName, func, !!capture);
        retVal = true;
      }
      else if (obj.attachEvent)
      {
        obj.attachEvent('on' + eventName, func, !!capture);
        retVal = true;
      }
    }

    return retVal;
  }

  // unregisters a callback function for an event on a HTML element
  function removeEventHandler(obj, eventName, func, capture)
  {
    var retVal = false;

    if (obj)
    {
      if (obj.removeEventListener)
      {
        obj.removeEventListener(eventName, func, !!capture);
        retVal = true;
      }
      else if (obj.detachEvent)
      {
        obj.detachEvent('on' + eventName, func);
        retVal = true;
      }
    }
  }

  // determiates the absolute position of a html element on the current page
  function getElemPos(elem, whichPos)
  {
    var retVal = null;
    whichPos = String(whichPos).toLowerCase();
    whichPos = whichPos.substr(0, 1).toUpperCase() + whichPos.substr(1);

    if (whichPos != 'Left' && whichPos != 'Top'
        && whichPos != 'Right' && whichPos != 'Bottom')
    {
      whichPos = null;
    }

    if (whichPos && elem)
    {
      var posVal = elem['offset' + whichPos];
      var parentOffset = elem.offsetParent;

      while (parentOffset)
      {
        posVal += parentOffset['offset' + whichPos];
        parentOffset = parentOffset.offsetParent;
      }

      // Gecko sometimes behaves strange determintaing height and top value
      // when an anchor element has child nodes
      if (whichPos == 'Top' && elem.hasChildNodes)
      {
        var children = elem.childNodes;

        for (var i = 0; i < children.length; ++i)
        {
          if (children[i].tagName)
          {
            posVal = Math.min(posVal,
                getElemPos(children[i], 'top'));
          }
        }
      }

      retVal = posVal;
    }

    return retVal;
  }

  // determiates the width and height of an html element
  //(also involves the child elements as Gecko's offsetHeight values
  // are sometimes not proper)
  function getElemHeight(elem)
  {
    var height = elem.offsetHeight;

    if (elem.hasChildNodes)
    {
      var children = elem.childNodes;

      for (var i = 0; i < children.length; ++i)
      {
        if (children[i].tagName)
        {
          height = Math.max(height,
              getElemHeight(children[i]));
        }
      }
    }

    return height;
  }
})();

