(function($){
    $.fn.scarousel = function(options) {
        if (typeof options == 'string') {
            var instance = $(this).data('scarousel'), args = Array.prototype.slice.call(arguments, 1);
            return instance[options].apply(instance, args);
        } else
            return this.each(function() {
                $(this).data('scarousel', new $sc(this, options));
        });
    };

    function log(message) {
            if (window.console && window.console.log) {
                console.log(message);
                return;
            }
            alert(message);
    }
    /*
        Private variables
    */
    // Default configuration properties.
    var defaults = {
        vertical: false,
        numVisible: 3,
        scrollInc: 1,
        start: 0,
        initCallback: null,
        loadNextItems: null,
        loadPrevItems: null,
        animation: {
            animationStartCallback: null,
            animationEndCallback: null,
            jquery: {
                animation: "normal",
                easing: "swing"
            },
            css3 : {
                animationClass: "slide",
                animation: "slide",
                duration: "0.6s",
                easing: "linear"
            }
        }
    };

    $.scarousel = function(element, options) {
        //Constants
        this.componentClass = "carousel-component";
        this.clipRegionClass = "carousel-clip-region";
        this.verticalClipRegionClass = "carousel-clip-region-vertical";
        this.listClass = "carousel-list";
        this.listItemClass = "carousel-list-item-";
        this.verticalListClass = "carousel-vertical";

        this.animating = false;
        this.first = 0;
        this.last = 0;
        this.prevFirst = this.first;
        this.prevLast = this.last;
        this.lastDirection = "";
        this.locked = false;
        this.isJump = false;
        this.options = $.extend({}, defaults, options || {});


        this.container = $(element);
        this.clip = this.container.children("." + this.clipRegionClass);
        this.list = this.clip.children("." + this.listClass);
        if (this.options.vertical) {
            this.list.removeClass(this.listClass).addClass(this.verticalListClass);
            this.clip.removeClass(this.clipRegionClass).addClass(this.verticalClipRegionClass);
        }

        //Determine the dimension property in CSS
        this.offsetProperty = this.options.vertical ? "top" : "left";
        this.dimensionProperty = this.options.vertical ? "height" : "width";

        //Calculate the clip region
        var itemDimension = this.getItemDimension();
        var clipRegionDimension = this.options.numVisible * itemDimension;
        this.clip.css(this.dimensionProperty, clipRegionDimension + "px");

        //calculate first and last index
        this.first = this.options.start;
        this.last = this.first + Math.min(this.size() - this.first, this.options.scrollInc);

        //Adjust the offset of the list
        var newListOffset = this.getListOffset() - this.first * itemDimension;
        this.list.css(this.offsetProperty, newListOffset);
        var self = this;
        this.windowResize = function() {
            var itemDimension = self.getItemDimension();
            var clipRegionDimension = self.options.numVisible * itemDimension;
            self.clip.css(self.dimensionProperty, clipRegionDimension + "px");

            var newListOffset = - self.first * itemDimension;
            self.list.css(self.offsetProperty, newListOffset);
        };

        $(window).unbind('resize.scarousel', this.windowResize).bind('resize.scarousel', this.windowResize);

        //setup the CSS 3 animation and callback
        if ($.browser.webkit && this.options.animation.css3) {
            if (this.options.animation.css3.animation == "slide") {
                this.list.css("-webkit-transform", "translateX(0px)");
            }
            //this.list.addClass(this.options.animation.css3.animationClass);
            var self = this;
            this.list[0].addEventListener("webkitTransitionEnd", function(event) {
                self.animating = false;
                if (self.options.animation.animationEndCallback) {
                    self.locked = false;
                    if (self.lastDirection.length > 0) { //HACK: prevent the first run
                        self.options.animation.animationEndCallback(self.lastDirection, self.isJump);
                    }
                }
            }, false);
        }

        //Disable text selection on the container in IE
        if ($.browser.msie) {
            this.container[0].attachEvent("onselectstart", function() {return false;});
        }

        //Show the carousel
        this.container.css("visibility", "visible");

        if (this.options.initCallback != null) {
            this.options.initCallback(this);
        }
    };

    // Create shortcut for internal use
    var $sc = $.scarousel;
    $sc.fn = $sc.prototype = {
        scarousel: '0.1'
    };

    $sc.fn.extend = $sc.extend = $.extend;

    $sc.fn.extend({
        /*
            Public methods
        */
        resetJump: function() {
            this.isJump = false;
        },
        goNext: function(numberOfNext) {
            if (this.locked) return; //We are at the end of the list

            this.locked = true;
            this.isJump = true;
            this.prevLast = (this.last + numberOfNext) - 1;
            this.prevFirst = (this.first + numberOfNext) - 1;
            this.last = this.last + numberOfNext;
            this.first = this.first + numberOfNext;

            //recalculate item dimension
            var clipDimension = this.getClipDimension();
            var itemDimension = Math.ceil(clipDimension / this.options.numVisible);
            
            if ($.browser.webkit && this.options.animation.css3) {
                this.animate(-itemDimension * this.options.scrollInc, "next");
            } else {
                this.animate(-itemDimension * (this.options.scrollInc + (numberOfNext - 1)), "next");
            }
        },
        goPrev: function(numberOfPrev) {
            if (this.locked) return;

            this.locked = true;
            this.isJump = true;
            this.prevLast = this.last - numberOfPrev - 1;
            this.prevFirst = this.first - numberOfPrev - 1;
            this.last = this.last - numberOfPrev;
            this.first = this.first - numberOfPrev;

            //recalculate item dimension
            var clipDimension = this.getClipDimension();
            var itemDimension = Math.ceil(clipDimension / this.options.numVisible);
            if ($.browser.webkit && this.options.animation.css3) {
                this.animate(itemDimension * this.options.scrollInc, "prev");
            } else {
                this.animate(itemDimension * (this.options.scrollInc + (numberOfPrev - 1)), "prev");
            }
        },
        next: function() {
            //console.log("next first:" + this.first + ", last: " + this.last + ", size: " + this.size());
            if (this.locked || this.last == this.size() - 1) return; //We are at the end of the list
            this.locked = true;
            this.isJump = false;
            this.prevLast = this.last;
            this.prevFirst = this.first;
            this.last++;
            this.first++;

            if (this.options.loadNextHandler) {
                this.options.loadNextHandler(this, this.prevFirst, this.prevLast);
            }
            //recalculate item dimension
            var clipDimension = this.getClipDimension();
            var itemDimension = Math.ceil(clipDimension / this.options.numVisible);
            this.animate(-itemDimension * this.options.scrollInc, "next");
        },
        prev: function() {
            //console.log("prev first:" + this.first + ", last: " + this.last + ", size: " + this.size());
            if (this.locked || this.first == 0) return; //We are at the beginning of the list
            this.locked = true;
            this.prevLast = this.last;
            this.prevFirst = this.first;
            this.first--;
            this.last--;

            if (this.options.loadPrevHandler) {
                this.options.loadPrevHandler(this, this.prevFirst, this.prevLast);
            }
            //recalculate item dimension
            var clipDimension = this.getClipDimension();
            var itemDimension = Math.ceil(clipDimension / this.options.numVisible);
            this.animate(itemDimension * this.options.scrollInc, "prev");
        },
        addOrUpdate: function(refId, html) {
            var self = this;
            var currentItem = this.list.children(self.listItemClass + refId);
            if (currentItem.size() > 0) {
                currentItem.html(html);
            } else {
                if (this.first + this.options.scrollInc - 1 > this.last) {
                    this.last++;
                }
               this.list.append($("<li/>").addClass(self.listItemClass + refId).html(html));
            }
        },

        size: function() {
            return this.list.children("li").size();
        },

        /*
            Private methods
        */
        animate: function(animationDimension, direction) {
            if (this.animating) return;
            this.animating = true;
            this.lastDirection = direction;

            if (this.options.animation.animationStartCallback) {
                this.options.animation.animationStartCallback(this, direction);
            }

            var listOffset = this.getListOffset();
            var newListOffset = listOffset + animationDimension;
            //log("animationDimension:" + animationDimension);
            //log("listOffset:" + listOffset);
            //log("newListOffset:" + newListOffset);
            
            if ($.browser.webkit && this.options.animation.css3) {
                var numAlreadyScrolled = Math.ceil(this.first / this.options.scrollInc);
                var animation = this.options.animation.css3.animation;
                var duration = this.options.animation.css3.duration;
                var easing = this.options.animation.css3.easing;
                if (this.options.animation.css3.animation == "slide") {
                    this.list.css("-webkit-transition-property", "-webkit-transform");
                    this.list.css("-webkit-transition-duration", duration);
                    this.list.css("-webkit-transition-timing-function", easing);
                    var newTranslateX = animationDimension * numAlreadyScrolled;
                    if (animationDimension > 0) {
                        newTranslateX = -newTranslateX;
                    }
                    this.list.css("-webkit-transform", "translate3d(" + newTranslateX + "px, 0, 0)");
                }
            } else {
                var self = this;
                var jQueryAnimationOptions = this.options.vertical ? {'top': newListOffset}: {'left': newListOffset};
                this.list.animate(jQueryAnimationOptions,
                                  this.options.animation.jquery.animation,
                                  this.options.animation.jquery.easing, function() {
                                      self.animating = false;
                                      self.list.css(self.offsetProperty, newListOffset + "px");
                                      if (self.options.animation.animationEndCallback) {
                                          self.locked = false;
                                          self.options.animation.animationEndCallback(direction, self.isJump);
                                      }
                                  });
            }
        },
        getItemDimension: function() {
            var items = this.list.children("li");
            var itemDimension = 0;
            var self = this;
            var fakeAdd = false;
            if (items.size() == 0) {
                this.addOrUpdate(0, "");
                fakeAdd = true;
            }
            items = this.list.children("li");
            items.each(function(index){
                var $item = $(this);
                itemDimension = self.dimension($item);
            });
            if (fakeAdd) {
                this.list.html("");
            }
            return itemDimension;
        },
        getClipDimension: function() {
            if (this.options.vertical) {
                return this.clip.height();
            }
            return this.clip.width();
        },

        getListOffset: function() {
            if (this.options.vertical) {
                return this.list[0].offsetTop;
            }
            return this.list[0].offsetLeft;
        },

        dimension: function(e) {
            var htmlElement = e.jquery != undefined ? e[0] : e;
            var jElement = $(e);
            if (this.options.vertical) {
                return jElement.outerHeight(true);
            }
            return jElement.outerWidth(true);
        }
    });
})(jQuery);
