// simplefade by Dan(s)
// not so simple anymore IS IT?
$.extend($.fn, {
    //  NOTE:  the elements in the container should be ordered in reverse
    //      they're displayed last-to-first
    //      in each iteration, the last item is shown, faded, and pushed to the front (eg. pop + unshift)
    simplefade : function(params){
        params = $.extend({
            waitTime: 1000,
            fadeTime: 1000,
            resizeParent: false,
            removeFirst: false,
            initialHide: false,
            fadeInSibling: false,
            timings: null,
            //  fired when your previous sibling is being faded out (eg. you're becoming visible)
            slideChangingCallback: null,
            //  fired when you become fully visible
            slideChangeCallback: null,
            //  fadeIfCallback allows you to pass in a function that evals to true or false,
            //  which will determine whether simplefade decides to fade for that interval
            fadeIfCallback: function(){ return true; }
        }, params);

        this.each(function(){
            var $self = $(this), $slides;
            function updateSlides() {
                $slides = $self.find(".simplefade-slide");
            }
            updateSlides();

            if (params.resizeParent) {
                resizeParent($self, $slides);
            }

            //  the one-slide scenario
            var count = $slides.size(), current = 0;
            if (count < 2) {
                //  ignore params.removeFirst
                if (params.initialHide) {
                    //  ensure that they're (it's) visible
                    $slides.fadeTo(0, 1);
                }

                //  and there's nothing more we can do for you
                return;
            }

            //  support functions
            //  sequence = fade, swap, wait, (repeat)

            var timings = params.timings || [];
            function timingFor(prop, dft) {
                var t = timings[current] || {};
                return t[prop] || dft || null;
            }

            function doFade(fNext) {
                var $child = $slides.filter(':last');

                var dur = timingFor('fade', params.fadeTime);
                var f = $.isFunction(fNext) ? fNext : doSwap;

                $child.fadeTo(dur, 0, function() {
                    //  let it finish before we do our thing
                    setTimeout(f, 0);
                });

                var $sib = $child.prev(".simplefade-slide");
                if (params.fadeInSibling) {
                    $sib.fadeTo(dur, 1);
                }

                if ($.isFunction(params.slideChangingCallback)) {
                    params.slideChangingCallback($sib);
                }
            }

            function doSwap(fNext) {
                var $child = $slides.filter(':last');
                if (! params.initialHide) {
                    $child.css('opacity', '');
                }

                $child.prependTo($self);
                updateSlides();
                current = (current + 1) % count;

                var f = $.isFunction(fNext) ? fNext : doWait;
                setTimeout(f, 0);

                if ($.isFunction(params.slideChangeCallback)) {
                    params.slideChangeCallback($slides.last());
                }
            }

            function doWait(fNext) {
                var dur = timingFor('wait', params.waitTime);
                var f = $.isFunction(fNext) ? fNext : doFade;
                _setTimeout(f, dur);
            }

            function _setTimeout(callback, dur){
                if (!params.fadeIfCallback()){
                    setTimeout(function(){ _setTimeout(callback, dur); }, dur);
                    return false;
                }
                setTimeout(callback, dur);
            }

            //  we don't move this class around
            //  it actually represents the initial slide shown
            $slides.filter(':last').addClass('last');
            if (params.initialHide) {
                $slides.not(":last").fadeTo(0, 0);
            }

            if (params.removeFirst) {
                //  we were having troubles with the first animation
                //      the fade was not seamless, and it didn't happen any subsequent times
                //      so the caller can provide an extra element which (1) fades out and (2) gets removed
                //      it should be the last element in the container
                //  we create mock timings to make it all work smoothly
                //      the initial fade is the first frame's pre-wait
                timings.unshift({
                    fade: timingFor('wait')
                });
                doFade(function() {
                    var $child = $slides.filter(':last');
                    $child.remove();
                    timings.shift();

                    //  okay, so we DO move this class :)
                    updateSlides();
                    $slides.filter(':last').addClass('last');

                    setTimeout(doFade, 0);
                });
            }
            else {
                //  perform the pre-wait
                doWait();
            }
        });

        // simulates element height setting as if the children were NOT positioned absolute,
        // even though they are
        function resizeParent($self, $slides){
            $self.height(detectMinSize($slides));
        }

        function detectMinSize($slides){
            var min = 0;
            $slides.each(function(){
                h = $(this).outerHeight();
                min = h > min ? h : min;
            });
            return min;
        }

        return this;
    }
});

