/*
 * kakahiaka.js
 *
 * 2013 Minori Yamashita <ympbyc@gmail.com>
 */

var kakahiaka = (function () {
    /*** private ***/

    function reset_BANG_ (app, state) {
        app._state = state;
        return app;
    }

    function get_watchers (app, key) {
        return app._watchers[key] || [];
    }

    function add_watch (app, key, f, immidiate) {
        if (_.has(app._watchers, key))
            app._watchers[key].push(f);
        else {
            app._watchers[key] = [f];
            if (immidiate && _.has(app._state, key))
                setTimeout(function () { f(deref(app), {}); }, 0);
        }
        return app;
    }


    function commit (app, diff, old_s, new_s) {
        _.each(diff, function (__, key_changed) {
            _.each(get_watchers(app, key_changed), function (watcher) {
                setTimeout(function () {
                    watcher(new_s, old_s, _.keys(diff));
                }, 0);
            });
        });
    }


    /*** public ***/

    /**
     * app :: state * (state -> IO ()) * (() -> state) -> App
     */
    function app (x, persist, recover) {
        var st = recover ? _.conj(x, recover()) : x;
        var a =  { _state: st
                   , _watchers: {}
                   , _persist: persist || function (st) {} };
        setTimeout(function () {
            commit(a, st, {}, st);
        }, 0);
        return a;
    }

    function deref (app) {
        return app._state;
    }


    /**
     * deftransition :: ({state} * ... -> {patch}) -> (App * ... -> undefined)
     */
    function deftransition (f) {
        return _.optarg(function (app, args) {
            var old_s = _.clone(deref(app));
            var diff  = _.apply(_.partial(f, old_s), args);
            if ( ! diff) return undefined;
            var new_s = _.merge(old_s, diff);
            commit(app, diff, old_s, new_s);
            app._persist(new_s);
            reset_BANG_(app, _.omit(new_s, new_s.__meta_keys || []));
            return undefined;
        });
    }


    function meta (diff, info) {
        return _.assoc(_.merge(diff, info), "__meta_keys", _.conj(_.keys(info), "__meta_keys"));
    }

    /**
     * watch_transition :: App * String * ({new state} * {old state} -> undefined) -> undefined
     */
    var watch_transition = add_watch;


    /**
     * simple_update :: App * String * Any -> undefined
     */
    var simple_update = deftransition(function (state, field, value) {
        return _.assoc({}, field, value);
    });


    return {
        app:              app,
        deref:            deref,
        deftransition:    deftransition,
        watch_transition: watch_transition,
        watch:            watch_transition,
        simple_update:    simple_update,
        meta:             meta
    };
}());

@@ -441,19 +441,17 @@
     else
- %7B
 %0A
+
         
@@ -480,20 +480,17 @@
  = %5Bf%5D;%0A
-    
+%0A
         
@@ -522,36 +522,32 @@
 p._state, key))%0A
-    
             setT
@@ -593,25 +593,16 @@
  %7D, 0);%0A
-        %7D
 %0A       
@@ -666,24 +666,65 @@
 s, new_s) %7B%0A
+        var changed_keys = _.keys(diff);%0A
         _.ea
@@ -916,28 +916,28 @@
  old_s, 
-_.keys(diff)
+changed_keys
 );%0A     
@@ -1457,24 +1457,32 @@
      return 
+_.clone(
 app._state;%0A
@@ -1475,24 +1475,25 @@
 e(app._state
+)
 ;%0A    %7D%0A%0A%0A  

fix bug around watch_transition's immidiate invocation