/**
 * Simple usage of kstream data by allowing jquery like binding to the events
 *
 * Usage:
 *<code>
 * kstream = KStream.init("somechannel", "http...ape-jsf", "ape.keyteq.no:6666");
 * kstream.bind("receive", function(data) { ... }, ["articles", "comments"});
 *</code>
 *
 * @author Raymond Julin
 */
(function() {
    KStream = {
        init: function(channel, baseUrl, server, domain) {
            if (!baseUrl)
                var baseUrl = 'http://ape.keyteq.no/ape-jsf'; //APE JSF 
            if (!server)
                var server = 'ape.keyteq.no:6666';
            if (!domain)
                var domain = 'keyteq.no';
            APE.Config.baseUrl = baseUrl; // 'http://ape.keyteq.no/ape-jsf'; //APE JSF 
            APE.Config.server = server; //'ape.keyteq.no:6666'; //APE server URL
            APE.Config.domain = domain; 
            APE.Config.transport = 2;
            (function(){
                for (var i = 0; i < arguments.length; i++)
                    APE.Config.scripts.push(APE.Config.baseUrl + '/Source/' + arguments[i] + '.js');
            })('mootools-core', 'Core/APE', 'Core/Events', 'Core/Core', 'Pipe/Pipe', 
                'Pipe/PipeProxy', 'Pipe/PipeMulti', 'Pipe/PipeSingle', 
                'Request/Request','Request/Request.Stack', 
                'Request/Request.CycledStack', 
                'Transport/Transport.longPolling',
                'Transport/Transport.SSE', 
                'Transport/Transport.XHRStreaming', 
                'Transport/Transport.JSONP', 'Core/Utility', 'Core/JSON');
            obj = {
                /**
                 * The APE jsf client object
                 */
                con: new APE.Client(),

                /**
                 * A stack of messages received by push from the server
                 * intermediately stored here and shifted out
                 * in order to controll the speed of it all
                 */
                stack: [],

                /**
                 * Keep track of wether a shift operation is running
                 * on the stack
                 */
                running: false,
                paused: false,

                /**
                 * Array over registered event handlers
                 */
                handlers : {
                    'receive': []
                },

                /**
                 * Responsible for deciding how much delay to use on messages
                 * based on the size of the message stack
                 * This is expected to be set by the user but has a predefined
                 * handler that works decently for most cases
                 */
                delayHandler : function(stackSize)
                {
                    if (stackSize <= 5)
                        return 2500;
                    else if (stackSize <= 10)
                        return 1500;
                    else if (stackSize <= 25)
                        return 1000;
                    else
                        return 500;
                },

                /**
                 * Override the default delay handler method
                 * Needs to be a method accepting one numeric argument
                 * and returning a delay in miliseconds
                 */
                setDelayHandler : function(handler)
                {
                    this.delayHandler = handler;
                },

                /**
                 * Start looping over the stack of messages
                 * This will handle smooth timing between new pushes
                 */
                run : function(sleep)
                {
                    if (this.stack.length == 0)
                        return;
                    if (!sleep)
                        sleep = 1000;
                    if (this.paused)
                        return;
                    // Keep track of this method to reuse it
                    var that = this;
                    that.running = true;
                    // shift the array using a timeout to achieve slow iteration of
                    // the array
                    setTimeout(function()
                    {
                        var elem = that.stack.shift();
                        // Run callbacks matching this entity
                        for (var i=0; i<that.handlers.receive.length; i++)
                        {
                            var it = that.handlers.receive[i];
                            if (!it.filter || it.filter.indexOf(elem.entity) != -1)
                                it.callback(elem, sleep);
                        }
                        // Need to get the length right here so its as up to date as possible
                        var length = that.stack.length;
                        // Hardcoded speed based on current stack length
                        // TODO Make this defineable for each use case
                        if (length < 1)
                            that.running = false;
                        else
                            that.run(that.delayHandler(length, elem.catchup));
                    }, sleep);
                },
                
                /**
                 * Pause the streaming
                 */
                pause : function()
                {
                    this.paused = true;
                },

                /**
                 * Restart the streaming after having called pause
                 */
                start : function()
                {
                    if (this.paused)
                    {
                        this.paused = false;
                        if (this.stack.length > 0)
                            this.run(this.delayHandler(this.stack.length, this.stack[0].catchup));
                    }
                },

                /**
                 * Connect to server and join a channel
                 */
                join : function(channel)
                {
                    this.con.load();
                    var that = this;
                    this.con.addEvent('load', function() {
                        that.con.core.start({"chan":channel});
                    });
                },

                /**
                 * Bind to events happening on the real time stream
                 * Possible events: "receive", "connect", "ready"
                 *
                 * @param string eventType
                 * @param function eventHandler The callback. Should accept one object as param:
                 *        callback = function(data) {...}
                 * @param array entityFilter Ensure this handler is only acted on certain entities
                 */
                bind : function(eventType, eventHandler, entityFilter) {
                    if (!entityFilter)
                        entityFilter = false;
                    var that = this;
                    switch (eventType)
                    {
                        case 'receive':
                            if (that.handlers.receive.length == 0)
                            {
                                that.handlers.receive.push({
                                    'callback' : eventHandler,
                                    'filter' : entityFilter});
                                this.con.onRaw('data', function(raw, pipe) {
                                    /**
                                     * Push an objects data to a stack over happenings
                                     * This stack is in terms processed by a method
                                     * that loops it till its empty and picks up
                                     * when its getting filled again
                                     */
                                    var catchup = (raw.data.catchup) ? true : false;
                                    that.stack.push({
                                        'time': raw.time,
                                        'message': raw.data.message,
                                        'entity': raw.data.entity,
                                        'id': raw.data.entityId,
                                        'data': raw.data.data,
                                        'catchup': catchup
                                    });
                                    if (!that.running)
                                        that.run(that.delayHandler(that.stack.length, catchup));
                                });
                            }
                            else
                            {
                                /**
                                 * Add the event handler never the less
                                 */
                                this.handlers.receive.push({
                                    'callback' : eventHandler,
                                    'filter' : entityFilter});
                            }
                            break;
                        case 'ready':
                            this.con.addEvent("ready", eventHandler);
                            break;
                        case 'connect':
                            this.con.onRaw("connected", function(raw,pipe) { eventHandler(raw.data); });
                            break;
                    }
                }
            }
            obj.join(channel);
            return obj;
        }
    };
}());
