/* Returns true if container is an ancestor or self node of containee */
function containsDOM (container, containee) {
  var isParent = false;
  do {
    if ((isParent = container == containee))
      break;
    containee = containee.parentNode;
  }
  while (containee != null);
  return isParent;
}

/* Given an element and an onmouseenter event on that element, returns
 * true if the mouse entered the element from a non-child element (i.e.
 * it didn't just get bubbled in from a child node). */
function checkMouseEnter (element, evt) {
  if (!evt)
    evt = window.event;
  if (element.contains && evt.fromElement)
    return !element.contains(evt.fromElement);
  else if (evt.relatedTarget)
    return !containsDOM(element, evt.relatedTarget);
  // this final case is hit when the mouse enters from another window */
  return true;
}

/* Given an element and an onmouseout event on that element, returns
 * true if the mouse left the element to a non-child element (i.e.
 * it didn't just get bubbled in from a child node). */
function checkMouseLeave (element, evt) {
  if (!evt)
    evt = window.event;
  if (element.contains && evt.toElement)
    return !element.contains(evt.toElement);
  else if (evt.relatedTarget)
    return !containsDOM(element, evt.relatedTarget);
  // this final case is hit when the mouse leaves to another window */
  return true;
}

/* Given an element and an onmouseout event on that element, returns true
 * if the mouse left to element el2 (or a descendent of it). */
function checkLeaveTo (element, evt, el2) {
  if (!evt)
    evt = window.event;
  if (!el2)
    return false;
  if (element.contains && evt.toElement) {
    return evt.toElement == el2 || el2.contains(evt.toElement);
  }
  else if (evt.relatedTarget) {
    return evt.relatedTarget == el2 || containsDOM(el2, evt.relatedTarget);
  }
  return false;
}

function createCookie(name,value,days)
{
  if (days) {
    var date = new Date();
    date.setTime(date.getTime()+(days*24*60*60*1000));
    var expires = "; expires="+date.toGMTString();
  }
  else
    var expires = "";
  document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name)
{
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for (var i=0;i < ca.length;i++) {
    var c = ca[i];
    while (c.charAt(0)==' ')
      c = c.substring(1,c.length);
    if (c.indexOf(nameEQ) == 0)
      return c.substring(nameEQ.length,c.length);
  }
  return null;
}

function eraseCookie(name)
{
  createCookie(name,"",-1);
}

/* Remove an element from the DOM */
function removeElement(element) {
  element.parentNode.removeChild(element);
}

function removeChildren(el) {
  while (el.childNodes.length > 0)
    el.removeChild(el.childNodes[0]);
}

function changeText(el, text) {
  removeChildren(el);
  el.appendChild(document.createTextNode(text));
}

/* Gets a DOM element by ID, in a multi-browser-compatible way */
function getElement(name) {
  if (document.getElementById)
    return document.getElementById(name);
  else if (document.all)
    return document.all[name];
  return null;
}

/* Gets the top of an element relative to the top of the rendered page */
function getElementTop(el) {
  var t = el.offsetTop;
  while ((el=el.offsetParent) != null) { t += el.offsetTop; }
  return t;
}

/* Gets the left of an element relative to the left of the rendered page */
function getElementLeft(el) {
  var l = el.offsetLeft;
  while ((el=el.offsetParent) != null) { l += el.offsetLeft; }
  return l;
}

var qsParm = null;
/* Parses the given query string and stores its contents in the global
 * associative array qsParm */
function processQueryString(query) {
  var parms = query.split('&amp;');
  qsParm = new Array();
  for (var i=0; i < parms.length; i++) {
    var pos = parms[i].indexOf('=');
    if (pos > 0) {
      var key = parms[i].substring(0,pos);
      var val = parms[i].substring(pos+1);
      qsParm[key] = val;
    }
  }
}

/* TODO: change raiseme to overlayElement */

var overlay = null;
/* Creates a copy of an element and overlays this duplicate on top of the previous
 * copy with a highlighted style.  This copy is removed when the mouse moves away. */
function overlayElement(el,event) {
  if (!checkMouseEnter(el,event))
    return;
  overlay = el.cloneNode(true);
  var t = getElementTop (el);
  var l = getElementLeft (el);
  overlay.setAttribute("onmouseover", null);
  overlay.style.top = t;
  overlay.style.left = l;
  overlay.style.position = "absolute";
  overlay.style.height = "auto";
  overlay.style.backgroundColor = "#ffffaa";
  overlay.style.borderColor = "#cccc66";
  overlay.onmouseout = function(event) {
    if (checkMouseLeave(this,event))
      removeElement(overlay);
  };
  el.onmouseout = function(event) {
    if (checkMouseLeave(this,event) && !checkLeaveTo(this,event,overlay))
      removeElement(overlay);
  };
  el.parentNode.insertBefore (overlay,el);
}

var images = null;
var curpos = 0;
var curbox = null;
var nextbox = null;
var prevbox = null;
var historykeeper = null;

function initSlideshow(imlist) {
  images = imlist;

  curbox = getElement('curbox');
  curbox.onclick = nextImg;

  nextbox = curbox.cloneNode (true);
  nextbox.id = 'nextbox';
  nextbox.style.visibility = 'hidden';
  nextbox.style.marginTop = -600 + 'px';
  nextbox.style.zIndex = 1;
  curbox.parentNode.insertBefore(nextbox, curbox.nextSibling);

  prevbox = curbox.cloneNode (true);
  prevbox.id = 'prevbox';
  prevbox.style.visibility = 'hidden';
  prevbox.style.marginTop = -600 + 'px';
  prevbox.style.zIndex = 1;
  curbox.parentNode.insertBefore(prevbox, curbox.nextSibling);

  processNewImageHash(window.location.hash.substring(1));

  historykeeper = new HistoryKeeper();
  historykeeper.AddListener(hashWatcher);

  var box = getElement('dodissolve');
  box.onclick = function() {
    if (this.checked)
      createCookie ('dodissolve', 'yes', 365);
    else
      createCookie ('dodissolve', 'no', 365);
  };
  if (readCookie ('dodissolve') == 'no')
    box.checked = false;
  else
    box.checked = true;

  var bg = getElement('bgtype');
  bg.onchange = function() {
    createCookie ('bgtype', this.selectedIndex, 365);
    setSlideshowBG (this.selectedIndex);
  };
  bg.selectedIndex = readCookie ('bgtype');
  setSlideshowBG (bg.selectedIndex);
}

function setSlideshowBG (val) {
  var l = getElement('imglink' + (curpos+1));
  if (val == 1) {
    getElement('thebody').className = 'blackbody';
    getElement('theimagebox').className = 'imageboxblack';
    getElement('curbox').className = 'viewimgboxblack';
    getElement('prevbox').className = 'viewimgboxblack';
    getElement('nextbox').className = 'viewimgboxblack';
    l.className = 'imglinkcurrentblack';
  }
  else {
    getElement('thebody').className = '';
    getElement('theimagebox').className = 'imagebox';
    getElement('curbox').className = 'viewimgbox';
    getElement('prevbox').className = 'viewimgbox';
    getElement('nextbox').className = 'viewimgbox';
    l.className = 'imglinkcurrent';
  }
}

function Photo (pos, url, width, height, name) {
  this.pos = pos;
  this.url = url;
  this.width = width;
  this.height = height;
  this.name = name;
}

var curhash = null;
function processNewImageHash(hash) {
  curhash = hash;
  processQueryString(curhash);
  var newpos = 0;
  if (qsParm['image'])
    newpos = parseInt(qsParm['image']) - 1;
  if (newpos < 0 || newpos >= images.length)
    gotoImg(0);
  else
    gotoImg(newpos);
}

function hashWatcher(hash) {
  if (hash == curhash)
    return;
  processNewImageHash(hash);
}

function updateHash() {
  curhash = 'image=' + (curpos+1);
  historykeeper.AddBookmark(curhash);
}

function loadCaption(pos) {
  var cap = getElement('capt' + (pos+1));
  cap = cap.cloneNode(true);
  cap.style.visibility = 'visible';
  cap.style.position = 'static';
  cap.style.width = 'auto';
  var capto = getElement('curcaption');
  if (capto.childNodes[0])
    capto.removeChild (capto.childNodes[0]);
  capto.appendChild(cap);

  changeText(getElement('slidename'), images[pos].name);
}

function highlightNavlink(pos) {
  var l = getElement('imglink' + (pos+1));
  if (getElement('bgtype').selectedIndex == 1)
    l.className = 'imglinkcurrentblack';
  else
    l.className = 'imglinkcurrent';
  l.onclick = null;
}

function unhighlightNavlink(pos) {
  var l = getElement('imglink' + (pos+1));
  l.className = 'imglink';
  l.onclick = function() { clickedGoto(this); };
}

function clickedGoto(me) {
  gotoImg(parseInt(me.id.substring(7))-1);
  updateHash();
}

function updateImgLink() {
  var nextbut = getElement('nextbut');
  var prevbut = getElement('prevbut');
  if (curpos < images.length-1) {
    curbox.onclick  = nextImg;
    nextbox.onclick = nextImg;
    curbox.style.cursor  = "pointer";
    nextbox.style.cursor = "pointer";

    nextbut.className = 'navbut';
    nextbut.onclick = nextImg;
  }
  else {
    curbox.onclick  = null;
    nextbox.onclick = null;
    curbox.style.cursor  = "default";
    nextbox.style.cursor = "default";

    nextbut.className = 'navbut disabledbut';
    nextbut.onclick = null;
  }

  if (curpos > 0) {
    prevbut.className = 'navbut';
    prevbut.onclick = prevImg;
  }
  else {
    prevbut.className = 'navbut disabledbut';
    prevbut.onclick = null;
  }
}

function gotoImg(pos) {
  unhighlightNavlink(curpos);
  curpos = pos;
  loadCaption(curpos);
  highlightNavlink(curpos);
  loadImg(curbox, curpos);
  if (curpos > 0)
    loadImg(prevbox, curpos-1);
  if (curpos < images.length-1)
    loadImg(nextbox, curpos+1);
  updateImgLink();
}

function loadImg(box, idx) {
  var img = box.childNodes[0];
  var im = images[idx];
  img.src = im.url;
  return img;
}

function setOpacity(obj, opacity) {
  // IE/Win
  if (typeof obj.style.filter != 'undefined')
    obj.style.filter = "alpha(opacity:"+parseInt(opacity*100)+")";
  // Safari older than 1.2, Konqueror
  if (typeof obj.style.KHTMLOpacity != 'undefined')
    obj.style.KHTMLOpacity = opacity;
  // Older Mozilla and Firefox
  if (typeof obj.style.MozOpacity != 'undefined')
    obj.style.MozOpacity = opacity;
  // Safari 1.2, newer Firefox and Mozilla, CSS3
  if (typeof obj.style.opacity != 'undefined')
    obj.style.opacity = opacity;
}

var interval = null;
var fadingbox = null;
var boxopacity = 0.0;

function swapComplete() {
  fadingbox.style.visibility = 'hidden';
  setOpacity(fadingbox, 1.0);
  if (curpos > 0)
    loadImg(prevbox, curpos-1);
  if (curpos < images.length - 1)
    loadImg(nextbox, curpos+1);
  curbox.childNodes[0].onload = null;
}

function stopFade()
{
  clearInterval (interval);
  interval = null;
}

function incOpacity() {
  if (boxopacity >= 1.0) {
    stopFade();
    var img = loadImg(curbox, curpos);
    /* In IE, this onload callback seems to cause the next transition to ignore
     * opacity changes, so we have to special case it.  For other browsers, it's
     * safest to use onload so that there is no visual glitch. */
    if (typeof fadingbox.style.opacity != 'undefined')
      img.onload = swapComplete;
    else
      swapComplete();
    updateHash();
    return;
  }
  boxopacity += 0.1;
  if (boxopacity >= 0.99)
    setOpacity (fadingbox, 0.99);
  else
    setOpacity (fadingbox, boxopacity);
}

function nextImg() {
  if (interval) {
    stopFade();
    loadImg(curbox, curpos);
    swapComplete();
    updateHash();
  }
  unhighlightNavlink(curpos);
  curpos++;
  loadCaption(curpos);
  highlightNavlink(curpos);

  fadingbox = nextbox;

  if (getElement('dodissolve').checked) {
    boxopacity = 0.0;
  }
  else {
    boxopacity = 1.0;
  }

  nextbox.style.visibility = 'visible';
  // This setOpacity has to occur after the visibility change or else IE ignores
  // further opacity changes (very weird).
  setOpacity (nextbox, boxopacity);
  interval = setInterval(incOpacity, 30);
  updateImgLink();
}

function prevImg() {
  if (interval) {
    stopFade();
    loadImg(curbox, curpos);
    swapComplete();
    updateHash();
  }
  unhighlightNavlink(curpos);
  curpos -= 1;
  loadCaption(curpos);
  highlightNavlink(curpos);

  fadingbox = prevbox;

  if (getElement('dodissolve').checked) {
    boxopacity = 0.0;
  }
  else {
    boxopacity = 1.0;
  }

  prevbox.style.visibility = 'visible';
  // This setOpacity has to occur after the visibility change or else IE ignores
  // further opacity changes (very weird).
  setOpacity (prevbox, boxopacity);
  interval = setInterval(incOpacity, 30);
  updateImgLink();
}

/*
	unFocusHistoryKeeper, version 0.8.3 (alpha) (2005/07/29)
	modified by David Moore to make the object fit my style
	Copyright: 2005, Kevin Newman (http://www.unfocus.com/Projects/HistoryKeeper/)
	License: http://creativecommons.org/licenses/LGPL/2.1/
*/
function HistoryKeeper() {
	// private variables
	var _pollInterval = 200;
	var _listeners = []; // there has been much debate over whether this is appropriate as an array on comp.lang.javascript :-)
	var _currentHash = _GetHash();
	
	// do constructor type stuff here
	// :TODO: add alternative hash update detection methods if one is available (anybody?)
	// :NOTE: this method doesn't work to detect changes in IE 5.5 or 6.0 when the back button is 
	// pressed (location and location.hash are not updated - even when an anchor is added like in IE 5.0)
	if (setInterval)
		var _intervalID = setInterval(_CheckHash, _pollInterval);

	// private methods
	function _GetHash() {
		return location.hash.substring(1);
	};
	function _SetHash($newHash) {
		window.location.hash = $newHash;
	};
	function _CheckHash() {
		var $newHash = _GetHash();
		if (_currentHash != $newHash) {
			_currentHash = $newHash;
			_NotifyListeners($newHash);
		}
	};
	function _NotifyListeners($newHash) {
		for (var i = _listeners.length; -1 < --i;)
			_listeners[i]($newHash);
	};
	
	// privileged methods
	this.AddListener = function($obj) {
		// consider throwing exceptions here (can't have NS4 compat then)
		if (!$obj) return false; // add an exception here
		// check if this is already in here
		for (var i = _listeners.length; -1 < --i;)
			if (_listeners[i] == $obj)
				return false; // add an exception here?
		_listeners.push($obj);
		return true;
	};
	this.RemoveListener = function($obj) {
		for (var i = _listeners.length; -1 < --i;) {
			if (_listeners[i] == $obj) {
				_listeners.splice(i,1);
				break;
			}
		}
	};
	
	this.AddBookmark = function($newHash) { // adds history and bookmark hash
		_currentHash = $newHash;
		_SetHash($newHash);
		_NotifyListeners($newHash);
	};
	
	/***************************
	* Browser specific patches *
	***************************/
	// do browser sniffing, fixes bugs that can't be detected through feature sniffing
	if (/MSIE/.test(navigator.userAgent) && !/Opera/.test(navigator.userAgent)) {
		// overwrite the old functions (gotta love javascript :-) )
		// IE 5.5 and 6.0 (I'm going to assume IE 7.x too - will test August 3) need the iframe method, since adding a hash makes no history
		if (navigator.appVersion.match(/MSIE (\d\.\d+)/)[1] >= 5.5) {
			// setup history IFrame
			var $historyFrame = false;
			this.AddBookmark = function($newHash) { // adds history and bookmark hash
				_currentHash = $newHash;
				_CreateHistoryHTML($newHash, true);
			};
			this._LoadIFrame = function($newHash) {
				// the first time this is called, it shouldn't notify, so it instead replaces itself with a version that will notify
				this._LoadIFrame = function($newHash) {
					_currentHash = $newHash;
					_SetHash($newHash);
					_NotifyListeners($newHash);
				};
			};
			function _CreateHistoryHTML($newHash) {
				$historyFrame.document.open('text/html');
				$historyFrame.document.write('<html><head></head><body onload="parent.historykeeper._LoadIFrame(\''+$newHash+'\');"></body></html>');
				$historyFrame.document.close();
			};
			// set up the History Frame, for AddHistory method
			function _SetUpHistoryFrame() {
				if (!window.frames['unFocusHistoryFrame']) {
					$historyFrame = document.createElement("iframe");
					$historyFrame.name = 'unFocusHistoryFrame';
					$historyFrame.id = 'unFocusHistoryFrame';
					$historyFrame.style.position = 'absolute';
					$historyFrame.style.top = '-900px';
					$historyFrame.style.left = '-900px';
					//$historyFrame.runtimeStyle.display = 'none';
					document.body.appendChild($historyFrame);
				}
				// reset the reference to the frame using the frames array - for some reason 
				// the other reference can't be used with document.open
				$historyFrame = window.frames[window.frames.length-1];
				// create root frame for the furthest back state
				_CreateHistoryHTML(_currentHash);
			};
			window.attachEvent('onload', _SetUpHistoryFrame);
		} else { // IE 5.0 needs an anchor before it will take a new hash value
			this.AddBookmark = function($newHash) { // adds history and bookmark hash
				_currentHash = $newHash;
				_CreateAnchor($newHash);
				_SetHash($newHash);
				_NotifyListeners($newHash);
			};
			function _CreateAnchor($newHash) {
				var $anchor = document.createElement('a');
				$anchor.setAttribute('name',$newHash);
				if (/MSIE/.test(navigator.userAgent)) $anchor = document.createElement('<a name="'+$newHash+'"></a>');
				$anchor.style.position = 'absolute';
				$anchor.style.left = document.body.scrollLeft;
				$anchor.style.top = document.body.scrollTop;
				//$anchor.innerHTML = $newHash;
				document.body.appendChild($anchor);
			};
		}
	}
};
