// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

/*<replacement>*/
var bufferShim = require('safe-buffer').Buffer;
/*</replacement>*/
require('../common');
var assert = require('assert/');

var stream = require('../../');
var Readable = stream.Readable;
var Writable = stream.Writable;

var totalChunks = 100;
var chunkSize = 99;
var expectTotalData = totalChunks * chunkSize;
var expectEndingData = expectTotalData;

var r = new Readable({ highWaterMark: 1000 });
var chunks = totalChunks;
r._read = function (n) {
  if (!(chunks % 2)) setImmediate(push);else if (!(chunks % 3)) process.nextTick(push);else push();
};

var totalPushed = 0;
function push() {
  var chunk = chunks-- > 0 ? bufferShim.alloc(chunkSize, 'x') : null;
  if (chunk) {
    totalPushed += chunk.length;
  }
  r.push(chunk);
}

read100();

// first we read 100 bytes
function read100() {
  readn(100, onData);
}

function readn(n, then) {
  console.error('read %d', n);
  expectEndingData -= n;
  (function read() {
    var c = r.read(n);
    if (!c) r.once('readable', read);else {
      assert.strictEqual(c.length, n);
      assert(!r._readableState.flowing);
      then();
    }
  })();
}

// then we listen to some data events
function onData() {
  expectEndingData -= 100;
  console.error('onData');
  var seen = 0;
  r.on('data', function od(c) {
    seen += c.length;
    if (seen >= 100) {
      // seen enough
      r.removeListener('data', od);
      r.pause();
      if (seen > 100) {
        // oh no, seen too much!
        // put the extra back.
        var diff = seen - 100;
        r.unshift(c.slice(c.length - diff));
        console.error('seen too much', seen, diff);
      }

      // Nothing should be lost in between
      setImmediate(pipeLittle);
    }
  });
}

// Just pipe 200 bytes, then unshift the extra and unpipe
function pipeLittle() {
  expectEndingData -= 200;
  console.error('pipe a little');
  var w = new Writable();
  var written = 0;
  w.on('finish', function () {
    assert.strictEqual(written, 200);
    setImmediate(read1234);
  });
  w._write = function (chunk, encoding, cb) {
    written += chunk.length;
    if (written >= 200) {
      r.unpipe(w);
      w.end();
      cb();
      if (written > 200) {
        var diff = written - 200;
        written -= diff;
        r.unshift(chunk.slice(chunk.length - diff));
      }
    } else {
      setImmediate(cb);
    }
  };
  r.pipe(w);
}

// now read 1234 more bytes
function read1234() {
  readn(1234, resumePause);
}

function resumePause() {
  console.error('resumePause');
  // don't read anything, just resume and re-pause a whole bunch
  r.resume();
  r.pause();
  r.resume();
  r.pause();
  r.resume();
  r.pause();
  r.resume();
  r.pause();
  r.resume();
  r.pause();
  setImmediate(pipe);
}

function pipe() {
  console.error('pipe the rest');
  var w = new Writable();
  var written = 0;
  w._write = function (chunk, encoding, cb) {
    written += chunk.length;
    cb();
  };
  w.on('finish', function () {
    console.error('written', written, totalPushed);
    assert.strictEqual(written, expectEndingData);
    assert.strictEqual(totalPushed, expectTotalData);
    console.log('ok');
  });
  r.pipe(w);
}