require '../../spec_helper'

describe "Freakset", ->

  describe 'instance', ->

    beforeEach ->
      @freakset = Freakset.build(store: { name: "value" })


    describe '#new', ->

      it 'should init stack and store with its correct empty values', ->
        flow = Freakset.build()
        expect(flow.stack).toEqual([])
        expect(flow.store).toEqual({})

      it 'should override initial values', ->
        flow = Freakset.build(stack: "stack", store: "store")
        expect(flow.stack).toEqual("stack")
        expect(flow.store).toEqual("store")


    describe '#isRunning', ->

      it 'should return false if the set has finished', ->
        @freakset.stack = []
        @freakset.commit()
        expect(@freakset.isRunning()).toBe(false)

      it 'should return true if the set is still running', ->
        @freakset.stack = [ "sa" ]
        @freakset.commit()
        expect(@freakset.isRunning()).toBe(true)

      it 'should return true if at least one innerFreakset is still running', ->
        inner = Freakset.build()
        inner.running = yes
        @freakset.innerFreaksets.push(inner)
        expect(@freakset.isRunning()).toBe(true)

      it 'should return true if a inner-innerFreakset is still running', ->
        innerInner = Freakset.build()
        innerInner.running = yes
        inner = Freakset.build()
        inner.running = no
        inner.innerFreaksets.push(innerInner)
        @freakset.innerFreaksets.push(inner)
        expect(@freakset.isRunning()).toBe(true)

      it 'should return false all inner(inner(inner)) sets have finished', ->
        innerInner = Freakset.build()
        innerInner.running = no
        inner = Freakset.build()
        inner.running = no
        inner.innerFreaksets.push(innerInner)
        @freakset.innerFreaksets.push(inner)
        expect(@freakset.isRunning()).toBe(false)


    describe '#root', ->

      it 'should return self if there is no outer Freakset', ->
        expect(@freakset.root()).toEqual(@freakset)

      it 'should return the most outer Freakset', ->
        inner = Freakset.build(outerFreakset: @freakset)
        innerInner = Freakset.build(outerFreakset: inner)
        expect(innerInner.root()).toEqual(@freakset)


    describe '#next', ->

      it 'should mark the set as running', ->
        @freakset.stack = [ "step" ]
        @freakset.next()
        expect(@freakset.isRunning()).toBe(true)

      it 'should get and remove the first actor of the stack', ->
        @freakset.stack = [ "test" ]
        @freakset.next()
        expect(@freakset.stack.length).toBe(0)

      it 'should return true if the stack is empty', ->
        @freakset.stack = []
        expect(@freakset.next()).toBe(true)

      it 'should #finish the freakset if the stack is empty', ->
        spyOn(@freakset, "finish")
        @freakset.stack = []
        @freakset.next()
        expect(@freakset.finish).toHaveBeenCalled()

      it 'should return false if the set has been canceled', ->
        @freakset.canceled = yes
        expect(@freakset.next()).toBe false


    describe '#commit', ->

      it 'should call #next on the current Freakset', ->
        spyOn(@freakset, 'next')
        @freakset.commit()
        expect(@freakset.next).toHaveBeenCalled()

      it 'should decrease the parallel step count if the Freakset is running in parallel', ->
        @freakset.next = jasmine.createSpy()
        @freakset.runInParallel = yes
        @freakset.parallelStepCount = 2
        @freakset.commit()
        expect(@freakset.parallelStepCount).toBe(1)


    describe '#compile', ->

      it 'should build and commit the root freakset', ->
        spyOn(@freakset, "buildAndRunFreakset")
        @freakset.compile()
        expect(@freakset.buildAndRunFreakset).toHaveBeenCalledWith(stack: @freakset.stack, store: {})

      it 'should build a store from the passed object', ->
        spyOn(@freakset, "buildAndRunFreakset")
        @freakset.compile({ test: "foo" })
        expect(@freakset.buildAndRunFreakset).toHaveBeenCalledWith(stack: @freakset.stack, store: { test: "foo" })

      it 'should attach a resolvedCallback if passed', ->
        fn = ->
        @freakset.compile {}, fn
        expect(@freakset.resolvedCallback).toBe(fn)

      it 'should return the newly build Freakset', ->
        spyOn(@freakset, "buildAndRunFreakset").andReturn('newFreakset')
        expect(@freakset.compile({})).toEqual('newFreakset')


    describe '#cancel', ->

      it 'should mark the root-set as canceled', ->
        @freakset.cancel()
        expect(@freakset.canceled).toBe true

      it 'should mark the root-set as finished', ->
        @freakset.cancel()
        expect(@freakset.running).toBe false

      it 'should resolve the root', ->
        spyOn(@freakset, 'resolveRoot')
        @freakset.cancel()
        expect(@freakset.resolveRoot).toHaveBeenCalled()


    describe '#resolveRoot', ->

      it 'should invoke the resolvedCallback (if defined) after the set has finished', ->
        fn = ->
        spyOn(fn, 'call')
        @freakset.resolvedCallback = fn
        @freakset.running = no
        @freakset.resolveRoot()
        expect(fn.call).toHaveBeenCalled()


    describe '#finish', ->

      it 'should mark the freakset as not running anymore', ->
        @freakset.stack = []
        @freakset.finish()
        expect(@freakset.running).toBe(false)

      it 'should call commit on the outerFreakset', ->
        @freakset.stack = []
        @freakset.outerFreakset = Freakset.build()
        spyOn(@freakset.outerFreakset, "commit")
        @freakset.finish()
        expect(@freakset.outerFreakset.commit).toHaveBeenCalled()

      it 'should ask its root to resolve', ->
        spyOn(@freakset, "resolveRoot")
        @freakset.stack = []
        @freakset.finish()
        expect(@freakset.resolveRoot).toHaveBeenCalled()

    describe '#hasActor', ->

      beforeEach ->
        @freakset.stack = [ { scope: "step1" }, { scope: "step2" }, { scope: "step2" }]

      it 'should return true if the freakset contains the wanted actor', ->
        expect(@freakset.hasActor('step1')).toBe true

      it 'should return false if the freakset does not contain the wanted actor', ->
        expect(@freakset.hasActor('stepX')).toBe false


    describe 'internal helper', ->


      describe '#buildAndRunFreakset', ->

        it 'should build new Freakset and commit it afterwards', ->
          set = { next: ( -> ), root: ( -> ) }
          spyOn(Freakset, 'build').andReturn(set)
          spyOn(set, 'next')
          @freakset.buildAndRunFreakset( [] )
          expect(set.next).toHaveBeenCalled()

        it 'should return the new Freakset', ->
          newFreakset = Freakset.build()
          spyOn(Freakset, 'build').andReturn(newFreakset)
          expect(@freakset.buildAndRunFreakset([])).toEqual(newFreakset)


        it 'should pass a copy of the stack to the new Freakset', ->
          stack = []
          spyOn(stack, "slice")
          @freakset.buildAndRunFreakset(stack: stack)
          expect(stack.slice).toHaveBeenCalled()

        it 'should pass the store and outerFreakset to the new one', ->
          spyOn(Freakset, 'build').andReturn { next: (->), root: (->) }
          @freakset.buildAndRunFreakset(stack: ["stack"], store: { name: 'Iam' }, outerFreakset: { test: 'Iam' } )
          expect(Freakset.build).toHaveBeenCalledWith( stack: ["stack"], store: { name: 'Iam' }, outerFreakset: { test: 'Iam' } )

        it 'should copy event listeners to the new Freakset', ->
          spyOn(@freakset, 'copyEventListeners')
          @freakset.buildAndRunFreakset([])
          expect(@freakset.copyEventListeners).toHaveBeenCalled()

      describe '#copyEventListeners', ->

        beforeEach ->
          @newFreakset = new Freakset()

        it 'should attach all listeners from the current freakset to the provided', ->
          listener = (->)
          @freakset.on('beforeRunningActor', listener)
          @freakset.copyEventListeners(@newFreakset)
          expect(@newFreakset.listeners('beforeRunningActor')).toEqual([listener])

        it 'should copy listeners for the "afterRunningActor" event', ->
          listener = (->)
          @freakset.on('afterRunningActor', listener)
          @freakset.copyEventListeners(@newFreakset)
          expect(@newFreakset.listeners('afterRunningActor')).toEqual([listener])

        it 'should copy listeners for the "aroundRunningActor" event', ->
          listener = (->)
          @freakset.on('aroundRunningActor', listener)
          @freakset.copyEventListeners(@newFreakset)
          expect(@newFreakset.listeners('aroundRunningActor')).toEqual([listener])

      describe '#runActor', ->

        beforeEach ->
          @actor = { scope: "test" }
          @runner = jasmine.createSpy("Runner")

        it 'should call the runner if not "aroundRunningActor" listeners exist', ->
          @freakset.runActor(@runner, @actor)
          expect(@runner).toHaveBeenCalledWith(@freakset, @actor)

        it 'should call #runEventFnChain if "aroundRunningActor" listeners exist', ->
          spyOn(@freakset, "runEventFnChain")
          @freakset.on 'aroundRunningActor', ( -> )
          @freakset.runActor(@runner, @actor)
          expect(@freakset.runEventFnChain).toHaveBeenCalled()


      describe '#runEventFnChain', ->

        beforeEach ->
          @actor = { scope: "test" }
          @runner = jasmine.createSpy("Runner")
          @fn1 = (set, actor, cb) -> cb()
          @fn2 = (set, actor, cb) -> cb()

        it 'should recursively build and run a callback chain', ->
          spyOn(@freakset, "runEventFnChain").andCallThrough()
          @freakset.runEventFnChain([@fn1, @fn2], @runner, @actor)
          expect(@freakset.runEventFnChain.callCount).toBe 2

        it 'should call the runner after walking through the callback chain', ->
          @freakset.runEventFnChain([@fn1, @fn2], @runner, @actor)
          expect(@runner).toHaveBeenCalledWith(@freakset, @actor)


      describe '#modifyStack', ->

        beforeEach ->
          @freakset = new Freakset()

        it 'should add the passed object to the stack', ->
          @freakset.stack = []
          @freakset.modifyStack @freakset, { foo: 'bar', peter: 'fox' }
          expect(@freakset.stack).toEqual( [ { foo: 'bar', peter: 'fox' } ])

        it 'should add the passed object to the top of the stack if specified (opts.insertAtTop=yes)', ->
          @freakset.stack = [ { existing: "step" }]
          @freakset.modifyStack @freakset, { newer: 'step' }, { insertAtTop: yes}
          expect(@freakset.stack).toEqual([ {newer: 'step'}, {existing: "step"}  ])

        it 'should add the object to the current position if the current Freakset is running (add Steps on the Fly)', ->
          @freakset.stack = [ { next: "step" }]
          @freakset.running = yes
          @freakset.modifyStack @freakset, { first: 'step' }
          expect(@freakset.stack).toEqual([ {first: 'step'}, {next: "step"}  ])

        it 'should insertBefore a scope', ->
          @freakset.stack = [ { scope: "step2" } ]
          @freakset.modifyStack @freakset, { scope: "step1" }, { insertBefore: "step2" }
          expect(@freakset.stack).toEqual [ { scope: "step1" }, { scope: "step2" } ]

        it 'should insertAfter a scope', ->
          @freakset.stack = [ { scope: "step2" }, { scope: "step3" } ]
          @freakset.modifyStack @freakset, { scope: "step1" }, { insertAfter: "step2" }
          expect(@freakset.stack).toEqual [ { scope: "step2" }, { scope: "step1" }, { scope: "step3" } ]

        it 'should replace an actor by scope', ->
          @freakset.stack = [ { scope: "step2" }, { scope: "step3" } ]
          @freakset.modifyStack @freakset, { scope: "step1" }, { replace: "step2" }
          expect(@freakset.stack).toEqual [ { scope: "step1" }, { scope: "step3" } ]

      describe '#findActorByScope', ->

        beforeEach ->
          @freakset.stack = [ { scope: "step1" }, { scope: "step2" }, { scope: "step2" }]

        it 'should find an actor by scope and return its index and belonging freakset', ->
          [index, set] = @freakset.findActorByScope(@freakset, 'step2')
          expect(index).toBe 1
          expect(set).toBe @freakset

        it 'should recursively find scopes', ->
          innerFreakset = { stack: [ { scope: "istep1" }, { scope: "istep2" } ] }
          @freakset.innerFreaksets = [ innerFreakset ]
          [index, set] = @freakset.findActorByScope(@freakset, 'istep2')
          expect(index).toBe 1
          expect(set).toBe innerFreakset
