/**
 * jCarousel - Riding carousels with jQuery
 *   http://sorgalla.com/jcarousel/
 *
 * 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(e, o) {
    this.init(e, o);
};

jQuery.jcarousel.prototype = {
    init: function(e, o) {
        this.o = {
            orientation: "horizontal",
            itemVisible: 3,
            itemScroll: null,
            scrollAnimation: "fast",
            autoScroll: 0,
            autoScrollStopOnInteract: true,
            wrap: false,
            itemWidth: null,
            itemHeight: null,
            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>'
        };

        if (o)
          jQuery.extend(this.o, o);

        this.horiz = this.o.orientation == "vertical" ? false : true;
        this.o.itemScroll = this.o.itemScroll || this.o.itemVisible;
        this.autoTimer = null;

        this.prepare(e);

        this.size = jQuery("li", this.list).size();
        this.end = this.size;

        this.itemFormat = {
            "float": "left",
            "styleFloat": "left",
            "overflow": "hidden",
            "listStyle": "none"
        };

        if (this.o.itemWidth)
            this.itemFormat.width  = this.o.itemWidth + "px";

        if (this.o.itemHeight)
             this.itemFormat.height = this.o.itemHeight + "px";

        this.calc();
        this.resize();

        var carousel = this;
        var idx = 1;
        jQuery("li", this.list).each(function() {
            carousel.format(this, idx++);
        });

        this.nextClick = function() { carousel.next(); };
        this.prevClick = function() { carousel.prev(); };

        this.buttons(false, false);

        this.first = this.prevFirst = this.o.itemVisible * -1 + 1;
        this.last = this.prevLast = 0;

        this.inAnimation = false;

        this.load(1, this.o.itemVisible);
        this.scroll(1);
        this.startAuto();
    },

    scope: function() {
        return this.scope;
    },

    get: function(idx) {
        return jQuery(".jcarousel-item-" + 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)
            jQuery(this.list).width(this.size * this.itemD + 100 + "px");
        else
            jQuery(this.list).height(this.size * this.itemD + 100 + "px");
    },

    format: function(item, idx) {
        return jQuery(item).css(this.itemFormat).addClass("jcarousel-item-" + idx);
    },

    scroll: function(idx) {
        if (this.inAnimation) return;
        this.inAnimation = false;

        this.prevFirst = this.first;
        this.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;

        this.animate();
    },

    animate: function() {
        var pos = this.itemD * (this.first - 1) * -1;

        if (this.o.scrollAnimation) {
            this.inAnimation = true;
            var carousel = this;
            jQuery(this.list).animate(this.horiz ? {"left": pos} : {"top": pos}, this.o.scrollAnimation, function() { carousel.scrolled(); });
        } else {
            jQuery(this.list)[this.horiz ? "left" : "top"](pos + "px");
            this.scrolled();
        }
    },

    scrolled: function() {
        if (this.first == 1)
            jQuery(this.list).top(this.top + "px").left(this.left + "px");

        this.inAnimation = false;
        this.notify(this.prevFirst, this.prevLast, this.first, this.last);
        this.load(this.last + 1, this.last + this.o.itemScroll);
    },

    notify: function(prevFirst, prevLast, first, last) {
        var carousel = this;

        if (prevFirst != first) {
            if (this.o.itemFirstOutHandler != null)
                this.get(prevFirst).each(function() { carousel.o.itemFirstOutHandler(carousel, this, prevFirst); });

            if (this.o.itemFirstInHandler != null)
                this.get(first).each(function() { carousel.o.itemFirstInHandler(carousel, this, first); });
        }

        if (prevLast != last) {
            if (this.o.itemLastOutHandler != null)
                this.get(prevLast).each(function() { carousel.o.itemLastOutHandler(carousel, this, prevLast); });

            if (this.o.itemLastInHandler != null)
                this.get(last).each(function() { carousel.o.itemLastInHandler(carousel, this, last); });
        }

        if (this.o.itemVisibleInHandler != null) {
            for (var i = first; i <= last; i++) {
                if (!(i >= prevFirst && i <= prevLast))
                    this.get(i).each(function() { carousel.o.itemVisibleInHandler(carousel, this, i); });
            }
        }

        if (this.o.itemVisibleOutHandler != null) {
            for (var i = prevFirst; i <= prevLast; i++) {
                if (!(i >= first && i <= last))
                    this.get(i).each(function() { carousel.o.itemVisibleOutHandler(carousel, this, i); });
            }
        }
    },

    buttons: function(next, prev) {
        if (this.o.noButtons) return;

        var carousel = this;

        if (this.o.nextButtonStateHandler != null)
            jQuery(".jcarousel-next", this.scope).each(function() { carousel.o.nextButtonStateHandler(carousel, this, next); });

        if (this.o.prevButtonStateHandler != null)
            jQuery(".jcarousel-prev", this.scope).each(function() { carousel.o.prevButtonStateHandler(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)
            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();
    },

    margin: function(e, p) {
        if (p == "marginRight" && jQuery.browser.safari) {
            var old = {"display": "block", "float": "none", "width": "auto"}, oWidth, oWidth2;

            jQuery.swap(e, old, function() { oWidth = e.offsetWidth; });

            old["marginRight"] = 0;
            jQuery.swap(e, old, function() { oWidth2 = e.offsetWidth; });

            return oWidth2 - oWidth;
        }

        return this.intval(jQuery.css(e, p));
    },

    calc: function() {
        if (this.size == 0) {
            var dummy = this.format(document.createElement("li"), 1).get(0);
            this.list.appendChild(dummy);
        }

        var i = jQuery("li", this.list).get(0);

        var itemWidth  = i.offsetWidth + this.margin(i, "marginLeft") + this.margin(i, "marginRight");
        var itemHeight = i.offsetHeight + this.margin(i, "marginTop") + this.margin(i, "marginBottom");

        if (this.horiz) {
            this.itemD = itemWidth;
            var clipW  = itemWidth * this.o.itemVisible - this.margin(i, "marginRight");
            var clipH  = itemHeight;
        } else {
            this.itemD = itemHeight;
            var clipW  = itemWidth;
            var clipH  = itemHeight * this.o.itemVisible - this.margin(i, "marginBottom");
        }

        jQuery(".jcarousel-clip", this.scope).css({
            "zIndex": 2,
            "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({
            "zIndex": 1,
            "position": "relative",
            "top": this.top + "px",
            "left": this.left + "px",
            "margin": 0,
            "padding": 0
        });

        if (dummy != undefined)
            this.list.removeChild(dummy);
    },

    prepare: function(e) {
        if (e.nodeName == "UL" || e.nodeName == "OL") {
            this.list = e;

            var scope = jQuery(this.list).parent().get(0);
            if (jQuery.className.has(scope, "jcarousel-clip")) {
                if (!jQuery.className.has(jQuery(scope).parent().get(0), "jcarousel-scope")) {
                    scope = jQuery(scope).wrap('<div class="jcarousel-scope"></div>');
                }
                scope = jQuery(scope).parent().get(0);
            } else if (!jQuery.className.has(scope, "jcarousel-scope")) {
                scope = jQuery(this.list).wrap('<div class="jcarousel-scope"></div>').parent().get(0);
            }

            this.scope = scope;
        } else {
            this.scope = e;
            this.list = jQuery("ul", this.scope).get(0) || jQuery("ol", this.scope).get(0);
        }

        if (!jQuery.className.has(jQuery(this.list).parent().get(0), "jcarousel-clip"))
            jQuery(this.list).wrap('<div class="jcarousel-clip"></div>');

        if (!this.o.noButtons) {
            if (jQuery(".jcarousel-prev", this.scope).size() == 0) {
                var dummy = jQuery(document.createElement("div")).html(this.o.buttonPrevHTML).get(0);
                jQuery(".jcarousel-clip", this.scope).before(jQuery(dummy.firstChild).addClass("jcarousel-prev"));
            }

            if (jQuery(".jcarousel-next", this.scope).size() == 0) {
                var dummy = jQuery(document.createElement("div")).html(this.o.buttonNextHTML).get(0);
                jQuery(".jcarousel-clip", this.scope).before(jQuery(dummy.firstChild).addClass("jcarousel-next"));
            }

            jQuery(".jcarousel-prev", this.scope).css({"zIndex": 3});
            jQuery(".jcarousel-next", this.scope).css({"zIndex": 3});
        }

        jQuery(this.list).addClass("jcarousel-list");
        jQuery(this.scope).addClass("jcarousel-scope").show().find(":hidden").show();
    },

    intval: function(v) {
        v = parseInt(v);
        return isNaN(v) ? 0 : v;
    }
};
