/*
All items MUST have the same width. Height is allowed to vary.
(That doesn't mean the images must be the same width - the items inside the
collection just have to be.)
The carousel is typically a ul that contains li tags.
A wrapper gets added around it (or you can add it yourself) and pagination gets
added inside the wrapper (or you can add that yourself too).

<div class="carouselWrapper">
<ul class="carousel" id="complete-carousel">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<div class="pagination"></div>
</div>

<ul class="carousel" id="partial-carousel">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>

<script>
$(document).ready(function() {
// You can output all the classes yourself and then use that:
$('#complete-carousel').scrollCarousel({
    wrapperClass: 'carouselWrapper'
});
$('#complete-carousel').fadeCarousel({
    wrapperClass: 'carouselWrapper'
});

// Or you can let the code add the wrapper and pagination classes for you:
$('#partial-carousel').scrollCarousel();
$('#partial-carousel').fadeCarousel();

});
</script>

*/

(function($) {

function log(message) {
    if (console && console.log) {
        console.log(message);
    }
}

function safeWidth($el) {
    var $img = $el.find('img');
    var iw = $img.innerWidth();
    if (iw) {
        return $el.outerWidth(true);
    } else {
        var widthAttr = $img.attr('width');
        if (widthAttr) {
            var parentWidth = $el.parent().innerWidth();
            if (parentWidth >= widthAttr) {
                return widthAttr;
            } else {
                return parentWidth;
            }
        } else {
            return $el.parent().innerWidth();
        }
    }
}
function safeHeight($el) {
    var $img = $el.find('img');
    var ih = $img.innerHeight();
    if (ih) {
        return $el.outerHeight(true);
    } else {
        // TODO: what if there are things than just an image?
        // TODO: and what if height is blank?
        var heightAttr = $img[0].attributes.height.value;
        var height = parseInt(heightAttr, 10);
        if ($img.innerWidth()) {
            var widthAttr = $img[0].attributes.width.value;
            var width = parseInt(widthAttr, 10);
            return height*($img.innerWidth()/width);
        }
        return height;
    }
}

function getItemWidth($items) {
    /*var width = $items.outerWidth(true);
    $items.each(function() {
        var w = $(this).outerWidth(true);
        if (w != width) {
            log("WARNING: The items don't all have the same width.");
        }
    });
    return width;*/
    return safeWidth($items);
}

function getMaxHeight($items) {
    var maxHeight = 0;
    $items.each(function() {
        maxHeight = Math.max(maxHeight, $(this).outerHeight(true));
    });
    return maxHeight;
}

function getOrAddWrapper($container, wrapperClass) {
    var $wrapper = $container.parents('.'+wrapperClass);
    if (!$wrapper.length) {
        $container.wrap('<div class="'+wrapperClass+'"></div>');
        $wrapper = $container.parents('.'+wrapperClass);
    }
    return $wrapper;
}

function getOrAddPagination($wrapper, paginationClass) {
    var $pagination = $wrapper.find('.'+paginationClass);
    if (!$pagination.length) {
        $wrapper.append('<div class="'+paginationClass+'"></div>');
        $pagination = $wrapper.find('.'+paginationClass);
    }
    return $pagination;
}

$.fn.scrollCarousel = function(settings) {
    var options = $.extend({
        // see threshold in jquery.resizethreshold.js
        resizeEvent: null, // null, 'resize', or 'threshold'
        pageSize: null,    // # of items per page (null for auto)
        itemWidth: null,   // null means auto: take the first one's width
        maxHeight: null,   // null means auto: take the highest height
        speed: 250,        // in milliseconds
        wrapperClass: 'scrollCarouselWrapper', // parent to use or add
        paginationClass: 'pagination',   // class in wrapper to use or add
        linkStyle: '01' // '01' or '1'
    }, settings);

    var $containers = this;
    var $nonempty = [];
    $containers.each(function() {
        if ($(this).find('> *:first-child').length) {
            $nonempty = $(this);
        }
    });
    function getPageSize() {
        if (options.pageSize && options.pageSize > 0) {
            return options.pageSize;
        }
        pageSize = 8;
        if ($nonempty.length) {
            var wrapperw = $nonempty.parents('.'+options.wrapperClass).width();
            var liw = $nonempty.find('> *:first-child').outerWidth();
            if (wrapperw && liw) {
                pageSize = Math.floor(wrapperw/liw);
            }
        }
        return pageSize;
    }

    var carousels = this;

    function makeCarousels() {
        carousels.each(function() {
            var $container = $(this);
            var $items = $container.find('> *');
            if ($items.length == 0) {
                return;
            }
            var itemWidth = parseInt(options.itemWidth) || getItemWidth($items);
            var maxHeight = parseInt(options.maxHeight) || getMaxHeight($items);
            var $wrapper = getOrAddWrapper($container, options.wrapperClass);
            var $pagination = getOrAddPagination($wrapper, options.paginationClass);

            pageSize = getPageSize();
            $pagination.empty();
            $pagination.removeClass('empty-pagination');

            var numItems = $items.length;
            var numPages = 0;
            if (numItems && pageSize) {
                numPages = Math.ceil(numItems/pageSize);
            }

            var pageWidth = pageSize*itemWidth;

            $wrapper.css('position', 'relative');
            //$wrapper.css('width', pageWidth+'px');
            $wrapper.css('overflow', 'hidden');

            $container.addClass('scrollCarousel');
            $container.css('position', 'absolute')
                      .css('width', numPages*pageWidth+'px')
                      .css('height', maxHeight+'px')
                      .css('left', '0')
                      .css('top', '0');

            if (numPages > 1) {
                for (var i=0; i<numPages; i++) {
                    var pageNum = i+1+'';
                    if (options.linkStyle == '01') {
                        var numChars = Math.max((numPages+'').length, 2);
                        while (pageNum.length < numChars) {
                            pageNum = '0'+pageNum;
                        }
                    }
                    var spanHtml = '<span rel="page-'+i+'">'+pageNum+'</span>';
                    $pagination.append(spanHtml);
                }

                $pagination.find('span').eq(0).addClass('selected');

                $wrapper.css('height', $container.outerHeight()+
                                       $pagination.outerHeight()+'px')

                $pagination.find('span').click(function() {
                    var $span = $(this);
                    $pagination.find('span.selected').removeClass('selected');
                    $span.addClass('selected');
                    var pageNum = parseInt($span.attr('rel').split('-')[1]);
                    var left = -pageNum*pageWidth+'px';
                    $container.animate({'left': left}, options.speed);
                });
            } else {
                $wrapper.css('height', $container.outerHeight()+'px')
                $pagination.addClass('empty-pagination');
            }
        });
    }

    makeCarousels();

    if (options.resizeEvent) {
        // automatically resize the carousel(s) when the window resizes
        $(window).bind(options.resizeEvent, function() {
            makeCarousels();
        });
    }

    return this;
};

$.fn.fadeCarousel = function(settings) {
    var options = $.extend({
        itemWidth: null, // null means auto: take the first one's width
        //maxHeight: null, // null means auto: take the highest height
        speed: 250,     // in milliseconds
        wrapperClass: 'fadeCarouselWrapper', // parent to use or add
        paginationClass: 'pagination',   // class in wrapper to use or add
        linkStyle: '01' // '01' or '1'
    }, settings);

    var carousels = this;
    function makeCarousels() {
        var grp = 0;
        carousels.each(function() {
            grp += 1;

            var $container = $(this);
            var $items = $container.find('> *');
            var itemWidth = parseInt(options.itemWidth) || getItemWidth($items);
            var $wrapper = getOrAddWrapper($container, options.wrapperClass);
            var $pagination = getOrAddPagination($wrapper, options.paginationClass);
            $pagination.empty();
            $pagination.removeClass('empty-pagination');

            var numItems = $items.length;

            if (numItems < 1) {
                return;
            }

            // make the container position relative, force dimensions, etc.
            $container.addClass('fadeCarousel');
            $container.css('width', itemWidth+'px');
            //$container.css('height', maxHeight+'px');
            $container.css('position', 'relative');

            // make all the items display absolute,
            // put them on top of each other and hide them
            $items
                .css('position', 'absolute')
                .css('left', 0)
                .css('top', 0)
                .css('z-index', 0)
                .css('display', 'none')
                .css('opacity', 0)  // and IE?
                .addClass('carousel-item');

            var num = 0;
            $items.each(function() {
                // The rel attribute maps to the class of the item. Would use id,
                // but what if we have multiple carousels on the same page? Rather
                // just use a class and then confine queries to the wrapper.
                num += 1;
                $(this).addClass('carousel-item-'+grp+'-'+num);
            });

            // show the first one
            $items.eq(0)
                // leave 1 level in between for the one we fade in later
                .css('z-index', 2)
                .css('display', 'block')
                .css('opacity', 1);

            $container.height(safeHeight($items.eq(0)));

            function switchTo($newspan) {
                if ($newspan.hasClass('selected')) {
                    return;
                }

                var $oldspan = $pagination.find('.selected');

                // current one
                var currentRel = $oldspan.attr('rel');
                var $currentItem = $items.filter('.'+currentRel);

                // the one we clicked on
                var nextRel = $newspan.attr('rel');
                var $nextItem = $items.filter('.'+nextRel);

                // swap selected as soon as possible
                $oldspan.removeClass('selected');
                $newspan.addClass('selected');

                // the next one goes at 1 so it is above the hidden ones,
                // but below the currently visible one
                $nextItem.css('z-index', 1).css('display', 'block');

                // fade the current one out
                $currentItem.animate({
                    opacity: 0
                }, options.speed, 'linear', function() {
                    // afterwards explicitely hide it at the back
                    $currentItem
                        .css('z-index', 0)
                        .css('display', 'none')
                        .css('opacity', 0);
                });

                // fade the new one in
                $nextItem.animate({
                    opacity: 1
                }, options.speed, 'linear', function() {
                    // afterwards explicitely show it at the top
                    $nextItem
                        .css('z-index', 2)
                        .css('opacity', 1);
                });

                $container.animate({
                    'height': safeHeight($nextItem)
                }, options.speed, 'linear');
            }

            if (numItems > 1) {
                for (var i=0; i<numItems; i++) {
                    var itemNum = i+1+'';
                    if (options.linkStyle == '01') {
                        var numChars = Math.max((numItems+'').length, 2);
                        while (itemNum.length < numChars) {
                            itemNum = '0'+itemNum;
                        }
                    }
                    var num = i+1;
                    var rel = 'carousel-item-'+grp+'-'+num;
                    $pagination.append('<span rel="'+rel+'">'+itemNum+'</span>');
                }
                $pagination.find('span').eq(0).addClass('selected');

                // click on an image to advance to the next one
                $container.find('> * > img').click(function() {
                    var $thisItem = $(this).parents('.carousel-item');
                    var index = $items.index($thisItem);
                    index += 1;
                    if (index >= numItems) {
                        index = 0;
                    }
                    var $nextItem = $items.eq(index);
                    var classes = $nextItem.attr('class').split(' ');
                    for (var i in classes) {
                        var klass = classes[i];
                        if (klass.indexOf('carousel-item-') == 0) {
                            var $nextSpan = $pagination.find('span[rel='+klass+']');
                            switchTo($nextSpan);
                        }
                    }
                });

                // click on a pagination number/dot span to jump to a specific one
                $pagination.find('span').click(function() {
                    switchTo($(this));
                });

            } else {
                $pagination.addClass('empty-pagination');
            }

        });
    }

    makeCarousels();

    if (options.resizeEvent) {
        // automatically resize the carousel(s) when the window resizes
        $(window).bind(options.resizeEvent, function() {
            makeCarousels();
        });
    }

    return this;
};

})(jQuery);

