'use strict'

const tap = require('tap')
const silentConsole = {
  log() {},
  error() {}
}
const common = require('../common')
const { Writable, Readable, Duplex } = require('../../lib/ours/index')
const assert = require('assert')
{
  // Multiple callback.
  new Writable({
    construct: common.mustCall((callback) => {
      callback()
      callback()
    })
  }).on(
    'error',
    common.expectsError({
      name: 'Error',
      code: 'ERR_MULTIPLE_CALLBACK'
    })
  )
}
{
  // Multiple callback.
  new Readable({
    construct: common.mustCall((callback) => {
      callback()
      callback()
    })
  }).on(
    'error',
    common.expectsError({
      name: 'Error',
      code: 'ERR_MULTIPLE_CALLBACK'
    })
  )
}
{
  // Synchronous error.

  new Writable({
    construct: common.mustCall((callback) => {
      callback(new Error('test'))
    })
  }).on(
    'error',
    common.expectsError({
      name: 'Error',
      message: 'test'
    })
  )
}
{
  // Synchronous error.

  new Readable({
    construct: common.mustCall((callback) => {
      callback(new Error('test'))
    })
  }).on(
    'error',
    common.expectsError({
      name: 'Error',
      message: 'test'
    })
  )
}
{
  // Asynchronous error.

  new Writable({
    construct: common.mustCall((callback) => {
      process.nextTick(callback, new Error('test'))
    })
  }).on(
    'error',
    common.expectsError({
      name: 'Error',
      message: 'test'
    })
  )
}
{
  // Asynchronous error.

  new Readable({
    construct: common.mustCall((callback) => {
      process.nextTick(callback, new Error('test'))
    })
  }).on(
    'error',
    common.expectsError({
      name: 'Error',
      message: 'test'
    })
  )
}
function testDestroy(factory) {
  {
    let constructed = false
    const s = factory({
      construct: common.mustCall((cb) => {
        constructed = true
        process.nextTick(cb)
      })
    })
    s.on(
      'close',
      common.mustCall(() => {
        assert.strictEqual(constructed, true)
      })
    )
    s.destroy()
  }
  {
    let constructed = false
    const s = factory({
      construct: common.mustCall((cb) => {
        constructed = true
        process.nextTick(cb)
      })
    })
    s.on(
      'close',
      common.mustCall(() => {
        assert.strictEqual(constructed, true)
      })
    )
    s.destroy(null, () => {
      assert.strictEqual(constructed, true)
    })
  }
  {
    let constructed = false
    const s = factory({
      construct: common.mustCall((cb) => {
        constructed = true
        process.nextTick(cb)
      })
    })
    s.on(
      'close',
      common.mustCall(() => {
        assert.strictEqual(constructed, true)
      })
    )
    s.destroy()
  }
  {
    let constructed = false
    const s = factory({
      construct: common.mustCall((cb) => {
        constructed = true
        process.nextTick(cb)
      })
    })
    s.on(
      'close',
      common.mustCall(() => {
        assert.strictEqual(constructed, true)
      })
    )
    s.on(
      'error',
      common.mustCall((err) => {
        assert.strictEqual(err.message, 'kaboom')
      })
    )
    s.destroy(new Error('kaboom'), (err) => {
      assert.strictEqual(err.message, 'kaboom')
      assert.strictEqual(constructed, true)
    })
  }
  {
    let constructed = false
    const s = factory({
      construct: common.mustCall((cb) => {
        constructed = true
        process.nextTick(cb)
      })
    })
    s.on(
      'error',
      common.mustCall(() => {
        assert.strictEqual(constructed, true)
      })
    )
    s.on(
      'close',
      common.mustCall(() => {
        assert.strictEqual(constructed, true)
      })
    )
    s.destroy(new Error())
  }
}
testDestroy(
  (opts) =>
    new Readable({
      read: common.mustNotCall(),
      ...opts
    })
)
testDestroy(
  (opts) =>
    new Writable({
      write: common.mustNotCall(),
      final: common.mustNotCall(),
      ...opts
    })
)
{
  let constructed = false
  const r = new Readable({
    autoDestroy: true,
    construct: common.mustCall((cb) => {
      constructed = true
      process.nextTick(cb)
    }),
    read: common.mustCall(() => {
      assert.strictEqual(constructed, true)
      r.push(null)
    })
  })
  r.on(
    'close',
    common.mustCall(() => {
      assert.strictEqual(constructed, true)
    })
  )
  r.on('data', common.mustNotCall())
}
{
  let constructed = false
  const w = new Writable({
    autoDestroy: true,
    construct: common.mustCall((cb) => {
      constructed = true
      process.nextTick(cb)
    }),
    write: common.mustCall((chunk, encoding, cb) => {
      assert.strictEqual(constructed, true)
      process.nextTick(cb)
    }),
    final: common.mustCall((cb) => {
      assert.strictEqual(constructed, true)
      process.nextTick(cb)
    })
  })
  w.on(
    'close',
    common.mustCall(() => {
      assert.strictEqual(constructed, true)
    })
  )
  w.end('data')
}
{
  let constructed = false
  const w = new Writable({
    autoDestroy: true,
    construct: common.mustCall((cb) => {
      constructed = true
      process.nextTick(cb)
    }),
    write: common.mustNotCall(),
    final: common.mustCall((cb) => {
      assert.strictEqual(constructed, true)
      process.nextTick(cb)
    })
  })
  w.on(
    'close',
    common.mustCall(() => {
      assert.strictEqual(constructed, true)
    })
  )
  w.end()
}
{
  new Duplex({
    construct: common.mustCall()
  })
}
{
  // https://github.com/nodejs/node/issues/34448

  let constructed = false
  const d = new Duplex({
    readable: false,
    construct: common.mustCall((callback) => {
      setImmediate(
        common.mustCall(() => {
          constructed = true
          callback()
        })
      )
    }),
    write(chunk, encoding, callback) {
      callback()
    },
    read() {
      this.push(null)
    }
  })
  d.resume()
  d.end('foo')
  d.on(
    'close',
    common.mustCall(() => {
      assert.strictEqual(constructed, true)
    })
  )
}
{
  // Construct should not cause stream to read.
  new Readable({
    construct: common.mustCall((callback) => {
      callback()
    }),
    read: common.mustNotCall()
  })
}

/* replacement start */
process.on('beforeExit', (code) => {
  if (code === 0) {
    tap.pass('test succeeded')
  } else {
    tap.fail(`test failed - exited code ${code}`)
  }
})
/* replacement end */
