const manifestUrl = '/app.manifest';

function appcacheTest(name, cb) {
  Tinytest.addAsync(`appcache - ${name}`, test => {
    return fetch(manifestUrl).then(
      res => cb(test, res),
      err => test.fail(err)
    );
  });
}


// Verify that the code status of the HTTP response is "OK"
appcacheTest('presence', (test, manifest) =>
  test.equal(manifest.status, 200, 'manifest not served'));


// Verify the content-type HTTP header
appcacheTest('content type', (test, manifest) =>
  test.equal(manifest.headers.get('content-type'), 'text/cache-manifest'));


// Verify that each section header is only set once.
appcacheTest('sections uniqueness', async (test, manifest) => {
  const content = await manifest.text();
  const mandatorySectionHeaders = ['CACHE:', 'NETWORK:', 'FALLBACK:'];
  const optionalSectionHeaders = ['SETTINGS'];
  const allSectionHeaders = [
    ...mandatorySectionHeaders,
    ...optionalSectionHeaders.filter(
      header => !mandatorySectionHeaders.includes(header)
    ),
  ];
  allSectionHeaders.forEach(sectionHeader => {
    const globalSearch = new RegExp(sectionHeader, "g");
    const matches = content.match(globalSearch) || [];
    test.isTrue(matches.length <= 1, `${sectionHeader} is set twice`);
    if (mandatorySectionHeaders.includes(sectionHeader)) {
      test.isTrue(matches.length == 1, `${sectionHeader} is not set`);
    }
  });
});


// Verify the content of the header and of each section of the manifest using
// regular expressions. Regular expressions matches malformed URIs but that's
// not what we're trying to catch here (the user is free to add its own content
// in the manifest -- even malformed).
appcacheTest('sections validity', async (test, manifest) => {
  const lines = (await manifest.text()).split('\n');
  let i = 0;
  let currentRegex = null;
  let line = null;

  const nextLine = () => lines[i++];

  const eof = () => i >= lines.length;

  const nextLineMatches = (expected, n) => {
    n = n || 1;
    for(let j = 0; j < n; j++) {
      const testFunc = toString.call(expected) === '[object RegExp]' ?
        'matches' :
        'equal';
      test[testFunc](nextLine(), expected);
    }
  };

  // Verify header validity
  nextLineMatches('CACHE MANIFEST');
  nextLineMatches('');
  nextLineMatches(/^# [a-z0-9]+$/i);
  nextLineMatches('');

  // Verify body validity
  while (! eof()) {
    line = nextLine();

    // There are three distinct sections: 'CACHE', 'FALLBACK', and 'NETWORK'.
    // A section start with its name suffixed by a colon. When we read a new
    // section header, we update the currentRegex expression for the next lines
    // of the section.
    // XXX There is also a 'SETTINGS' section, not used by this package. If this
    // section is used, the test will fail.
    if (line === 'CACHE:' || line === 'NETWORK:')
      currentRegex = /^\S+$/;

    else if (line === 'FALLBACK:')
      currentRegex = /^\S+ \S+$/;

    // Blank lines and lines starting with a `#` (comments) are valid
    else if (line == '' || line.match(/^#.+/))
      continue;

    // Outside sections, only blanks lines and comments are valid
    else if (currentRegex === null)
      test.fail(`Invalid line ${i}: ${line}`);

    // Inside a section, a star is a valid expression
    else if (line === '*')
      continue;

    // If it is not a blank line, not a comment, and not a header it must
    // match the current section format
    else
      test.matches(line, currentRegex, `line ${i}`);
  }
});


// Verify that resources declared on the server with the `onlineOnly` parameter
// are present in the network section of the manifest. The `appcache` package
// also automatically add the manifest (`app.manifest`) add the star symbol to
// this list and therefore we also check the presence of these two elements.
appcacheTest('network section content', async (test, manifest) => {
  const shouldBePresentInNetworkSection = [
    "/app.manifest",
    "/online/",
    "/bigimage.jpg",
    "/largedata.json",
    "*"
  ];
  const lines = (await manifest.text()).split('\n');
  const startNetworkSection = lines.indexOf('NETWORK:');

  // We search the end of the 'NETWORK:' section by looking at the beginning
  // of any potential other section. By default we set this value to
  // `lines.length - 1` which is the index of the last line.
  const otherSections = ['CACHE:', 'FALLBACK:', 'SETTINGS'];
  const endNetworkSection = otherSections.reduce((min, sectionName) => {
    const position = lines.indexOf(sectionName);
    return position > startNetworkSection && position < min ? position : min;
  }, lines.length - 1);

  // We remove the first line because it's the 'NETWORK:' header line.
  const networkLines = lines.slice(startNetworkSection + 1, endNetworkSection);

  shouldBePresentInNetworkSection.forEach(
    item => test.include(networkLines, item)
  );
});
