/**
 * jCarousel - Riding carousels with jQuery
 *
 * Written by Jan Sorgalla
 *   http://sorgalla.com
 *
 * Licensed under the MIT License
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Built on top of the jQuery library
 *   http://jquery.com
 *
 * Inspired by the "Carousel Component" by Bill Scott
 *   http://billwscott.com/carousel/
 */

jQuery.fn.extend({
    jcarousel: function(o) {
        return this.each(function() {
            new jQuery.jcarousel(this, o);
        });
    }
});

jQuery.jcarousel = function(c, o) {
    this.init(c, o);
};

jQuery.jcarousel.prototype = {

    o: {
        orientation: "horizontal",
        itemWidth: null,
        itemHeight: null,
        itemVisible: 3,
        itemScroll: null, // Will be set to itemVisible in init() if not passed
        scrollAnimation: "fast",
        autoScroll: 0,
        autoScrollStopOnInteract: true,
        wrap: false,
        loadItemHandler: null,
        nextButtonStateHandler: null,
        prevButtonStateHandler: null,
        itemFirstInHandler: null,
        itemFirstOutHandler: null,
        itemLastInHandler: null,
        itemLastOutHandler: null,
        itemVisibleInHandler: null,
        itemVisibleOutHandler: null,
        noButtons: false,
        buttonNextHTML: '<button type="button">&gt;&gt;</button>',
        buttonPrevHTML: '<button type="button">&lt;&lt;</button>'
    },

    init: function(c, o) {
        if (o) {
            jQuery.extend(this.o, o);
        }

        this.horiz = this.o.orientation == "vertical" ? false : true;

        this.scope = this.prepare(c);
        this.list  = jQuery("ul", this.scope).get(0) || jQuery("ol", this.scope).get(0);

        // Set itemScroll to itemVisible if not set
        this.o.itemScroll = this.o.itemScroll || this.o.itemVisible;
        this.autoTimer = null;

        this.size = jQuery("li", this.list).size();
        this.end = this.size;

        this.nextClick = function() { carousel.next(); };
        this.prevClick = function() { carousel.prev(); };

        var carousel = this;

        this.itemFormat = {
            "float": "left",
            "styleFloat": "left",
            "overflow": "hidden",
            "listStyle": "none"
        };

        this.calc();

        if (this.size > 0) {
            this.resize();

            var idx = 1;

            jQuery("li", this.list).each(function() {
                carousel.format(this, idx++);
            });
        }

        this.buttons(false, false);

        this.first = this.o.itemVisible * -1 + 1;
        this.last  = 0;

        this.inAnimation = false;

        // Preload first items
        this.load(1, this.o.itemVisible);
        this.scroll(1);
        this.startAuto();
    },

    scope: function() {
        return this.scope;
    },

    get: function(idx) {
        return jQuery("li[@jCarouselItemIdx=" + idx + "]", this.list);
    },

    add: function(idx, s) {
        var item = this.get(idx);

        if (item.size() == 0) {
            var item = this.format(document.createElement("li"), idx);
            jQuery(this.list).append(item);

            this.size++;
            this.resize();
        }

        item.html(s);

        return item;
    },

    available: function(first, last) {
        if (this.end >= last) return true;

        this.end = last;
        return false;
    },

    loaded: function() {
        if (this.first > 1 && this.last < this.size) {
            this.buttons(true, true);
        } else if (this.first == 1 && this.last < this.size) {
            this.buttons(true, false);
        } else if (this.first > 1 && this.last >= this.size) {
            this.buttons(this.o.wrap, true);
        }
    },

    next: function() {
        this.stopAuto();

        if (this.o.autoScrollStopOnInteract) {
            this.disableAuto();
        }

        this.doNext();
    },

    prev: function() {
        this.stopAuto();

        if (this.o.autoScrollStopOnInteract) {
            this.disableAuto();
        }

        this.doPrev();
    },

    // --- Private ---

    startAuto: function() {
        if (this.o.autoScroll > 0) {
            var carousel = this;
            this.autoTimer = setTimeout(function() { carousel.doNext(); }, this.o.autoScroll * 1000);
        }
    },

    stopAuto: function() {
        if (this.autoTimer !== null) {
            clearTimeout(this.autoTimer);
            this.autoTimer = null;
        }
    },

    disableAuto: function() {
        this.stopAuto();
        this.o.autoScroll = 0;
    },

    resize: function() {
        if (this.size == 0) {
            return;
        }
        
        if (this.horiz) {
            var listW = this.size * this.itemD + 100;
            var listH = this.o.itemHeight;
        } else {
            var listW = this.o.itemWidth;
            var listH = this.size * this.itemD + 100;
        }
        
        jQuery(this.list).width(listW + "px").height(listH + "px");
    },

    format: function(item, idx) {
        return jQuery(item).css(this.itemFormat).attr("jCarouselItemIdx", idx);
    },

    scroll: function(idx) {
        if (this.inAnimation) return;
        this.inAnimation = false;

        var prevFirst = this.first;
        var prevLast  = this.last;

        idx = idx < 1 ? 1 : idx;

        var last = idx + this.o.itemVisible - 1;
        last = (last > this.size) ? this.size : last;

        var first = last - this.o.itemVisible + 1;
        first = (first < 1) ? 1 : first;

        last = first + this.o.itemVisible - 1;

        this.first = first;
        this.last  = last;

        var pos = this.itemD * (this.first - 1) * -1;
        var arg = this.horiz ? {"left": pos} : {"top": pos};

        if (this.o.scrollAnimation) {
            this.inAnimation = true;
            var carousel = this;
            jQuery(this.list).animate(arg, this.o.scrollAnimation, function() { carousel.scrolled(); });
        } else {
            jQuery(this.list)[this.horiz ? "left" : "top"](pos + "px");
            this.scrolled();
        }

        this.trigger(prevFirst, prevLast, this.first, this.last);
    },

    scrolled: function() {
        if (this.first == 1) {
            // For some reason, jQuery.animate() don't want
            // to move the list to the startposition...
            jQuery(this.list).top(this.top + "px").left(this.left + "px");
        }
        this.inAnimation = false;

        // Preload next items
        this.load(this.last + 1, this.last + this.o.itemScroll);
    },

    trigger: function(prevFirst, prevLast, first, last) {
        var carousel = this;

        if (prevFirst != first) {
            if (this.o.itemFirstOutHandler != null) {
                var firstOutHandler = this.o.itemFirstOutHandler;
                this.get(prevFirst).each(function() {
                    firstOutHandler(carousel, this);
                });
            }

            if (this.o.itemFirstInHandler != null) {
                var firstInHandler = this.o.itemFirstInHandler;
                this.get(first).each(function() {
                    firstInHandler(carousel, this);
                });
            }
        }

        if (prevLast != last) {
            if (this.o.itemLastOutHandler != null) {
                var lastOutHandler = this.o.itemLastOutHandler;
                this.get(prevLast).each(function() {
                    lastOutHandler(carousel, this);
                });
            }

            if (this.o.itemLastInHandler != null) {
                var lastInHandler = this.o.itemLastInHandler;
                this.get(last).each(function() {
                    lastInHandler(carousel, this);
                });
            }
        }

        if (this.o.itemVisibleInHandler != null) {
            var visibleInHandler = this.o.itemVisibleInHandler;
            for (var i = first; i <= last; i++) {
                if (!(i >= prevFirst && i <= prevLast)) {
                    this.get(i).each(function() {
                         visibleInHandler(carousel, this);
                   });
                }
            }
        }

        if (this.o.itemVisibleOutHandler != null) {
            var visibleOutHandler = this.o.itemVisibleOutHandler;
            for (var i = prevFirst; i <= prevLast; i++) {
                if (!(i >= first && i <= last)) {
                    this.get(i).each(function() {
                        visibleOutHandler(carousel, this);
                    });
                }
            }
        }
    },

    buttons: function(next, prev) {
        if (this.o.noButtons) return;

        var carousel = this;

        if (this.o.nextButtonStateHandler != null) {
            var nextHandler = this.o.nextButtonStateHandler;
            jQuery(".jcarousel-next", this.scope).each(function() {
                nextHandler(carousel, this, next);
            });
        }

        if (this.o.prevButtonStateHandler != null) {
            var prevHandler = this.o.prevButtonStateHandler;
            jQuery(".jcarousel-prev", this.scope).each(function() {
                prevHandler(carousel, this, prev);
            });
        }

        jQuery(".jcarousel-next", this.scope)[next ? "bind" : "unbind"]("click", this.nextClick)[next ? "removeClass" : "addClass"]("jcarousel-next-disabled")[next ? "removeAttr" : "attr"]("disabled", true);
        jQuery(".jcarousel-prev", this.scope)[prev ? "bind" : "unbind"]("click", this.prevClick)[prev ? "removeClass" : "addClass"]("jcarousel-prev-disabled")[prev ? "removeAttr" : "attr"]("disabled", true);
    },

    load: function(first, last) {
        this.buttons(false, false);

        if (this.o.loadItemHandler != null) {
            // loaded() must be called by loadItemHandler()
            this.o.loadItemHandler(this, first, last, this.available(first, last));
        } else {
            this.loaded();
        }
    },

    doNext: function() {
        this.scroll((this.o.wrap && this.last == this.size) ? 1 : this.first + this.o.itemScroll);

        if (this.o.wrap || this.last < this.size) {
            this.startAuto();
        }
    },

    doPrev: function() {
        this.scroll(this.first - this.o.itemScroll);
        this.startAuto();
    },

    calc: function() {
        if (this.size == 0) {
            var dummy = document.createElement("li");
            this.list.appendChild(dummy);
        }

        var defW = 75;
        var defH = 75;

        var i = jQuery(jQuery("li", this.list).get(0));
        var lr = this.intval(i.css("marginLeft"));
            lr += this.intval(i.css("marginRight"));
            lr += this.intval(i.css("paddingLeft"));
            lr += this.intval(i.css("paddingRight"));
            lr += this.intval(i.css("borderLeftWidth"));
            lr += this.intval(i.css("borderRightWidth"));

        var tb = this.intval(i.css("marginTop"));
            tb += this.intval(i.css("marginBottom"));
            tb += this.intval(i.css("paddingTop"));
            tb += this.intval(i.css("paddingBottom"));
            tb += this.intval(i.css("borderTopWidth"));
            tb += this.intval(i.css("borderBottomWidth"));

        if (!this.o.itemWidth) {
            // Try to get itemWidth from the first item if not passed as option.
            // May fail on IE (returns "auto" of not explicitly set)
            if (this.intval(i.width()) > 0) {
                this.o.itemWidth = this.intval(i.width()) + lr;
            } else {
                this.itemFormat.width = defW - lr + "px";
                this.o.itemWidth = defW;
            }
        } else {
            this.itemFormat.width = this.o.itemWidth - lr + "px";
        }

        if (!this.o.itemHeight) {
            // Try to get itemHeight from the first item if not passed as option.
            // May fail on IE (returns "auto" of not explicitly set)
            if (this.intval(i.height()) > 0) {
                this.o.itemHeight = this.intval(i.height()) + tb;
            } else {
                this.itemFormat.height = defH - tb + "px";
                this.o.itemHeight = defH;
            }
        } else {
            this.itemFormat.height = this.o.itemHeight - tb + "px";
        }


        if (this.horiz) {
            this.itemD = this.o.itemWidth;
            var clipW  = this.o.itemWidth * this.o.itemVisible;
            var clipH  = this.o.itemHeight;
        } else {
            this.itemD = this.o.itemHeight;
            var clipW  = this.o.itemWidth;
            var clipH  = this.o.itemHeight * this.o.itemVisible;
        }
	
        jQuery(".jcarousel-clip", this.scope).css({
            "zIndex": 3,
            "padding": 0,
            "margin": 0,
            "width":  clipW + "px",
            "height": clipH + "px",
            "overflow": "hidden",
            "position": "relative"
        });

        this.top  = this.intval(jQuery(this.list).top());
        this.left = this.intval(jQuery(this.list).left());

        jQuery(this.list).css({
            // "top" and "left" must be set, jQuery.animate() fails
            // otherwise on first animation
            "zIndex": 1,
            "position": "relative",
            "top": this.top + "px",
            "left": this.left + "px",
            "margin": 0,
            "padding": 0,
            "overflow": "hidden"
        }).width(1000 + "px");

        if (dummy != undefined) {
            this.list.removeChild(dummy);
        }
    },

    prepare: function(c) {
        if (c.nodeName == "UL" || c.nodeName == "OL") {
            // Full wrapping
            c = jQuery(c).addClass("jcarousel-list")
                         .show()
                         .wrap('<div class="jcarousel-scope"><div class="jcarousel-clip"></div></div>').parent();

            if (!this.o.noButtons) {
                var dummy = document.createElement("div");
                dummy.innerHTML = this.o.buttonPrevHTML;
                c.before(jQuery(dummy.firstChild).addClass("jcarousel-prev"));
                dummy.innerHTML = this.o.buttonNextHTML;
                c.before(jQuery(dummy.firstChild).addClass("jcarousel-next"));
            }

            // Show it, possibly hidden from browsers having javascript disabled
            return c.parent().get(0);
        }

        var list = jQuery("ul", c) || jQuery("ol", c);
        list.addClass("jcarousel-list");

        // Check for clip
        if (jQuery(".jcarousel-clip", c).size() == 0) {
            list.wrap('<div class="jcarousel-clip"></div>');
        }

        if (!this.o.noButtons) {
            // Check for prev
            if (jQuery(".jcarousel-prev", c).size() == 0) {
                var dummy = jQuery(document.createElement("div")).html(this.o.buttonPrevHTML).get(0);
                jQuery(".jcarousel-clip", c).before(jQuery(dummy.firstChild).addClass("jcarousel-prev"));
            }

            // Check for next
            if (jQuery(".jcarousel-next", c).size() == 0) {
                var dummy = jQuery(document.createElement("div")).html(this.o.buttonNextHTML).get(0);
                jQuery(".jcarousel-clip", c).before(jQuery(dummy.firstChild).addClass("jcarousel-next"));
            }
        }

        jQuery(c).addClass("jcarousel-scope").show().find(":hidden").show();

        return c;
    },

    intval: function(v) {
        v = parseInt(v);
        return isNaN(v) ? 0 : v;
    }
};
