// Copyright 2021 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

// This file defines a fuzzer that validates operations in a multi-threaded
// setting.

#include "parameters.h"
#include "random.h"
#include "support/command-line.h"
#include "workload.h"
#include <cstdlib>
#include <iostream>
#include <optional>
#include <pthread.h>
#include <random>
#include <stdlib.h>
#include <string>
#include <unistd.h>

// Used to define the size of the bytes to seed the Random object.
#define NUM_RAND_BYTES 4096

namespace wasmfs {
using RandEngine = std::mt19937_64;
bool VERBOSE = false;

uint64_t getSeed() {
  // Return a (truly) random 64-bit value.
  std::random_device rand;
  return std::uniform_int_distribution<uint64_t>{}(rand);
}

struct Fuzzer {
  void run(uint64_t seed) {
    RandEngine getRand(seed);
    printf("Running with seed: %llu\n", seed);

    std::vector<char> bytes(NUM_RAND_BYTES);
    for (size_t i = 0; i < bytes.size(); i += sizeof(uint64_t)) {
      *(uint64_t*)(bytes.data() + i) = getRand();
    }

    Random rand(std::move(bytes));

    // Create a workload with the Random seed.
    auto workload = ReadWrite(rand);

    workload.execute();
  }
};
} // namespace wasmfs

int main(int argc, const char* argv[]) {
  using namespace wasmfs;

  Options options("wasmfs-fuzzer", "Fuzz multi-threaded WasmFS operations");
  std::optional<uint64_t> seed;

  options.add("--seed",
              "",
              "Run a single workload generated by the given seed",
              Options::Arguments::One,
              [&](Options*, const std::string& arg) {
                seed = uint64_t(std::stoull(arg));
              });

  options.add("--verbose",
              "-v",
              "Run with verbose logging",
              Options::Arguments::Zero,
              [&](Options*, const std::string& arg) { VERBOSE = true; });

  options.parse(argc, argv);

  Fuzzer fuzzer;

  if (seed) {
    fuzzer.run(*seed);
  } else {
    size_t i = 0;
    RandEngine nextSeed(getSeed());
    while (true) {
      printf("Iteration %zu \n", ++i);
      printf("Seed %llu \n", getSeed());
      fuzzer.run(nextSeed());
    }
  }
  return 0;
}
