/* 
    <div class="alternation"> 
        <div>...</div>
        <div>...</div>
        <div>...</div>
    </div>

    $('.alternation').alternation({
        alternation: alternation type: "sequence" or "random"            (default: 'sequence')
        timeout:     alternation time (in ms)                            (default: 2000)
        animation:   animation type: "fade" or "slide"                   (default: "fade")
        speed:       animation speed (in ms or "slow", "normal", "fast") (default: "normal")
        class:       container element CSS class                         (default: "alternation") 
        height:      container element CSS height                        (default: "auto")
    });

    based on work of Torsten Baldes <http://medienfreunde.com/lab/innerfade/> which in turn is
    based on work of Matt Oakes <http://portfolio.gizone.co.uk/applications/slideshow/>
*/

(function($) {

    /* jQuery dynamic object method */
    $.fn.alternation = function(options) {
        return this.each(function() {   
            $.alternation(this, options);
        });
    };

    /* jQuery static function */
    $.alternation = function(container, options) {
        var settings = {
            'alternation': 'sequence',
            'timeout':     2000,
            'animation':   'fade',
            'speed':       'normal',
            'class':       'alternation',
            'height':      'auto'
        };
        if (options)
            $.extend(settings, options);
        var elements = $(container).children();
        if (elements.length > 1) {
            $(container).css('position', 'relative');
            $(container).css('height', settings["height"]);
            $(container).addClass(settings["class"]);
            for (var i = 0; i < elements.length; i++) {
                $(elements[i]).css('z-index', String(elements.length-i));
                $(elements[i]).css('position', 'absolute');
                $(elements[i]).hide();
            };
            if (settings["alternation"] == "sequence") {
                setTimeout(function() {
                    $.alternation._next(elements, settings, 1, 0);
                }, settings.timeout);
                $(elements[0]).show();
            } else if (settings["alternation"] == "random") {
                setTimeout(function() {
                    do {
                        current = Math.floor(Math.random() * elements.length);
                    } while (current == 0)
                    $.alternation._next(elements, settings, current, 0);
                }, settings.timeout);
                $(elements[0]).show();
            } else
                alert('jQuery: alternation: parameter "alternation" must either be "sequence" or "random"');
        }
    }

    /* jQuery static function */
    $.alternation._next = function(elements, settings, current, last) {
        if (settings["animation"] == 'slide') {
            $(elements[last]).slideUp(settings.speed,
                $(elements[current]).slideDown(settings.speed));
        } else if (settings["animation"] == 'fade') {
            $(elements[last]).fadeOut(settings.speed);
            $(elements[current]).fadeIn(settings.speed);
        } else
            alert('jQuery: alternation: parameter "animation" must either be "slide" or "fade"');
        if (settings["alternation"] == "sequence") {
            if ((current + 1) < elements.length) {
                current = current + 1;
                last = current - 1;
            } else {
                current = 0;
                last = elements.length - 1;
            }
        } else if (settings["alternation"] == "random") {
            last = current;
            while (current == last)
                current = Math.floor(Math.random() * elements.length);
        } else
            alert('jQuery: alternation: parameter "alternation" must either be "sequence" or "random"');
        setTimeout((function() {
            $.alternation._next(elements, settings, current, last);
        }), settings.timeout);
    };

})(jQuery);


/*  object constructor  */
jQuery.scheduler = function () {
    this.bucket = {};
    return;
};

/*  object methods  */
jQuery.scheduler.prototype = {
    /*  schedule a task  */
    schedule: function () {
        /*  schedule context with default parameters */
        var ctx = {
            "_scheduler": this,         /* internal: back-reference to scheduler object */
            "_handle":    null,         /* internal: unique handle of low-level task */
            "id":         null,         /* unique identifier of high-level schedule */
            "time":       1000,         /* time in milliseconds after which the task is run */
            "repeat":     false,        /* whether schedule should be automatically repeated */
            "protect":    false,        /* whether schedule should be protected from double scheduling */
            "obj":        null,         /* function context object ("this") */
            "func":       function(){}, /* function to call */
            "args":       []            /* function arguments to pass */
        };

        /*  helper function for portable checking whether something is a function  */
	    var isfn = function (fn) {
            return (
                   !!fn
                && typeof fn != "string"
                && typeof fn[0] == "undefined"
                && RegExp("function", "i").test(fn + "")
            );
	    };

        /*  parse arguments into context parameters (part 1/2):
            support the flexible way of an associated array */
        var i = 0;
        if (typeof arguments[i] == "object") {
            for (var option in arguments[i])
                if (   typeof ctx[option] != "undefined"
                    && option.substr(0, 1) != "_")
                    ctx[option] = arguments[i][option];
            i++;
        }

        /*  parse arguments into context parameters (part 2/2):
            support: schedule([time [, repeat], ]{{obj, methodname} | func}[, arg, ...]); */
        if (typeof arguments[i] == "number")
            ctx["time"] = arguments[i++];
        if (typeof arguments[i] == "boolean")
            ctx["repeat"] = arguments[i++];
        if (typeof arguments[i] == "boolean")
            ctx["protect"] = arguments[i++];
        if (   typeof arguments[i] == "object"
            && typeof arguments[i+1] == "string"
            && isfn(arguments[i][arguments[i+1]])) {
            ctx["obj"] = arguments[i++];
            ctx["func"] = arguments[i++];
        }
        else if (typeof arguments[i] != "undefined")
            ctx["func"] = arguments[i++];
        while (typeof arguments[i] != "undefined")
            ctx["args"].push(arguments[i++]);

        /*  determine unique identifier of task  */
        if (ctx["id"] == null)
            ctx["id"] = (  String(ctx["repeat"])  + ":"
                         + String(ctx["protect"]) + ":"
                         + String(ctx["time"])    + ":"
                         + String(ctx["obj"])     + ":"
                         + String(ctx["func"])    + ":"
                         + String(ctx["args"])         );

        /*  optionally protect from duplicate calls  */
        if (ctx["protect"])
            if (typeof this.bucket[ctx["id"]] != "undefined")
                return this.bucket[ctx["id"]];

        /*  support execution of methods by name and arbitrary scripts  */
        if (!isfn(ctx["func"])) {
            if (   ctx["obj"] != null
                && typeof ctx["obj"] == "object"
	            && typeof ctx["func"] == "string"
                && isfn(ctx["obj"][ctx["func"]]))
                /*  method by name  */
                ctx["func"] = ctx["obj"][ctx["func"]];
            else
                /*  arbitrary script  */
                ctx["func"] = eval("function () { " + ctx["func"] + " }");
        }

        /*  pass-through to internal scheduling operation  */
        ctx["_handle"] = this._schedule(ctx);

        /*  store context into bucket of scheduler object  */
        this.bucket[ctx["id"]] = ctx;

        /*  return context  */
        return ctx;
    },

    /*  re-schedule a task  */
    reschedule: function (ctx) {
        if (typeof ctx == "string")
            ctx = this.bucket[ctx];

        /*  pass-through to internal scheduling operation  */
        ctx["_handle"] = this._schedule(ctx);

        /*  return context  */
        return ctx;
    },

    /*  internal scheduling operation  */
    _schedule: function (ctx) {
        /*  closure to act as the call trampoline function  */
        var trampoline = function () {
            /*  jump into function  */
            var obj = (ctx["obj"] != null ? ctx["obj"] : ctx);
            (ctx["func"]).apply(obj, ctx["args"]);

            /*  either repeat scheduling and keep in bucket or
                just stop scheduling and delete from scheduler bucket  */
            if (   /* not cancelled from inside... */
                   typeof (ctx["_scheduler"]).bucket[ctx["id"]] != "undefined"
                && /* ...and repeating requested */
                   ctx["repeat"])
                (ctx["_scheduler"])._schedule(ctx);
            else
                delete (ctx["_scheduler"]).bucket[ctx["id"]];
        }

        /*  schedule task and return handle  */
        return setTimeout(trampoline, ctx["time"]);
    },

    /*  cancel a scheduled task  */
    cancel: function (ctx) {
        if (typeof ctx == "string")
            ctx = this.bucket[ctx];

        /*  cancel scheduled task  */
        if (typeof ctx == "object") {
            clearTimeout(ctx["_handle"]);
            delete this.bucket[ctx["id"]];
        }
    }
};

/* ============================== */

/* integrate a global instance of the scheduler into the global jQuery object */
jQuery.extend({
    scheduler$: new jQuery.scheduler(),
	schedule:   function () { return jQuery.scheduler$.schedule.apply  (jQuery.scheduler$, arguments) },
	reschedule: function () { return jQuery.scheduler$.reschedule.apply(jQuery.scheduler$, arguments) },
	cancel:     function () { return jQuery.scheduler$.cancel.apply    (jQuery.scheduler$, arguments) }
});

/* integrate scheduling convinience method into all jQuery objects */
jQuery.fn.extend({
	schedule: function () {
        var a = arguments;
		return this.each(function () {
            var time = a[0];
            var func = a[1];
            var args = [];
            for (var i = 2; i < a.length; i++)
                args.push(a[i]);
            return jQuery.schedule({
                id:      this,
                repeat:  false,
                protect: false,
                time:    time,
                obj:     this,
                func:    func,
                args:    args
            });
		});
	}
});

