/**
 * Test for the "alternative data stream" - closing the stream with an error.
 *
 * - we load a URL with preference for an alt data (check what we get is the raw data,
 *   since there was nothing previously cached)
 * - we store something in alt data (using the asyncWait method)
 * - then we abort the operation calling closeWithStatus()
 * - we flush the HTTP cache
 * - we reload the same URL using a new channel, again prefering the alt data be loaded
 * - again we receive the data from the server.
 */

"use strict";

const { HttpServer } = ChromeUtils.importESModule(
  "resource://testing-common/httpd.sys.mjs"
);

ChromeUtils.defineLazyGetter(this, "URL", function () {
  return "http://localhost:" + httpServer.identity.primaryPort + "/content";
});

var httpServer = null;

// needs to be rooted
var cacheFlushObserver = (cacheFlushObserver = {
  observe() {
    cacheFlushObserver = null;
    readServerContentAgain();
  },
});

var currentThread = null;

function make_channel(url) {
  return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
}

function inChildProcess() {
  return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
}

const responseContent = "response body";
const responseContent2 = "response body 2";
const altContent = "!@#$%^&*()";
const altContentType = "text/binary";

var shouldPassRevalidation = true;

var cache_storage = null;

function contentHandler(metadata, response) {
  response.setHeader("Content-Type", "text/plain");
  response.setHeader("Cache-Control", "no-cache");
  response.setHeader("ETag", "test-etag1");

  let etag;
  try {
    etag = metadata.getHeader("If-None-Match");
  } catch (ex) {
    etag = "";
  }

  if (etag == "test-etag1" && shouldPassRevalidation) {
    response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
  } else {
    var content = shouldPassRevalidation ? responseContent : responseContent2;
    response.bodyOutputStream.write(content, content.length);
  }
}

function check_has_alt_data_in_index(aHasAltData, callback) {
  if (inChildProcess()) {
    callback();
    return;
  }

  syncWithCacheIOThread(() => {
    var hasAltData = {};
    cache_storage.getCacheIndexEntryAttrs(createURI(URL), "", hasAltData, {});
    Assert.equal(hasAltData.value, aHasAltData);
    callback();
  }, true);
}

function run_test() {
  do_get_profile();
  httpServer = new HttpServer();
  httpServer.registerPathHandler("/content", contentHandler);
  httpServer.start(-1);
  do_test_pending();

  if (!inChildProcess()) {
    cache_storage = getCacheStorage("disk");
    wait_for_cache_index(asyncOpen);
  } else {
    asyncOpen();
  }
}

function asyncOpen() {
  var chan = make_channel(URL);

  var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
  cc.preferAlternativeDataType(
    altContentType,
    "",
    Ci.nsICacheInfoChannel.ASYNC
  );

  chan.asyncOpen(new ChannelListener(readServerContent, null));
}

function readServerContent(request, buffer) {
  var cc = request.QueryInterface(Ci.nsICacheInfoChannel);

  Assert.equal(buffer, responseContent);
  Assert.equal(cc.alternativeDataType, "");
  check_has_alt_data_in_index(false, () => {
    if (!inChildProcess()) {
      currentThread = Services.tm.currentThread;
    }

    executeSoon(() => {
      var os = cc.openAlternativeOutputStream(
        altContentType,
        altContent.length
      );

      var aos = os.QueryInterface(Ci.nsIAsyncOutputStream);
      aos.asyncWait(
        _ => {
          os.write(altContent, altContent.length);
          aos.closeWithStatus(Cr.NS_ERROR_FAILURE);
          executeSoon(flushAndReadServerContentAgain);
        },
        0,
        0,
        currentThread
      );
    });
  });
}

function flushAndReadServerContentAgain() {
  // We need to do a GC pass to ensure the cache entry has been freed.
  gc();
  if (!inChildProcess()) {
    Services.cache2
      .QueryInterface(Ci.nsICacheTesting)
      .flush(cacheFlushObserver);
  } else {
    do_send_remote_message("flush");
    do_await_remote_message("flushed").then(() => {
      readServerContentAgain();
    });
  }
}

function readServerContentAgain() {
  var chan = make_channel(URL);
  var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
  cc.preferAlternativeDataType(
    "dummy1",
    "text/javascript",
    Ci.nsICacheInfoChannel.ASYNC
  );
  cc.preferAlternativeDataType(
    altContentType,
    "text/plain",
    Ci.nsICacheInfoChannel.ASYNC
  );
  cc.preferAlternativeDataType("dummy2", "", Ci.nsICacheInfoChannel.ASYNC);

  chan.asyncOpen(new ChannelListener(readServerContentAgainCB, null));
}

function readServerContentAgainCB(request, buffer) {
  var cc = request.QueryInterface(Ci.nsICacheInfoChannel);

  Assert.equal(buffer, responseContent);
  Assert.equal(cc.alternativeDataType, "");
  check_has_alt_data_in_index(false, () => httpServer.stop(do_test_finished));
}
