/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')

const comp = (...fns) => x => {
  for (let f of Array.from(fns.slice(0).reverse())) {
    x = f(x)
  }
  return x
}

const testWithLib = (name, t) =>
  describe(`with ${name} implementation`, () => {
    describe('stream', () => {
      it('`map` should work', () => {
        const a = stream()
        expect(a.transduce(t.map(x => x * 2))).to.emit([value(2), value(4), value(6), end()], () =>
          send(a, [value(1), value(2), value(3), end()])
        )
      })

      it('`filter` should work', () => {
        const a = stream()
        expect(a.transduce(t.filter(x => x % 2 === 0))).to.emit([value(2), value(4), end()], () =>
          send(a, [value(1), value(2), value(3), value(4), end()])
        )
      })

      it('`take` should work', () => {
        let a = stream()
        expect(a.transduce(t.take(2))).to.emit([value(1), value(2), end()], () =>
          send(a, [value(1), value(2), value(3), value(4)])
        )
        a = stream()
        expect(a.transduce(t.take(2))).to.emit([value(1), end()], () => send(a, [value(1), end()]))
      })

      it('`map.filter.take` should work', () => {
        const tr = comp(t.map(x => x + 10), t.filter(x => x % 2 === 0), t.take(2))
        const a = stream()
        expect(a.transduce(tr)).to.emit([value(12), value(14), end()], () =>
          send(a, [value(1), value(2), value(3), value(4), value(5), value(6)])
        )
      })

      if (t.partitionAll) {
        it('`partitionAll` should work', () => {
          let a = stream()
          expect(a.transduce(t.partitionAll(2))).to.emit([value([1, 2]), value([3, 4]), end()], () =>
            send(a, [value(1), value(2), value(3), value(4), end()])
          )
          a = stream()
          expect(a.transduce(t.partitionAll(2))).to.emit([value([1, 2]), value([3]), end()], () =>
            send(a, [value(1), value(2), value(3), end()])
          )
        })
        it('`take.partitionAll` should work', () => {
          let tr = comp(t.take(3), t.partitionAll(2))
          let a = stream()
          expect(a.transduce(tr)).to.emit([value([1, 2]), value([3]), end()], () =>
            send(a, [value(1), value(2), value(3), value(4), end()])
          )
          tr = comp(t.take(2), t.partitionAll(2))
          a = stream()
          expect(a.transduce(tr)).to.emit([value([1, 2]), end()], () =>
            send(a, [value(1), value(2), value(3), value(4), end()])
          )
        })
      }
    })

    describe('property', () => {
      it('`map` should work', () => {
        const a = send(prop(), [value(1)])
        expect(a.transduce(t.map(x => x * 2))).to.emit([value(2, {current: true}), value(4), value(6), end()], () =>
          send(a, [value(2), value(3), end()])
        )
      })

      it('`filter` should work', () => {
        let a = send(prop(), [value(1)])
        expect(a.transduce(t.filter(x => x % 2 === 0))).to.emit([value(2), value(4), end()], () =>
          send(a, [value(2), value(3), value(4), end()])
        )
        a = send(prop(), [value(2)])
        expect(a.transduce(t.filter(x => x % 2 === 0))).to.emit([value(2, {current: true}), value(4), end()], () =>
          send(a, [value(1), value(3), value(4), end()])
        )
      })

      it('`take` should work', () => {
        let a = send(prop(), [value(1)])
        expect(a.transduce(t.take(2))).to.emit([value(1, {current: true}), value(2), end()], () =>
          send(a, [value(2), value(3), value(4)])
        )
        a = send(prop(), [value(1)])
        expect(a.transduce(t.take(3))).to.emit([value(1, {current: true}), value(2), end()], () =>
          send(a, [value(2), end()])
        )
      })

      it('`map.filter.take` should work', () => {
        const tr = comp(t.map(x => x + 10), t.filter(x => x % 2 === 0), t.take(2))
        let a = send(prop(), [value(1)])
        expect(a.transduce(tr)).to.emit([value(12), value(14), end()], () =>
          send(a, [value(2), value(3), value(4), value(5), value(6)])
        )
        a = send(prop(), [value(2)])
        expect(a.transduce(tr)).to.emit([value(12, {current: true}), value(14), end()], () =>
          send(a, [value(1), value(3), value(4), value(5), value(6)])
        )
      })

      if (t.partitionAll) {
        it('`partitionAll` should work', () => {
          let a = send(prop(), [value(1)])
          expect(a.transduce(t.partitionAll(2))).to.emit([value([1, 2]), value([3, 4]), end()], () =>
            send(a, [value(2), value(3), value(4), end()])
          )
          a = send(prop(), [value(1)])
          expect(a.transduce(t.partitionAll(2))).to.emit([value([1, 2]), value([3]), end()], () =>
            send(a, [value(2), value(3), end()])
          )
        })
        it('`take.partitionAll` should work', () => {
          let tr = comp(t.take(3), t.partitionAll(2))
          let a = send(prop(), [value(1)])
          expect(a.transduce(tr)).to.emit([value([1, 2]), value([3]), end()], () =>
            send(a, [value(2), value(3), value(4), end()])
          )
          tr = comp(t.take(2), t.partitionAll(2))
          a = send(prop(), [value(1)])
          expect(a.transduce(tr)).to.emit([value([1, 2]), end()], () => send(a, [value(2), value(3), value(4), end()]))
        })
      }
    })
  })

const noop = x => x

describe('transduce', () => {
  describe('with `noop` transducer', () => {
    describe('stream', () => {
      it('should return stream', () => {
        expect(stream().transduce(noop)).to.be.observable.stream()
      })

      it('should activate/deactivate source', () => {
        const a = stream()
        expect(a.transduce(noop)).to.activate(a)
      })

      it('should be ended if source was ended', () =>
        expect(send(stream(), [end()]).transduce(noop)).to.emit([end({current: true})]))

      it('should handle events', () => {
        const a = stream()
        expect(a.transduce(noop)).to.emit([value(1), value(2), error(4), value(3), end()], () =>
          send(a, [value(1), value(2), error(4), value(3), end()])
        )
      })
    })

    describe('property', () => {
      it('should return property', () => {
        expect(prop().transduce(noop)).to.be.observable.property()
      })

      it('should activate/deactivate source', () => {
        const a = prop()
        expect(a.transduce(noop)).to.activate(a)
      })

      it('should be ended if source was ended', () =>
        expect(send(prop(), [end()]).transduce(noop)).to.emit([end({current: true})]))

      it('should handle events and current', () => {
        let a = send(prop(), [value(1)])
        expect(a.transduce(noop)).to.emit([value(1, {current: true}), value(2), error(4), value(3), end()], () =>
          send(a, [value(2), error(4), value(3), end()])
        )
        a = send(prop(), [error(0)])
        expect(a.transduce(noop)).to.emit([error(0, {current: true}), value(2), error(4), value(3), end()], () =>
          send(a, [value(2), error(4), value(3), end()])
        )
      })
    })
  })

  testWithLib('Cognitect Labs', require('transducers-js'))
  testWithLib("James Long's", require('transducers.js'))
})
