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

// Reduce the amount of data on slow platforms.
const getDataBlockSize = () => {
  if (mozinfo.os == "android") {
    // Android is much slower than desktop.
    if (mozinfo.verify) {
      return 54; // Chaos mode on android
    }

    return 3333;
  }

  if (isInChaosMode()) {
    return 333;
  }

  return 33333;
};

/* exported testSteps */
async function testSteps() {
  const name = this.window ? window.location.pathname : "Splendid Test";
  const request = indexedDB.open(name, 1);

  (ev => {
    const os = ev.target.result.createObjectStore("testObjectStore", {
      keyPath: "id",
      autoIncrement: false,
    });

    os.createIndex("testObjectStoreIndexA", "indexA", { unique: true });
    os.createIndex("testObjectStoreIndexB", "indexB", { unique: false });
    os.createIndex("testObjectStoreIndexC", "indexC", { unique: false });
  })(await expectingUpgrade(request));

  const db = (await expectingSuccess(request)).target.result;

  const objectStore = db
    .transaction(["testObjectStore"], "readwrite")
    .objectStore("testObjectStore");

  const dataBlock = getDataBlockSize();
  const lastIndex = 3 * dataBlock;

  info("We will now add " + lastIndex + " blobs to our object store");

  const addSegment = async (from, dataValue) => {
    for (let i = from; i <= from + dataBlock - 1; ++i) {
      await objectStore.add({
        id: i,
        indexA: i,
        indexB: lastIndex + 1 - i,
        indexC: i % 3,
        value: dataValue,
      });
    }
  };

  const expectedBegin = getRandomView(512);
  const expectedMiddle = getRandomView(512);
  const expectedEnd = getRandomView(512);
  ok(
    !compareBuffers(expectedBegin, expectedMiddle),
    "Are all buffers different?"
  );
  ok(!compareBuffers(expectedBegin, expectedEnd), "Are all buffers different?");
  ok(
    !compareBuffers(expectedMiddle, expectedEnd),
    "Are all buffers different?"
  );

  const dataValueBegin = getBlob(expectedBegin);
  await addSegment(1, dataValueBegin);

  const dataValueMiddle = getBlob(expectedMiddle);
  await addSegment(dataBlock + 1, dataValueMiddle);

  const dataValueEnd = getBlob(expectedEnd);
  await addSegment(2 * dataBlock + 1, dataValueEnd);

  // Performance issue of 1860486 occurs here
  await new Promise((res, rej) => {
    let isDone = false;
    const deleteReq = objectStore.delete(
      IDBKeyRange.bound(6, lastIndex - 5, false, false)
    );
    deleteReq.onsuccess = () => {
      isDone = true;
      res();
    };
    deleteReq.onerror = err => {
      isDone = true;
      rej(err);
    };

    /**
     * The deletion should be over in 20 seconds or less on desktop. With the
     * regression, the operation can take more than 30 minutes. We use one
     * minute to reduce intermittent failures due to the CI environment.
     *
     * Note that this is not a magical timeout for the completion of an
     * asynchronous request: we are testing a hang and using an explicit timeout
     * will avoid the much longer default timeout which is way too long to be
     * acceptable in real use cases.
     *
     * Maintenance plan: If disk contention and slow hardware lead to too many
     * intermittent failures, the regression cutoff could be increased to 2-3
     * minutes or the test could be turned into a raptor performance test.
     */
    const minutes = 60 * 1000;
    const performance_regression_cutoff = 1 * minutes;
    do_timeout(performance_regression_cutoff, () => {
      if (!isDone) {
        rej(Error("Performance regression detected"));
      }
    });
  });

  const getIndexedItems = async indexName => {
    let actuals = [];
    return new Promise(res => {
      db
        .transaction(["testObjectStore"], "readonly")
        .objectStore("testObjectStore")
        .index(indexName)
        .openCursor().onsuccess = ev => {
        const cursor = ev.target.result;
        if (!cursor) {
          res(actuals);
        } else {
          actuals.push(cursor.value.value);
          cursor.continue();
        }
      };
    });
  };

  const checkValuesEqualTo = async (actuals, from, to, expected) => {
    for (let i = from; i < to; ++i) {
      const actual = new Uint8Array(await actuals[i].arrayBuffer());
      if (!compareBuffers(actual, expected)) {
        return i;
      }
    }
    return undefined;
  };

  const itemsA = await getIndexedItems("testObjectStoreIndexA");
  equal(itemsA.length, 10);

  const mismatchABegin = await checkValuesEqualTo(itemsA, 0, 5, expectedBegin);
  equal(
    mismatchABegin,
    undefined,
    "First index with value mismatch is " + mismatchABegin
  );
  const mismatchAEnd = await checkValuesEqualTo(itemsA, 5, 10, expectedEnd);
  equal(
    mismatchAEnd,
    undefined,
    "First index with value mismatch is " + mismatchAEnd
  );

  const itemsB = await getIndexedItems("testObjectStoreIndexB");

  equal(itemsB.length, 10);
  const mismatchBEnd = await checkValuesEqualTo(itemsB, 0, 5, expectedEnd);
  equal(
    mismatchBEnd,
    undefined,
    "First index with value mismatch is " + mismatchBEnd
  );
  const mismatchBBegin = await checkValuesEqualTo(itemsB, 5, 10, expectedBegin);
  equal(
    mismatchBBegin,
    undefined,
    "First index with value mismatch is " + mismatchBBegin
  );

  const actualsC = await getIndexedItems("testObjectStoreIndexC");

  equal(actualsC.length, 10);
  let countBegin = 0;
  let countEnd = 0;
  for (let i = 0; i < 10; ++i) {
    const actual = new Uint8Array(await actualsC[i].arrayBuffer());
    if (compareBuffers(actual, expectedBegin)) {
      ++countBegin;
    } else if (compareBuffers(actual, expectedEnd)) {
      ++countEnd;
    }
  }

  equal(countBegin, 5);
  equal(countEnd, 5);

  await db.close();
}
