'use strict'

const tap = require('tap')
const silentConsole = {
  log() {},
  error() {}
}
const common = require('../common')
const assert = require('assert')
const { Readable } = require('../../lib/ours/index')
{
  // Check that strings are saved as Buffer
  const readable = new Readable({
    read() {}
  })
  const string = 'abc'
  readable.on(
    'data',
    common.mustCall((chunk) => {
      assert(Buffer.isBuffer(chunk))
      assert.strictEqual(chunk.toString('utf8'), string)
    }, 1)
  )
  readable.unshift(string)
}
{
  // Check that data goes at the beginning
  const readable = new Readable({
    read() {}
  })
  const unshift = 'front'
  const push = 'back'
  const expected = [unshift, push]
  readable.on(
    'data',
    common.mustCall((chunk) => {
      assert.strictEqual(chunk.toString('utf8'), expected.shift())
    }, 2)
  )
  readable.push(push)
  readable.unshift(unshift)
}
{
  // Check that buffer is saved with correct encoding
  const readable = new Readable({
    read() {}
  })
  const encoding = 'base64'
  const string = Buffer.from('abc').toString(encoding)
  readable.on(
    'data',
    common.mustCall((chunk) => {
      assert.strictEqual(chunk.toString(encoding), string)
    }, 1)
  )
  readable.unshift(string, encoding)
}
{
  const streamEncoding = 'base64'
  function checkEncoding(readable) {
    // chunk encodings
    const encodings = ['utf8', 'binary', 'hex', 'base64']
    const expected = []
    readable.on(
      'data',
      common.mustCall((chunk) => {
        const { encoding, string } = expected.pop()
        assert.strictEqual(chunk.toString(encoding), string)
      }, encodings.length)
    )
    for (const encoding of encodings) {
      const string = 'abc'

      // If encoding is the same as the state.encoding the string is
      // saved as is
      const expect = encoding !== streamEncoding ? Buffer.from(string, encoding).toString(streamEncoding) : string
      expected.push({
        encoding,
        string: expect
      })
      readable.unshift(string, encoding)
    }
  }
  const r1 = new Readable({
    read() {}
  })
  r1.setEncoding(streamEncoding)
  checkEncoding(r1)
  const r2 = new Readable({
    read() {},
    encoding: streamEncoding
  })
  checkEncoding(r2)
}
{
  // Both .push & .unshift should have the same behaviour
  // When setting an encoding, each chunk should be emitted with that encoding
  const encoding = 'base64'
  function checkEncoding(readable) {
    const string = 'abc'
    readable.on(
      'data',
      common.mustCall((chunk) => {
        assert.strictEqual(chunk, Buffer.from(string).toString(encoding))
      }, 2)
    )
    readable.push(string)
    readable.unshift(string)
  }
  const r1 = new Readable({
    read() {}
  })
  r1.setEncoding(encoding)
  checkEncoding(r1)
  const r2 = new Readable({
    read() {},
    encoding
  })
  checkEncoding(r2)
}
{
  // Check that ObjectMode works
  const readable = new Readable({
    objectMode: true,
    read() {}
  })
  const chunks = ['a', 1, {}, []]
  readable.on(
    'data',
    common.mustCall((chunk) => {
      assert.strictEqual(chunk, chunks.pop())
    }, chunks.length)
  )
  for (const chunk of chunks) {
    readable.unshift(chunk)
  }
}
{
  // Should not throw: https://github.com/nodejs/node/issues/27192
  const highWaterMark = 50
  class ArrayReader extends Readable {
    constructor(opt) {
      super({
        highWaterMark
      })
      // The error happened only when pushing above hwm
      this.buffer = new Array(highWaterMark * 2).fill(0).map(String)
    }
    _read(size) {
      while (this.buffer.length) {
        const chunk = this.buffer.shift()
        if (!this.buffer.length) {
          this.push(chunk)
          this.push(null)
          return true
        }
        if (!this.push(chunk)) return
      }
    }
  }
  function onRead() {
    while (null !== stream.read()) {
      // Remove the 'readable' listener before unshifting
      stream.removeListener('readable', onRead)
      stream.unshift('a')
      stream.on('data', (chunk) => {
        silentConsole.log(chunk.length)
      })
      break
    }
  }
  const stream = new ArrayReader()
  stream.once('readable', common.mustCall(onRead))
  stream.on('end', common.mustCall())
}

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