var boundary = "1234567891011121314151617";

// fn(body) should create a Body subclass with content body treated as
// FormData and return it.
function testFormDataParsing(fn) {

  function makeTest(shouldPass, input, testFn) {
    var obj = fn(input);
    return obj.formData().then(function(fd) {
      ok(shouldPass, "Expected test to be valid FormData for " + input);
      if (testFn) {
        return testFn(fd);
      }
    }, function(e) {
      if (shouldPass) {
        ok(false, "Expected test to pass for " + input);
      } else {
        ok(e.name == "TypeError", "Error should be a TypeError.");
      }
    });
  }

  // [shouldPass?, input, testFn]
  var tests =
    [
      [ true,

        boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',

        function(fd) {
          is(fd.get("greeting"), '"hello"');
        }
      ],
      [ false,

        // Invalid disposition.
        boundary +
        '\r\nContent-Disposition: form-datafoobar; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ true,

        '--' +
        boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',

        function(fd) {
          is(fd.get("greeting"), '"hello"');
        }
      ],
      [ false,
        boundary + "\r\n\r\n" + boundary + '-',
      ],
      [ false,
        // No valid ending.
        boundary + "\r\n\r\n" + boundary,
      ],
      [ false,

        // One '-' prefix is not allowed. 2 or none.
        '-' +
        boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        'invalid' +
        boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary + 'suffix' +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary + 'suffix' +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        // Partial boundary
        boundary.substr(3) +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary +
        // Missing '\n' at beginning.
        '\rContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary +
        // No form-data.
        '\r\nContent-Disposition: mixed; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary +
        // No headers.
        '\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary +
        // No content-disposition.
        '\r\nContent-Dispositypo: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary +
        // No name.
        '\r\nContent-Disposition: form-data;\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary +
        // Missing empty line between headers and body.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        // Empty entry followed by valid entry.
        boundary + "\r\n\r\n" + boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary + '-',
      ],
      [ false,

        boundary +
        // Header followed by empty line, but empty body not followed by
        // newline.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
        boundary + '-',
      ],
      [ true,

        boundary +
        // Empty body followed by newline.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n\r\n' +
        boundary + '-',

        function(fd) {
          is(fd.get("greeting"), "", "Empty value is allowed.");
        }
      ],
      [ false,
        boundary +
        // Value is boundary itself.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
        boundary + '\r\n' +
        boundary + '-',
      ],
      [ false,
        boundary +
        // Variant of above with no valid ending boundary.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
        boundary
      ],
      [ true,
        boundary +
        // Unquoted filename with empty body.
        '\r\nContent-Disposition: form-data; name="file"; filename=file1.txt\r\n\r\n\r\n' +
        boundary + '-',

        function(fd) {
          var f = fd.get("file");
          ok(f instanceof File, "Entry with filename attribute should be read as File.");
          is(f.name, "file1.txt", "Filename should match.");
          is(f.type, "text/plain", "Default content-type should be text/plain.");
          return readAsText(f).then(function(text) {
            is(text, "", "File should be empty.");
          });
        }
      ],
      [ true,
        boundary +
        // Quoted filename with empty body.
        '\r\nContent-Disposition: form-data; name="file"; filename="file1.txt"\r\n\r\n\r\n' +
        boundary + '-',

        function(fd) {
          var f = fd.get("file");
          ok(f instanceof File, "Entry with filename attribute should be read as File.");
          is(f.name, "file1.txt", "Filename should match.");
          is(f.type, "text/plain", "Default content-type should be text/plain.");
          return readAsText(f).then(function(text) {
            is(text, "", "File should be empty.");
          });
        }
      ],
      [ false,
        boundary +
        // Invalid filename
        '\r\nContent-Disposition: form-data; name="file"; filename="[\n@;xt"\r\n\r\n\r\n' +
        boundary + '-',
      ],
      [ true,
        boundary +
        '\r\nContent-Disposition: form-data; name="file"; filename="[@;xt"\r\n\r\n\r\n' +
        boundary + '-',

        function(fd) {
          var f = fd.get("file");
          ok(f instanceof File, "Entry with filename attribute should be read as File.");
          is(f.name, "[@", "Filename should match.");
        }
      ],
      [ true,
        boundary +
        '\r\nContent-Disposition: form-data; name="file"; filename="file with   spaces"\r\n\r\n\r\n' +
        boundary + '-',

        function(fd) {
          var f = fd.get("file");
          ok(f instanceof File, "Entry with filename attribute should be read as File.");
          is(f.name, "file with spaces", "Filename should match.");
        }
      ],
      [ true,
        boundary + '\r\n' +
        'Content-Disposition: form-data; name="file"; filename="xml.txt"\r\n' +
        'content-type       : application/xml\r\n' +
        '\r\n' +
        '<body>foobar\r\n\r\n</body>\r\n' +
        boundary + '-',

        function(fd) {
          var f = fd.get("file");
          ok(f instanceof File, "Entry with filename attribute should be read as File.");
          is(f.name, "xml.txt", "Filename should match.");
          is(f.type, "application/xml", "content-type should be application/xml.");
          return readAsText(f).then(function(text) {
            is(text, "<body>foobar\r\n\r\n</body>", "File should have correct text.");
          });
        }
      ],
    ];

  var promises = [];
  for (var i = 0; i < tests.length; ++i) {
    var test = tests[i];
    promises.push(makeTest(test[0], test[1], test[2]));
  }

  return Promise.all(promises);
}

function makeRequest(body) {
  var req = new Request("", { method: 'post', body: body,
                              headers: {
                                'Content-Type': 'multipart/form-data; boundary=' + boundary
                              }});
  return req;
}

function makeResponse(body) {
  var res = new Response(body, { headers: {
                                   'Content-Type': 'multipart/form-data; boundary=' + boundary
                                 }});
  return res;
}

function runTest() {
  return Promise.all([testFormDataParsing(makeRequest),
                      testFormDataParsing(makeResponse)]);
}