/*!
 * jCarousel - Riding carousels with jQuery
 *   http://sorgalla.com/jcarousel/
 *
 * Copyright (c) 2006 Jan Sorgalla (http://sorgalla.com)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Built on top of the jQuery library
 *   http://jquery.com
 *
 * Inspired by the "Carousel Component" by Bill Scott
 *   http://billwscott.com/carousel/
 */

/*global window, jQuery */
(function($) {
    // Default configuration properties.
    var defaults = {
        vertical: false,
        rtl: false,
        start: 1,
        offset: 1,
        size: null,
        scroll: 3,
        visible: null,
        animation: 'normal',
        easing: 'swing',
        auto: 0,
        wrap: null,
        initCallback: null,
        reloadCallback: null,
        itemLoadCallback: null,
        itemFirstInCallback: null,
        itemFirstOutCallback: null,
        itemLastInCallback: null,
        itemLastOutCallback: null,
        itemVisibleInCallback: null,
        itemVisibleOutCallback: null,
        buttonNextHTML: '<div></div>',
        buttonPrevHTML: '<div></div>',
        buttonNextEvent: 'click',
        buttonPrevEvent: 'click',
        buttonNextCallback: null,
        buttonPrevCallback: null,
        itemFallbackDimension: null
    }, windowLoaded = false;

    $(window).bind('load.jcarousel', function() { windowLoaded = true; });

    /**
     * The jCarousel object.
     *
     * @constructor
     * @class jcarousel
     * @param e {HTMLElement} The element to create the carousel for.
     * @param o {Object} A set of key/value pairs to set as configuration properties.
     * @cat Plugins/jCarousel
     */
    $.jcarousel = function(e, o) {
        this.options    = $.extend({}, defaults, o || {});

        this.locked          = false;
        this.autoStopped     = false;

        this.container       = null;
        this.clip            = null;
        this.list            = null;
        this.buttonNext      = null;
        this.buttonPrev      = null;
        this.buttonNextState = null;
        this.buttonPrevState = null;

        // Only set if not explicitly passed as option
        if (!o || o.rtl === undefined) {
            this.options.rtl = ($(e).attr('dir') || $('html').attr('dir') || '').toLowerCase() == 'rtl';
        }

        this.wh = !this.options.vertical ? 'width' : 'height';
        this.lt = !this.options.vertical ? (this.options.rtl ? 'right' : 'left') : 'top';

        // Extract skin class
        var skin = '', split = e.className.split(' ');

        for (var i = 0; i < split.length; i++) {
            if (split[i].indexOf('jcarousel-skin') != -1) {
                $(e).removeClass(split[i]);
                skin = split[i];
                break;
            }
        }

        if (e.nodeName.toUpperCase() == 'UL' || e.nodeName.toUpperCase() == 'OL') {
            this.list = $(e);
            this.container = this.list.parent();

            if (this.container.hasClass('jcarousel-clip')) {
                if (!this.container.parent().hasClass('jcarousel-container')) {
                    this.container = this.container.wrap('<div></div>');
                }

                this.container = this.container.parent();
            } else if (!this.container.hasClass('jcarousel-container')) {
                this.container = this.list.wrap('<div></div>').parent();
            }
        } else {
            this.container = $(e);
            this.list = this.container.find('ul,ol').eq(0);
        }

        if (skin !== '' && this.container.parent()[0].className.indexOf('jcarousel-skin') == -1) {
            this.container.wrap('<div class=" '+ skin + '"></div>');
        }

        this.clip = this.list.parent();

        if (!this.clip.length || !this.clip.hasClass('jcarousel-clip')) {
            this.clip = this.list.wrap('<div></div>').parent();
        }

        this.buttonNext = $('.jcarousel-next', this.container);

        if (this.buttonNext.size() === 0 && this.options.buttonNextHTML !== null) {
            this.buttonNext = this.clip.after(this.options.buttonNextHTML).next();
        }

        this.buttonNext.addClass(this.className('jcarousel-next'));

        this.buttonPrev = $('.jcarousel-prev', this.container);

        if (this.buttonPrev.size() === 0 && this.options.buttonPrevHTML !== null) {
            this.buttonPrev = this.clip.after(this.options.buttonPrevHTML).next();
        }

        this.buttonPrev.addClass(this.className('jcarousel-prev'));

        this.clip.addClass(this.className('jcarousel-clip')).css({
            overflow: 'hidden',
            position: 'relative'
        });

        this.list.addClass(this.className('jcarousel-list')).css({
            overflow: 'hidden',
            position: 'relative',
            top: 0,
            margin: 0,
            padding: 0
        }).css((this.options.rtl ? 'right' : 'left'), 0);

        this.container.addClass(this.className('jcarousel-container')).css({
            position: 'relative'
        });

        if (!this.options.vertical && this.options.rtl) {
            this.container.addClass('jcarousel-direction-rtl').attr('dir', 'rtl');
        }

        var di = this.options.visible !== null ? Math.ceil(this.clipping() / this.options.visible) : null;
        var li = this.list.children('li');

        var self = this;

        if (li.size() > 0) {
            var wh = 0, j = this.options.offset;
            li.each(function() {
                self.format(this, j++);
                wh += self.dimension(this, di);
            });

            this.list.css(this.wh, (wh + 100) + 'px');

            // Only set if not explicitly passed as option
            if (!o || o.size === undefined) {
                this.options.size = li.size();
            }
        }

        // For whatever reason, .show() does not work in Safari...
        this.container.css('display', 'block');
        this.buttonNext.css('display', 'block');
        this.buttonPrev.css('display', 'block');

        this.funcNext   = function() { self.next(); };
        this.funcPrev   = function() { self.prev(); };
        this.funcResize = function() { self.reload(); };

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

        if (!windowLoaded && $.browser.safari) {
            this.buttons(false, false);
            $(window).bind('load.jcarousel', function() { self.setup(); });
        } else {
            this.setup();
        }
    };

    // Create shortcut for internal use
    var $jc = $.jcarousel;

    $jc.fn = $jc.prototype = {
        jcarousel: '0.2.7'
    };

    $jc.fn.extend = $jc.extend = $.extend;

    $jc.fn.extend({
        /**
         * Setups the carousel.
         *
         * @method setup
         * @return undefined
         */
        setup: function() {
            this.first     = null;
            this.last      = null;
            this.prevFirst = null;
            this.prevLast  = null;
            this.animating = false;
            this.timer     = null;
            this.tail      = null;
            this.inTail    = false;

            if (this.locked) {
                return;
            }

            this.list.css(this.lt, this.pos(this.options.offset) + 'px');
            var p = this.pos(this.options.start, true);
            this.prevFirst = this.prevLast = null;
            this.animate(p, false);

            $(window).unbind('resize.jcarousel', this.funcResize).bind('resize.jcarousel', this.funcResize);
        },

        /**
         * Clears the list and resets the carousel.
         *
         * @method reset
         * @return undefined
         */
        reset: function() {
            this.list.empty();

            this.list.css(this.lt, '0px');
            this.list.css(this.wh, '10px');

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

            this.setup();
        },

        /**
         * Reloads the carousel and adjusts positions.
         *
         * @method reload
         * @return undefined
         */
        reload: function() {
            if (this.tail !== null && this.inTail) {
                this.list.css(this.lt, $jc.intval(this.list.css(this.lt)) + this.tail);
            }

            this.tail   = null;
            this.inTail = false;

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

            if (this.options.visible !== null) {
                var self = this;
                var di = Math.ceil(this.clipping() / this.options.visible), wh = 0, lt = 0;
                this.list.children('li').each(function(i) {
                    wh += self.dimension(this, di);
                    if (i + 1 < self.first) {
                        lt = wh;
                    }
                });

                this.list.css(this.wh, wh + 'px');
                this.list.css(this.lt, -lt + 'px');
            }

            this.scroll(this.first, false);
        },

        /**
         * Locks the carousel.
         *
         * @method lock
         * @return undefined
         */
        lock: function() {
            this.locked = true;
            this.buttons();
        },

        /**
         * Unlocks the carousel.
         *
         * @method unlock
         * @return undefined
         */
        unlock: function() {
            this.locked = false;
            this.buttons();
        },

        /**
         * Sets the size of the carousel.
         *
         * @method size
         * @return undefined
         * @param s {Number} The size of the carousel.
         */
        size: function(s) {
            if (s !== undefined) {
                this.options.size = s;
                if (!this.locked) {
                    this.buttons();
                }
            }

            return this.options.size;
        },

        /**
         * Checks whether a list element exists for the given index (or index range).
         *
         * @method get
         * @return bool
         * @param i {Number} The index of the (first) element.
         * @param i2 {Number} The index of the last element.
         */
        has: function(i, i2) {
            if (i2 === undefined || !i2) {
                i2 = i;
            }

            if (this.options.size !== null && i2 > this.options.size) {
                i2 = this.options.size;
            }

            for (var j = i; j <= i2; j++) {
                var e = this.get(j);
                if (!e.length || e.hasClass('jcarousel-item-placeholder')) {
                    return false;
                }
            }

            return true;
        },

        /**
         * Returns a jQuery object with list element for the given index.
         *
         * @method get
         * @return jQuery
         * @param i {Number} The index of the element.
         */
        get: function(i) {
            return $('.jcarousel-item-' + i, this.list);
        },

        /**
         * Adds an element for the given index to the list.
         * If the element already exists, it updates the inner html.
         * Returns the created element as jQuery object.
         *
         * @method add
         * @return jQuery
         * @param i {Number} The index of the element.
         * @param s {String} The innerHTML of the element.
         */
        add: function(i, s) {
            var e = this.get(i), old = 0, n = $(s);

            if (e.length === 0) {
                var c, j = $jc.intval(i);
                e = this.create(i);
                while (true) {
                    c = this.get(--j);
                    if (j <= 0 || c.length) {
                        if (j <= 0) {
                            this.list.prepend(e);
                        } else {
                            c.after(e);
                        }
                        break;
                    }
                }
            } else {
                old = this.dimension(e);
            }

            if (n.get(0).nodeName.toUpperCase() == 'LI') {
                e.replaceWith(n);
                e = n;
            } else {
                e.empty().append(s);
            }

            this.format(e.removeClass(this.className('jcarousel-item-placeholder')), i);

            var di = this.options.visible !== null ? Math.ceil(this.clipping() / this.options.visible) : null;
            var wh = this.dimension(e, di) - old;

            if (i > 0 && i < this.first) {
                this.list.css(this.lt, $jc.intval(this.list.css(this.lt)) - wh + 'px');
            }

            this.list.css(this.wh, $jc.intval(this.list.css(this.wh)) + wh + 'px');

            return e;
        },

        /**
         * Removes an element for the given index from the list.
         *
         * @method remove
         * @return undefined
         * @param i {Number} The index of the element.
         */
        remove: function(i) {
            var e = this.get(i);

            // Check if item exists and is not currently visible
            if (!e.length || (i >= this.first && i <= this.last)) {
                return;
            }

            var d = this.dimension(e);

            if (i < this.first) {
                this.list.css(this.lt, $jc.intval(this.list.css(this.lt)) + d + 'px');
            }

            e.remove();

            this.list.css(this.wh, $jc.intval(this.list.css(this.wh)) - d + 'px');
        },

        /**
         * Moves the carousel forwards.
         *
         * @method next
         * @return undefined
         */
        next: function() {
            if (this.tail !== null && !this.inTail) {
                this.scrollTail(false);
            } else {
                this.scroll(((this.options.wrap == 'both' || this.options.wrap == 'last') && this.options.size !== null && this.last == this.options.size) ? 1 : this.first + this.options.scroll);
            }
        },

        /**
         * Moves the carousel backwards.
         *
         * @method prev
         * @return undefined
         */
        prev: function() {
            if (this.tail !== null && this.inTail) {
                this.scrollTail(true);
            } else {
                this.scroll(((this.options.wrap == 'both' || this.options.wrap == 'first') && this.options.size !== null && this.first == 1) ? this.options.size : this.first - this.options.scroll);
            }
        },

        /**
         * Scrolls the tail of the carousel.
         *
         * @method scrollTail
         * @return undefined
         * @param b {Boolean} Whether scroll the tail back or forward.
         */
        scrollTail: function(b) {
            if (this.locked || this.animating || !this.tail) {
                return;
            }

            this.pauseAuto();

            var pos  = $jc.intval(this.list.css(this.lt));

            pos = !b ? pos - this.tail : pos + this.tail;
            this.inTail = !b;

            // Save for callbacks
            this.prevFirst = this.first;
            this.prevLast  = this.last;

            this.animate(pos);
        },

        /**
         * Scrolls the carousel to a certain position.
         *
         * @method scroll
         * @return undefined
         * @param i {Number} The index of the element to scoll to.
         * @param a {Boolean} Flag indicating whether to perform animation.
         */
        scroll: function(i, a) {
            if (this.locked || this.animating) {
                return;
            }

            this.pauseAuto();
            this.animate(this.pos(i), a);
        },

        /**
         * Prepares the carousel and return the position for a certian index.
         *
         * @method pos
         * @return {Number}
         * @param i {Number} The index of the element to scoll to.
         * @param fv {Boolean} Whether to force last item to be visible.
         */
        pos: function(i, fv) {
            var pos  = $jc.intval(this.list.css(this.lt));

            if (this.locked || this.animating) {
                return pos;
            }

            if (this.options.wrap != 'circular') {
                i = i < 1 ? 1 : (this.options.size && i > this.options.size ? this.options.size : i);
            }

            var back = this.first > i;

            // Create placeholders, new list width/height
            // and new list position
            var f = this.options.wrap != 'circular' && this.first <= 1 ? 1 : this.first;
            var c = back ? this.get(f) : this.get(this.last);
            var j = back ? f : f - 1;
            var e = null, l = 0, p = false, d = 0, g;

            while (back ? --j >= i : ++j < i) {
                e = this.get(j);
                p = !e.length;
                if (e.length === 0) {
                    e = this.create(j).addClass(this.className('jcarousel-item-placeholder'));
                    c[back ? 'before' : 'after' ](e);

                    if (this.first !== null && this.options.wrap == 'circular' && this.options.size !== null && (j <= 0 || j > this.options.size)) {
                        g = this.get(this.index(j));
                        if (g.length) {
                            e = this.add(j, g.clone(true));
                        }
                    }
                }

                c = e;
                d = this.dimension(e);

                if (p) {
                    l += d;
                }

                if (this.first !== null && (this.options.wrap == 'circular' || (j >= 1 && (this.options.size === null || j <= this.options.size)))) {
                    pos = back ? pos + d : pos - d;
                }
            }

            // Calculate visible items
            var clipping = this.clipping(), cache = [], visible = 0, v = 0;
            c = this.get(i - 1);
            j = i;

            while (++visible) {
                e = this.get(j);
                p = !e.length;
                if (e.length === 0) {
                    e = this.create(j).addClass(this.className('jcarousel-item-placeholder'));
                    // This should only happen on a next scroll
                    if (c.length === 0) {
                        this.list.prepend(e);
                    } else {
                        c[back ? 'before' : 'after' ](e);
                    }

                    if (this.first !== null && this.options.wrap == 'circular' && this.options.size !== null && (j <= 0 || j > this.options.size)) {
                        g = this.get(this.index(j));
                        if (g.length) {
                            e = this.add(j, g.clone(true));
                        }
                    }
                }

                c = e;
                d = this.dimension(e);
                if (d === 0) {
                    throw new Error('jCarousel: No width/height set for items. This will cause an infinite loop. Aborting...');
                }

                if (this.options.wrap != 'circular' && this.options.size !== null && j > this.options.size) {
                    cache.push(e);
                } else if (p) {
                    l += d;
                }

                v += d;

                if (v >= clipping) {
                    break;
                }

                j++;
            }

             // Remove out-of-range placeholders
            for (var x = 0; x < cache.length; x++) {
                cache[x].remove();
            }

            // Resize list
            if (l > 0) {
                this.list.css(this.wh, this.dimension(this.list) + l + 'px');

                if (back) {
                    pos -= l;
                    this.list.css(this.lt, $jc.intval(this.list.css(this.lt)) - l + 'px');
                }
            }

            // Calculate first and last item
            var last = i + visible - 1;
            if (this.options.wrap != 'circular' && this.options.size && last > this.options.size) {
                last = this.options.size;
            }

            if (j > last) {
                visible = 0;
                j = last;
                v = 0;
                while (++visible) {
                    e = this.get(j--);
                    if (!e.length) {
                        break;
                    }
                    v += this.dimension(e);
                    if (v >= clipping) {
                        break;
                    }
                }
            }

            var first = last - visible + 1;
            if (this.options.wrap != 'circular' && first < 1) {
                first = 1;
            }

            if (this.inTail && back) {
                pos += this.tail;
                this.inTail = false;
            }

            this.tail = null;
            if (this.options.wrap != 'circular' && last == this.options.size && (last - visible + 1) >= 1) {
                var m = $jc.margin(this.get(last), !this.options.vertical ? 'marginRight' : 'marginBottom');
                if ((v - m) > clipping) {
                    this.tail = v - clipping - m;
                }
            }

            if (fv && i === this.options.size && this.tail) {
                pos -= this.tail;
                this.inTail = true;
            }

            // Adjust position
            while (i-- > first) {
                pos += this.dimension(this.get(i));
            }

            // Save visible item range
            this.prevFirst = this.first;
            this.prevLast  = this.last;
            this.first     = first;
            this.last      = last;

            return pos;
        },

        /**
         * Animates the carousel to a certain position.
         *
         * @method animate
         * @return undefined
         * @param p {Number} Position to scroll to.
         * @param a {Boolean} Flag indicating whether to perform animation.
         */
        animate: function(p, a) {
            if (this.locked || this.animating) {
                return;
            }

            this.animating = true;

            var self = this;
            var scrolled = function() {
                self.animating = false;

                if (p === 0) {
                    self.list.css(self.lt,  0);
                }

                if (!self.autoStopped && (self.options.wrap == 'circular' || self.options.wrap == 'both' || self.options.wrap == 'last' || self.options.size === null || self.last < self.options.size || (self.last == self.options.size && self.tail !== null && !self.inTail))) {
                    self.startAuto();
                }

                self.buttons();
                self.notify('onAfterAnimation');

                // This function removes items which are appended automatically for circulation.
                // This prevents the list from growing infinitely.
                if (self.options.wrap == 'circular' && self.options.size !== null) {
                    for (var i = self.prevFirst; i <= self.prevLast; i++) {
                        if (i !== null && !(i >= self.first && i <= self.last) && (i < 1 || i > self.options.size)) {
                            self.remove(i);
                        }
                    }
                }
            };

            this.notify('onBeforeAnimation');

            // Animate
            if (!this.options.animation || a === false) {
                this.list.css(this.lt, p + 'px');
                scrolled();
            } else {
                var o = !this.options.vertical ? (this.options.rtl ? {'right': p} : {'left': p}) : {'top': p};
                this.list.animate(o, this.options.animation, this.options.easing, scrolled);
            }
        },

        /**
         * Starts autoscrolling.
         *
         * @method auto
         * @return undefined
         * @param s {Number} Seconds to periodically autoscroll the content.
         */
        startAuto: function(s) {
            if (s !== undefined) {
                this.options.auto = s;
            }

            if (this.options.auto === 0) {
                return this.stopAuto();
            }

            if (this.timer !== null) {
                return;
            }

            this.autoStopped = false;

            var self = this;
            this.timer = window.setTimeout(function() { self.next(); }, this.options.auto * 1000);
        },

        /**
         * Stops autoscrolling.
         *
         * @method stopAuto
         * @return undefined
         */
        stopAuto: function() {
            this.pauseAuto();
            this.autoStopped = true;
        },

        /**
         * Pauses autoscrolling.
         *
         * @method pauseAuto
         * @return undefined
         */
        pauseAuto: function() {
            if (this.timer === null) {
                return;
            }

            window.clearTimeout(this.timer);
            this.timer = null;
        },

        /**
         * Sets the states of the prev/next buttons.
         *
         * @method buttons
         * @return undefined
         */
        buttons: function(n, p) {
            if (n == null) {
                n = !this.locked && this.options.size !== 0 && ((this.options.wrap && this.options.wrap != 'first') || this.options.size === null || this.last < this.options.size);
                if (!this.locked && (!this.options.wrap || this.options.wrap == 'first') && this.options.size !== null && this.last >= this.options.size) {
                    n = this.tail !== null && !this.inTail;
                }
            }

            if (p == null) {
                p = !this.locked && this.options.size !== 0 && ((this.options.wrap && this.options.wrap != 'last') || this.first > 1);
                if (!this.locked && (!this.options.wrap || this.options.wrap == 'last') && this.options.size !== null && this.first == 1) {
                    p = this.tail !== null && this.inTail;
                }
            }

            var self = this;

            if (this.buttonNext.size() > 0) {
                this.buttonNext.unbind(this.options.buttonNextEvent + '.jcarousel', this.funcNext);

                if (n) {
                    this.buttonNext.bind(this.options.buttonNextEvent + '.jcarousel', this.funcNext);
                }

                this.buttonNext[n ? 'removeClass' : 'addClass'](this.className('jcarousel-next-disabled')).attr('disabled', n ? false : true);

                if (this.options.buttonNextCallback !== null && this.buttonNext.data('jcarouselstate') != n) {
                    this.buttonNext.each(function() { self.options.buttonNextCallback(self, this, n); }).data('jcarouselstate', n);
                }
            } else {
                if (this.options.buttonNextCallback !== null && this.buttonNextState != n) {
                    this.options.buttonNextCallback(self, null, n);
                }
            }

            if (this.buttonPrev.size() > 0) {
                this.buttonPrev.unbind(this.options.buttonPrevEvent + '.jcarousel', this.funcPrev);

                if (p) {
                    this.buttonPrev.bind(this.options.buttonPrevEvent + '.jcarousel', this.funcPrev);
                }

                this.buttonPrev[p ? 'removeClass' : 'addClass'](this.className('jcarousel-prev-disabled')).attr('disabled', p ? false : true);

                if (this.options.buttonPrevCallback !== null && this.buttonPrev.data('jcarouselstate') != p) {
                    this.buttonPrev.each(function() { self.options.buttonPrevCallback(self, this, p); }).data('jcarouselstate', p);
                }
            } else {
                if (this.options.buttonPrevCallback !== null && this.buttonPrevState != p) {
                    this.options.buttonPrevCallback(self, null, p);
                }
            }

            this.buttonNextState = n;
            this.buttonPrevState = p;
        },

        /**
         * Notify callback of a specified event.
         *
         * @method notify
         * @return undefined
         * @param evt {String} The event name
         */
        notify: function(evt) {
            var state = this.prevFirst === null ? 'init' : (this.prevFirst < this.first ? 'next' : 'prev');

            // Load items
            this.callback('itemLoadCallback', evt, state);

            if (this.prevFirst !== this.first) {
                this.callback('itemFirstInCallback', evt, state, this.first);
                this.callback('itemFirstOutCallback', evt, state, this.prevFirst);
            }

            if (this.prevLast !== this.last) {
                this.callback('itemLastInCallback', evt, state, this.last);
                this.callback('itemLastOutCallback', evt, state, this.prevLast);
            }

            this.callback('itemVisibleInCallback', evt, state, this.first, this.last, this.prevFirst, this.prevLast);
            this.callback('itemVisibleOutCallback', evt, state, this.prevFirst, this.prevLast, this.first, this.last);
        },

        callback: function(cb, evt, state, i1, i2, i3, i4) {
            if (this.options[cb] == null || (typeof this.options[cb] != 'object' && evt != 'onAfterAnimation')) {
                return;
            }

            var callback = typeof this.options[cb] == 'object' ? this.options[cb][evt] : this.options[cb];

            if (!$.isFunction(callback)) {
                return;
            }

            var self = this;

            if (i1 === undefined) {
                callback(self, state, evt);
            } else if (i2 === undefined) {
                this.get(i1).each(function() { callback(self, this, i1, state, evt); });
            } else {
                var call = function(i) {
                    self.get(i).each(function() { callback(self, this, i, state, evt); });
                };
                for (var i = i1; i <= i2; i++) {
                    if (i !== null && !(i >= i3 && i <= i4)) {
                        call(i);
                    }
                }
            }
        },

        create: function(i) {
            return this.format('<li></li>', i);
        },

        format: function(e, i) {
            e = $(e);
            var split = e.get(0).className.split(' ');
            for (var j = 0; j < split.length; j++) {
                if (split[j].indexOf('jcarousel-') != -1) {
                    e.removeClass(split[j]);
                }
            }
            e.addClass(this.className('jcarousel-item')).addClass(this.className('jcarousel-item-' + i)).css({
                'float': (this.options.rtl ? 'right' : 'left'),
                'list-style': 'none'
            }).attr('jcarouselindex', i);
            return e;
        },

        className: function(c) {
            return c + ' ' + c + (!this.options.vertical ? '-horizontal' : '-vertical');
        },

        dimension: function(e, d) {
            var el = e.jquery !== undefined ? e[0] : e;

            var old = !this.options.vertical ?
                (el.offsetWidth || $jc.intval(this.options.itemFallbackDimension)) + $jc.margin(el, 'marginLeft') + $jc.margin(el, 'marginRight') :
                (el.offsetHeight || $jc.intval(this.options.itemFallbackDimension)) + $jc.margin(el, 'marginTop') + $jc.margin(el, 'marginBottom');

            if (d == null || old == d) {
                return old;
            }

            var w = !this.options.vertical ?
                d - $jc.margin(el, 'marginLeft') - $jc.margin(el, 'marginRight') :
                d - $jc.margin(el, 'marginTop') - $jc.margin(el, 'marginBottom');

            $(el).css(this.wh, w + 'px');

            return this.dimension(el);
        },

        clipping: function() {
            return !this.options.vertical ?
                this.clip[0].offsetWidth - $jc.intval(this.clip.css('borderLeftWidth')) - $jc.intval(this.clip.css('borderRightWidth')) :
                this.clip[0].offsetHeight - $jc.intval(this.clip.css('borderTopWidth')) - $jc.intval(this.clip.css('borderBottomWidth'));
        },

        index: function(i, s) {
            if (s == null) {
                s = this.options.size;
            }

            return Math.round((((i-1) / s) - Math.floor((i-1) / s)) * s) + 1;
        }
    });

    $jc.extend({
        /**
         * Gets/Sets the global default configuration properties.
         *
         * @method defaults
         * @return {Object}
         * @param d {Object} A set of key/value pairs to set as configuration properties.
         */
        defaults: function(d) {
            return $.extend(defaults, d || {});
        },

        margin: function(e, p) {
            if (!e) {
                return 0;
            }

            var el = e.jquery !== undefined ? e[0] : e;

            if (p == 'marginRight' && $.browser.safari) {
                var old = {'display': 'block', 'float': 'none', 'width': 'auto'}, oWidth, oWidth2;

                $.swap(el, old, function() { oWidth = el.offsetWidth; });

                old.marginRight = 0;
                $.swap(el, old, function() { oWidth2 = el.offsetWidth; });

                return oWidth2 - oWidth;
            }

            return $jc.intval($.css(el, p));
        },

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

    /**
     * Creates a carousel for all matched elements.
     *
     * @example $("#mycarousel").jcarousel();
     * @before <ul id="mycarousel" class="jcarousel-skin-name"><li>First item</li><li>Second item</li></ul>
     * @result
     *
     * <div class="jcarousel-skin-name">
     *   <div class="jcarousel-container">
     *     <div class="jcarousel-clip">
     *       <ul class="jcarousel-list">
     *         <li class="jcarousel-item-1">First item</li>
     *         <li class="jcarousel-item-2">Second item</li>
     *       </ul>
     *     </div>
     *     <div disabled="disabled" class="jcarousel-prev jcarousel-prev-disabled"></div>
     *     <div class="jcarousel-next"></div>
     *   </div>
     * </div>
     *
     * @method jcarousel
     * @return jQuery
     * @param o {Hash|String} A set of key/value pairs to set as configuration properties or a method name to call on a formerly created instance.
     */
    $.fn.jcarousel = function(o) {
        if (typeof o == 'string') {
            var instance = $(this).data('jcarousel'), args = Array.prototype.slice.call(arguments, 1);
            return instance[o].apply(instance, args);
        } else {
            return this.each(function() {
                $(this).data('jcarousel', new $jc(this, o));
            });
        }
    };

})(jQuery);


/*!
 * jQzoom Evolution Library v2.3  - Javascript Image magnifier
 * http://www.mind-projects.it
 *
 * Copyright 2011, Engineer Marco Renzi
 * Licensed under the BSD license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the organization nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * Date: 03 May 2011 22:16:00
 */
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(8($){9 v=($.1A.2j&&$.1A.2k<7);9 w=$(1B.1C);9 y=$(y);9 z=F;$.3d.13=8(b){G 5.1Y(8(){9 a=5.3e.3f();A(a==\'a\'){Q 13(5,b)}})};13=8(g,h){9 j=2l;j=$(g).1D("13");A(j)G j;9 k=5;9 l=$.1E({},$.13.2m,h||{});k.3g=g;g.1p=$(g).R(\'1p\');g.1F=F;g.3h=F;g.1u=F;g.1h=F;g.1b={};g.2n=2l;g.14={};g.1G=F;$(g).D({\'3i-1i\':\'1v\',\'3j-3k\':\'1v\'});9 m=$("3l:3m(0)",g);g.V=$(g).R(\'V\');g.1Z=m.R(\'V\');9 n=($.1w(g.V).Y>0)?g.V:g.1Z;9 p=Q 2o(m);9 q=Q 2p();9 r=Q 2q();9 s=Q 2r();9 t=Q 2s();$(g).1H(\'2t\',8(e){e.2u();G F});9 u=[\'20\',\'1c\',\'1j\',\'1q\'];A($.3n($.1w(l.H),u)<0){l.H=\'20\'}$.1E(k,{21:8(){A($(".L",g).Y==0){g.L=$(\'<Z/>\').1I(\'L\');m.3o(g.L)}A(l.H==\'1j\'){l.15=p.w;l.16=p.h}A($(".22",g).Y==0){q.S()}A($(".23",g).Y==0){r.S()}A($(".2v",g).Y==0){t.S()}A(l.24||l.H==\'1c\'||l.1J){k.1K()}k.2w()},2w:8(){A(l.H==\'1c\'){$(".L",g).3p(8(){g.1G=17});$(".L",g).3q(8(){g.1G=F});1B.1C.3r=8(){G F};$(".L",g).D({1L:\'1r\'});$(".22",g).D({1L:\'3s\'})}A(l.H==\'1j\'){$(".1M",g).D({1L:\'3t\'})}$(".L",g).1H(\'3u 3v\',8(a){m.R(\'V\',\'\');$(g).R(\'V\',\'\');g.1F=17;p.1s();A(g.1h){k.25(a)}1k{k.1K()}});$(".L",g).1H(\'3w\',8(a){k.2x()});$(".L",g).1H(\'3x\',8(e){A(e.26>p.E.r||e.26<p.E.l||e.27<p.E.t||e.27>p.E.b){q.1N();G F}g.1F=17;A(g.1h&&!$(\'.23\',g).3y(\':2y\')){k.25(e)}A(g.1h&&(l.H!=\'1c\'||(l.H==\'1c\'&&g.1G))){q.1l(e)}});9 c=Q 2z();9 i=0;9 d=Q 2z();d=$(\'a\').3z(8(){9 a=Q 3A("3B[\\\\s]*:[\\\\s]*\'"+$.1w(g.1p)+"\'","i");9 b=$(5).R(\'1p\');A(a.3C(b)){G 5}});A(d.Y>0){9 f=d.3D(0,1);d.3E(f)}d.1Y(8(){A(l.24){9 a=$.1E({},1O("("+$.1w($(5).R(\'1p\'))+")"));c[i]=Q 28();c[i].1d=a.1x;i++}$(5).2t(8(e){A($(5).3F(\'29\')){G F}d.1Y(8(){$(5).3G(\'29\')});e.2u();k.2A(5);G F})})},1K:8(){A(g.1h==F&&g.1u==F){9 a=$(g).R(\'2B\');g.1u=17;s.2C(a)}},25:8(e){3H(g.2n);q.T();r.T()},2x:8(e){1P(l.H){1t\'1c\':W;1r:m.R(\'V\',g.1Z);$(g).R(\'V\',g.V);A(l.1J){q.1N()}1k{r.O();q.O()}W}g.1F=F},2A:8(a){g.1u=F;g.1h=F;9 b=Q 3I();b=$.1E({},1O("("+$.1w($(a).R(\'1p\'))+")"));A(b.1Q&&b.1x){9 c=b.1Q;9 d=b.1x;$(a).1I(\'29\');$(g).R(\'2B\',d);m.R(\'1d\',c);q.O();r.O();k.1K()}1k{2a(\'2D :: 2E 2F 1R 1x 2G 1Q.\');2b\'2D :: 2E 2F 1R 1x 2G 1Q.\';}G F}});A(m[0].3J){p.1s();A($(".L",g).Y==0)k.21()}8 2o(c){9 d=5;5.6=c[0];5.2H=8(){9 a=0;a=c.D(\'2c-B-P\');M=\'\';9 b=0;b=c.D(\'2c-C-P\');K=\'\';A(a){1R(i=0;i<3;i++){9 x=[];x=a.1S(i,1);A(2I(x)==F){M=M+\'\'+a.1S(i,1)}1k{W}}}A(b){1R(i=0;i<3;i++){A(!2I(b.1S(i,1))){K=K+b.1S(i,1)}1k{W}}}d.M=(M.Y>0)?1O(M):0;d.K=(K.Y>0)?1O(K):0};5.1s=8(){d.2H();d.w=c.P();d.h=c.12();d.1m=c.3K();d.1e=c.3L();d.E=c.1f();d.E.l=c.1f().C+d.K;d.E.t=c.1f().B+d.M;d.E.r=d.w+d.E.l;d.E.b=d.h+d.E.t;d.2J=c.1f().C+d.1m;d.3M=c.1f().B+d.1e};5.6.2K=8(){2a(\'1T 1U 1V X.\');2b\'1T 1U 1V X.\';};5.6.2L=8(){d.1s();A($(".L",g).Y==0)k.21()};G d};8 2s(){9 a=5;5.S=8(){5.6=$(\'<Z/>\').1I(\'2v\').D(\'2d\',\'2M\').2N(l.2O);$(\'.L\',g).S(5.6)};5.T=8(){5.6.B=(p.1e-5.6.12())/2;5.6.C=(p.1m-5.6.P())/2;5.6.D({B:5.6.B,C:5.6.C,11:\'18\',2d:\'2y\'})};5.O=8(){5.6.D(\'2d\',\'2M\')};G 5}8 2p(){9 d=5;5.6=$(\'<Z/>\').1I(\'22\');5.S=8(){$(\'.L\',g).S($(5.6).O());A(l.H==\'1q\'){5.X=Q 28();5.X.1d=p.6.1d;$(5.6).2e().S(5.X)}};5.2P=8(){5.6.w=(1W((l.15)/g.1b.x)>p.w)?p.w:(1W(l.15/g.1b.x));5.6.h=(1W((l.16)/g.1b.y)>p.h)?p.h:(1W(l.16/g.1b.y));5.6.B=(p.1e-5.6.h-2)/2;5.6.C=(p.1m-5.6.w-2)/2;5.6.D({B:0,C:0,P:5.6.w+\'I\',12:5.6.h+\'I\',11:\'18\',1g:\'1v\',2f:1+\'I\'});A(l.H==\'1q\'){5.X.1d=p.6.1d;$(5.6).D({\'2g\':1});$(5.X).D({11:\'18\',1g:\'1y\',C:-(5.6.C+1-p.K)+\'I\',B:-(5.6.B+1-p.M)+\'I\'})}};5.1N=8(){5.6.B=(p.1e-5.6.h-2)/2;5.6.C=(p.1m-5.6.w-2)/2;5.6.D({B:5.6.B,C:5.6.C});A(l.H==\'1q\'){$(5.X).D({11:\'18\',1g:\'1y\',C:-(5.6.C+1-p.K)+\'I\',B:-(5.6.B+1-p.M)+\'I\'})}s.1l()};5.1l=8(e){g.14.x=e.26;g.14.y=e.27;9 b=0;9 c=0;8 2Q(a){G g.14.x-(a.w)/2<p.E.l}8 2R(a){G g.14.x+(a.w)/2>p.E.r}8 2S(a){G g.14.y-(a.h)/2<p.E.t}8 2T(a){G g.14.y+(a.h)/2>p.E.b}b=g.14.x+p.K-p.E.l-(5.6.w+2)/2;c=g.14.y+p.M-p.E.t-(5.6.h+2)/2;A(2Q(5.6)){b=p.K-1}1k A(2R(5.6)){b=p.w+p.K-5.6.w-1}A(2S(5.6)){c=p.M-1}1k A(2T(5.6)){c=p.h+p.M-5.6.h-1}5.6.C=b;5.6.B=c;5.6.D({\'C\':b+\'I\',\'B\':c+\'I\'});A(l.H==\'1q\'){A($.1A.2j&&$.1A.2k>7){$(5.6).2e().S(5.X)}$(5.X).D({11:\'18\',1g:\'1y\',C:-(5.6.C+1-p.K)+\'I\',B:-(5.6.B+1-p.M)+\'I\'})}s.1l()};5.O=8(){m.D({\'2g\':1});5.6.O()};5.T=8(){A(l.H!=\'1j\'&&(l.2U||l.H==\'1c\')){5.6.T()}A(l.H==\'1q\'){m.D({\'2g\':l.2V})}};5.2h=8(){9 o={};o.C=d.6.C;o.B=d.6.B;G o};G 5};8 2q(){9 b=5;5.6=$("<Z 1z=\'23\'><Z 1z=\'1M\'><Z 1z=\'1X\'></Z><Z 1z=\'2i\'></Z></Z></Z>");5.U=$(\'<2W 1z="3N" 1d="3O:\\\'\\\';" 3P="0" 3Q="0" 3R="2X" 3S="3T" 3U="0" ></2W>\');5.1l=8(){5.6.1n=0;5.6.1o=0;A(l.H!=\'1j\'){1P(l.11){1t"C":5.6.1n=(p.E.l-p.K-J.N(l.19)-l.15>0)?(0-l.15-J.N(l.19)):(p.1m+J.N(l.19));5.6.1o=J.N(l.1a);W;1t"B":5.6.1n=J.N(l.19);5.6.1o=(p.E.t-p.M-J.N(l.1a)-l.16>0)?(0-l.16-J.N(l.1a)):(p.1e+J.N(l.1a));W;1t"2X":5.6.1n=J.N(l.19);5.6.1o=(p.E.t-p.M+p.1e+J.N(l.1a)+l.16<2Y.12)?(p.1e+J.N(l.1a)):(0-l.16-J.N(l.1a));W;1r:5.6.1n=(p.2J+J.N(l.19)+l.15<2Y.P)?(p.1m+J.N(l.19)):(0-l.15-J.N(l.19));5.6.1o=J.N(l.1a);W}}5.6.D({\'C\':5.6.1n+\'I\',\'B\':5.6.1o+\'I\'});G 5};5.S=8(){$(\'.L\',g).S(5.6);5.6.D({11:\'18\',1g:\'1v\',2Z:3V});A(l.H==\'1j\'){5.6.D({1L:\'1r\'});9 a=(p.K==0)?1:p.K;$(\'.1M\',5.6).D({2f:a+\'I\'})}$(\'.1M\',5.6).D({P:J.30(l.15)+\'I\',2f:a+\'I\'});$(\'.2i\',5.6).D({P:\'31%\',12:J.30(l.16)+\'I\'});$(\'.1X\',5.6).D({P:\'31%\',11:\'18\'});$(\'.1X\',5.6).O();A(l.V&&n.Y>0){$(\'.1X\',5.6).2N(n).T()}b.1l()};5.O=8(){1P(l.32){1t\'3W\':5.6.3X(l.33,8(){});W;1r:5.6.O();W}5.U.O()};5.T=8(){1P(l.34){1t\'3Y\':5.6.35();5.6.35(l.36,8(){});W;1r:5.6.T();W}A(v&&l.H!=\'1j\'){5.U.P=5.6.P();5.U.12=5.6.12();5.U.C=5.6.1n;5.U.B=5.6.1o;5.U.D({1g:\'1y\',11:"18",C:5.U.C,B:5.U.B,2Z:3Z,P:5.U.P+\'I\',12:5.U.12+\'I\'});$(\'.L\',g).S(5.U);5.U.T()}}};8 2r(){9 c=5;5.6=Q 28();5.2C=8(a){t.T();5.40=a;5.6.1i.11=\'18\';5.6.1i.2c=\'37\';5.6.1i.1g=\'1v\';5.6.1i.C=\'-41\';5.6.1i.B=\'37\';1B.1C.42(5.6);5.6.1d=a};5.1s=8(){9 a=$(5.6);9 b={};5.6.1i.1g=\'1y\';c.w=a.P();c.h=a.12();c.E=a.1f();c.E.l=a.1f().C;c.E.t=a.1f().B;c.E.r=c.w+c.E.l;c.E.b=c.h+c.E.t;b.x=(c.w/p.w);b.y=(c.h/p.h);g.1b=b;1B.1C.43(5.6);$(\'.2i\',g).2e().S(5.6);q.2P()};5.6.2K=8(){2a(\'1T 1U 1V 38 39 X.\');2b\'1T 1U 1V 38 39 X.\';};5.6.2L=8(){c.1s();t.O();g.1u=F;g.1h=17;A(l.H==\'1c\'||l.1J){q.T();r.T();q.1N()}};5.1l=8(){9 a=-g.1b.x*(q.2h().C-p.K+1);9 b=-g.1b.y*(q.2h().B-p.M+1);$(5.6).D({\'C\':a+\'I\',\'B\':b+\'I\'})};G 5};$(g).1D("13",k)};$.13={2m:{H:\'20\',15:3a,16:3a,19:10,1a:0,11:"44",24:17,2O:\'45 46\',V:17,2U:17,2V:0.4,1J:F,34:\'T\',32:\'O\',36:\'47\',33:\'48\'},3b:8(a){9 b=$(a).1D(\'13\');b.3b();G F},3c:8(a){9 b=$(a).1D(\'13\');b.3c();G F},49:8(a){z=17},4a:8(a){z=F}}})(4b);',62,260,'|||||this|node||function|var|||||||||||||||||||||||||||if|top|left|css|pos|false|return|zoomType|px|Math|bleft|zoomPad|btop|abs|hide|width|new|attr|append|show|ieframe|title|break|image|length|div||position|height|jqzoom|mousepos|zoomWidth|zoomHeight|true|absolute|xOffset|yOffset|scale|drag|src|oh|offset|display|largeimageloaded|style|innerzoom|else|setposition|ow|leftpos|toppos|rel|reverse|default|fetchdata|case|largeimageloading|none|trim|largeimage|block|class|browser|document|body|data|extend|zoom_active|mouseDown|bind|addClass|alwaysOn|load|cursor|zoomWrapper|setcenter|eval|switch|smallimage|for|substr|Problems|while|loading|parseInt|zoomWrapperTitle|each|imagetitle|standard|create|zoomPup|zoomWindow|preloadImages|activate|pageX|pageY|Image|zoomThumbActive|alert|throw|border|visibility|empty|borderWidth|opacity|getoffset|zoomWrapperImage|msie|version|null|defaults|timer|Smallimage|Lens|Stage|Largeimage|Loader|click|preventDefault|zoomPreload|init|deactivate|visible|Array|swapimage|href|loadimage|ERROR|Missing|parameter|or|findborder|isNaN|rightlimit|onerror|onload|hidden|html|preloadText|setdimensions|overleft|overright|overtop|overbottom|lens|imageOpacity|iframe|bottom|screen|zIndex|round|100|hideEffect|fadeoutSpeed|showEffect|fadeIn|fadeinSpeed|0px|the|big|300|disable|enable|fn|nodeName|toLowerCase|el|zoom_disabled|outline|text|decoration|img|eq|inArray|wrap|mousedown|mouseup|ondragstart|move|crosshair|mouseenter|mouseover|mouseleave|mousemove|is|filter|RegExp|gallery|test|splice|push|hasClass|removeClass|clearTimeout|Object|complete|outerWidth|outerHeight|bottomlimit|zoomIframe|javascript|marginwidth|marginheight|align|scrolling|no|frameborder|5001|fadeout|fadeOut|fadein|99|url|5000px|appendChild|removeChild|right|Loading|zoom|slow|2000|disableAll|enableAll|jQuery'.split('|'),0,{}))

