(function(x){
    function filEvent( e )  { return function(l){ return l.__event !== e; } }
    function filFunct( f )  { return function(l){ return f !== l; } }
    function diff(a,b)      { return a.filter(function(n){return b.indexOf(n) === -1; }); }
    function debounce( t,f ){ var x; return function(){ clearTimeout(x); x = setTimeout(f,t); }}
    function Observable( val, o ){
        val = val || null;
        if ( val && val.call ) return new Computed( val, o );
        if ( val && val.push ) return new ObservableArray( val, o );
        var listeners = [];
        function obs( v )      { return arguments.length ? set( v ) : get(); }
        function get()         { var d; if (Computed && (d = Computed.__detected) ) d.push(obs); emit('get'); return val; }
        function set( v )      { var o = val; val = v; emit('change', {oldValue : o, newValue : v}); return obs; }
        function emit( e, d )  { listeners.filter(function(l){ return l.__event===e;}).forEach(function(l){ l.call( null, d );}); return obs; }
        function on( e, l )    { l.__event = e; listeners.push( l ); emit('addListener', l ); return obs; }
        function off( e )      { listeners = listeners.filter(e.match ? filEvent(e) : filFunct(e)); emit('removeListener', e ); return obs; }
        obs.get  = get;
        obs.set  = set;
        obs.emit = obs.trigger = emit;
        obs.on   = obs.addEventListener = on;
        obs.off  = obs.removeEventListener = off;
        obs.isObservable = obs.isSimpleObservable = true;
        return obs;
    }
    function ObservableArray( arr, o ){
        arr = arr || [];
        var listeners = [];
        function obs( v )      { return arguments.length ? set( v ) : get(); }
        function get()         { var d; if (Computed && (d = Computed.__detected) ) d.push(obs); emit('get'); return arr; }
        function set( v )      { if ( !v.push ) throw new Error( "Cannot replace array with some other variable type" );
                                 var o = arr; arr = v; emit('change',{oldValue:o,newValue:v,added:v,deleted:o}); return obs;
                               }
        function emit( e, d )  { listeners.filter(function(l){ return l.__event===e;}).forEach(function(l){ l.call( null, d );}); return obs; }
        function on( e, l )    { l.__event = e; listeners.push( l ); emit('addListener', l ); return obs; }
        function off( e )      { listeners = listeners.filter(e.match ? filEvent(e) : filFunct(e)); emit('removeListener', e ); return obs; }
        function pop()         { var o = [].slice.call(arr), a = arr.pop(); emit('change',{oldValue:o,newValue:arr,deleted:[a]}); return a; }
        function shift()       { var o = [].slice.call(arr), a = arr.shift(); emit('change', {oldValue:o,newValue:arr,deleted:[a]}); return a; }
        function push()        { var o = [].slice.call(arr), a = [].slice.call(arguments); [].push.apply(arr,a); emit('change',{oldValue:o,newValue:arr,added:a});return obs; }
        function unshift()     { var o = [].slice.call(arr), a = [].slice.call(arguments); [].unshift.apply(arr,a); emit('change',{oldValue:o,newValue:arr,added:a}); return obs; }
        function splice()      { var o = [].slice.call(arr), a = [].slice.call(arguments); [].splice.apply(arr,a);
                                 emit('change',{oldValue:o,newValue:arr,added:diff(arr,o),deleted:diff(o,arr)}); return obs; }
        function sort()        { var o = [].slice.call(arr), a = [].slice.call(arguments); arr = [].sort.apply(arr,a); emit('change', {oldValue:o, newValue:arr}); return obs; }
        function reverse()     { var o = [].slice.call(arr), a = [].slice.call(arguments); arr = [].reverse.apply(arr,a); emit('change', {oldValue:o, newValue:arr}); return obs; }
        function filter()      { var o = [].slice.call(arr), a = [].slice.call(arguments); arr = [].filter.apply(arr,a);
                                 emit('change', {oldValue:o, newValue:arr,deleted:diff(o,arr)}); return obs; }
        obs.get     = get;
        obs.set     = set;
        obs.emit    = obs.trigger = emit;
        obs.on      = obs.addEventListener = on;
        obs.off     = obs.removeEventListener = off;
        obs.pop     = pop;
        obs.shift   = shift;
        obs.push    = push;
        obs.unshift = unshift;
        obs.filter  = filter;
        obs.isObservable = obs.isObservableArray = true;
        return obs;
    }
    function Computed( fn, o ){
        if (!fn || !fn.call) throw new Error( "Computed needs a computing function!" );
        var deps = [], listeners = [], val, o = o || {};
        function obs()        { return get(); }
        function update()     { var o = val; val = fn(); emit('change', {oldValue: o, newValue: val}); return obs; }
        function get()        { var d; if (Computed && (d = Computed.__detected) ) d.push(obs); emit('get'); return val; }
        function emit( e, d ) { listeners.filter(function(l){ return l.__event===e;}).forEach(function(l){ l.call( null, d );}); return obs; }
        function on( e, l )   { l.__event = e; listeners.push( l ); emit('addListener', l ); return obs; }
        function off( e )     { listeners = listeners.filter(e.match ? filEvent(e) : filFunct(e)); emit('removeListener', e ); return obs; }
        function attach()     { deps.forEach(function(obs){ obs.on('change', o.debounce ? debounce(o.debounce,update) : update); }); }
        function detach()     { deps.forEach(function(obs){ obs.off(update); }); }
        obs.get     = get;                                         // add functions
        obs.emit    = obs.trigger = emit;
        obs.on      = obs.addEventListener = on;
        obs.off     = obs.removeEventListener = off;
        obs.update  = update;
        obs.destroy = detach;
        obs.isObservable = obs.isComputedObservable = true;
        Computed.__detected = [];                                 // prepare dependency detection
        update();                                                 // start dependency detection
        deps = Computed.__detected;          // evaluate dependency detection
        delete Computed.__detected;                               // clean up dependency detection
        attach(); // set up listeners
        return obs;
    }
    x.Computed = Computed;
    x.Observable = Observable;
    x.ObservableArray = ObservableArray;
    x.O = function(v,o){ return new Observable(v,o); };
}(window));