//*****************************************************************************
// Do not remove this notice.
//
// Copyright 2001 by Mike Hall.
// See http://www.brainjar.com for terms of use.
//
// *HEAVILY* modified by jon r. luini, chime interactive (c)2005
// see http://www.chime.com/ for  contact info/use permissions
// depends on myGetElementById() (see chime_request.js)
//*****************************************************************************

// Determine browser and version.

// note from jon: this is really the wrong way to do detection since
// it is treating a browser useragent as a definitive evaluation of
// object model. specifically, it breaks opera which is why there is
// a check for opera *first*
function Browser() {

  var ua, s, i;

  this.isIE    = false;
  this.isNS    = false;
  this.version = null;

  ua = navigator.userAgent;

  s = "Opera";
  if ((i = ua.indexOf(s)) >= 0) {
    this.isNS = true;
    this.version = parseFloat(ua.substr(i + s.length));
    return;
  }

  s = "MSIE";
  if ((i = ua.indexOf(s)) >= 0) {
    this.isIE = true;
    this.version = parseFloat(ua.substr(i + s.length));
    return;
  }

  s = "Netscape6/";
  if ((i = ua.indexOf(s)) >= 0) {
    this.isNS = true;
    this.version = parseFloat(ua.substr(i + s.length));
    return;
  }

  // Treat any other "Gecko" browser as NS 6.1.

  s = "Gecko";
  if ((i = ua.indexOf(s)) >= 0) {
    this.isNS = true;
    this.version = 6.1;
    return;
  }
}

var browser = new Browser();

// Global object to hold drag information.

var dragObj = new Object();
dragObj.zIndex = 999;

function dragStart (event, constrain, id, position, dropId)
{
  var el;
  var x, y;

  if (!event) var event = window.event;

  // If an element id was given, find it. Otherwise use the element being
  // clicked on.

  if (id)
    dragObj.elNode = myGetElementById(id);
  else {
    if (browser.isIE)
      dragObj.elNode = window.event.srcElement;
    if (browser.isNS)
      dragObj.elNode = event.target;

    // If this is a text node, use its parent element.

    if (dragObj.elNode.nodeType == 3)
      dragObj.elNode = dragObj.elNode.parentNode;
  }

  // Get cursor position with respect to the page.

  /* this is more compatable code that the version below */
  if (event.pageX || event.pageY)
  {
    x = event.pageX;
    y = event.pageY;
  }
  else if (event.clientX || event.clientY)
  {
    x = event.clientX + document.body.scrollLeft;
    y = event.clientY + document.body.scrollTop;
  }

  // this is a hack around the fact that you can be in the middle
  // of a move event and then move off the page and release the mouse
  // but the mouseup event doesn't fire. this makes it so when you
  // click again it at least puts things back the way they should be
  if ( dragObj.running )
    dragStop();

  // if this object isn't positioned, it's not really dragable,
  // so we change that until the drag is done
  if ( position && ! dragObj.elNode.style.position )
  {
    dragObj.elEmptyPosition = true;
    dragObj.elNode.style.position = "absolute";
    dragObj.elNode.style.top = dragObj.elNode.offsetTop;
    dragObj.elNode.style.left = dragObj.elNode.offsetLeft;
    /* don't use getAbsOffset since we position relative to our parent
    dragObj.elNode.style.top = getAbsOffset(dragObj.elNode, 'offsetTop');
    dragObj.elNode.style.left = getAbsOffset(dragObj.elNode, 'offsetLeft');
    */
  }

  // Save starting positions of cursor and element.

  // this is a bit of a hack-- everything in here depends on the
  // initial value of .style.top but bottom-positioned elements
  // don't have one set. so in that case we just pull offsetTop
  // but depending on what this object is nested in, that might
  // not be the correct value. for our purposes, we're always
  // working with a 0-aligned top element though, so it does :)
  // the other code is to deal with the fact that we're called 
  // after minimizePanel has been triggered and the height is changed
  // this also depends on the global pageHeight being set properly
  if ( ! dragObj.elNode.style.top && dragObj.elNode.style.bottom )
  {
    var myHeight = dragObj.elNode.saveHeight;
    var emptyTop = dragObj.elNode.top;
    var bottom = parseInt (dragObj.elNode.style.bottom, 10);
    var newtop;
    if ( ! myHeight ) myHeight = dragObj.elNode.offsetHeight;

    newtop = pageHeight - bottom - myHeight;

    // i'd like to set bottom to the null/undef(whatever) that top
    // was, but Windows/IE errors out on this. it seems to work that
    // just resetting top overrides bottom, though i haven't seen any
    // doc that makes that 100% clear.
    // dragObj.elNode.style.bottom = emptyTop;	// use whatever top was
    dragObj.elNode.style.top = newtop + "px";
  }

  // reset these just to be sure they aren't left over from previous drag
  dragObj.dropable = false;
  dragObj.dropObj = undefined;

  if ( dropId )
      dragObj.dropObj = myGetElementById (dropId);

  dragObj.constrain = constrain;
  dragObj.constrain_top = dragObj.elNode.getAttribute ("drag_constrain_top");
  dragObj.cursorStartX = x;
  dragObj.cursorStartY = y;

  dragObj.elStartLeft  = dragObj.elNode.offsetLeft;
  dragObj.elStartTop   = dragObj.elNode.offsetTop;
  /* don't use getAbsOffset since we position relative to our parent
  dragObj.elStartLeft  = getAbsOffset (dragObj.elNode, "offsetLeft"); //parseInt(dragObj.elNode.style.left, 10);
  dragObj.elStartTop   = getAbsOffset (dragObj.elNode, "offsetTop"); //parseInt(dragObj.elNode.style.top,  10);
  */

  dragObj.elStartZindex = parseInt(dragObj.elNode.style.zIndex, 10);
  dragObj.elStartClassName = dragObj.elNode.className;

  if (isNaN(dragObj.elStartLeft)) dragObj.elStartLeft = 0;
  if (isNaN(dragObj.elStartTop))  dragObj.elStartTop  = 0;
  if (isNaN(dragObj.elStartZindex))  dragObj.elStartZindex  = 0;

  debug ("drag start coords are x=" + dragObj.elStartLeft + " y=" + dragObj.elStartTop + " top="+dragObj.elNode.offsetTop);
  // Save current and then update element's z-index.
  dragObj.elNode.style.zIndex = dragObj.zIndex;

  // update class name to include "dragging"
  classUpdate (dragObj.elNode, "dragging", "not_dragging");

  // look for any puzzle pieces that we need to update to show
  if ( dragObj.elNode.getAttribute ("puzzle_bg") )
  {
      var bgImg = "url('images/puzzles/"
	  + dragObj.elNode.getAttribute ("puzzle_bg")
	  + "')";

      dragObj.elNode.style.backgroundImage = bgImg;
      dragObj.elNode.puzzlePieceShown = true;
  }

  // Capture mouseMove and mouseUp events on the page.
  if (browser.isIE) {
    document.attachEvent("onmousemove", dragGo);
    document.attachEvent("onmouseup",   dragStop);
    window.event.cancelBubble = true;
    window.event.returnValue = false;
  }
  if (browser.isNS) {
    document.addEventListener("mousemove", dragGo,   true);
    document.addEventListener("mouseup",   dragStop, true);
    event.preventDefault();
  }

  dragObj.running = true;
}

function dragGo (event)
{
  var x, y;
  var newX, newY;
//  var maxTop = 132;		// nav obscures top 132px
  var maxTop = 0;		// we're now in an absolutely positioned div
  var scrollBarWidth = 15;	// width of vert scrollbar
  var displaceY = 132;		// we're displaced by this much from the top

  /* this is more compatable code that the version below */
  if (event.pageX || event.pageY)
  {
    x = event.pageX;
    y = event.pageY;
  }
  else if (event.clientX || event.clientY)
  {
    x = event.clientX + document.body.scrollLeft;
    y = event.clientY + document.body.scrollTop;
  }

  // Move drag element by the same amount the cursor has moved.
  newX = (dragObj.elStartLeft + x - dragObj.cursorStartX);
  newY = (dragObj.elStartTop + y - dragObj.cursorStartY);

  // constrain to only moving within the page
  if ( dragObj.constrain )
  {
      var myWidth = dragObj.elNode.offsetWidth;
      var myHeight = dragObj.elNode.offsetHeight;
      var thisMaxTop = maxTop;

      if ( dragObj.constrain_top )
	thisMaxTop = dragObj.constrain_top;

      if ( dragObj.constrain.toUpperCase() == "FILL" )
      {
	  if ( newX > 0 ) newX = 0;
	  if ( newY > 0 ) newY = 0;
	  if ( myWidth + newX < pageWidth )
	    newX = pageWidth - myWidth;
	  if ( myHeight + newY + displaceY < pageHeight )
	    newY = pageHeight - myHeight - displaceY;
      }
      else if ( dragObj.constrain.toUpperCase() == "VISIBLE" )
      {
	var bufferWidth = (myWidth < 10 ? myWidth : 10);
	var bufferHeight = (myHeight < 10 ? myHeight : 10);
	if ( newX + myWidth < bufferWidth )
	    newX = bufferWidth - myWidth;
	else if ( newX > pageWidth - bufferWidth )
	    newX = pageWidth - bufferWidth;
	if ( newY + myHeight < bufferHeight )
	    newY = bufferHeight - myHeight;
	else if ( newY + displaceY > pageHeight - bufferHeight )
	    newY = pageHeight - bufferHeight - displaceY;
      }
      else if ( dragObj.constrain.toUpperCase() == "TOPVISIBLE" )
      {
	// this let's it be dragged outside of the frame to the right
	// so only 20 px shows.
	// var bufferWidth = (myWidth < 20 ? myWidth : 20);
	// this makes it so the entire panel must be visible on the right
	var bufferWidth = myWidth;
	var bufferHeight = (myHeight < 30 ? myHeight : 30);
	if ( newX + myWidth < bufferWidth )
	    newX = bufferWidth - myWidth;
	else if ( newX > pageWidth - bufferWidth - scrollBarWidth )
	    newX = pageWidth - bufferWidth - scrollBarWidth;
	if ( newY < thisMaxTop )
	    newY = thisMaxTop;
	else if ( newY + displaceY > pageHeight - bufferHeight )
	    newY = pageHeight - bufferHeight - displaceY;
      }
      else // default => if ( dragObj.constrain.toUpperCase() == "INSIDE" )
      {
	if ( newX < 0 )
	    newX = 0;
	else if ( newX > pageWidth - myWidth - scrollBarWidth )
	    newX = pageWidth - myWidth - scrollBarWidth;
	if ( newY < thisMaxTop )
	    newY = thisMaxTop;
	else if ( newY + displaceY > pageHeight - myHeight )
	    newY = pageHeight - myHeight - displaceY;
      }
  }

  debug(dragObj.elNode.nodeName + " " + dragObj.elNode.getAttribute ('ID')
    + " move from ("
    + dragObj.elNode.style.left + "," + dragObj.elNode.style.top
    + ") to ("
    + newX + "," + newY
    + ")"
    + " dim ("
    + dragObj.elNode.offsetWidth + "," + dragObj.elNode.offsetHeight
    + ")"
    + " page is " + pageWidth + "," + pageHeight
    );

  dragObj.elNode.style.left = newX + "px";
  dragObj.elNode.style.top  = newY + "px";

  // if there's a dropObj set, see if we're inside it
  if ( dragObj.dropObj )
  {
    if ( intersect (dragObj.elNode, dragObj.dropObj) )
    {
	if ( ! dragObj.dropable )
	{
	    classUpdate (dragObj.dropObj, "dropping");
	    dragObj.dropable = true;
	    if ( dragObj.dropObj.ondropover )
		dragObj.dropObj.ondropover(dragObj.elNode);
	}
    }
    else if ( dragObj.dropable )
    {
	classUpdate (dragObj.dropObj, undefined, "dropping"); 
	dragObj.dropable = false;
	if ( dragObj.dropObj.ondropout )
	    dragObj.dropObj.ondropout(dragObj.elNode);
    }
  }

  if (browser.isIE) {
    window.event.cancelBubble = true;
    window.event.returnValue = false;
  }
  if (browser.isNS)
    event.preventDefault();
}

function dragStop()
{
  var magThreshold = 20;
  var newLeft;
  var newTop;

  if ( ! dragObj.running )
    return;

  // if this is dropable, drop it!
  if ( dragObj.dropable )
  {
    classUpdate (dragObj.dropObj, undefined, "dropping"); 
    if ( dragObj.dropObj.ondropout ) dragObj.dropObj.ondropout(dragObj.elNode);
    if ( dragObj.dropObj.ondrop ) dragObj.dropObj.ondrop (dragObj.elNode);
  }

  // put the object back where it was
  if ( dragObj.elEmptyPosition )
  {
    dragObj.elNode.style.position = "";
    dragObj.elNode.style.top = "";
    dragObj.elNode.style.left = "";
  }

  dragObj.elNode.style.zIndex = dragObj.elStartZindex;
  dragObj.elNode.className = dragObj.elStartClassName;
  // this is if you want to force "not_dragging" to be added 
  // regardless of whether it was there or not
  //
  // classUpdate (dragObj.elNode, "not_dragging", "dragging");

  var ds = "DRAGSTOP " + dragObj.elNode.getAttribute('ID')
      + " @ " + dragObj.elNode.offsetLeft + "," + dragObj.elNode.offsetTop
      + " siblings=" ;

  for (var node = dragObj.elNode.parentNode.firstChild; node; node = node.nextSibling )
  {
    // skip non-DIV objects or this object
    if ( node.nodeName != "DIV" || node == dragObj.elNode )
	continue;

    // also skip anything that doesn't have a dancefloor_number tag since 
    // that defines objects we want to be magenetic to
    if ( ! node.getAttribute('dancefloor_number') )
	continue;

    // also skip anything that's not visible
    if ( node.style.visibility && node.style.visibility == "hidden" )
	continue;

    // see if our left is close to their right
    var dlr = dragObj.elNode.offsetLeft - (node.offsetLeft + node.offsetWidth);

    // see if our top is close to their top
    var dtt = dragObj.elNode.offsetTop - node.offsetTop;

    // see if our top is close to their bottom
    var dtb = dragObj.elNode.offsetTop - (node.offsetTop + node.offsetHeight);

    // see if our left is close to their left
    var dll = dragObj.elNode.offsetLeft - node.offsetLeft;

    // see if our bottom is close to their top
    var dbt = (dragObj.elNode.offsetTop + dragObj.elNode.offsetHeight) - node.offsetTop;

    // see if our bottom is close to their bottom
    var dbb = (dragObj.elNode.offsetTop + dragObj.elNode.offsetHeight) - (node.offsetTop + node.offsetHeight);

    // see if our right is close to their left
    var drl = (dragObj.elNode.offsetLeft + dragObj.elNode.offsetWidth) - node.offsetLeft;

    // see if our right is close to their right
    var drr = (dragObj.elNode.offsetLeft + dragObj.elNode.offsetWidth) - (node.offsetLeft + node.offsetWidth);

    // go through and do some magnetic resets in order of priority 
    // we go least to greatest using the threshold and don't bother
    // differentiating between siblings-- anything close is fine for
    // our purposes

    // init to something > magThreshold
    var verDiff = magThreshold + 100;
    var horDiff = magThreshold + 100;
    var verSpacer = 0;
    var horSpacer = 0;

    if ( Math.abs(drr) <= magThreshold && Math.abs(drr) < Math.abs(horDiff) )
	horDiff = drr;

    if ( Math.abs(drl) <= magThreshold && Math.abs(drl) < Math.abs(horDiff) )
    {
	horDiff = drl;
	horSpacer = -2;
    }

    if ( Math.abs(dll) <= magThreshold && Math.abs(dll) < Math.abs(horDiff) )
	horDiff = dll;

    if ( Math.abs(dlr) <= magThreshold && Math.abs(dlr) < Math.abs(horDiff) )
    {
	horDiff = dlr;
	horSpacer = 2;
    }

    if ( Math.abs(dbt) <= magThreshold && Math.abs(dbt) < Math.abs(verDiff) )
    {
	verDiff = dbt;
	verSpacer = -2;
    }

    if ( Math.abs(dbb) <= magThreshold && Math.abs(dbb) < Math.abs(verDiff) )
	verDiff = dbb;

    if ( Math.abs(dtb) <= magThreshold && Math.abs(dtb) < Math.abs(verDiff) )
    {
	verDiff = dtb;
	verSpacer = 2;
    }

    if ( Math.abs(dtt) <= magThreshold && Math.abs(dtt) < Math.abs(verDiff) )
	verDiff = dtt;

    ds += node.getAttribute('ID') 
       + "(" + node.offsetWidth + "x" + node.offsetHeight + ")"
       + " @ " + node.offsetLeft + "," + node.offsetTop
       + " horDiff="+horDiff + " horSpacer=" + horSpacer
       + " verDiff="+verDiff + " verSpacer=" + verSpacer;

    // now set whichever was found
    if ( Math.abs(horDiff) <= magThreshold ) 
	newLeft = dragObj.elNode.offsetLeft - horDiff + horSpacer;

    if ( Math.abs(verDiff) <= magThreshold ) 
	newTop = dragObj.elNode.offsetTop - verDiff + verSpacer;

    // make sure we don't snap off of the screen 
    if ( newTop < 0 ) newTop = 0;

//       + " dlr=" + dlr + " dtt=" + dtt + " dtb=" + dtb
//       + " dll=" + dll + " dbt=" + dbt + " dbb=" + dbb
//       + " drl=" + drl + " drr=" + drr

    ds += "  ";
  }

  if ( newLeft ) dragObj.elNode.style.left = newLeft + "px";
  if ( newTop ) dragObj.elNode.style.top = newTop + "px";

  ds += " newleft=" + newLeft + " newtop=" + newTop;
//  debug (ds);

  // Stop capturing mousemove and mouseup events.
  if (browser.isIE) {
    document.detachEvent("onmousemove", dragGo);
    document.detachEvent("onmouseup",   dragStop);
  }
  if (browser.isNS) {
    document.removeEventListener("mousemove", dragGo,   true);
    document.removeEventListener("mouseup",   dragStop, true);
  }

  dragObj.running = false;

  // if this panel is a puzzle piece, check to see if we're
  // close enough to the other pieces to activate!
  if ( dragObj.elNode.getAttribute("puzzle_num") )
    puzzleCheck (dragObj.elNode);
}

/*
 * classUpdate() - this updates a className by adding a specific
 *   className, and optionally removing an existing one (if present)
 */
function classUpdate (obj, addClass, removeClass)
{
    var curClass = obj.className;
    var newClass = "";

    if ( curClass && curClass.indexOf (" ") > -1 )
    {
	var classAry = curClass.split (" ");
	for (var i=0; i < classAry.length; i++)
	{
	    // don't include dups
	    if ( addClass && classAry[i] == addClass )
		continue;

	    if ( classAry[i] != removeClass )
	    {
		if ( newClass != "" ) newClass += " ";
		newClass += classAry[i];
	    }
	}
    }
    else
	newClass += curClass;

    if ( addClass )
    {
	if ( newClass != "" ) newClass += " ";
	newClass += addClass;
    }

    obj.className = newClass;
//    debug ("NEW CLASS \"" + obj.className + "\"");
}

function getAbsOffset(obj, which)
{
    var val = 0;

    if ( ! obj ) return val;

    if ( obj[which] ) val += obj[which];
    while ( obj = obj.offsetParent )
	if ( obj[which] )
	    val += obj[which];

    return val;
}

function intersect (obj1, obj2)
{
    var obj1Top, obj1Left, obj1Bottom, obj1Right;
    var obj2Top, obj2Left, obj2Bottom, obj2Right;

    obj1Top = getAbsOffset (obj1, "offsetTop");
    obj1Left = getAbsOffset (obj1, "offsetLeft");
    obj1Bottom = obj1Top + obj1.offsetHeight;
    obj1Right = obj1Left + obj1.offsetWidth;
    obj2Top = getAbsOffset (obj2, "offsetTop");
    obj2Left = getAbsOffset (obj2, "offsetLeft");
    obj2Bottom = obj2Top + obj2.offsetHeight;
    obj2Right = obj2Left + obj2.offsetWidth;

    /*
    debug (obj1.getAttribute('ID') + " @ " +
	obj1Left + "," + obj1Top + " - " + obj1Right + "," + obj1Bottom
	+ " " + obj2.getAttribute('ID') + " @ " +
	obj2Left + "," + obj2Top + " - " + obj2Right + "," + obj2Bottom);
    */

    // Check if the boxes intersect
    // (From: Cormen, Leiserson, and Rivests' "Algorithms", page 889)
    return ( (obj1Right > obj2Left) && (obj2Right > obj1Left)
	    && (obj1Bottom > obj2Top) && (obj2Bottom > obj1Top) );
}

/*
 * puzzleCheck() - this looks to see if all four pieces of a puzzle
 *   are placed next to each other in the right orientation
 */
function puzzleCheck (obj)
{
    var placed = new Array (4);
    var threshold = 5;
    var i;

    for (var node = obj.parentNode.firstChild; node; node = node.nextSibling )
    {
	var num;

	// skip non-DIV objects or this object
	if ( node.nodeName != "DIV" || ! (num=node.getAttribute('puzzle_num')) )
	    continue;

	// not revealed yet? skip it and just abort now!
	if ( ! node.puzzlePieceShown )
	    return false;

	placed[num] = new Object();
	placed[num].x1 = getAbsOffset (node, "offsetLeft");
	placed[num].x2 = placed[num].x1 + node.offsetWidth;
	placed[num].y1 = getAbsOffset (node, "offsetTop");
	placed[num].y2 = placed[num].y1 + node.offsetHeight;
	placed[num].name = obj.getAttribute('ID');
    }

    for ( i = 1; i <= 4; i++ )
    {
	// this one not placed yet, return false now
	if ( ! placed[i] )
	    return false;
    }

    // first check 1 against 2 
    if ( Math.abs (placed[2].x1 - placed[1].x2) > threshold
	|| Math.abs (placed[2].y1 - placed[1].y1) > threshold)
    {
	return false;
    }

    // check 1 against 3
    if ( Math.abs (placed[1].x1 - placed[3].x1) > threshold
	|| Math.abs (placed[3].y1 - placed[1].y2) > threshold)
    {
	return false;
    }

    // so far, so good, panels 1, 2 and 3 are solid so now check 2 against 4
    if ( Math.abs (placed[2].x1 - placed[4].x1) > threshold
	|| Math.abs (placed[4].y1 - placed[2].y2) > threshold)
    {
	return false;
    }

//    alert ("YOU ARE A PUZZLE WIZARD!");
    alert ("Keep people together forever and ever");
    return true;
}

