
var EventArrow = new Class({
    Extends: ArrowA,

    initialize: function(eventname){
        this.parent( function(target, k){
               var f = this;
               function handler(event){
                   target.removeEvent(eventname, handler);
                   k.removeEvent('cancel', onCancel);
                   k.cont(event);
               }

               function onCancel(){
                 try{
                   target.removeEvent(eventname, handler);
                 } catch ( e ) {} // some objects in IE 8 may throw an exception here :(
                 k.removeEvent('cancel', onCancel);
               }

               k.addEvent('cancel', onCancel);
               target.addEvent(eventname, handler);
        });
    }

});

var BufferingEvent = new Class({
    Implements: Events,

    initialize: function(obj, eventname, opts){
        var o = opts || {
            latestOnly: false,
            initialA: null
        };
        this.obj = obj;
        this.eventname = eventname;
        this.buffers = {};
        this.latestOnly = o.latestOnly ? true : false;
        this.initialA = o.initialA ? o.initialA.A() : null;

        obj.addEvent(eventname, this.onEvent.bind(this));
    },

    onEvent: function(){
        var a = $A(arguments);
        for(var i in this.buffers){
            if(!this.latestOnly){ this.buffers[i].push(a); } 
            else { this.buffers[i][0] = a; }
        }
        this.fireEvent('newValue');
    },

    A: function(){
        var self  = this;
        return new ArrowA(function(x, k){

            var root = k.findRootProcess();

            function continueProcess(){
                self.removeEvent('newValue', continueProcess );
                var value = self.buffers[root.id].shift();
                k.cont(value);
            }

            function onRootProcessFinish(){ 
                self.removeEvent('newValue', continueProcess );
                delete self.buffers[root.id]; 
            }
            root.addEvents({'cancel': onRootProcessFinish, 'finish': onRootProcessFinish});

            function onProcessFinish(){ self.removeEvent('newValue', continueProcess ); }
            k.addEvents({'cancel': onProcessFinish, 'finish': onProcessFinish});

            var buffer = self.buffers[root.id];
            if(!buffer){
                buffer = self.buffers[root.id] = [];

                if(self.initialA){ 
                    k.cont( self.obj, self.initialA ); 
                } 
                else { self.addEvent('newValue', continueProcess); }
            }else if(buffer.length){
                k.cont(self.buffers[root.id].shift());
            } else {
                self.addEvent('newValue', continueProcess);
            }
        });
    }
});

function $sourceA(obj, eventname, opts){
    return new BufferingEvent(obj, eventname, opts).A();
}

function $onEvent(eventname){
    return new EventArrow(eventname);
}

Function.implement({
    onEvent: function(eventName){
        return new EventArrow(eventName).next(this);
    }
});

ArrowA.implement({
    onEvent: function(eventName){
        return new EventArrow(eventName).next(this);
    }
});


Native.implement([Element, Document, Window], {
    onEvent: function(name){
        return $constA( this ).next(  $onEvent(name) );
    },

    sourceA: function(eventName, opts){
        return $sourceA(this, eventName, opts);
    },

    A: function(){
        var self = this;
        return new ArrowA(function(x,k){
                k.cont(self);
            });
    }
});

Process.implement({
    onEvent: function(name){
        return $constA( this ).next( $onEvent(name) );
    },

    A: function(){
        var self = this;
        return new ArrowA(function(x, k){
            k.cont(self);
        });
    },

    next: function(){
        return this.onEvent('advance');
    }
});

