/* */ 
(function(Buffer) {
  var common = require('../common');
  var MultipartParserStub = GENTLY.stub('./multipart_parser', 'MultipartParser'),
      QuerystringParserStub = GENTLY.stub('./querystring_parser', 'QuerystringParser'),
      EventEmitterStub = GENTLY.stub('events', 'EventEmitter'),
      StreamStub = GENTLY.stub('stream', 'Stream'),
      FileStub = GENTLY.stub('./file');
  var formidable = require(common.lib + '/index'),
      IncomingForm = formidable.IncomingForm,
      events = require('events'),
      fs = require('fs'),
      path = require('path'),
      Buffer = require('buffer').Buffer,
      fixtures = require(TEST_FIXTURES + '/multipart'),
      form,
      gently;
  function test(test) {
    gently = new Gently();
    gently.expect(EventEmitterStub, 'call');
    form = new IncomingForm();
    test();
    gently.verify(test.name);
  }
  test(function constructor() {
    assert.strictEqual(form.error, null);
    assert.strictEqual(form.ended, false);
    assert.strictEqual(form.type, null);
    assert.strictEqual(form.headers, null);
    assert.strictEqual(form.keepExtensions, false);
    assert.doesNotThrow(function() {
      assert(fs.statSync(form.uploadDir).isDirectory());
    });
    assert.strictEqual(form.encoding, 'utf-8');
    assert.strictEqual(form.bytesReceived, null);
    assert.strictEqual(form.bytesExpected, null);
    assert.strictEqual(form.maxFieldsSize, 2 * 1024 * 1024);
    assert.strictEqual(form._parser, null);
    assert.strictEqual(form._flushing, 0);
    assert.strictEqual(form._fieldsSize, 0);
    assert.ok(form instanceof EventEmitterStub);
    assert.equal(form.constructor.name, 'IncomingForm');
    (function testSimpleConstructor() {
      gently.expect(EventEmitterStub, 'call');
      var form = IncomingForm();
      assert.ok(form instanceof IncomingForm);
    })();
    (function testSimpleConstructorShortcut() {
      gently.expect(EventEmitterStub, 'call');
      var form = formidable();
      assert.ok(form instanceof IncomingForm);
    })();
  });
  test(function parse() {
    var REQ = {headers: {}},
        emit = {};
    gently.expect(form, 'writeHeaders', function(headers) {
      assert.strictEqual(headers, REQ.headers);
    });
    var EVENTS = ['error', 'aborted', 'data', 'end'];
    gently.expect(REQ, 'on', EVENTS.length, function(event, fn) {
      assert.equal(event, EVENTS.shift());
      emit[event] = fn;
      return this;
    });
    form.parse(REQ);
    (function testPause() {
      gently.expect(REQ, 'pause');
      assert.strictEqual(form.pause(), true);
    })();
    (function testPauseCriticalException() {
      form.ended = false;
      var ERR = new Error('dasdsa');
      gently.expect(REQ, 'pause', function() {
        throw ERR;
      });
      gently.expect(form, '_error', function(err) {
        assert.strictEqual(err, ERR);
      });
      assert.strictEqual(form.pause(), false);
    })();
    (function testPauseHarmlessException() {
      form.ended = true;
      var ERR = new Error('dasdsa');
      gently.expect(REQ, 'pause', function() {
        throw ERR;
      });
      assert.strictEqual(form.pause(), false);
    })();
    (function testResume() {
      gently.expect(REQ, 'resume');
      assert.strictEqual(form.resume(), true);
    })();
    (function testResumeCriticalException() {
      form.ended = false;
      var ERR = new Error('dasdsa');
      gently.expect(REQ, 'resume', function() {
        throw ERR;
      });
      gently.expect(form, '_error', function(err) {
        assert.strictEqual(err, ERR);
      });
      assert.strictEqual(form.resume(), false);
    })();
    (function testResumeHarmlessException() {
      form.ended = true;
      var ERR = new Error('dasdsa');
      gently.expect(REQ, 'resume', function() {
        throw ERR;
      });
      assert.strictEqual(form.resume(), false);
    })();
    (function testEmitError() {
      var ERR = new Error('something bad happened');
      gently.expect(form, '_error', function(err) {
        assert.strictEqual(err, ERR);
      });
      emit.error(ERR);
    })();
    (function testEmitAborted() {
      gently.expect(form, 'emit', function(event) {
        assert.equal(event, 'aborted');
      });
      gently.expect(form, '_error');
      emit.aborted();
    })();
    (function testEmitData() {
      var BUFFER = [1, 2, 3];
      gently.expect(form, 'write', function(buffer) {
        assert.strictEqual(buffer, BUFFER);
      });
      emit.data(BUFFER);
    })();
    (function testEmitEnd() {
      form._parser = {};
      (function testWithError() {
        var ERR = new Error('haha');
        gently.expect(form._parser, 'end', function() {
          return ERR;
        });
        gently.expect(form, '_error', function(err) {
          assert.strictEqual(err, ERR);
        });
        emit.end();
      })();
      (function testWithoutError() {
        gently.expect(form._parser, 'end');
        emit.end();
      })();
      (function testAfterError() {
        form.error = true;
        emit.end();
      })();
    })();
    (function testWithCallback() {
      gently.expect(EventEmitterStub, 'call');
      var form = new IncomingForm(),
          REQ = {headers: {}},
          parseCalled = 0;
      gently.expect(form, 'on', 4, function(event, fn) {
        if (event == 'field') {
          fn('field1', 'foo');
          fn('field1', 'bar');
          fn('field2', 'nice');
        }
        if (event == 'file') {
          fn('file1', '1');
          fn('file1', '2');
          fn('file2', '3');
        }
        if (event == 'end') {
          fn();
        }
        return this;
      });
      gently.expect(form, 'writeHeaders');
      gently.expect(REQ, 'on', 4, function() {
        return this;
      });
      var parseCbOk = function(err, fields, files) {
        assert.deepEqual(fields, {
          field1: 'bar',
          field2: 'nice'
        });
        assert.deepEqual(files, {
          file1: '2',
          file2: '3'
        });
      };
      form.parse(REQ, parseCbOk);
      var ERR = new Error('test');
      gently.expect(form, 'on', 3, function(event, fn) {
        if (event == 'field') {
          fn('foo', 'bar');
        }
        if (event == 'error') {
          fn(ERR);
          gently.expect(form, 'on');
          gently.expect(form, 'writeHeaders');
          gently.expect(REQ, 'on', 4, function() {
            return this;
          });
        }
        return this;
      });
      form.parse(REQ, function parseCbErr(err, fields, files) {
        assert.strictEqual(err, ERR);
        assert.deepEqual(fields, {foo: 'bar'});
      });
    })();
    (function testWriteOrder() {
      gently.expect(EventEmitterStub, 'call');
      var form = new IncomingForm();
      var REQ = new events.EventEmitter();
      var BUF = {};
      var DATACB = null;
      REQ.on('newListener', function(event, fn) {
        if ('data' === event)
          fn(BUF);
      });
      gently.expect(form, 'writeHeaders');
      gently.expect(form, 'write', function(buf) {
        assert.strictEqual(buf, BUF);
      });
      form.parse(REQ);
    })();
  });
  test(function pause() {
    assert.strictEqual(form.pause(), false);
  });
  test(function resume() {
    assert.strictEqual(form.resume(), false);
  });
  test(function writeHeaders() {
    var HEADERS = {};
    gently.expect(form, '_parseContentLength');
    gently.expect(form, '_parseContentType');
    form.writeHeaders(HEADERS);
    assert.strictEqual(form.headers, HEADERS);
  });
  test(function write() {
    var parser = {},
        BUFFER = [1, 2, 3];
    form._parser = parser;
    form.bytesExpected = 523423;
    (function testBasic() {
      gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) {
        assert.equal(event, 'progress');
        assert.equal(bytesReceived, BUFFER.length);
        assert.equal(bytesExpected, form.bytesExpected);
      });
      gently.expect(parser, 'write', function(buffer) {
        assert.strictEqual(buffer, BUFFER);
        return buffer.length;
      });
      assert.equal(form.write(BUFFER), BUFFER.length);
      assert.equal(form.bytesReceived, BUFFER.length);
    })();
    (function testParserError() {
      gently.expect(form, 'emit');
      gently.expect(parser, 'write', function(buffer) {
        assert.strictEqual(buffer, BUFFER);
        return buffer.length - 1;
      });
      gently.expect(form, '_error', function(err) {
        assert.ok(err.message.match(/parser error/i));
      });
      assert.equal(form.write(BUFFER), BUFFER.length - 1);
      assert.equal(form.bytesReceived, BUFFER.length + BUFFER.length);
    })();
    (function testUninitialized() {
      delete form._parser;
      gently.expect(form, '_error', function(err) {
        assert.ok(err.message.match(/unintialized parser/i));
      });
      form.write(BUFFER);
    })();
  });
  test(function parseContentType() {
    var HEADERS = {};
    form.headers = {'content-type': 'application/x-www-form-urlencoded'};
    gently.expect(form, '_initUrlencoded');
    form._parseContentType();
    form.headers = {'content-type': 'broken-client/urlencoded-stupid'};
    gently.expect(form, '_initUrlencoded');
    form._parseContentType();
    var BOUNDARY = '---------------------------57814261102167618332366269';
    form.headers = {'content-type': 'multipart/form-data; boundary=' + BOUNDARY};
    gently.expect(form, '_initMultipart', function(boundary) {
      assert.equal(boundary, BOUNDARY);
    });
    form._parseContentType();
    (function testQuotedBoundary() {
      form.headers = {'content-type': 'multipart/form-data; boundary="' + BOUNDARY + '"'};
      gently.expect(form, '_initMultipart', function(boundary) {
        assert.equal(boundary, BOUNDARY);
      });
      form._parseContentType();
    })();
    (function testNoBoundary() {
      form.headers = {'content-type': 'multipart/form-data'};
      gently.expect(form, '_error', function(err) {
        assert.ok(err.message.match(/no multipart boundary/i));
      });
      form._parseContentType();
    })();
    (function testNoContentType() {
      form.headers = {};
      gently.expect(form, '_error', function(err) {
        assert.ok(err.message.match(/no content-type/i));
      });
      form._parseContentType();
    })();
    (function testUnknownContentType() {
      form.headers = {'content-type': 'invalid'};
      gently.expect(form, '_error', function(err) {
        assert.ok(err.message.match(/unknown content-type/i));
      });
      form._parseContentType();
    })();
  });
  test(function parseContentLength() {
    var HEADERS = {};
    form.headers = {};
    gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) {
      assert.equal(event, 'progress');
      assert.equal(bytesReceived, 0);
      assert.equal(bytesExpected, 0);
    });
    form._parseContentLength();
    form.headers['content-length'] = '8';
    gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) {
      assert.equal(event, 'progress');
      assert.equal(bytesReceived, 0);
      assert.equal(bytesExpected, 8);
    });
    form._parseContentLength();
    assert.strictEqual(form.bytesReceived, 0);
    assert.strictEqual(form.bytesExpected, 8);
    form.headers['content-length'] = '08';
    gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) {
      assert.equal(event, 'progress');
      assert.equal(bytesReceived, 0);
      assert.equal(bytesExpected, 8);
    });
    form._parseContentLength();
    assert.strictEqual(form.bytesExpected, 8);
  });
  test(function _initMultipart() {
    var BOUNDARY = '123',
        PARSER;
    gently.expect(MultipartParserStub, 'new', function() {
      PARSER = this;
    });
    gently.expect(MultipartParserStub.prototype, 'initWithBoundary', function(boundary) {
      assert.equal(boundary, BOUNDARY);
    });
    form._initMultipart(BOUNDARY);
    assert.equal(form.type, 'multipart');
    assert.strictEqual(form._parser, PARSER);
    (function testRegularField() {
      var PART;
      gently.expect(StreamStub, 'new', function() {
        PART = this;
      });
      gently.expect(form, 'onPart', function(part) {
        assert.strictEqual(part, PART);
        assert.deepEqual(part.headers, {
          'content-disposition': 'form-data; name="field1"',
          'foo': 'bar'
        });
        assert.equal(part.name, 'field1');
        var strings = ['hello', ' world'];
        gently.expect(part, 'emit', 2, function(event, b) {
          assert.equal(event, 'data');
          assert.equal(b.toString(), strings.shift());
        });
        gently.expect(part, 'emit', function(event, b) {
          assert.equal(event, 'end');
        });
      });
      PARSER.onPartBegin();
      PARSER.onHeaderField(new Buffer('content-disposition'), 0, 10);
      PARSER.onHeaderField(new Buffer('content-disposition'), 10, 19);
      PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 0, 14);
      PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 14, 24);
      PARSER.onHeaderEnd();
      PARSER.onHeaderField(new Buffer('foo'), 0, 3);
      PARSER.onHeaderValue(new Buffer('bar'), 0, 3);
      PARSER.onHeaderEnd();
      PARSER.onHeadersEnd();
      PARSER.onPartData(new Buffer('hello world'), 0, 5);
      PARSER.onPartData(new Buffer('hello world'), 5, 11);
      PARSER.onPartEnd();
    })();
    (function testFileField() {
      var PART;
      gently.expect(StreamStub, 'new', function() {
        PART = this;
      });
      gently.expect(form, 'onPart', function(part) {
        assert.deepEqual(part.headers, {
          'content-disposition': 'form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"',
          'content-type': 'text/plain'
        });
        assert.equal(part.name, 'field2');
        assert.equal(part.filename, 'Sun"et.jpg');
        assert.equal(part.mime, 'text/plain');
        gently.expect(part, 'emit', function(event, b) {
          assert.equal(event, 'data');
          assert.equal(b.toString(), '... contents of file1.txt ...');
        });
        gently.expect(part, 'emit', function(event, b) {
          assert.equal(event, 'end');
        });
      });
      PARSER.onPartBegin();
      PARSER.onHeaderField(new Buffer('content-disposition'), 0, 19);
      PARSER.onHeaderValue(new Buffer('form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"'), 0, 85);
      PARSER.onHeaderEnd();
      PARSER.onHeaderField(new Buffer('Content-Type'), 0, 12);
      PARSER.onHeaderValue(new Buffer('text/plain'), 0, 10);
      PARSER.onHeaderEnd();
      PARSER.onHeadersEnd();
      PARSER.onPartData(new Buffer('... contents of file1.txt ...'), 0, 29);
      PARSER.onPartEnd();
    })();
    (function testEnd() {
      gently.expect(form, '_maybeEnd');
      PARSER.onEnd();
      assert.ok(form.ended);
    })();
  });
  test(function _fileName() {
    return;
  });
  test(function _initUrlencoded() {
    var PARSER;
    gently.expect(QuerystringParserStub, 'new', function() {
      PARSER = this;
    });
    form._initUrlencoded();
    assert.equal(form.type, 'urlencoded');
    assert.strictEqual(form._parser, PARSER);
    (function testOnField() {
      var KEY = 'KEY',
          VAL = 'VAL';
      gently.expect(form, 'emit', function(field, key, val) {
        assert.equal(field, 'field');
        assert.equal(key, KEY);
        assert.equal(val, VAL);
      });
      PARSER.onField(KEY, VAL);
    })();
    (function testOnEnd() {
      gently.expect(form, '_maybeEnd');
      PARSER.onEnd();
      assert.equal(form.ended, true);
    })();
  });
  test(function _error() {
    var ERR = new Error('bla');
    gently.expect(form, 'pause');
    gently.expect(form, 'emit', function(event, err) {
      assert.equal(event, 'error');
      assert.strictEqual(err, ERR);
    });
    form._error(ERR);
    assert.strictEqual(form.error, ERR);
    form._error(ERR);
  });
  test(function onPart() {
    var PART = {};
    gently.expect(form, 'handlePart', function(part) {
      assert.strictEqual(part, PART);
    });
    form.onPart(PART);
  });
  test(function handlePart() {
    (function testUtf8Field() {
      var PART = new events.EventEmitter();
      PART.name = 'my_field';
      gently.expect(form, 'emit', function(event, field, value) {
        assert.equal(event, 'field');
        assert.equal(field, 'my_field');
        assert.equal(value, 'hello world: €');
      });
      form.handlePart(PART);
      PART.emit('data', new Buffer('hello'));
      PART.emit('data', new Buffer(' world: '));
      PART.emit('data', new Buffer([0xE2]));
      PART.emit('data', new Buffer([0x82, 0xAC]));
      PART.emit('end');
    })();
    (function testBinaryField() {
      var PART = new events.EventEmitter();
      PART.name = 'my_field2';
      gently.expect(form, 'emit', function(event, field, value) {
        assert.equal(event, 'field');
        assert.equal(field, 'my_field2');
        assert.equal(value, 'hello world: ' + new Buffer([0xE2, 0x82, 0xAC]).toString('binary'));
      });
      form.encoding = 'binary';
      form.handlePart(PART);
      PART.emit('data', new Buffer('hello'));
      PART.emit('data', new Buffer(' world: '));
      PART.emit('data', new Buffer([0xE2]));
      PART.emit('data', new Buffer([0x82, 0xAC]));
      PART.emit('end');
    })();
    (function testFieldSize() {
      form.maxFieldsSize = 8;
      var PART = new events.EventEmitter();
      PART.name = 'my_field';
      gently.expect(form, '_error', function(err) {
        assert.equal(err.message, 'maxFieldsSize exceeded, received 9 bytes of field data');
      });
      form.handlePart(PART);
      form._fieldsSize = 1;
      PART.emit('data', new Buffer(7));
      PART.emit('data', new Buffer(1));
    })();
    (function testFilePart() {
      var PART = new events.EventEmitter(),
          FILE = new events.EventEmitter(),
          PATH = '/foo/bar';
      PART.name = 'my_file';
      PART.filename = 'sweet.txt';
      PART.mime = 'sweet.txt';
      gently.expect(form, '_uploadPath', function(filename) {
        assert.equal(filename, PART.filename);
        return PATH;
      });
      gently.expect(FileStub, 'new', function(properties) {
        assert.equal(properties.path, PATH);
        assert.equal(properties.name, PART.filename);
        assert.equal(properties.type, PART.mime);
        FILE = this;
        gently.expect(form, 'emit', function(event, field, file) {
          assert.equal(event, 'fileBegin');
          assert.strictEqual(field, PART.name);
          assert.strictEqual(file, FILE);
        });
        gently.expect(FILE, 'open');
      });
      form.handlePart(PART);
      assert.equal(form._flushing, 1);
      var BUFFER;
      gently.expect(form, 'pause');
      gently.expect(FILE, 'write', function(buffer, cb) {
        assert.strictEqual(buffer, BUFFER);
        gently.expect(form, 'resume');
        cb();
      });
      PART.emit('data', BUFFER = new Buffer('test'));
      gently.expect(FILE, 'end', function(cb) {
        gently.expect(form, 'emit', function(event, field, file) {
          assert.equal(event, 'file');
          assert.strictEqual(file, FILE);
        });
        gently.expect(form, '_maybeEnd');
        cb();
        assert.equal(form._flushing, 0);
      });
      PART.emit('end');
    })();
  });
  test(function _uploadPath() {
    (function testUniqueId() {
      var UUID_A,
          UUID_B;
      gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) {
        assert.equal(uploadDir, form.uploadDir);
        UUID_A = uuid;
      });
      form._uploadPath();
      gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) {
        UUID_B = uuid;
      });
      form._uploadPath();
      assert.notEqual(UUID_A, UUID_B);
    })();
    (function testFileExtension() {
      form.keepExtensions = true;
      var FILENAME = 'foo.jpg',
          EXT = '.bar';
      gently.expect(GENTLY.hijacked.path, 'extname', function(filename) {
        assert.equal(filename, FILENAME);
        gently.restore(path, 'extname');
        return EXT;
      });
      gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, name) {
        assert.equal(path.extname(name), EXT);
      });
      form._uploadPath(FILENAME);
    })();
  });
  test(function _maybeEnd() {
    gently.expect(form, 'emit', 0);
    form._maybeEnd();
    form.ended = true;
    form._flushing = 1;
    form._maybeEnd();
    gently.expect(form, 'emit', function(event) {
      assert.equal(event, 'end');
    });
    form.ended = true;
    form._flushing = 0;
    form._maybeEnd();
  });
})(require('buffer').Buffer);
