// Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// VERSION 0.26

var Carousel = Class.create();
Carousel.prototype = {
	// Constructor
	initialize: function(carouselElemID) {
	this.carouselElemID = carouselElemID;

	this.options = Object.extend({
		numVisible:           4,
		scrollInc:            3,
		animParameters:      {},
		buttonStateHandler:  null,
		animHandler:         null,
		ajaxHandler:         null,
		initDoneHandler:     null,
		queue:               "carousel",
		size:                0,
		prevElementID:       "prev-arrow",
		nextElementID:       "next-arrow",
		headElementID:       "head-arrow",  // gilm
		tailElementID:       "tail-arrow",  // gilm
		findrElementID:      "carousel-findr",  // gilm
		ajaxParameters:      null,
		url:                 null
	}, arguments[1] || {});

	this.dummy = 0;
	this.initDone = false;
	this.animRunning = "none";
	this.selectedThumb = false; // gilm:
	this.selectedPhotoId = false;
	this.photos = new Array();
	this._photosPreload = new Array();

	// add afterFinish options to animParameters (store old function)
	this.animAfterFinish = this.options.animParameters.afterFinish;
	Object.extend(this.options.animParameters, {afterFinish:  this._animDone.bind(this), queue: { position:'end', scope: this.options.queue }});

	// Event bindings
	this.prevScroll = this._prevScroll.bindAsEventListener(this);
	this.nextScroll = this._nextScroll.bindAsEventListener(this);
	this.headScroll = this._headScroll.bindAsEventListener(this); // gilm
	this.tailScroll = this._tailScroll.bindAsEventListener(this); // gilm
	this.onComplete = this._onComplete.bindAsEventListener(this);
	this.onFailure  = this._onFailure.bindAsEventListener(this);

	Event.observe(this.options.prevElementID, "click", this.prevScroll);
	Event.observe(this.options.nextElementID, "click", this.nextScroll);
	Event.observe(this.options.headElementID, "click", this.headScroll); // gilm
	Event.observe(this.options.tailElementID, "click", this.tailScroll); // gilm

	// gilm:
	this.eventMouseDown = this._startDrag.bindAsEventListener(this);
	this.eventMouseUp   = this._endDrag.bindAsEventListener(this);
	this.eventMouseMove = this._update.bindAsEventListener(this);
	
	var findrSliderClass = "findr_slider";
	this.findrSlider = document.getElementsByClassName(findrSliderClass, $(this.options.findrElementID))[0];
	
	Event.observe(this.findrSlider, "mousedown", this.eventMouseDown);
	Event.observe(document, "mouseup", this.eventMouseUp);
	Event.observe(document, "mousemove", this.eventMouseMove);

	// gilm
	var wh = 634; // FIXME $(this.options.findrElementID).getDimensions();
	this.sliderWidth = wh - 37;

	// Get DOM UL element
	var carouselListClass = "carousel-list";
	this.carouselList = document.getElementsByClassName(carouselListClass, $(carouselElemID))[0];

	var carouselStatusClass = "carousel-status"; // gilm
	this.carouselStatus = document.getElementsByClassName(carouselStatusClass, $(carouselElemID))[0];

	// Init data
	this._init();
	},

	// Destructor
	destroy: function() {
		Event.stopObserving(this.options.prevElementID, "click", this.prevScroll);
		Event.stopObserving(this.options.nextElementID, "click", this.nextScroll);
		Event.stopObserving(this.options.headElementID, "click", this.headScroll); // gilm
		Event.stopObserving(this.options.tailElementID, "click", this.tailScroll); // gilm
		Event.stopObserving(this.findrSlider, "mousedown", this.eventMouseDown); // gilm
		Event.stopObserving(document, "mouseup", this.eventMouseUp); // gilm
		Event.stopObserving(document, "mousemove", this.eventMouseMove); // gilm	
	},

	scrollTo: function(newStart) {
		var old_inc = this.options.scrollInc;
		this.ignoreNoMoreImages = true;
		if(newStart > this.currentIndex) {
			this.options.scrollInc = newStart - this.currentIndex;
			this._nextScroll(this);
		} else {
			this.options.scrollInc = this.currentIndex - newStart;
			this._prevScroll(this);
		}
		
		this.options.scrollInc = old_inc;
	},

	/* "Private" functions */
	_init: function() {
		this.currentIndex = 0;

		// Ajax content
		this.carouselStatus.innerHTML = '<h1>Loading photos..</h1>'; // gilm
		this.pendingRequests = new Array();
		this.pendingRequests[0] = 1;
                this._request(0, this.options.numVisible);
	},

	_headScroll: function(event) {
		if (this.animRunning != "none" || this.currentIndex == 0) {
			return;
		}

		this.scrollTo(0);
	},

	_tailScroll: function(event) {
		if (this.animRunning != "none") {
			return;
		}

		this.scrollTo(this.options.size - this.options.numVisible);
	},

	_prevScroll: function(event) {
		if (this.animRunning != "none" || this.currentIndex == 0) {
			return;
		}

		var inc = this.options.scrollInc;

		if (this.currentIndex - inc < 0) {
			inc = this.currentIndex;
		}

		this._scroll(inc)		  
		return false;
	},

	_nextScroll: function(event) {    
		if (this.animRunning != "none") {
			return false;
		}

		if (this.currentIndex + this.options.numVisible + this.options.scrollInc <= this.options.size) {
			this._scroll(-this.options.scrollInc);
		}
		
		return false;
	},

	_request: function(start, nb) {
		if (this.options.ajaxHandler) {
			this.options.ajaxHandler(this, "before");
		}

		var params = "start=" + start + "&nb=" + nb;
		if (this.options.ajaxParameters != null) {
			params += "&" + this.options.ajaxParameters;
		}

		new Ajax.Request(this.options.url, {parameters: params, onComplete: this.onComplete, onFailure: this.onFailure});
	},
	
	_parseResults: function(json) {
		// gilm:
		this.options.size = json.total;
		for (var i=0; i<json.photos.length; i++) {
			var ix = json.photos[i].ix;
			this.photos[ix] = new Object();
			this.photos[ix].thumb = json.photos[i].th;
			this.photos[ix].id = json.photos[i].id;	
		}
	},  

	_onThumbClicked: function(name, index) {
		if (this.selectedThumb) {
			var e = $(this.selectedThumb);
			if (e) {
				e.className = "pickrThumb";
			}
		}

		if (this.photos[index]) {
			this.selectedThumb = name;
			this.selectedPhotoId = this.photos[index].id;
		
			var e = $(name);
			if (e) {
				e.className = "pickrThumbSelected";
			}
		} else {
			// clicked on a thumb that hasn't been loaded yet
			this.selectedPhotoId = false;
			this.selectedThumb = false;
		}
	},

	_kickstartPreloader: function(id, url) {
		if (/MSIE/.test(navigator.userAgent)) {
			// MSIE is buggy with img.onload
			// so just shove everything at once
			var e = this._photosPreload.shift();
			while (e) {
				var em = $(e.id);
				if (em) {
					em.style.background = "url('" + e.url + "') no-repeat";
				}

				e = this._photosPreload.shift();
			}

			return;
		}

		if (id) {
			// item ended?
			var e = $(id);
			if (e) {
				// still valid?
				e.style.background = "url('" + url + "') no-repeat";
			}
		}

		var e = this._photosPreload.shift();
		if (e) {
			// another element queued?
			var img = document.createElement("img");
			img.onload = this._kickstartPreloader.bind(this, e.id, e.url);
			img.alt = '';
			img.src = e.url;
		}
	},

	_updateVisible: function() {
		var index0 = this.currentIndex - 8;
		if (index0 < 0) {
			index0 = 0;
		}

		var index1 = this.currentIndex + this.options.numVisible + 8;
		if (index1 >= this.options.size) {
			index1 = this.options.size;
		}

		this._photosPreload.clear();

		for (var i=index0; i<=index1; i++) {
			var name = "pickrThumb" + i;
			if ($(name) == null || typeof($(name)) == 'undefined') {
				// dont add twice
				var div = document.createElement("div");
				div.setAttribute('id', name);
				div.className = 'pickrThumb';
				var l = i * 80;
				div.style.left = l + "px";
				div.onclick = this._onThumbClicked.bind(this, name, i);
				$(this.carouselList).appendChild(div);
			}
			         		
			if (this.photos[i]) {
				var div = $(name);
				if (div.style.background.indexOf('url') < 0) {
					var e = new Object();
					e.id = name;
					e.url = this.photos[i].thumb;
					this._photosPreload.push(e);
				}
			}

		}

		//alert(this.photosPreload.toJSONString());
		//this._kickstartPreloader(null, null);
		this._kickstartPreloader(null, null);

		// remove elements that are not visible anymore [..index0-1] and [index+1..]
		var list = $(this.carouselList);
		var i = 0;
		while (i < list.childNodes.length) {
			var index = list.childNodes[i].id.substr("pickrThumb".length);
			if (index < index0 || index > index1) {
				$(this.carouselList).removeChild(list.childNodes[i]);
			} else {
				i++;
			}
		}
	},

	_onComplete: function(originalRequest) {
		this.carouselStatus.innerHTML = ''; // gilm
		this._parseResults(originalRequest.responseText.evalJSON()); // gilm:
		// Compute how many new elements we have
		var size = this.options.size;
		//this.options.size = this.carouselList.getElementsByTagName("li").length;
		var inc = this.options.size - size;

		// First run, compute li size
		if (this.initDone == false) {
			this._getDivElementSize();
			this.currentIndex = 0;
			this.initDone = true;
			if (this.options.initDoneHandler) 
				this.options.initDoneHandler(this);

			// Update button states
			this._updateButtonStateHandler(this.options.prevElementID, false);
			this._updateButtonStateHandler(this.options.nextElementID, this.options.size == this.options.numVisible);
			this.noMoreImages = this.options.size < this.options.numVisible;
		}
		// Add images
		else {
			if (!this.ignoreNoMoreImages) {
				this.noMoreImages = inc != this.options.scrollInc;
			} else {
				this.ignoreNoMoreImages = false;
			}
			
			// Add images
			if (inc > 0) {
				this._scroll(-inc, this.noMoreImages)
			}
			// No more images, disable next button
			else {
				if (this.nbInCache >0) {
					this._scroll(-this.nbInCache, true);
				}

				this._updateButtonStateHandler(this.options.nextElementID, false);
			}
		}

		this._updateVisible(); // gilm
		
		if (this.options.ajaxHandler) {
			this.options.ajaxHandler(this, "after");
		}
	},

	_onFailure: function(originalRequest){    
	},

	_animDone: function(event) {  
		if (this.options.animHandler)
			this.options.animHandler(this.carouselElemID, "after", this.animRunning);

		this.animRunning = "none";
		// Call animAfterFinish if exists
		if (this.animAfterFinish)
			this.animAfterFinish(event);
	},

	_updateButtonStateHandler: function(button, state) {
		if (this.options.buttonStateHandler) 
			this.options.buttonStateHandler(button, state)
	},

	_scroll: function(delta, forceDisableNext) {      
		this.animRunning = delta > 0 ? "prev" : "next";

		if (this.options.animHandler)
			this.options.animHandler(this.carouselElemID, "before", this.animRunning);

		this.currentIndex -= delta;

		// gilm
		var curPos = parseFloat(this.findrSlider.getStyle('left'));
		var totalElements = this.options.size - this.options.numVisible;
		var newPos = Math.floor((this.currentIndex * this.sliderWidth) / totalElements); //
		if (newPos < 0) {
			newPos = 0;
		}

		new Effect.Parallel(
			[
				new Effect.MoveBy(this.carouselList, 0, delta * this.elementSize, {sync: true}),
				new Effect.MoveBy(this.findrSlider, 0, newPos - curPos, {sync: true})
			],
			this.options.animParameters
		);

		// send ajax request if we reached images that we dont have
		var index0 = this.currentIndex - 10;
		if (index0 < 0) {
			index0 = 0;
		}

		var index1 = this.currentIndex + 20;
		if (index1 > this.options.size) {
			index1 = this.options.size;
		}

		for (var i=index0; i<index1; i++) {
			if (this.photos[i]) {
				var e = $('pickrThumb' + i);
				if (e) {
					if (e.style.background.indexOf('url') < 0) {
						continue;
					}
				}
			}

			// unknown photo, send ajax request
			var page = Math.floor(i / 100);
			//
			if (!this.pendingRequests[page]) {
				this.pendingRequests[page] = 1;
		                this._request(page * 100, 100);
			}
		}

		this._updateVisible(); // gilm

		this._updateButtonStateHandler(this.options.prevElementID, this.currentIndex != 0);

		if (this.noMoreImages == false)
			enable = true;
		else
			enable = (this.currentIndex + this.options.numVisible < this.options.size);
			
		this._updateButtonStateHandler(this.options.nextElementID, (forceDisableNext ? false : enable));
	},

	_getDivElementSize: function() {
		this.elementSize = 80;
	},
	
	_startDrag: function(event) {
		if (Event.isLeftClick(event)) {
			this.slidrPos = 0;
			this.active = true;
			this.dragging = false;
			Event.stop(event);
		}
	},
	
	_finishDrag: function(event, b) {
		var pos = this.slidrPos;
		var totalElements = this.options.size - this.options.numVisible;
		var idx = Math.round((pos * totalElements) / this.sliderWidth); //
		//alert("Total " + this.options.size + " elements, with " + this.options.numVisible + " visible " + idx + " index");
		this.scrollTo(idx);
	},
	
	_endDrag: function(event) {
		if (this.active && this.dragging) {
			this._finishDrag(event, true);
			Event.stop(event);
		}
		
		this.active = false;
		this.dragging = false;
	},

	_update: function(event) {
		if (this.active) {
			var pointer = [Event.pointerX(event), Event.pointerY(event)];
			if (this.dragging == false) {
				// cursor moves for the first time
				this.dragging = true;

				var sliderOffset = Position.cumulativeOffset($(this.findrSlider));
				this.startDrag = pointer[0] - sliderOffset[0];
			}

			var offsets = Position.cumulativeOffset($(this.options.findrElementID));
			var pos = pointer[0] - offsets[0] - this.startDrag;

			if (pos < 0) {
				pos = 0;
			}

			if (pos > this.sliderWidth) {
				pos = this.sliderWidth;
			}

			this.slidrPos = pos;
			$(this.findrSlider).style.left = pos + "px";
			
			//this.draw(event);
			Event.stop(event);
		}
	}
}
