/*************************************************!
*
*   project:    liteAccordion - a horizontal accordion plugin for jQuery
*   author:     Nicola Hibbert
*   url:        http://nicolahibbert.com/liteaccordion-v2/
*   demo:       http://www.nicolahibbert.com/demo/liteAccordion/
*
*   Version:    2.2.0
*   Copyright:  (c) 2010-2013 Nicola Hibbert
*   Licence:    MIT
*
**************************************************/

;(function($) {

    var LiteAccordion = function(elem, options) {

        var defaults = {
            containerWidth : 960,                   // fixed (px)
            containerHeight : 320,                  // fixed (px)
            headerWidth : 48,                       // fixed (px)

            activateOn : 'click',                   // click or mouseover
            firstSlide : 1,                         // displays slide (n) on page load
            slideSpeed : 800,                       // slide animation speed
            onTriggerSlide : function(e) {},        // callback on slide activate
            onSlideAnimComplete : function() {},    // callback on slide anim complete

            autoPlay : false,                       // automatically cycle through slides
            pauseOnHover : false,                   // pause on hover
            cycleSpeed : 6000,                      // time between slide cycles
            easing : 'swing',                       // custom easing function

            theme : 'basic',                        // basic, dark, light, or stitch
            rounded : false,                        // square or rounded corners
            enumerateSlides : false,                // put numbers on slides
            linkable : false                        // link slides via hash
        },

        // merge defaults with options in new settings object
            settings = $.extend({}, defaults, options),

        // 'globals'
            slides = elem.children('ol').children('li'),
            header = slides.children(':first-child'),
            slideLen = slides.length,
            slideWidth = settings.containerWidth - slideLen * settings.headerWidth,

        // public methods
            methods = {

                // start elem animation
                play : function(index) {
                    var next = core.nextSlide(index && index);

                    if (core.playing) return;

                    // start autoplay
                    core.playing = setInterval(function() {
                        header.eq(next()).trigger('click.liteAccordion');
                    }, settings.cycleSpeed);
                },

                // stop elem animation
                stop : function() {
                    clearInterval(core.playing);
                    core.playing = 0;
                },

                // trigger next slide
                next : function() {
                    methods.stop();
                    header.eq(core.currentSlide === slideLen - 1 ? 0 : core.currentSlide + 1).trigger('click.liteAccordion');
                },

                // trigger previous slide
                prev : function() {
                    methods.stop();
                    header.eq(core.currentSlide - 1).trigger('click.liteAccordion');
                },

                // destroy plugin instance
                destroy : function() {
                    // stop autoplay
                    methods.stop();

                    // remove hashchange event bound to window
                    $(window).off('.liteAccordion');

                    // remove generated styles, classes, data, events
                    elem
                        .attr('style', '')
                        .removeClass('liteAccordion basic dark light stitch')
                        .removeData('liteAccordion')
                        .off('.liteAccordion')
                        .find('li > :first-child')
                        .off('.liteAccordion')
                        .filter('.selected')
                        .removeClass('selected')
                        .end()
                        .find('b')
                        .remove();

                    slides
                        .removeClass('slide')
                        .children()
                        .attr('style', '');
                },

                // poke around the internals (NOT CHAINABLE)
                debug : function() {
                    return {
                        elem : elem,
                        defaults : defaults,
                        settings : settings,
                        methods : methods,
                        core : core
                    };
                }
            },

        // core utility and animation methods
            core = {

                // set style properties
                setStyles : function() {
                    // set container height and width, theme and corner style
                    elem
                        .width(settings.containerWidth)
                        .height(settings.containerHeight)
                        .addClass('liteAccordion')
                        .addClass(settings.rounded && 'rounded')
                        .addClass(settings.theme);

                    // set slide heights
                    slides
                        .addClass('slide')
                        .children(':first-child')
                        .height(settings.headerWidth);

                    // set slide positions
                    core.setSlidePositions();
                },

                // set initial positions for each slide
                setSlidePositions : function() {
                    var selected = header.filter('.selected');

                    // account for already selected slide
                    if (!selected.length) header.eq(settings.firstSlide - 1).addClass('selected');

                    header.each(function(index) {
                        var $this = $(this),
                            left = index * settings.headerWidth,
                            margin = header.first().next(),
                            offset = parseInt(margin.css('marginLeft'), 10) || parseInt(margin.css('marginRight'), 10) || 0;

                        // compensate for already selected slide on resize
                        if (selected.length) {
                            if (index > header.index(selected)) left += slideWidth;
                        } else {
                            if (index >= settings.firstSlide) left += slideWidth;
                        }

                        // set each slide position
                        $this
                            .css('left', left)
                            .width(settings.containerHeight)
                            .next()
                                .width(slideWidth - offset)
                                .css({ left : left, paddingLeft : settings.headerWidth });

                        // add number to bottom of tab
                        settings.enumerateSlides && $this.append('<b>' + (index + 1) + '</b>');

                    });
                },

                // bind events
                bindEvents : function() {
                    // bind click and mouseover events
                    if (settings.activateOn === 'click') {
                        header.on('click.liteAccordion', core.triggerSlide);
                    } else if (settings.activateOn === 'mouseover') {
                        header.on('click.liteAccordion mouseover.liteAccordion', core.triggerSlide);
                    }

                    // bind hashchange event
                    if (settings.linkable) {
                        $(window).on('hashchange.liteAccordion', function(e) {
                            var url = slides.filter(function() {
                                return $(this).attr('data-slide-name') === window.location.hash.split('#')[1];
                            });

                            // if slide name exists
                            if (url.length) {
                                // trigger slide
                                core.triggerSlide.call(url.children('h2')[0], e);
                            }
                        });
                    }

                    // pause on hover (can't use custom events with $.hover())
                    if (settings.pauseOnHover && settings.autoPlay) {
                        elem
                            .on('mouseover.liteAccordion', function() {
                                core.playing && methods.stop();
                            })
                            .on('mouseout.liteAccordion', function() {
                                !core.playing && methods.play(core.currentSlide);
                            });
                    }
                },

                // counter for autoPlay (zero index firstSlide on init)
                currentSlide : settings.firstSlide - 1,

                // next slide index
                nextSlide : function(index) {
                    var next = index + 1 || core.currentSlide + 1;

                    // closure
                    return function() {
                        return next++ % slideLen;
                    };
                },

                // holds interval counter
                playing : 0,

                slideAnimCompleteFlag : false,

                // trigger slide animation
                triggerSlide : function(e) {
                    var $this = $(this),
                        tab = {
                            elem : $this,
                            index : header.index($this),
                            next : $this.next(),
                            prev : $this.parent().prev().children('h2'),
                            parent : $this.parent()
                        };

                    // current hash not correct?
                    if (settings.linkable && tab.parent.attr('data-slide-name')) {
                        if (tab.parent.attr('data-slide-name') !== window.location.hash.split('#')[1]) {
                            // exit early and try again (prevents double trigger (issue #60))
                            return window.location.hash = '#' + tab.parent.attr('data-slide-name');
                        }
                    }

                    // update core.currentSlide
                    core.currentSlide = tab.index;

                    // reset onSlideAnimComplete callback flag
                    core.slideAnimCompleteFlag = false;

                    // trigger callback in context of sibling div (jQuery wrapped)
                    settings.onTriggerSlide.call(tab.next, $this);

                    // animate
                    if ($this.hasClass('selected') && $this.position().left < slideWidth / 2) {
                        // animate single selected tab
                        core.animSlide.call(tab);
                    } else {
                        // animate groups
                        core.animSlideGroup(tab);
                    }

                    // stop autoplay, reset current slide index in core.nextSlide closure
                    if (settings.autoPlay) {
                        methods.stop();
                        methods.play(header.index(header.filter('.selected')));
                    }
                },

                animSlide : function(triggerTab) {
                    var _this = this;

                    // set pos for single selected tab
                    if (typeof this.pos === 'undefined') this.pos = slideWidth;

                    // remove, then add selected class
                    header.removeClass('selected').filter(this.elem).addClass('selected');

                    // if slide index not zero
                    if (!!this.index) {
                        this.elem
                            .add(this.next)
                            .stop(true)
                            .animate({
                                left : this.pos + this.index * settings.headerWidth
                            },
                                settings.slideSpeed,
                                settings.easing,
                                function() {
                                    // flag ensures that fn is only called one time per triggerSlide
                                    if (!core.slideAnimCompleteFlag) {
                                        // trigger onSlideAnimComplete callback in context of sibling div (jQuery wrapped)
                                        settings.onSlideAnimComplete.call(triggerTab ? triggerTab.next : _this.prev.next());
                                        core.slideAnimCompleteFlag = true;
                                    }
                                });

                            // remove, then add selected class
                            header.removeClass('selected').filter(this.prev).addClass('selected');

                    }
                },

                // animates left and right groups of slides
                animSlideGroup : function(triggerTab) {
                    var group = ['left', 'right'];

                    $.each(group, function(index, side) {
                        var filterExpr, left;

                        if (side === 'left')  {
                            filterExpr = ':lt(' + (triggerTab.index + 1) + ')';
                            left = 0;
                        } else {
                            filterExpr = ':gt(' + triggerTab.index + ')';
                            left = slideWidth;
                        }

                        slides
                            .filter(filterExpr)
                            .children('h2')
                            .each(function() {
                                var $this = $(this),
                                    tab = {
                                        elem : $this,
                                        index : header.index($this),
                                        next : $this.next(),
                                        prev : $this.parent().prev().children('h2'),
                                        pos : left
                                    };

                                // trigger item anim, pass original trigger context for callback fn
                                core.animSlide.call(tab, triggerTab);
                            });

                    });

                    // remove, then add selected class
                    header.removeClass('selected').filter(triggerTab.elem).addClass('selected');
                },

                ieClass : function(version) {
                    if (version < 7) methods.destroy();
                    if (version >= 10) return;
                    if (version === 7 || version === 8) {
                        slides.each(function(index) {
                            $(this).addClass('slide-' + index);
                        });
                    }

                    elem.addClass('ie ie' + version);
                },

                init : function() {
                    var ua = navigator.userAgent,
                        index = ua.indexOf('MSIE');

                    // test for ie
                    if (index !== -1) {
                        ua = ua.slice(index + 5, index + 7);
                        core.ieClass(+ua);
                    }

                    // init styles and events
                    core.setStyles();
                    core.bindEvents();

                    // check slide speed is not faster than cycle speed
                    if (settings.cycleSpeed < settings.slideSpeed) settings.cycleSpeed = settings.slideSpeed;

                    // init autoplay
                    settings.autoPlay && methods.play();
                }
            };

        // init plugin
        core.init();

        // expose methods
        return methods;

    };

    $.fn.liteAccordion = function(method) {
        var elem = this,
            instance = elem.data('liteAccordion');

        // if creating a new instance
        if (typeof method === 'object' || !method) {
            return elem.each(function() {
                var liteAccordion;

                // if plugin already instantiated, return
                if (instance) return;

                // otherwise create a new instance
                liteAccordion = new LiteAccordion(elem, method);
                elem.data('liteAccordion', liteAccordion);
            });

        // otherwise, call method on current instance
        } else if (typeof method === 'string' && instance[method]) {
            // debug method isn't chainable b/c we need the debug object to be returned
            if (method === 'debug') {
                return instance[method].call(elem);
            } else { // the rest of the methods are chainable though
                instance[method].call(elem);
                return elem;
            }
        }
    };

})(jQuery);