/**
 * Module dependencies.
 */

var Parser = require('../');
var assert = require('assert');
var inherits = require('util').inherits;
var Writable = require('stream').Writable;

// for node v0.6.x-v0.8.x support
if (!Writable) Writable = require('readable-stream/writable');

describe('Writable streams', function () {

  var val = 1337;
  var buf = new Buffer(4);
  buf.writeUInt32LE(val, 0);

  it('should have the `_bytes()` function', function () {
    var w = new Writable();
    Parser(w);
    assert.equal('function', typeof w._bytes);
  });

  it('should have the `_skipBytes()` function', function () {
    var w = new Writable();
    Parser(w);
    assert.equal('function', typeof w._skipBytes);
  });

  it('should *not* have the `_passthrough()` function', function () {
    var w = new Writable();
    Parser(w);
    assert.notEqual('function', typeof w._passthrough);
  });

  it('should read 4 bytes in one chunk', function (done) {
    var w = new Writable();
    Parser(w);

    // read 4 bytes
    w._bytes(4, function (chunk) {
      assert.equal(chunk.length, buf.length);
      assert.equal(val, chunk.readUInt32LE(0));
      done();
    });

    w.end(buf);
  });

  it('should read 4 bytes in multiple chunks', function (done) {
    var w = new Writable();
    Parser(w);

    // read 4 bytes
    w._bytes(4, function (chunk) {
      assert.equal(chunk.length, buf.length);
      assert.equal(val, chunk.readUInt32LE(0));
      done();
    });

    for (var i = 0; i < buf.length; i++) {
      w.write(new Buffer([ buf[i] ]));
    }
    w.end();
  });

  it('should read 1 byte, 2 bytes, then 3 bytes', function (done) {
    var w = new Writable();
    Parser(w);

    // read 1 byte
    w._bytes(1, readone);
    function readone (chunk) {
      assert.equal(1, chunk.length);
      assert.equal(0, chunk[0]);
      w._bytes(2, readtwo);
    }
    function readtwo (chunk) {
      assert.equal(2, chunk.length);
      assert.equal(0, chunk[0]);
      assert.equal(1, chunk[1]);
      w._bytes(3, readthree);
    }
    function readthree (chunk) {
      assert.equal(3, chunk.length);
      assert.equal(0, chunk[0]);
      assert.equal(1, chunk[1]);
      assert.equal(2, chunk[2]);
      done();
    }

    w.end(new Buffer([ 0, 0, 1, 0, 1, 2 ]));
  });

  it('should work when mixing in to a subclass\' `prototype`', function (done) {
    function MyWritable () {
      Writable.call(this);
      this._bytes(2, this.onbytes);
    }
    inherits(MyWritable, Writable);

    // mixin to the `prototype`
    Parser(MyWritable.prototype);

    var count = 2;
    MyWritable.prototype.onbytes = function (buf) {
      assert.equal(2, buf.length);
      assert.equal(0, buf[0]);
      assert.equal(1, buf[1]);
      --count;
      if (!count) done();
    };

    var a = new MyWritable();
    var b = new MyWritable();

    // interleave write()s
    a.write(new Buffer([ 0 ]));
    b.write(new Buffer([ 0 ]));
    a.write(new Buffer([ 1 ]));
    b.write(new Buffer([ 1 ]));
    a.end();
    b.end();
  });

  it('should *not* allow you to buffer Infinity bytes', function () {
    // buffering to Infinity would just be silly...
    var w = new Writable();
    Parser(w);
    assert.throws(function () {
      w._bytes(Infinity);
    });
  });

  it('should skip 3 bytes then buffer 3 bytes', function (done) {
    var w = new Writable();
    Parser(w);

    w._skipBytes(3, function () {
      assert.equal(arguments.length, 0);
      w._bytes(3, function (data) {
        assert.equal(arguments.length, 1);
        assert.equal(data.toString('ascii'), 'lo\n');
        done();
      });
    });

    w.end('hello\n');
  });

  describe('async', function () {

    it('should accept a callback function for `_bytes()`', function (done) {
      var w = new Writable();
      var data = 'test';
      Parser(w);
      w._bytes(data.length, function (chunk, fn) {
        setTimeout(fn, 25);
      });
      w.on('finish', function () {
        done();
      });
      w.end(data);
    });

    it('should emit an "error" event when data is written with no parsing function', function (done) {
      var w = new Writable();
      Parser(w);
      w.once('error', function (err) {
        assert(err);
        done();
      });
      w.write('a');
    });

  });

  describe('FrameParser', function () {
    function FrameParser () {
      Writable.call(this);
      this._bytes(1, this.onsize);
    }
    inherits(FrameParser, Writable);

    // mixin to the `prototype`
    Parser(FrameParser.prototype);

    FrameParser.prototype.onsize = function (buf) {
      var size = buf.readUInt8(0);
      this._bytes(size, this.onframe);
    };

    FrameParser.prototype.onframe = function (buf) {
      this.emit('frame', buf.toString());

      // begin parsing the next "frame"
      this._bytes(1, this.onsize);
    };

    it('should emit 1 "frame" event', function (done) {
      var p = new FrameParser();
      var s = 'a string';
      p.on('frame', function (frame) {
        assert.equal(s, frame);
        done();
      });
      p.write(new Buffer([ s.length ]));
      p.write(new Buffer(s));
      p.end();
    });

    it('should emit 2 "frame" events', function (done) {
      var p = new FrameParser();
      var s = 'a string';
      var s2 = 'done';
      var count = 0;
      p.on('frame', function (frame) {
        count++;
        if (s2 == frame) {
          assert.equal(2, count);
          done();
        }
      });
      p.write(new Buffer([ s.length ]));
      p.write(new Buffer(s));
      p.write(new Buffer([ s2.length ]));
      p.write(new Buffer(s2));
      p.end();
    });

  });

});