/*Scrolling speed: Will move (remaining distance / sfStepDivisor) for every step*/
var sfStepDivisor = 1.3;
/*Step refresh interval in ms. Slower systems may have trouble handling short refresh intervals.*/
var sfStepTimeout = 50;
/*Power used while dragging*/
var sfDragPower = 1.0;
/*Period after user interaction for which we consider him using it (in ms).*/
var sfActivityTolerance = 8000;
/*Maximum period between two scrolling signals (mouse wheel) to be considered belonging together*/
var sfScrollTolerance = 1000;
/*Power used while scrolling with the mouse wheel*/
var sfScrollPower = 1.8;
var sfObject;
/*
 @param config 	imgWidthNormal		Width of center slide thumbnails
 imgWidthTilted		Width of left and right slide thumbnails
 imgHeight			Height of slide thumbnails
 slideDistance		1 .. 100
 onCenterClick		function(slidenumber) to handle user click on the currently centered slide
 containerElement	DOM Parent Element
 handleSlideMove		function(percentage) receiving movement updates, for instance to synchronize an external slider control. Set to null if unused.
 pathLeft			Path to left thumbnails, e.g. "photos/left"
 pathCenter			Path to center thumbnails, e.g. "photos/center"
 pathRight			Path to right thumbnails, e.g. "photos/right"
 transparentImg		Path to transparent 1x1 pixel GIF; used as placeholder, e.g. "images/transparent.gif"
 images				Array with thumbnail source files in the pathLeft/Center/Right directories, e.g. ["1.jpg", "2.jpg", ..]
 cursorOpenHand		Path to open hand cursor, e.g. "images/openhand.cur"
 cursorClosedHand	Path to closed hand cursor, e.g. "images/closedhand.cur"
 */
function Slideflow(config) {

	sfObject = this;
	
	for (s_key in config)
		this[s_key] = config[s_key];

	this.glideToSlide = sfGlideToSlide;
	this.glideToPerc = sfGlideToPerc;
	this.moveToPerc = sfMoveToPerc;
	this.glideTo = sfGlideTo;
	this.moveTo = sfMoveTo;
	this.refresh = sfRefresh;
	this.disableMoveUpdate = sfDisableMoveUpdate;
	this.isActive = sfIsActive;
	this.scroll = sfScroll;
	this.clearActivityTimeout = sfClearActivityTimeout;
	this.prepareImgs = sfPrepareImgs;
	
	this.sfCurrent;
	this.target = 0;
	this.timer = 0;
	this.centerItem = 1;
	this.moveUpdate = true;
	this.didDrag = false;

	this.isMouseDown = false;
	this.lastMouseUp = undefined;
	this.marginwidth = this.imgWidthTilted;
		
	this.prepareImgs();
	
	this.imgCenter = new Array();
	
	for (var i=0; i < this.images.length; i++) {
		var imgC = new Image; imgC.src = this.images[i][1]; this.imgCenter.push(imgC);
	}

	/*
	 Show the first sldide - use the following code to start with the last slide:
	 this.current = (this.images.length-1) * (-this.slideDistance);
	 */
	this.current = 0;
	
	this.refresh();
	
	sfUpdateCursors();
	
	this.savedMouseMove = document.onmousemove;
	document.onmousemove = sfMouseMove;

	this.savedMouseUp = document.onmouseup;
	document.onmouseup = sfMouseUp;

	window.onresize = function() { sfObject.refresh(); }
	
	return this;
}

/* Glides to a specific slide */
function sfGlideToSlide(slide) {
	this.glideTo((slide-1) * (-this.slideDistance));
}

/* Glides to a percentage */
function sfGlideToPerc(perc) {
	this.glideTo(perc * (this.images.length-1) * (-this.slideDistance) / 100);
}

/* Moves to a percentage */
function sfMoveToPerc(pos) {
	this.moveTo(perc * (this.images.length-1) * (-this.slideDistance) / 100);
}

/* Handles mouse scrolling, with incrasing power when multiple events are received within sfScrollTolerance
 * @param	delta	Number of scroll units to handle, like -1 or +1
 */
function sfScroll(delta) {
	if (this.scrollStartTime && (new Date()).getTime() < this.scrollStartTime.getTime() + sfScrollTolerance) {
		this.scrollDelta += delta;
	}
	else {
		this.scrollStartTime = new Date();
		this.scrollStartPosition = this.current;
		this.scrollDelta = delta;
	}
	
	var sign = (this.scrollDelta < 0 ? -1 : 1);
	
	var relOffset = sign * (Math.pow(Math.abs(this.scrollDelta), sfScrollPower)) * this.slideDistance;
	var target = this.scrollStartPosition + relOffset;
	this.glideTo(target);
}

/**
 * (Re-)calculate size and prepare placeholders
 */
function sfRefresh() {
	this.size = this.containerElement.clientWidth - this.marginwidth;
	this.moveTo(this.current);
}

/**
 * Updates cursors
 * Cursors are set directly - not using classes for IE6 compatibility
 */
function sfUpdateCursors() {
  try {
    if (sfObject.isMouseDown) {
      sfObject.containerElement.getElementsBySelector('img').each(function (item) {
        item.setStyle({cursor: ''});
      });
      //sfObject.containerElement.setStyle({cursor: 'url(' + sfObject.cursorClosedHand + '), move !important'});
    } else {
      sfObject.containerElement.getElementsBySelector('img').each(function (item) {
        if (item.number == sfObject.centerItem) {
          item.setStyle({cursor: ''});
          item.setStyle({cursor: 'pointer'});
        } else {
          //item.setStyle({cursor: 'url(' + sfObject.cursorOpenHand + '), default !important'});
        }
      });

      sfObject.containerElement.setStyle({cursor: ''});
    }
  } catch (e) {
  }
}

/**
 * Determines whether the user is currently using the slideflow based on sfActivityTolerance
 */
function sfIsActive() {
	return this.isMouseDown || 
		(this.lastMouseUp && (new Date()).getTime() < this.lastMouseUp.getTime() + sfActivityTolerance);
}

/**
 * Prevents handleSlideMove updates until the glideTo() destination is reached
 */
function sfDisableMoveUpdate() {
	this.moveUpdate = false;
}

/** internal use **/

/**
 * Glides to a position on the path
 *
 * @param	pos
 */
function sfGlideTo(pos) {
	pos = Math.min(0, Math.max(pos, (this.images.length-5) * (-this.slideDistance)));

	this.target = pos;
	if (this.timer == 0) {
		sfStep();
	}
}

/**
 * Moves to a position on the path
 */
function sfMoveTo(pos) {
	var headedLeft = pos < this.current;
	this.current = pos;

	var height = this.containerElement.clientHeight;

	var images = this.containerElement.getElementsByTagName("img");

	var zIndex = images.length;
	
	for (index = 0; index < images.length; index++) {
		var image = images.item(index);
		
		var xs = (pos * 11);

		var n = image.number;
		var t = (headedLeft /* this.target < this.current  */ ? 0.35 : 0.65);
		var width;

		image.src = this.imgCenter[n-1].src;
		if (pos < -this.slideDistance * t) {
			width = this.imgWidthTilted;
			zIndex++;
		} else if (pos > this.slideDistance * (1-t)) {
			width = this.imgWidthTilted;
			zIndex--;
		}
		else {
			width = this.imgWidthNormal;
			this.centerItem = n;
			zIndex++;
		}

		image.setStyle({
		    left: (this.marginwidth/2 + xs - width/2) + 'px',
				top: ((this.containerElement.clientHeight/2) - (this.imgHeight / 2)) + 'px',
				width: width + 'px',
				height: (this.imgHeight) + 'px',
				zIndex: zIndex,
				id: ""
				});

		//image.title = "pos/z=" + pos/z + "; i="+index+"; z=" + parseInt(z) + "; pos="+ parseInt(pos) + "; xs=" + parseInt(xs) + "; zIndex=" + zIndex;
		pos += this.slideDistance;
	}
	
	if (this.handleSlideMove && this.moveUpdate) {
		//this.handleSlideMove(this.current * 100 / ((this.images.length-1) * (-this.slideDistance)));
		this.handleSlideMove(pos);
	}
}


/**
 * Prepares image placeholders
 */
function sfPrepareImgs() {
	for (var i=1; i<=this.images.length; i++) {
		var newImage = document.createElement("img");
		Element.extend(newImage);
		newImage.number = i;
		//newImage.src = this.transparentImg;
		newImage.onclick = function() { return sfClick(this); }
		newImage.onmousedown = function() { return sfMouseDown(this); }
		this.containerElement.appendChild(newImage);
	}
}

/**
 * Progress one step of a glide operation
 */
function sfStep() {
	if (sfObject.target < sfObject.current - 1 || sfObject.target > sfObject.current + 1) {
		sfObject.moveTo(sfObject.current + (sfObject.target - sfObject.current) / sfStepDivisor);
		window.setTimeout(sfStep, sfStepTimeout);
		sfObject.timer = 1;
	} else {
		sfObject.moveTo(sfObject.target);
		sfObject.timer = 0;
		sfObject.moveUpdate = true;
		sfUpdateCursors();
	}
}

function sfClearActivityTimeout() {
	this.lastMouseUp = undefined;
}

/**
 * Handles mouse down on slide
 */
function sfMouseDown (item) {
	sfObject.didDrag = false; /* reset */

	/* Center item: Handled by onClick handler */
	if (item.number == sfObject.centerItem)
		return true;

	/* All others: Start drag */
	sfObject.isMouseDown = true;
	sfUpdateCursors();

	sfObject.dragStartX = window.n_mouseX;
	sfObject.dragStartPos = sfObject.current;

	return false;
}

/**
 * Handles all mouse up events; forwards to previous handler
 */
function sfMouseUp (e_event, b_watching) {
	if (sfObject.isMouseDown) {
		sfObject.lastMouseUp = new Date();
		sfObject.isMouseDown = false;
		sfUpdateCursors();
	}

	if (sfObject.savedMouseUp)
		return sfObject.savedMouseUp(e_event);
	else
		return false;
}

/**
 * Handles all mouse movement; forwards to previous handler
 */
function sfMouseMove (e_event) {
	if (!e_event && window.event) e_event = window.event;

	if (e_event) {
		window.n_mouseX = e_event.clientX;
		window.n_mouseY = e_event.clientY;
	}

	if (sfObject.isMouseDown) {
		sfObject.didDrag = true;
		var offset = sfObject.dragStartX - window.n_mouseX;
		var containerWidth =  sfObject.images.length * sfObject.imgWidthTilted * 0.5;
		var percOffset = offset / containerWidth;
		
		var sign = (percOffset < 0 ? -1 : 1);
		
		var relOffset = sign * (Math.pow(Math.abs(percOffset)*100, sfDragPower) / 100) * (sfObject.images.length-1) * (-sfObject.slideDistance);
		var target = sfObject.dragStartPos + relOffset;
		sfObject.glideTo(target);
		
		return false;
	}

	if (sfObject.savedMouseMove)
		return sfObject.savedMouseMove(e_event);
	else
		return false;
}

/**
 * Click handler for slides
 */
function sfClick (item) {
	if (sfObject.didDrag)
		return false;
  
  var images = sfObject.containerElement.getElementsByTagName("img");

	if (item.number == sfObject.centerItem || images.length - 0 < item.number) {
	  if (this.current < images.length - 0) {
      sfObject.glideToSlide(item.number);
    }
    sfObject.onCenterClick(item.number);
  } else {
		sfObject.glideToSlide(item.number);
		sfObject.onCenterClick(item.number);
	}
	
	return false;
}

