﻿            

var PCarousel2 = function(configParams, callback){
    //default settings for the carousel
    var config = {
        speed       : 300, //animation speed in ms
        direction   : 'horizontal', //direction of animation, 'horizontal' or 'vertical' are valid options
        listItem    : null, //DOM obj of the <UL> where we work with
        nextButton  : null, //DOM obj of the next-button
        prevButton  : null, //DOM obj of the prev-button
        continues   : false, //determines whether to continue after last element in an loop
        loopType    : 'finite', //sets the type of loop 'finite' to rwd/ffd to end or start, or 'infinite' to have a fluent experience
        offset      : 0, //offset of the current selected item in pixels
        autoLoopInterval    : 0, //timer for automated scroll, 0 for none
        callback    : function(){},
                
        bind : function(params, callback){
            if(params){
                for(param in params){
                    eval("config." + param + " = params." + param);
                }
            }
            if(typeof(callback) == "function"){
                config.callback = callback;
            }
            //config binding done
            config.init = true;
        },
        init : false, //determines if the binding of config is done
        dummy : null //to not forget the appending of the extra , after the last variable
    }; 
    //bind the configuration
    config.bind(configParams, callback);
        
    //private methods and variables for object
    var priv = {
        curIndex : 0,
        $listItems : null,
        stepSize : 0,
        lock : !config.init,
        queue : Array(),
        maxItemsInView : 0,
        timer : null,
        
        //test whether we have everything required to get going
        hasListItem : function(){
            if(!config.listItem){
                logic.writeDebug("PCarousel2: No valid listItem has been bound, please specify", 2);
                return false;
            }
            return true;
        },
        
        moveTo : function(newIndex){
            try {
                //if locked, do nothing
			    if(priv.lock){
			        logic.writeDebug("PCarousel: Not moving, locked");
				    return;
			    }
    			
			    //if the newIndex is not set, try to get the last item from queue
			    var fromQueue = false;
		        if(typeof(newIndex) == "undefined"){
		            if(priv.queue.length > 0){
		                fromQueue = true;
		                newIndex = priv.queue[priv.queue.length-1];
		                priv.queue.length = new Array();
		            }
		            else {
		                logic.writeDebug("PCarousel: Not moving, nothing in queue");
		                return;
		            }
		        }
    		    
			    logic.writeDebug("PCarousel2: moveTo(), newindex: " + newIndex, 0);
			    var doStep = false;
			    var isLastStep = priv.isLastStep();
			    var isFirstStep = priv.isFirstStep()
    			
			    //correct for continues loops
			    newIndex = priv.continuesCorrections(newIndex, isLastStep, isFirstStep);			
    			
			    //test if we can actually move			    
			    if(config.continues || ((newIndex > priv.curIndex && !isLastStep) || (newIndex < priv.curIndex && !isFirstStep))){
			        //now lock
			        priv.lock = true;
			        doStep = true;
    			    
			        var newItemOffset = 0;
    			    
			        //determine action to take on animation
			        var animationAction = "";
			        if(config.direction == 'vertical'){
			            newItemOffset = Math.min(priv.$listItems[newIndex].offsetTop, $(config.listItem).height() - $(config.listItem).parent().height());
			            animationAction = {"top" : -1*(newItemOffset + config.offset) + "px"};
			        }
			        else {
			            newItemOffset = Math.min(priv.$listItems[newIndex].offsetLeft, $(config.listItem).width() - $(config.listItem).parent().width());
			            animationAction = {"left" : -1*(newItemOffset + config.offset) + "px"};
			        }
    			    
			        //remove class from old index
			        $(priv.$listItems[priv.curIndex]).removeClass("selected");
			        $(priv.$listItems[newIndex]).addClass("selected");
    			    
			        //do the animation, and set the "selected" class
			        $(config.listItem).animate(animationAction, config.speed,
		                function(){
		                    //set the nextPrevButtons
		                    priv.testNextPrevButtons();
		                    //correction for first and last items when using "To()" function in infinite loop
		                    if(fromQueue && config.offset != 0 && config.continues && config.loopType == 'infinite'){
		                        priv.continuesCorrections(null, priv.isLastStep(), priv.isFirstStep());
		                    }
		                    //make the callback
		                    config.callback();
		                    //unlock
		                    priv.lock = false;
		                    //see if we need to move into the queue object
		                    if(priv.queue.length > 0){
		                        priv.moveTo();
		                    }
		                }
		           );
			    }
    			
			    //set the curIndex
			    if(doStep) {
			        logic.writeDebug("PCarousel: setting new index to(1): " + newIndex);
			        priv.curIndex = newIndex;
			    }
			    //if we havent moved but we are not at the last index, set the index
			    else if((newIndex > priv.curIndex && newIndex < priv.$listItems.length && isLastStep) || (newIndex < priv.curIndex && newIndex >= 0 && isFirstStep)){
			        logic.writeDebug("PCarousel: setting new index to(2): " + newIndex);
			        //remove class from old index
			        $(priv.$listItems[priv.curIndex]).removeClass("selected");
			        $(priv.$listItems[newIndex]).addClass("selected");
			        priv.curIndex = newIndex;
			    }
			}
			catch(err){
			    //priv.curIndex = 0;
			    priv.lock = false;
			    logic.writeDebug("PCarousel2: Not moving due to error moving to index: " + newIndex, 2);
			    logic.writeDebug(err, 2);
			}
        },
        
        testNextPrevButtons : function(){
            if(!config.continues){
                if(config.prevButton && priv.isFirstStep()){
                    $(config.prevButton).addClass("inactive");
                }
                else if(config.prevButton){
                    $(config.prevButton).removeClass("inactive");
                }
                
                if(config.nextButton && priv.isLastStep()){
                    $(config.nextButton).addClass("inactive");
                }
                else if(config.nextButton){
                    $(config.nextButton).removeClass("inactive");
                }
            }
        },
        
        //is last step
        isLastStep : function(){
            //test whether this is the last step by checking the heights
            if(config.direction == 'vertical'){
                var parentHeight = $(config.listItem).parent().height();
                //if the <UL> has less height then its parent, including newly set css top attribute, we are done stepping               
                if(Math.abs(parseInt($(config.listItem).css("top"))) + priv.stepSize*priv.maxItemsInView + config.offset >= $(config.listItem).height()){
                //if(($(config.listItem).height() + parseInt($(config.listItem).css("top")) ) <= priv.stepSize*priv.maxItemsInView + config.offset){ //if loop for continues
                //if(($(config.listItem).height() + parseInt($(config.listItem).css("top"))) <= parentHeight){ // original if
                    return true;
                }
            }
            else {
                var parentWidth = $(config.listItem).parent().width();
                //if the <UL> has less width then its parent, including newly set css left attribute, we are done stepping               
                //if(($(config.listItem).width() + parseInt($(config.listItem).css("left")) - priv.stepSize) <= priv.stepSize*priv.maxItemsInView + config.offset){
                if(Math.abs(parseInt($(config.listItem).css("left"))) + priv.stepSize*(priv.maxItemsInView) + config.offset >= $(config.listItem).width()){
                    logic.writeDebug("Is Last Step");
                    return true;
                }
            }
            return false;
        },
        
        //is first step
        isFirstStep : function(){
            //test whether this is the last step by checking the heights
            var topPx = parseInt($(config.listItem).css("top"));
                if(isNaN(topPx)){
                    topPx = 0;
                }
            if(config.direction == 'vertical'){
                //if the css property top is set to 0, presume first step
                if(Math.abs(leftPx) + config.offset <= 0){
                    return true;
                }
            }
            else {
                 //if the css property left is set to 0, presume first step
                var leftPx = parseInt($(config.listItem).css("left"));
                if(isNaN(leftPx)){
                    leftPx = 0;
                }
                if(Math.abs(leftPx) + config.offset <= 0){
                //if(parseInt($(config.listItem).css("left")) + priv.stepSize == -1*config.offset){
                    logic.writeDebug("Is First Step");
                    return true;
                }
            }
            return false;
        },
        
        getFirstStep : function(){
            if(config.loopType == 'finite'){
                return 0;
            }
            else if(false){
                //get the last item from the <UL> and compare it against the current array
                var firstItem = $("li:first", config.listItem).get(0);
                for(var i = 0; i<priv.$listItems.length; i++){
                    if(firstItem == priv.$listItems[i]){
                        return i - priv.maxItemsInView;
                    }
                }
            }
        },
        
        getLastStep : function(){
            if(config.loopType == 'finite'){
                return priv.$listItems.length - Math.floor($(config.listItem).parent().height()/priv.stepSize);
            }
            else {
                //get the last item from the <UL> and compare it against the current array
                var lastItem = $("li:last", config.listItem).get(0);
                for(var i = 0; i<priv.$listItems.length; i++){
                    if(lastItem == priv.$listItems[i]){
                        return i;
                    }
                }
            }
        },
        
        continuesCorrections : function(newIndex, isLastStep, isFirstStep){
            //no correction is needed if we are not in a loop
            if(!config.continues){
                return newIndex;
            }
            //continues finite, and last/first step set new index
			if((config.continues && config.loopType == 'finite') && (newIndex > priv.curIndex && isLastStep)){
			    newIndex = priv.getFirstStep();
			}
			else if((config.continues && config.loopType == 'finite') && (newIndex < priv.curIndex && isFirstStep)){
			    newIndex = priv.getLastStep();
			}
			
			//corrections for infinte loop
			if((config.continues && config.loopType == 'infinite') && (isLastStep)){
			     //get the first item from the <UL> and place it after the last
		        var firstItem = $("li:first", config.listItem);
		        
		        //first place the <UL> so the animation will be fluent
		        if(config.direction == 'vertical'){
		            var top = parseInt($(config.listItem).css("top"));
                    if(isNaN(top)){
                        top = 0;
                    }
		            $(config.listItem).css("top", (top + priv.stepSize) + "px");
		        }
		        else {
		            var left = parseInt($(config.listItem).css("left"));
                    if(isNaN(left)){
                        left = 0;
                    }
		            $(config.listItem).css("left", (left + priv.stepSize) + "px");
		        }
		        $("li:last", config.listItem).after(firstItem);
		        
			}
			else if((config.continues && config.loopType == 'infinite') && (isFirstStep)){
			    //get the last item from the <UL> and place it after the first
		        var lastItem = $("li:last", config.listItem);
		        
		        //first place the <UL> so the animation will be fluent
		        if(config.direction == 'vertical'){
		            var top = parseInt($(config.listItem).css("top"));
                    if(isNaN(top)){
                        top = 0;
                    }
                    $(config.listItem).css("top", (top - priv.stepSize) + "px");
                }
                else {
                    var left = parseInt($(config.listItem).css("left"));
                    if(isNaN(left)){
                        left = 0;
                    }
                    $(config.listItem).css("left", (left - priv.stepSize) + "px");
                }
		        $("li:first", config.listItem).before(lastItem);
			}
			
			//correct for infinite looptype
			if(config.continues && config.loopType == 'infinite'){
			    if(newIndex >= priv.$listItems.length){
			        newIndex = 0;
			    }
			    else if(newIndex < 0){
			        newIndex = priv.$listItems.length-1;
			    }
			}
			
			return newIndex;
        },
        
        startAutoLoopInterval	: function(){
			if(config.autoLoopInterval > 0 && priv.timer == null){
				priv.timer = window.setInterval(priv.next, config.autoLoopInterval);
			}
		},
		
		stopAutoLoopInterval	: function(){
			try {
				if(priv.timer != null){
					window.clearInterval(priv.timer);
					priv.timer = null;
				}
			}
			catch(err){
				priv.timer = null;
			}
		},
        
        
        init : function(){
            if(!config.listItem){
                return;
            }  
            //set $listItems
			if(!priv.$listItems){
			    priv.$listItems = $("li", config.listItem);
			}
		    
		    //start autoLoop
		    if(config.autoLoopInterval > 0){
		        //make sure we stop the automated scroll when we hover over the navigationObject
			    $(config.listItem).hover( 
				    function(evt){
					    priv.stopAutoLoopInterval();
				    },
				    function(evt){
					    priv.startAutoLoopInterval();
				    }
			    );
		        priv.startAutoLoopInterval();
		    }
		    	
			//presumed all items are equally sized
            priv.stepSize = (config.direction == 'vertical' ? priv.$listItems.height() : priv.$listItems.width());
			
			//correct the container width for vertical scrolls
			if(config.direction != 'vertical'){
			    $(config.listItem).width(priv.$listItems.length * priv.stepSize);
			}
						
			//attach some info
			priv.$listItems.each(
			    function(index){
			        $(this).attr("rel", index);
			    }
			);
			
			//set max number of items in view
			if(config.direction == 'vertical'){
			    priv.maxItemsInView = Math.ceil($(config.listItem).parent().height()/priv.stepSize);
			}
			else {
			    priv.maxItemsInView = Math.ceil($(config.listItem).parent().width()/priv.stepSize);
			}
			
			//if we dont have enough items to actually do a continues loop, set to false
			var sizeTest = false;
			if(config.direction == 'vertical'){
			    sizeTest = ($(config.listItem).height() <= $(config.listItem).parent().height());
			}
			else {
			    sizeTest = $(config.listItem).width() <= $(config.listItem).parent().width()
			}
			
			if(config.continues && sizeTest){
			    config.continues = false;
			    
			    //disable the next and prev buttons
			    if(config.nextButton){
			        $(config.nextButton).addClass("inactive");
			    }
			    if(config.prevButton){
			        $(config.prevButton).addClass("inactive");
			    }
			}
			else {
			    //bind the next and prev buttons
			    if(config.nextButton){
			        logic.writeDebug("binding click");
			        $(config.nextButton).bind("click",
			            function(evt){
			                priv.next();
			            }
			        );
			    }
			    if(config.prevButton){
			        $(config.prevButton).unbind("click");
			        $(config.prevButton).bind("click",
			            function(evt){
			                priv.prev();
			            }
			        );
			    }
			    //correct loop for first item
			    priv.continuesCorrections(priv.curIndex-1, false, true);
			    //go to the first item
			    priv.to(priv.curIndex);
			    priv.testNextPrevButtons();
			}
        },
        
        next : function(tmpParams, tmpCallback) {
            logic.writeDebug("PCarousel2: Next(), index: " + (priv.curIndex+1), 0);
            priv.moveTo(parseInt(priv.curIndex) + 1);
        },
        prev : function(tmpParams, tmpCallback){
            logic.writeDebug("PCarousel2: Prev(), index: " + (priv.curIndex-1), 0);
            priv.moveTo(parseInt(priv.curIndex)-1);
        },
        to : function(index, tmpParams, tmpCallback){
            logic.writeDebug("PCarousel2: To(" + index + ")");
            priv.queue.push(parseInt(index));
            priv.moveTo();
        },
        dummy : null //to not forget the appending of the extra , after the last variable
    };
    priv.init();
   
    //public methods for the object
    return {
        /**
        * Moves the carousel one forward
        **/
        Next : function(tmpParams, tmpCallback){
            priv.next(tmpParams, tmpCallback);
        },
        
        /**
        * Moves the carousel one back
        **/
        Prev : function(tmpParams, tmpCallback){
            priv.prev(tmpParams, tmpCallback);
        },
        
        /**
        * Moves the carousel to the specified index
        **/
        To : function(index, tmpParams, tmpCallback){
            priv.to(index, tmpParams, tmpCallback);
        },
        
        /**
        * Moves the carousel to the specified item
        **/
        ToItem : function(){
            //TODO
        },
        
        /**
        * Returns the index of the currently selected item
        **/
        GetCurrentIndex : function(){
            return priv.curIndex;
        },
        
        /**
        * Returns the currently selected item
        **/
        GetCurrentItem : function(){
            return priv.$listItems[priv.curIndex];
        },
        
        GetItemByIndex : function(index){
            return priv.$listItems[index];
        },
        
        GetNumberOfItemsInView : function(){
            return priv.maxItemsInView;
        },
        
        GetItemsInView : function(){
            var items = $(priv.$listItems[priv.curIndex]).nextAll("li:lt(3)").andSelf().get();
            items.unshift(items.pop());
            return items;
        },
        
        IsLocked : function(){
            return priv.lock;
        },
    
        TestConfig : function(configParam){
            var value = eval("config." + configParam);
            return value;
        },
        TestCallback : function(){
            config.callback();
        },
        
        dummy : null //to not forget the appending of the extra , after the last variable
    }
};