/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

// We need the profile directory so the test harness will clean up our test
// files.
do_get_profile();

const OUTPUT_STREAM_CONTRACT_ID = "@mozilla.org/network/file-output-stream;1";
const SAFE_OUTPUT_STREAM_CONTRACT_ID =
  "@mozilla.org/network/safe-file-output-stream;1";

////////////////////////////////////////////////////////////////////////////////
//// Helper Methods

/**
 * Generates a leafName for a file that does not exist, but does *not*
 * create the file. Similar to createUnique except for the fact that createUnique
 * does create the file.
 *
 * @param aFile
 *        The file to modify in order for it to have a unique leafname.
 */
function ensure_unique(aFile) {
  ensure_unique.fileIndex = ensure_unique.fileIndex || 0;

  var leafName = aFile.leafName;
  while (aFile.clone().exists()) {
    aFile.leafName = leafName + "_" + ensure_unique.fileIndex++;
  }
}

/**
 * Tests for files being accessed at the right time. Streams that use
 * DEFER_OPEN should only open or create the file when an operation is
 * done, and not during Init().
 *
 * Note that for writing, we check for actual writing in test_NetUtil (async)
 * and in sync_operations in this file (sync), whereas in this function we
 * just check that the file is *not* created during init.
 *
 * @param aContractId
 *        The contract ID to use for the output stream
 * @param aDeferOpen
 *        Whether to check with DEFER_OPEN or not
 * @param aTrickDeferredOpen
 *        Whether we try to 'trick' deferred opens by changing the file object before
 *        the actual open. The stream should have a clone, so changes to the file
 *        object after Init and before Open should not affect it.
 */
function check_access(aContractId, aDeferOpen, aTrickDeferredOpen) {
  const LEAF_NAME = "filestreams-test-file.tmp";
  const TRICKY_LEAF_NAME = "BetYouDidNotExpectThat.tmp";
  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
  file.append(LEAF_NAME);

  // Writing

  ensure_unique(file);
  let ostream = Cc[aContractId].createInstance(Ci.nsIFileOutputStream);
  ostream.init(
    file,
    -1,
    -1,
    aDeferOpen ? Ci.nsIFileOutputStream.DEFER_OPEN : 0
  );
  Assert.equal(aDeferOpen, !file.clone().exists()); // If defer, should not exist and vice versa
  if (aDeferOpen) {
    // File should appear when we do write to it.
    if (aTrickDeferredOpen) {
      // See |@param aDeferOpen| in the JavaDoc comment for this function
      file.leafName = TRICKY_LEAF_NAME;
    }
    ostream.write("data", 4);
    if (aTrickDeferredOpen) {
      file.leafName = LEAF_NAME;
    }
    // We did a write, so the file should now exist
    Assert.ok(file.clone().exists());
  }
  ostream.close();

  // Reading

  ensure_unique(file);
  let istream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
    Ci.nsIFileInputStream
  );
  var initOk, getOk;
  try {
    istream.init(
      file,
      -1,
      0,
      aDeferOpen ? Ci.nsIFileInputStream.DEFER_OPEN : 0
    );
    initOk = true;
  } catch (e) {
    initOk = false;
  }
  try {
    let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
      Ci.nsIFileInputStream
    );
    fstream.init(file, -1, 0, 0);
    getOk = true;
  } catch (e) {
    getOk = false;
  }

  // If the open is deferred, then Init should succeed even though the file we
  // intend to read does not exist, and then trying to read from it should
  // fail. The other case is where the open is not deferred, and there we should
  // get an error when we Init (and also when we try to read).
  Assert.ok(
    (aDeferOpen && initOk && !getOk) || (!aDeferOpen && !initOk && !getOk)
  );
  istream.close();
}

/**
 * We test async operations in test_NetUtil.js, and here test for simple sync
 * operations on input streams.
 *
 * @param aDeferOpen
 *        Whether to use DEFER_OPEN in the streams.
 */
function sync_operations(aDeferOpen) {
  const TEST_DATA = "this is a test string";
  const LEAF_NAME = "filestreams-test-file.tmp";

  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
  file.append(LEAF_NAME);
  file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);

  let ostream = Cc[OUTPUT_STREAM_CONTRACT_ID].createInstance(
    Ci.nsIFileOutputStream
  );
  ostream.init(
    file,
    -1,
    -1,
    aDeferOpen ? Ci.nsIFileOutputStream.DEFER_OPEN : 0
  );

  ostream.write(TEST_DATA, TEST_DATA.length);
  ostream.close();

  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
    Ci.nsIFileInputStream
  );
  fstream.init(file, -1, 0, aDeferOpen ? Ci.nsIFileInputStream.DEFER_OPEN : 0);

  let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
    Ci.nsIConverterInputStream
  );
  cstream.init(fstream, "UTF-8", 0, 0);

  let string = {};
  cstream.readString(-1, string);
  cstream.close();
  fstream.close();

  Assert.equal(string.value, TEST_DATA);
}

////////////////////////////////////////////////////////////////////////////////
//// Tests

function test_access() {
  check_access(OUTPUT_STREAM_CONTRACT_ID, false, false);
}

function test_access_trick() {
  check_access(OUTPUT_STREAM_CONTRACT_ID, false, true);
}

function test_access_defer() {
  check_access(OUTPUT_STREAM_CONTRACT_ID, true, false);
}

function test_access_defer_trick() {
  check_access(OUTPUT_STREAM_CONTRACT_ID, true, true);
}

function test_access_safe() {
  check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, false, false);
}

function test_access_safe_trick() {
  check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, false, true);
}

function test_access_safe_defer() {
  check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, true, false);
}

function test_access_safe_defer_trick() {
  check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, true, true);
}

function test_sync_operations() {
  sync_operations();
}

function test_sync_operations_deferred() {
  sync_operations(true);
}

function do_test_zero_size_buffered(disableBuffering) {
  const LEAF_NAME = "filestreams-test-file.tmp";
  const BUFFERSIZE = 4096;

  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
  file.append(LEAF_NAME);
  file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);

  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
    Ci.nsIFileInputStream
  );
  fstream.init(
    file,
    -1,
    0,
    Ci.nsIFileInputStream.CLOSE_ON_EOF | Ci.nsIFileInputStream.REOPEN_ON_REWIND
  );

  var buffered = Cc[
    "@mozilla.org/network/buffered-input-stream;1"
  ].createInstance(Ci.nsIBufferedInputStream);
  buffered.init(fstream, BUFFERSIZE);

  if (disableBuffering) {
    buffered.QueryInterface(Ci.nsIStreamBufferAccess).disableBuffering();
  }

  // Scriptable input streams clamp read sizes to the return value of
  // available(), so don't quite do what we want here.
  let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
    Ci.nsIConverterInputStream
  );
  cstream.init(buffered, "UTF-8", 0, 0);

  Assert.equal(buffered.available(), 0);

  // Now try reading from this stream
  let string = {};
  Assert.equal(cstream.readString(BUFFERSIZE, string), 0);
  Assert.equal(string.value, "");

  // Now check that available() throws
  var exceptionThrown = false;
  try {
    Assert.equal(buffered.available(), 0);
  } catch (e) {
    exceptionThrown = true;
  }
  Assert.ok(exceptionThrown);

  // OK, now seek back to start
  buffered.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);

  // Now check that available() does not throw
  exceptionThrown = false;
  try {
    Assert.equal(buffered.available(), 0);
  } catch (e) {
    exceptionThrown = true;
  }
  Assert.ok(!exceptionThrown);
}

function test_zero_size_buffered() {
  do_test_zero_size_buffered(false);
  do_test_zero_size_buffered(true);
}

////////////////////////////////////////////////////////////////////////////////
//// Test Runner

var tests = [
  test_access,
  test_access_trick,
  test_access_defer,
  test_access_defer_trick,
  test_access_safe,
  test_access_safe_trick,
  test_access_safe_defer,
  test_access_safe_defer_trick,
  test_sync_operations,
  test_sync_operations_deferred,
  test_zero_size_buffered,
];

function run_test() {
  tests.forEach(function (test) {
    test();
  });
}
