
require-macros:
   earl-mocha ->
      describe, it, before, after, before-each, after-each
      xdescribe, xit
      assert, asserts
      expect-error

require:
   ..src ->
      deprox, read-proxy, write-proxy, Directory, ID
      Patch, Tracks
      reactive-function, System
   .people ->
      static-dir
      alice, bob, clara, donald, emily, francis, gerard, helen
      aid, bid, cid, did, eid, fid, gid, hid


count-calls{f} =
   rval{*args} =
      rval.count += 1
      f.apply{this, args}
   rval.count = 0
   rval

predicate! ArrayProx{p} =
   Array? deprox{p}


describe "utilities":

   it "count-calls":
      count-calls! f{x, y} = x + y
      assert f{10, 20} == 30
      assert f.count == 1
      assert f{7, 8} == 15
      assert f.count == 2

   it "ArrayProx predicate":
      p1 = write-proxy{{1, 2, 3}, Patch{}}
      assert [ArrayProx? p1]
      p2 = read-proxy{{1, 2, 3}, Tracks{}} ;; {=}}
      assert [ArrayProx? p2]
      p3 = write-proxy{{a = 1, b = 2}, Patch{}}
      assert not [ArrayProx? p3]
      assert [ArrayProx? {1, 2, 3}]


describe "Reactive functions":

   before-each:

      count-calls! reactive-function! @say-name{person} =
         {greeting = 'Hello, my name is {person.name}!'}

      count-calls! reactive-function! @say-names{people} =
         people each
            ArrayProx? subp -> @say-names{subp}
            person -> 'Hello, my name is {person.name}!'

      count-calls! reactive-function! @say-parent-names{person} =
         {mother = @say-name{person.mother}
          father = @say-name{person.father}}


   it "called through System":
      sys = System{clara, @say-name}
      assert sys.get{} == {greeting = 'Hello, my name is clara!'}

   it "can be updated":

      sys = System{clara, @say-name}
      assert sys.get{} == {greeting = 'Hello, my name is clara!'}

      sys.transact with {clara} ->
         clara.name = "clairette"
      assert sys.get{} == {greeting = 'Hello, my name is clairette!'}

   it "is parsimonious":

      assert @say-name.orig.count == 0

      ;; Called to initialize
      sys = System{clara, @say-name}
      assert @say-name.orig.count == 1

      ;; Change is irrelevant because @say-name does not use age,
      ;; so @say-name should not be called again.
      sys.transact with {clara} ->
         clara.age = 21
      assert @say-name.orig.count == 1

      ;; Change is relevant, @say-name should be called
      sys.transact with {clara} ->
         clara.name = "clairette"
      assert @say-name.orig.count == 2

      ;; Irrelevant change again
      sys.transact with {clara} ->
         clara.mother = clara.father
      assert @say-name.orig.count == 2

      ;; Relevant change again
      sys.transact with {clara} ->
         clara.name = "bob"
      assert @say-name.orig.count == 3

      assert sys.get{} == {greeting = 'Hello, my name is bob!'}

   it "composes":
      sys = System{clara, @say-parent-names}

      assert sys.get{} == {
         mother = {greeting = 'Hello, my name is alice!'}
         father = {greeting = 'Hello, my name is bob!'}
      }

   it "compositions can be modified":

      sys = System{clara, @say-parent-names}

      sys.transact with {clara} ->
         clara.mother.name = .balice

      assert sys.get{} == {
         mother = {greeting = 'Hello, my name is balice!'}
         father = {greeting = 'Hello, my name is bob!'}
      }


   it "compositions can be modified and are parsimonious":

      assert @say-parent-names.orig.count == 0
      assert @say-name.orig.count == 0

      sys = System{clara, @say-parent-names}

      assert @say-parent-names.orig.count == 1
      assert @say-name.orig.count == 2

      sys.transact with {clara} ->
         clara.mother.name = .balice

      assert @say-parent-names.orig.count == 1
      assert @say-name.orig.count == 3

      sys.transact with {clara} ->
         clara.father.age = 88

      assert @say-parent-names.orig.count == 1
      assert @say-name.orig.count == 3

      sys.transact with {clara} ->
         clara.father.name = .gob
         clara.mother = clara.father

      assert @say-parent-names.orig.count == 2
      assert @say-name.orig.count == 4

      assert sys.get{} == {
         mother = {greeting = 'Hello, my name is gob!'}
         father = {greeting = 'Hello, my name is gob!'}
      }

   it "operates in-place":
      sys = System{clara, @say-parent-names}
      orig = sys.get{}
      assert orig == {
         mother = {greeting = 'Hello, my name is alice!'}
         father = {greeting = 'Hello, my name is bob!'}
      }
      sys.transact with {clara} ->
         {=> mother, => father} = clara
         {clara.mother, clara.father} = {father, mother}
      assert orig == {
         mother = {greeting = 'Hello, my name is bob!'}
         father = {greeting = 'Hello, my name is alice!'}
      }


   describe "custom root":

      it "actions":
         storage = {}
         sys = System{clara, @say-name} with
            action{x} = storage.push{1}
         assert sys.get{} == {greeting = 'Hello, my name is clara!'}
         assert storage == {1}

      it "call action only when root changes":
         storage = {}
         sys = System{{alice, {bob, clara}}, @say-names} with
            action{x} = storage.push{1}
         assert storage == {1}

         sys.transact with {people} ->
            people.get{0}.name = "alison"
         assert storage == {1, 1}

         sys.transact with {people} ->
            people.get{1}.get{0}.name = "balthazar"
         assert storage == {1, 1}

         assert sys.get{} == {
            'Hello, my name is alison!'
            {'Hello, my name is balthazar!'
             'Hello, my name is clara!'}
         }

         sys.transact with {people} ->
            people.set{1, donald}
         assert storage == {1, 1, 1}

         assert sys.get{} == {
            'Hello, my name is alison!'
            'Hello, my name is donald!'
         }


   describe "on arrays":

      before-each:
         @people = {alice, bob, clara}
         @sys = System{@people, @say-names}

      it "basic":
         assert @sys.get{} == {
            'Hello, my name is alice!'
            'Hello, my name is bob!'
            'Hello, my name is clara!'
         }
         @sys.transact with {people} ->
            people.get{0}.name = .albert

         assert @sys.get{} == {
            'Hello, my name is albert!'
            'Hello, my name is bob!'
            'Hello, my name is clara!'
         }

         @sys.transact with {people} ->
            people.set{0, donald}
         assert @sys.get{} == {
            'Hello, my name is donald!'
            'Hello, my name is bob!'
            'Hello, my name is clara!'
         }

      it "modify with each":
         @sys.transact with {people} ->
            people each person -> person.name += "o"
         assert @sys.get{} == {
            'Hello, my name is aliceo!'
            'Hello, my name is bobo!'
            'Hello, my name is clarao!'
         }

      it "push value":
         @sys.transact with {people} ->
            people.push with donald
         assert @sys.get{} == {
            'Hello, my name is alice!'
            'Hello, my name is bob!'
            'Hello, my name is clara!'
            'Hello, my name is donald!'
         }

   describe "miscellaneous tests":

      it "swap":
         sys = System{clara, @say-parent-names}
         sys.transact with {clara} ->
            {=> mother, => father} = clara
            {clara.mother, clara.father} = {father, mother}
         assert sys.get{} == {
            mother = {greeting = 'Hello, my name is bob!'}
            father = {greeting = 'Hello, my name is alice!'}
         }

      it "change all":
         sys = System{clara, @say-parent-names}
         sys.transact with {clara} ->
            {=> mother, => father} = clara
            mother.name = .yann
            father.name = .zoe
            {clara.mother, clara.father} = {father, mother}
         assert sys.get{} == {
            mother = {greeting = 'Hello, my name is zoe!'}
            father = {greeting = 'Hello, my name is yann!'}
         }

      it "deep change":
         sys = System{clara, @say-parent-names}
         sys.transact with {clara} ->
            clara.mother.name = .gertrude
         assert sys.get{} == {
            mother = {greeting = 'Hello, my name is gertrude!'}
            father = {greeting = 'Hello, my name is bob!'}
         }

      it "some test":

         sys = System{clara, @say-parent-names}

         sys.transact with {clara} ->
            clara.mother = emily
            clara.father = donald

         assert sys.get{} == {
            mother = {greeting = 'Hello, my name is emily!'}
            father = {greeting = 'Hello, my name is donald!'}
         }

         sys.transact with {clara} ->
            clara.mother.name = .esmeralda
            clara.father.name = .dumbledore

         assert sys.get{} == {
            mother = {greeting = 'Hello, my name is esmeralda!'}
            father = {greeting = 'Hello, my name is dumbledore!'}
         }

      it "megatest":

         let people = {{alice}, clara, {bob, emily}}
         sys = System{people, @say-names}

         assert sys.get{} == {
            {'Hello, my name is alice!'}
            'Hello, my name is clara!'
            {'Hello, my name is bob!'
             'Hello, my name is emily!'}
         }
         assert @say-names.orig.count == 3

         sys.transact with {people} ->
            people.get{1}.name = "clairette"
            people.push with static-dir.acquire{{francis, gerard}}

         assert sys.get{} == {
            {'Hello, my name is alice!'}
            'Hello, my name is clairette!'
            {'Hello, my name is bob!'
             'Hello, my name is emily!'}
            {'Hello, my name is francis!'
             'Hello, my name is gerard!'}
         }
         assert @say-names.orig.count == 5

         sys.transact with {people} ->
            people.get{2}.splice{1, 0, helen}

         assert sys.get{} == {
            {'Hello, my name is alice!'}
            'Hello, my name is clairette!'
            {'Hello, my name is bob!'
             'Hello, my name is helen!'
             'Hello, my name is emily!'}
            {'Hello, my name is francis!'
             'Hello, my name is gerard!'}
         }
         assert @say-names.orig.count == 6

         sys.transact with {people} ->
            helper{people} where helper{each match} =
               ArrayProx? subp -> helper{subp}
               person -> person.name += "-tron"

         assert sys.get{} == {
            {'Hello, my name is alice-tron!'}
            'Hello, my name is clairette-tron!'
            {'Hello, my name is bob-tron!'
             'Hello, my name is helen-tron!'
             'Hello, my name is emily-tron!'}
            {'Hello, my name is francis-tron!'
             'Hello, my name is gerard-tron!'}
         }
         assert @say-names.orig.count == 10

         sys.transact with {people} ->
            helper{people.get{2}} where helper{each match} =
               ArrayProx? subp -> helper{subp}
               person -> person.name += "-mega"

         assert sys.get{} == {
            {'Hello, my name is alice-tron!'}
            'Hello, my name is clairette-tron!'
            {'Hello, my name is bob-tron-mega!'
             'Hello, my name is helen-tron-mega!'
             'Hello, my name is emily-tron-mega!'}
            {'Hello, my name is francis-tron!'
             'Hello, my name is gerard-tron!'}
         }
         assert @say-names.orig.count == 11

         ;; start = +[new Date{}]
         ;; iters = 100
         ;; 1..iters each i ->
         ;;    sys.transact with {people} ->
         ;;       helper{people} where helper{each match} =
         ;;          ArrayProx? subp -> helper{subp}
         ;;          person -> person.name += "-tron"
         ;; end = +[new Date{}]
         ;; print [end - start] / iters

      it "redundancies":

         let people = {{alice}, bob, {bob, clara, bob}, donald}
         sys = System{people, @say-names}

         assert sys.get{} == {
            {'Hello, my name is alice!'}
            'Hello, my name is bob!'
            {'Hello, my name is bob!'
             'Hello, my name is clara!'
             'Hello, my name is bob!'}
            'Hello, my name is donald!'
         }
         assert @say-names.orig.count == 3


         sys.transact with {people} ->
            people.get{1}.name = "banana"

         assert sys.get{} == {
            {'Hello, my name is alice!'}
            'Hello, my name is banana!'
            {'Hello, my name is banana!'
             'Hello, my name is clara!'
             'Hello, my name is banana!'}
            'Hello, my name is donald!'
         }
         assert @say-names.orig.count == 5


describe "Nested reactive":

   before-each:

      count-calls! reactive-function! @one{person} =
         {'A:{person.name}'}

      count-calls! reactive-function! @two{person} =
         {@one{person}, 'B:{person.name}'}

      count-calls! reactive-function! @three{person} =
         {@two{person}, 'C:{person.name}'}

   it "initially correct":
      sys = System{clara, @three}
      assert sys.get{} == {{{"A:clara"}, "B:clara"}, "C:clara"}

   it "update":
      sys = System{clara, @three}
      sys.transact with {clara} ->
         clara.name = .coco
      assert sys.get{} == {{{"A:coco"}, "B:coco"}, "C:coco"}

   it "using commit":
      sys = System{clara, @three}
      _clara = sys.model{}
      _clara.name = .coco
      sys.commit{}
      assert sys.get{} == {{{"A:coco"}, "B:coco"}, "C:coco"}


