/*
 * Author : Michael Bosworth 2007
 *
 *
 * Note : This class is dependant on : /web/common/js/lib/prototype/prototype.lite.js
 *                                     /web/common/js/lib/moofx/moo.fx.js
 *                                     /web/common/js/lib/moofx/moo.fx.utils.js
 *
 * These files must be included prior to this script to function.
*/
function Carousel(){
    var self = this;
    this.iwidth = null;                 /* Number , width of an item */
    this.numViewable = null;            /* Number , number of items viewable an any one time */
    this.index = null;                  /* Number , specifies the index of the first viewable item */
    this.endIndex = null;               /* Number , specifies the index of the last viewable item */
    this.activeDiv = null;              /* Element , HTML div element containing all carousel items */
    this.parentDiv = null;              /* Element , HTML div element containg the active div */
    this.args = null;                   /* Array , contains information vital for rendering carousel items */
    this.monitor = null;                /* Element , HTML element which displays the carousel status */
    this.hasMonitor = false;            /* Boolean , specifies if the carousel has an associated monitor */
    this.activeID = null;               /* String , HTML div element containing all carousel items */
    this.width = null;                  /* Number , Width of the carousel */
    this.height = null;                 /* Number , height of the carousel */
    this.speed = 500;                   /* Number , duration of the scrolling animation */
    this.scrollLock = false;            /* Boolean , enables(false) and disables(true) scrolling functions */
    this.debug = false;                 /* Boolean , enables and disables alert messages */
    this.useFirebug = false;            /* Boolean , enables error messages to be sent to Firebug console in Firefox */
    this.groupID = null;                /* Number , unique ID given to a dynamic HTML div so that it may be removed from the page after use */
    this.loadedImages = null;           /* Array , contains images already preloaded */
    this.imageIndex = 0;                /* Number , contains the index for all image sources in this.args */
    this.clean = false;                 /* Boolean , specificies if the carousel active has been cleaned of text nodes */

    /* Appends the carousel to the pages and populates with the minimum amount of viewable items */
    this.init = function(args_ary,item_width,width,height,num_viewable,parent_id,active_id,monitor_id,speed){
        this.args = args_ary;
        this.iwidth = item_width;
        this.width = width;
        this.height = height;
        this.numViewable = num_viewable;
        this.parentDiv = $(parent_id);
        this.activeID = active_id;
        this.monitor = $(monitor_id);
        this.loadedImages = new Array();
        if(speed != null){this.speed = speed;}
        /* test to see if element exists */
        if(this.parentDiv == null){
            this.throwError("Could Not Find Element " + parent_id);
            return false;
        }
        if(this.monitor != null){
            this.hasMonitor = true;
        }
        this.create();
        return false;
    };

    /* Show error messages if in debug mode */
    this.throwError = function(message){
        if(this.debug && this.useFirebug){
            console.log("%s",message);    
        }
        else if(this.debug && !this.useFirebug){
            alert("Carousel Error: " + message);
        }
    };

    /* preloads images into the broswer cache before displaying them */
    this.preloadImages = function(ary, callback){
          var imageSrcAry = new Array();
          var updatedIndexes = new Array();
          for(var i=0; i<ary.length;i++){
              if(this.loadedImages[ary[i]] == null){
                imageSrcAry.push(this.args[ary[i]][this.imageIndex]);
                updatedIndexes.push(ary[i]);
              }
          }
          if(imageSrcAry.length > 0){
              var ip = new ImgPreloader(imageSrcAry, updatedIndexes, this.loadedImages, function(aImages,args){
                  callback();
              });
          }
          else{
            callback(); 
          }
    }

    /* Appends the active div to the pages based on initialized main variables */;
    this.create = function(){
        /* create and style viewable slideshow element */      
        if(this.activeDiv == null){
            this.activeDiv = new Element('div',{'id':this.activeID});

            /* add new element to the page */
            this.parentDiv.appendChild(this.activeDiv);
            if(this.args != null){
                this.populate();
            }
        }
        if(this.width != null){this.activeDiv.setStyle('width', this.width + "px");}
        if(this.height != null){this.activeDiv.setStyle('height', this.height + "px");}
    };

    /* Populates the active div with the number of items viewable */;
    this.populate = function(args_ary){
        if(args_ary != null){
            this.args = args_ary;
        }
        this.clear();
        var loadNum = 0;
        if(this.numViewable >= this.args.length){
            this.scrollLock = true;
            loadNum = this.args.length;
        }
        else{
            loadNum = this.numViewable;
        }

        /* Build image src array */
        var initImages = new Array();
        for(var i=0; i<loadNum; i++){
            initImages[i] = i;
        }

        /* Preload images */
        this.preloadImages(initImages, function(a,b){
            /* Load items into carousel */ 
            var item;
			for(var i=0; i<loadNum; i++){
				item = self.render(i,false);
				self.activeDiv.appendChild(item);
            }

            /* intialize monitor and index  */
            self.index = 1;
            self.updateMonitor();
        });
    };

    /* WARNING: You must define this function to dynamically render an item and append it to the active div */;
    this.render = function(index,hide){};

    /* Shifts the carousel to the left based on the number of items per scroll */
    this.scrollRight = function(itemsPerScroll){
        if(!this.scrollLock){
            this.scrollLock = true;
            if(this.activeDiv == null){
                this.throwError("Active Div is null. You Must Intialize Carousel with 'init()' Before Calling This Method");
                return;
            }
            if(itemsPerScroll > this.numViewable){
                this.throwError("You Are trying To Scroll More Items Than Are Viewable!");
                return;
            }
            if(itemsPerScroll == null){ itemsPerScroll = 1; }
            if(itemsPerScroll > 1){
                this.multiScrollRight(itemsPerScroll);
            }
            else{
                this.updateMonitor(-1);
				this.render(this.index-1,true).injectInside(this.activeDiv);
                this.shiftRight();
            }
        }
    };

    /* Shifts the carousel to the right based on the number of items per scroll */
    this.scrollLeft = function(itemsPerScroll){
        if(!this.scrollLock){
            this.scrollLock = true;
            if(this.activeDiv == null){
                this.throwError("Active Div is null. You Must Intialize Carousel with 'init()' Before Calling This Method");
                return;
            }
            if(itemsPerScroll > this.numViewable){
                this.throwError("You Are trying To Scroll More Items Than Are Viewable!");
                return;
            }
            if(itemsPerScroll == null){ itemsPerScroll = 1; }
            if(itemsPerScroll > 1){
                this.multiScrollLeft(itemsPerScroll);
            }
            else{
                this.updateMonitor(1);
                this.render(this.endIndex-1,true).injectInside(this.activeDiv);
                this.shiftLeft();
            }
        }
    };

    /* Shifts multiple items to the left at one time */
    this.multiScrollRight = function(itemsPerScroll){
        /* Obtain indexes for new Group */
        var indexes = new Array();
        var scrollDistance = this.getScrollDistance(itemsPerScroll);
        for(var i=0; i<itemsPerScroll; i++){
            this.updateMonitor(1);
            indexes[i] = this.endIndex-1;
        }

        this.preloadImages(indexes, function(){
            self.group(itemsPerScroll,false);
            var newGroup = self.createGroup(indexes);
            newGroup.injectInside(self.activeDiv);
            self.shiftRight(scrollDistance);
        });
    };

    /* Shifts multiple items to the right at one time */
    this.multiScrollLeft = function(itemsPerScroll){
        /* Obtain indexes */
        var indexes = new Array();
        var scrollDistance = this.getScrollDistance(itemsPerScroll);
        for(var i=0; i<itemsPerScroll; i++){
            this.updateMonitor(-1);
            indexes[i] = this.endIndex-1;
        }

        this.preloadImages(indexes, function(){
            self.group(itemsPerScroll,true);
            var newGroup = self.createGroup(indexes);
            newGroup.injectInside(self.activeDiv);
            self.shiftLeft(scrollDistance);
        });
    };

    /* Returns distance (pixels) of the scroll */
    this.getScrollDistance = function(itemsPerScroll){
        if(itemsPerScroll * this.iwidth > this.width){
            return this.width;
        }
        else{
            return itemsPerScroll * this.iwidth;
        }
    };

    /* Returns the items contained within the group to the active div and removes the remaining empty HTML div element */
    this.ungroup = function(wrapper){
        var group = wrapper.firstChild;
        while(group.firstChild){
            this.activeDiv.insertBefore(group.removeChild(group.firstChild),wrapper);
        }
        this.activeDiv.removeChild(wrapper);
    };

    /* groups all items in the passed array by placing an HTML div Element around them */
    this.group = function(numItems,startAtBegining){
        var wrapper = document.createElement("div");
        var group = document.createElement("div");
        wrapper.style.overflow = "hidden";
        wrapper.id = "group" + Math.random();
        group.style.width =  this.getScrollDistance(numItems) + "px";
        group.style.height = this.height + "px";
        wrapper.injectInside(this.activeDiv);
        wrapper.appendChild(group);
        if(!this.clean){this.removeTextNodes();}

        if(startAtBegining){
            for(var i=0; i<numItems; i++){
                group.appendChild(this.activeDiv.firstChild);
            }
        }
        else{
            var index = this.activeDiv.childNodes.length - 1 - numItems;
            for(var i=0; i<numItems; i++){
                group.appendChild(this.activeDiv.getChildren()[index]);
            }
        }
    };

    /* Creates and returns a group of new items  */
    this.createGroup = function(ary){
        var wrapper = document.createElement("div");
        var group = document.createElement("div");
        wrapper.style.overflow = "hidden";
        group.style.width = this.getScrollDistance(ary.length) + "px";
        group.style.height = this.height + "px";
        wrapper.style.width = "0px";
        wrapper.id = this.groupID = "group" + Math.random();
        wrapper.appendChild(group);
        for(var i=0; i<ary.length; i++){
            group.appendChild(this.render(ary[i],false));
        }
        return wrapper;
    };

    /* Inserts the last child before the first child and animates a 0 - scrollLength change in width */
    /* The new last child undergoes a scrollLength - 0 change in width and is removed */
    this.shiftRight = function(scrollLength){
        if(scrollLength == null){scrollLength = this.iwidth;}
        var lChild = this.activeDiv.lastChild;
        this.activeDiv.insertBefore(lChild,this.activeDiv.firstChild);
		
		var effect1 = new Fx.Style(lChild.id, 'width', {duration: this.speed, onComplete:function(){
            if(self.activeDiv.firstChild.id.match(self.groupID)){
                self.ungroup(self.activeDiv.firstChild);
            }
            self.scrollLock = false;
        }}).start(0,scrollLength);
        var newlChild = this.activeDiv.lastChild;
		var effect2 = new Fx.Style(newlChild.id, 'width',{duration: this.speed, onComplete:function(){
            self.activeDiv.firstChild.parentNode.removeChild(self.activeDiv.lastChild);
            self.scrollLock = false;
        }}).start(scrollLength-10,0);
    };

    /* Animates a scrollLength - 0 change in width and removes the first child  */
    /* Animates a 0 - scrollLength change in widht of the last child */
    this.shiftLeft = function(scrollLength){
        if(scrollLength == null){scrollLength = this.iwidth;}
        var fChild = this.activeDiv.firstChild;
        var lChild = this.activeDiv.lastChild;
		var effect1 = new Fx.Style(fChild.id, 'width',{duration:this.speed, onComplete:function(){
            self.activeDiv.firstChild.parentNode.removeChild(self.activeDiv.firstChild);
            self.scrollLock = false;
        }}).start(scrollLength-10,0);
		var effect2 = new Fx.Style(lChild.id, 'width', {duration: this.speed, onComplete:function(){
            if(self.activeDiv.lastChild.id.match(self.groupID)){
                self.ungroup(self.activeDiv.lastChild);
            }
            self.scrollLock = false;
        }}).start(0,scrollLength);
    };

    /* removes everything from within the active div */
    this.clear = function(){
        while(this.activeDiv.firstChild){
            this.activeDiv.removeChild(this.activeDiv.firstChild);
        }
    };

    /* displays index status to an HTML element if Element id was provided during initialization (optional)  */
    this.updateMonitor = function(num){
        var string;
        var size = this.args.length;
        if(this.index == null){
            this.index = 1;
        }
        else{
            if(num > 0){
                this.index += num;
                if(this.index > size){
                    this.index -= size;
                }

            }
            if(num < 0){
                this.index += num;
                if(this.index < 1){
                    this.index += size;
                }
            }
        }
        if(this.numViewable > 1){
            if(this.numViewable > size){
                string = this.index + "-" + size + " of " + size;
            }
            else{
                if(this.index + this.numViewable -1 > size){
                    this.endIndex = this.index + this.numViewable - 1 - size;
                    string = this.index + "-" + this.endIndex + " of " + size;
                }
                else{
                    this.endIndex = this.index + this.numViewable - 1;
                    string = this.index + "-" + this.endIndex + " of " + size;
                }
            }
        }
        else{
            this.endIndex = this.index;
            string = this.index + " of " + size;
        }
        if(this.hasMonitor){
            this.monitor.innerHTML = string;
        }

    };

    /* returns the relative path of the image */
    this.getRelativePath = function(srcString){
        var startIndex = srcString.indexOf(".com",0);
        return srcString.substring(startIndex + 4, srcString.length);
    };

    /* if the carousel does not initialize itself there may be unwanted text nodes in the carousel.
       This function removes any unwanted text nodes. */
    this.removeTextNodes = function(){
        for(var i =0; i<this.activeDiv.childNodes.length; i++){
            if(this.activeDiv.childNodes[i].nodeType == 3){
                this.activeDiv.firstChild.parentNode.removeChild(this.activeDiv.childNodes[i]);
            }
        }
        this.clean = true;
    };
}


/* Image Preloader */
function ImgPreloader(images, indexes, objects, callback)
{
	/* store the callback */
	this.callback = callback;

	/* initialize internal state. */
	this.nLoaded = 0;
	this.nProcessed = 0;
	this.aImages = objects;

	/* record the number of images. */
	this.nImages = images.length;

	/* for each image, call preload() */
	for ( var i = 0; i < images.length; i++ )
		this.preload(images[i],indexes[i]);
}
ImgPreloader.prototype.preload = function(image,index)
{
	/* create new Image object and add to array */
	var oImage = new Image;
	this.aImages[index] = oImage;

	/* set up event handlers for the Image object */
	oImage.onload = ImgPreloader.prototype.onload;
	oImage.onerror = ImgPreloader.prototype.onerror;
	oImage.onabort = ImgPreloader.prototype.onabort;

	/* assign pointer back to this. */
	oImage.oImgPreloader = this;
	oImage.bLoaded = false;
	oImage.source = image;

	/* assign the .src property of the Image object */
	oImage.src = image;
}
ImgPreloader.prototype.onComplete = function()
{
	this.nProcessed++;
	if ( this.nProcessed == this.nImages )
		this.callback(this.aImages);
}
ImgPreloader.prototype.onload = function()
{
	this.bLoaded = true;
	this.oImgPreloader.nLoaded++;
	this.oImgPreloader.onComplete();
}
ImgPreloader.prototype.onerror = function()
{
	this.bError = true;
	this.oImgPreloader.onComplete();
}
ImgPreloader.prototype.onabort = function()
{
	this.bAbort = true;
	this.oImgPreloader.onComplete();
}