'use strict';

const common = require('../common');
const assert = require('assert');
const { Readable } = require('stream');

{
  // 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', common.mustCall((chunk) => {
        // console.log(chunk.length);
      }, 50));
      break;
    }
  }

  const stream = new ArrayReader();
  stream.once('readable', common.mustCall(onRead));
  stream.on('end', common.mustCall());

}
