/**
 * $Id: bearpaw-showmagic.js 116 2007-11-30 20:00:02Z svollbehr $
 *
 *
 * Copyright (C) 2005-2007 The Bearpaw Project Work Group
 * Copyright (C) 2007 BEHR Software Systems. All Rights Reserved.
 *
 * @package bearPaw
 */



/* Timing   *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Default constructor.
 *
 * @class     The Timing class consists of utility static methods helping with
 *            timing related matters.
 * @author    Sven Vollbehr <sven.vollbehr@behrss.eu>
 * @copyright 2005, 2006, 2007 The Bearpaw Project Work Group
 * @version   $Rev: 116 $
 * @constructor
 */
function Timing () {}

/**
 * The timer reference if set.
 *
 * @private
 */
Timing.timer = null;

/**
 * The timing object stack containing elements that are currently faded.
 *
 * @private
 */
Timing.stack = {};

/**
 * Number of active elements in stack.
 *
 * @private
 */
Timing.stackCount = 0;

/**
 * Adds a zooming effect to specified element.
 *
 * Additionally one may define a callback function onafterzoom on given element
 * that gets called once the effect has been carried out on the element.
 *
 * @param {HTMLElement} elem           the element to apply the zoom effect on
 * @param {Integer}     times          the number of intervals
 * @param {Integer}     endX           the destination x coordinate of the elem
 * @param {Integer}     endY           the destination y coordinate of the elem
 * @param {Integer}     endW           the destination width of the elem
 * @param {Integer}     endH           the destination height of the elem
 * @param {HTMLElement} opacityElem    the element to apply the opacity effect
 *                      on if needed
 * @param {Integer}     startingFactor a floating point between 0 and 1
 *                      corresponding to the starting point of the opacity
 *                      effect
 * @param {Integer}     endingFactor   a floating point between 0 and 1
 *                      corresponding to the ending point of the opacity
 *                      effect
 */
Timing.addZoom = function (elem, times, endX, endY, endW, endH,
                           opacityElem, startingFactor, endingFactor)
{
  // WARNING! Please note that each element must have an id defined. This is
  // used as an index for element stack. A reference to the element would be
  // the preferrable index but some implementations return unexpected results
  // with this.
  var count = 0;
  if (Timing.stack[elem.id] && Timing.stack[elem.id].count > 0) {
    if (endW == Timing.stack[elem.id].endW)
      return;//count = 0;
    else
      count = Timing.stack[elem.id].times - Timing.stack[elem.id].count;
  }
  Timing.stack[elem.id] =
    { type: "zoom", elem: elem, count: count, times: times,
      startX: getElementPositionX(elem), startY: getElementPositionY(elem),
      startW: elem.offsetWidth, startH: elem.offsetHeight,
      endX: endX, endY: endY, endW: endW, endH: endH,
      opacityElem: opacityElem, factor: (endingFactor - startingFactor) / times,
      startingFactor: startingFactor, endingFactor: endingFactor };
  if (!Timing.timer)
    Timing.timer = setInterval("Timing.__handleTiming()", 50);
}

/**
 * Adds a fading effect to specified element.
 *
 * Additionally one may define a callback function onafterfade on given element
 * that gets called once the effect has been carried out on the element.
 *
 * @param {String}      type           currently supported types are color and
 *                      opacity
 * @param {HTMLElement} elem           the element to apply the effect on
 */
Timing.addFade = function (type, elem, factor, startingFactor, endingFactor)
{
  // WARNING! Please note that each element must have an id defined. This is
  // used as an index for element stack. A reference to the element would be
  // the preferrable index but some implementations return unexpected results
  // with this.
  var count = 0;
  if (Timing.stack[elem.id] && Timing.stack[elem.id].count > 0)
    count = (Timing.stack[elem.id].startingFactor == startingFactor &&
             Timing.stack[elem.id].endingFactor == endingFactor) ?
      Timing.stack[elem.id].count : (factor - Timing.stack[elem.id].count);
  Timing.stack[elem.id] =
    { type: type, elem: elem, factor: factor,
      startingFactor: startingFactor, endingFactor: endingFactor };
  if (type == "color")
    Timing.stack[elem.id].count = count;
  if (!Timing.timer)
    Timing.timer = setInterval("Timing.__handleTiming()", 50);
}

/**
 * Adds opacity fading effect on given element.
 *
 * @param {HTMLElement} elem           the element to apply the effect on
 * @param {Integer}     factor         the factor by which the fading happens
 * @param {Integer}     startingFactor a floating point between 0 and 1
 *                      corresponding to the starting point of the effect
 * @param {Integer}     endingFactor   a floating point between 0 and 1
 *                      corresponding to the ending point of the effect
 */
Timing.addOpacityFade = function (elem, factor, startingFactor, endingFactor)
{
  Timing.addFade("opacity", elem, factor, startingFactor, endingFactor);
}

/**
 * Adds color fading effect on given element.
 *
 * @param {HTMLElement} elem           the element to apply the effect on
 * @param {Integer}     numVariations  the number of intermediate colours
 * @param {Integer}     startingColor  the colour to start the fade on in either
 *                      #[0-9a-z]{6} or rgb(\d+,\d+,\d+) form
 * @param {Integer}     endingColor    the colour to fade to in either
 *                      #[0-9a-z]{6} or rgb(\d+,\d+,\d+) form
 * @param {Function}    onafterfade    the function to be called after the
 *                      effect has been carried out on the element
 */
Timing.addColorFade = function (elem, numVariations, startingColor, endingColor)
{
  Timing.addFade("color", elem, numVariations, startingColor, endingColor);
}

/**
 * Performs the fading effect on the elements it has been added to. After the
 * fading effect is done the callback function is called, should it be defined.
 *
 * @private
 */
Timing.__handleTiming = function ()
{
  var count = 0;
  for (var e in Timing.stack)
    if (Timing.stack[e])
      count++;
  if (count == 0) {
    clearTimeout(Timing.timer);
    Timing.timer = null;
  }

  for (var e in Timing.stack) {
    if (!Timing.stack[e])
      continue;

    if (Timing.stack[e].type == "zoom") {
    	Timing.stack[e].elem.style.left =
    	  (Timing.stack[e].startX +
    	   (Timing.stack[e].endX - Timing.stack[e].startX) /
    	   Timing.stack[e].times * Timing.stack[e].count) + "px";
    	Timing.stack[e].elem.style.top =
    	  (Timing.stack[e].startY +
    	   (Timing.stack[e].endY - Timing.stack[e].startY) /
    	   Timing.stack[e].times * Timing.stack[e].count) + "px";
    	Timing.stack[e].elem.style.width =
    	  (Timing.stack[e].startW +
    	   (Timing.stack[e].endW - Timing.stack[e].startW) /
    	   Timing.stack[e].times * Timing.stack[e].count) + "px";
    	Timing.stack[e].elem.style.height =
    	  (Timing.stack[e].startH +
    	   (Timing.stack[e].endH - Timing.stack[e].startH) /
    	   Timing.stack[e].times * Timing.stack[e].count) + "px";

			if (Timing.stack[e].count == Timing.stack[e].times) {
	      if (!Timing.stack[e].elem.onafterzoom)
	        Timing.stack[e].elem.onafterzoom =
	          Timing.stack[e].elem.getAttribute("onafterzoom");
	      if (typeof Timing.stack[e].elem.onafterzoom != "function")
	        Timing.stack[e].elem.onafterzoom =
	          new Function(Timing.stack[e].elem.onafterzoom);
	      Timing.stack[e].elem.onafterzoom();
	      if (!Timing.stack[e].opacityElem) {
		      Timing.stack[e] = null;
		      continue;
	      }
	    } else Timing.stack[e].count++;
    }
    if (Timing.stack[e].type == "opacity" ||
        Timing.stack[e].type == "zoom") {
      var elem;
      if (Timing.stack[e].type == "opacity")
        elem = Timing.stack[e].elem;
      else
        elem = Timing.stack[e].opacityElem;

      if (!elem) // Continue only if zoom needs opacity changing
        continue;
        
      var opacity = Timing.stack[e].startingFactor;
      if (elem.style.filter != undefined) {
        var filter;
        if (elem.filters["DXImageTransform.Microsoft.AlphaImageLoader"])
          opacity = Timing.stack[e].endingFactor;
        else if (filter = elem.filters["DXImageTransform.Microsoft.Alpha"]) {
          try {
            opacity = + (filter.opacity / 100) + Timing.stack[e].factor;
            filter.opacity = opacity * 100;
          } catch (ignore) {
            opacity = Timing.stack[e].endingFactor;
          }
        } else
          elem.style.filter +=
            "progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=" +
            (opacity * 100) + ")";
      } else {
        opacity = + (elem.style.opacity ? elem.style.opacity : opacity) +
          Timing.stack[e].factor;
        elem.style.opacity = opacity;
      }
      
      if ((Timing.stack[e].factor > 0 &&
           opacity >= Timing.stack[e].endingFactor) ||
          (Timing.stack[e].factor < 0 &&
           opacity <= Timing.stack[e].endingFactor)) {
        if (elem.filters &&
            elem.filters["DXImageTransform.Microsoft.Alpha"])
          try {
            elem.filters["DXImageTransform.Microsoft.Alpha"].opacity =
              (Timing.stack[e].endingFactor * 100);
          } catch (ignore) {}
        elem.style.opacity = Timing.stack[e].endingFactor;

	      if (Timing.stack[e].type == "zoom") continue;

        if (!elem.onafterfade)
          elem.onafterfade =
            elem.getAttribute("onafterfade");
        if (typeof elem.onafterfade != "function")
          elem.onafterfade =
            new Function(elem.onafterfade);
        elem.onafterfade();
        Timing.stack[e] = null;
        continue;
      }
    }
    if (Timing.stack[e].type == "color") {
      var color = Timing.stack[e].elem.style.backgroundColor ?
        Timing.stack[e].elem.style.backgroundColor :
        Timing.stack[e].startingFactor;

      function getRGB(color)
      {
        var r = 255, g = 255, b = 255;
        if (/^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/i
            .test(color)) {
          r = parseInt(RegExp.$1);
          g = parseInt(RegExp.$2);
          b = parseInt(RegExp.$3);
        }
        if (/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/i
            .test(color)) {
          r = parseInt(RegExp.$1, 16);
          g = parseInt(RegExp.$2, 16);
          b = parseInt(RegExp.$3, 16);
        }
        return { r: r, g: g, b: b };
      }

      var currentRGB = getRGB(color);
      var startRGB = getRGB(Timing.stack[e].startingFactor);
      var endRGB = getRGB(Timing.stack[e].endingFactor);

      Timing.stack[e].count++;

      if ((endRGB.r == 255 && endRGB.g == 255 && endRGB.b == 255 &&
           (currentRGB.r != 255 || currentRGB.g != 255 ||
            currentRGB.b != 255)) ||
          (endRGB.r == 0 && endRGB.g == 0 && endRGB.b == 0 &&
           (currentRGB.r != 0 || currentRGB.g != 0 || currentRGB.b != 0)) ||
          (currentRGB.r != endRGB.r && currentRGB.g != endRGB.g &&
           currentRGB.b != endRGB.b)) {
        var r, g, b;
        r = currentRGB.r + Math.ceil((endRGB.r - startRGB.r) /
          Timing.stack[e].factor);
        r = (r < 0 ? 0 : (r > 255 ? 255 : r));
        g = currentRGB.g + Math.ceil((endRGB.g - startRGB.g) /
          Timing.stack[e].factor);
        g = (g < 0 ? 0 : (g > 255 ? 255 : g));
        b = currentRGB.b + Math.ceil((endRGB.b - startRGB.b) /
          Timing.stack[e].factor);
        b = (b < 0 ? 0 : (b > 255 ? 255 : b));

        Timing.stack[e].elem.style.backgroundColor =
          "rgb(" + r + "," + g + "," + b + ")";
      } else {
        if (!Timing.stack[e].elem.onafterfade)
          Timing.stack[e].elem.onafterfade =
            Timing.stack[e].elem.getAttribute("onafterfade");
        if (typeof Timing.stack[e].elem.onafterfade != "function")
          Timing.stack[e].elem.onafterfade =
            new Function(Timing.stack[e].elem.onafterfade);
        Timing.stack[e].elem.onafterfade();
        Timing.stack[e] = null;
        continue;
      }
    }
  }
}



/* Motion   *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Default constructor.
 *
 * @class     The Motion class helps with moving objects.
 * @author    Sven Vollbehr <sven.vollbehr@behrss.eu>
 * @copyright 2005, 2006, 2007 The Bearpaw Project Work Group
 * @version   $Rev: 116 $
 * @constructor
 */
function Motion () {}

/**
 * A reference to the currently dragged element.
 *
 * @type HTMLElement
 * @private
 */
Motion.draggedElement = null;

/**
 * Makes the specified element draggable. If element is not specified the source
 * element from the event is used.
 *
 * @param {HTMLElement} elem the element to make draggable
 */
Motion.drag = function (event, elem)
{
  if (!event) event = window.event;
  if (!elem)
    elem = event.srcElement ? event.srcElement : event.target;
  if (elem.nodeType == 3) // text node
    elem = elem.parentNode;

  Motion.draggedElement = elem;
  Motion.draggedElement.elementOffsetX = getElementPositionX(elem);
  Motion.draggedElement.elementOffsetY = getElementPositionY(elem);
  Motion.draggedElement.mouseOffsetX = getMousePositionX(event);
  Motion.draggedElement.mouseOffsetY = getMousePositionY(event);

  Event.addDOMListener(document, "mousemove", Motion.__handleMouseMove);
  Event.addDOMListener(document, "mouseup",   Motion.__handleMouseUp);

  Event.stopPropagation(event);
}

/**
 * Handles mouse movement to drag the element if dragging has been started.
 *
 * @private
 */
Motion.__handleMouseMove = function (event)
{
  if (!event) event = window.event;

  if (Motion.draggedElement) {
    Motion.draggedElement.style.left = (Motion.draggedElement.elementOffsetX +
      getMousePositionX(event) - Motion.draggedElement.mouseOffsetX) + "px";
    Motion.draggedElement.style.top  = (Motion.draggedElement.elementOffsetY +
      getMousePositionY(event) - Motion.draggedElement.mouseOffsetY) + "px";
  } else
    Motion.__handleMouseUp(event);

  Event.stopPropagation(event);
}

/**
 * Handles mouse up event to stop dragging.
 *
 * @private
 */
Motion.__handleMouseUp = function (event)
{
  if (!event) event = window.event;

  Motion.draggedElement = null;

  Event.removeDOMListener(document, "mousemove", Motion.__handleMouseMove);
  Event.removeDOMListener(document, "mouseup",   Motion.__handleMouseUp);
}
