/**
 * @license
 * jQuery Tools 1.2.2 Scrollable - New wave UI design
 *
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 *
 * http://flowplayer.org/tools/scrollable.html
 *
 * Since: March 2008
 * Date:    Wed May 19 06:53:17 2010 +0000
 */
(function ($) {

    // static constructs
    $.tools = $.tools || {version: '1.2.2'};

    $.tools.scrollable = {

        conf: {
            activeClass: 'active',
            circular: false,
            clonedClass: 'cloned',
            disabledClass: 'disabled',
            easing: 'swing',
            initialIndex: 0,
            item: null,
            items: '.items',
            keyboard: true,
            mousewheel: false,
            next: '.next',
            prev: '.prev',
            speed: 400,
            vertical: false,
            wheelSpeed: 0
        }
    };

    // get hidden element's width or height even though it's hidden
    function dim(el, key) {
        var v = parseInt(el.css(key), 10);
        if (v) {
            return v;
        }
        var s = el[0].currentStyle;
        return s && s.width && parseInt(s.width, 10);
    }

    function find(root, query) {
        var el = $(query);
        return el.length < 2 ? el : root.parent().find(query);
    }

    var current;

    // constructor
    function Scrollable(root, conf) {

        // current instance
        var self = this,
            fire = root.add(self),
            itemWrap = root.children(),
            index = 0,
            vertical = conf.vertical;

        if (!current) {
            current = self;
        }
        if (itemWrap.length > 1) {
            itemWrap = $(conf.items, root);
        }

        // methods
        $.extend(self, {

            getConf: function () {
                return conf;
            },

            getIndex: function () {
                return index;
            },

            getSize: function () {
                return self.getItems().size();
            },

            getNaviButtons: function () {
                return prev.add(next);
            },

            getRoot: function () {
                return root;
            },

            getItemWrap: function () {
                return itemWrap;
            },

            getItems: function () {
                return itemWrap.children(conf.item).not("." + conf.clonedClass);
            },

            move: function (offset, time) {
                return self.seekTo(index + offset, time);
            },

            next: function (time) {
                return self.move(1, time);
            },

            prev: function (time) {
                return self.move(-1, time);
            },

            begin: function (time) {
                return self.seekTo(0, time);
            },

            end: function (time) {
                return self.seekTo(self.getSize() - 1, time);
            },

            focus: function () {
                current = self;
                return self;
            },

            addItem: function (item) {
                item = $(item);

                if (!conf.circular) {
                    itemWrap.append(item);
                } else {
                    $(".cloned:last").before(item);
                    $(".cloned:first").replaceWith(item.clone().addClass(conf.clonedClass));
                }

                fire.trigger("onAddItem", [item]);
                return self;
            },


            /* all seeking functions depend on this */
            seekTo: function (i, time, fn) {

                // avoid seeking from end clone to the beginning
                if (conf.circular && i === 0 && index == -1 && time !== 0) {
                    return self;
                }

                // check that index is sane
                if (!conf.circular && i < 0 || i > self.getSize() || i < -1) {
                    return self;
                }

                var item = i;

                if (i.jquery) {
                    i = self.getItems().index(i);
                } else {
                    item = self.getItems().eq(i);
                }

                // onBeforeSeek
                var e = $.Event("onBeforeSeek");
                if (!fn) {
                    fire.trigger(e, [i, time]);
                    if (e.isDefaultPrevented() || !item.length) {
                        return self;
                    }
                }

                var props = vertical ? {top: -item.position().top} : {left: -item.position().left};

                index = i;
                current = self;
                if (time === undefined) {
                    time = conf.speed;
                }

                itemWrap.animate(props, time, conf.easing, fn || function () {
                        fire.trigger("onSeek", [i]);
                    });

                return self;
            }

        });

        // callbacks
        $.each(['onBeforeSeek', 'onSeek', 'onAddItem'], function (i, name) {

            // configuration
            if ($.isFunction(conf[name])) {
                $(self).bind(name, conf[name]);
            }

            self[name] = function (fn) {
                $(self).bind(name, fn);
                return self;
            };
        });

        // circular loop
        if (conf.circular) {

            var cloned1 = self.getItems().slice(-1).clone().prependTo(itemWrap),
                cloned2 = self.getItems().eq(1).clone().appendTo(itemWrap);

            cloned1.add(cloned2).addClass(conf.clonedClass);

            self.onBeforeSeek(function (e, i, time) {


                if (e.isDefaultPrevented()) {
                    return;
                }

                /*
                 1. animate to the clone without event triggering
                 2. seek to correct position with 0 speed
                 */
                if (i == -1) {
                    self.seekTo(cloned1, time, function () {
                        self.end(0);
                    });
                    return e.preventDefault();

                } else if (i == self.getSize()) {
                    self.seekTo(cloned2, time, function () {
                        self.begin(0);
                    });
                }

            });

            // seek over the cloned item
            self.seekTo(0, 0);
        }

        // next/prev buttons
        var prev = find(root, conf.prev).click(function () {
                self.prev();
            }),
            next = find(root, conf.next).click(function () {
                self.next();
            });

        if (!conf.circular && self.getSize() > 1) {

            self.onBeforeSeek(function (e, i) {
                prev.toggleClass(conf.disabledClass, i <= 0);
                next.toggleClass(conf.disabledClass, i >= self.getSize() - 1);
            });
        }

        // mousewheel support
        if (conf.mousewheel && $.fn.mousewheel) {
            root.mousewheel(function (e, delta) {
                if (conf.mousewheel) {
                    self.move(delta < 0 ? 1 : -1, conf.wheelSpeed || 50);
                    return false;
                }
            });
        }

        if (conf.keyboard) {

            $(document).bind("keydown.scrollable", function (evt) {

                // skip certain conditions
                if (!conf.keyboard || evt.altKey || evt.ctrlKey || $(evt.target).is(":input")) {
                    return;
                }

                // does this instance have focus?
                if (conf.keyboard != 'static' && current != self) {
                    return;
                }

                var key = evt.keyCode;

                if (vertical && (key == 38 || key == 40)) {
                    self.move(key == 38 ? -1 : 1);
                    return evt.preventDefault();
                }

                if (!vertical && (key == 37 || key == 39)) {
                    self.move(key == 37 ? -1 : 1);
                    return evt.preventDefault();
                }

            });
        }

        // initial index
        $(self).trigger("onBeforeSeek", [conf.initialIndex]);
    }


    // jQuery plugin implementation
    $.fn.scrollable = function (conf) {

        // already constructed --> return API
        var el = this.data("scrollable");
        if (el) {
            return el;
        }

        conf = $.extend({}, $.tools.scrollable.conf, conf);

        this.each(function () {
            el = new Scrollable($(this), conf);
            $(this).data("scrollable", el);
        });

        return conf.api ? el : this;

    };


})(jQuery);
