#include <random>
#include <thread>

#include <benchmark/benchmark_api.h>
#include <gflags/gflags.h>
#include <glog/logging.h>

#include "data_structures/concurrent/concurrent_map.hpp"
#include "utils/random/random_generator.hpp"

/*
  ConcurrentMap Benchmark Test:
    - tests time of Insertion, Contain and Delete operations

    - benchmarking time per operation

    - test run ConcurrentMap with the following keys and values:
        - <int,int>
        - <int, string>
        - <string, int>
        - <string, string>
*/

using utils::random::NumberGenerator;
using utils::random::PairGenerator;
using utils::random::StringGenerator;

using IntegerGenerator = NumberGenerator<std::uniform_int_distribution<int>,
                                         std::default_random_engine, int>;

DEFINE_int32(start, 0, "Range start");
DEFINE_int32(end, 1000000000, "Range end");
DEFINE_int32(threads, 1, "Number of threads");
DEFINE_int32(string_length, 128, "String length");

// Global arguments
int MAX_ELEMENTS = 1 << 18, MULTIPLIER = 2;
int THREADS, RANGE_START, RANGE_END, STRING_LENGTH;

/*
  ConcurrentMap Insertion Benchmark Test
*/
template <class K, class V>
static void InsertValue(benchmark::State &state, ConcurrentMap<K, V> *map,
                        const std::vector<std::pair<K, V>> &elements) {
  while (state.KeepRunning()) {
    auto accessor = map->access();
    for (int start = 0; start < state.range(0); start++) {
      accessor.insert(elements[start].first, elements[start].second);
    }
  }
  state.SetComplexityN(state.range(0));
}

/*
  ConcurrentMap Deletion Benchmark Test
*/
template <class K, class V>
static void DeleteValue(benchmark::State &state, ConcurrentMap<K, V> *map,
                        const std::vector<std::pair<K, V>> elements) {
  while (state.KeepRunning()) {
    auto accessor = map->access();
    for (int start = 0; start < state.range(0); start++) {
      accessor.remove(elements[start].first);
    }
  }
  state.SetComplexityN(state.range(0));
}

/*
  ConcurrentMap Contains Benchmark Test
*/
template <class K, class V>
static void ContainsValue(benchmark::State &state, ConcurrentMap<K, V> *map,
                          const std::vector<std::pair<K, V>> elements) {
  while (state.KeepRunning()) {
    auto accessor = map->access();
    for (int start = 0; start < state.range(0); start++) {
      accessor.contains(elements[start].first);
    }
  }
  state.SetComplexityN(state.range(0));
}

auto BM_InsertValue = [](benchmark::State &state, auto *map, auto &elements) {
  InsertValue(state, map, elements);
};

auto BM_DeleteValue = [](benchmark::State &state, auto *map, auto elements) {
  DeleteValue(state, map, elements);
};

auto BM_ContainsValue = [](benchmark::State &state, auto *map, auto elements) {
  ContainsValue(state, map, elements);
};

/*
  Commandline Argument Parsing

  Arguments:
   * Integer Range Minimum
      -start number

   *  Integer Range Maximum
      - end number

   * Number of threads
      - threads number

   * Random String lenght
      -string-length number
*/
void parse_arguments() {
  RANGE_START = FLAGS_start;
  RANGE_END = FLAGS_end;

  THREADS = std::min(FLAGS_threads,
                     static_cast<int>(std::thread::hardware_concurrency()));
  STRING_LENGTH = FLAGS_string_length;
}

int main(int argc, char **argv) {
  benchmark::Initialize(&argc, argv);
  parse_arguments();
  google::InitGoogleLogging(argv[0]);

  StringGenerator sg(STRING_LENGTH);
  IntegerGenerator ig(RANGE_START, RANGE_END);

  /*
    Creates RandomGenerators, ConcurentMaps and Random Element Vectors for the
    following use cases:

      Map elements contain keys and value for:
        <int, int>,
        <int, string>
        <string, int>
        <string, string>
  */

  // random generators for tests
  PairGenerator<IntegerGenerator, IntegerGenerator> piig(&ig, &ig);
  PairGenerator<StringGenerator, StringGenerator> pssg(&sg, &sg);
  PairGenerator<StringGenerator, IntegerGenerator> psig(&sg, &ig);
  PairGenerator<IntegerGenerator, StringGenerator> pisg(&ig, &sg);

  // maps used for testing
  ConcurrentMap<int, int> ii_map;
  ConcurrentMap<int, std::string> is_map;
  ConcurrentMap<std::string, int> si_map;
  ConcurrentMap<std::string, std::string> ss_map;

  // random elements for testing
  auto ii_elems = utils::random::generate_vector(piig, MAX_ELEMENTS);
  auto is_elems = utils::random::generate_vector(pisg, MAX_ELEMENTS);
  auto si_elems = utils::random::generate_vector(psig, MAX_ELEMENTS);
  auto ss_elems = utils::random::generate_vector(pssg, MAX_ELEMENTS);

  /* insertion Tests */

  benchmark::RegisterBenchmark("InsertValue[Int, Int]", BM_InsertValue, &ii_map,
                               ii_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("InsertValue[Int, String]", BM_InsertValue,
                               &is_map, is_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("InsertValue[String, Int]", BM_InsertValue,
                               &si_map, si_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("InsertValue[String, String]", BM_InsertValue,
                               &ss_map, ss_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  // Contains Benchmark Tests

  benchmark::RegisterBenchmark("ContainsValue[Int, Int]", BM_ContainsValue,
                               &ii_map, ii_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("ContainsValue[Int, String]", BM_ContainsValue,
                               &is_map, is_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("ContainsValue[String, Int]", BM_ContainsValue,
                               &si_map, si_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("ContainsValue[String, String]",
                               BM_ContainsValue, &ss_map, ss_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  // Deletion Banchamark Tests

  benchmark::RegisterBenchmark("DeleteValue[Int, Int]", BM_DeleteValue, &ii_map,
                               ii_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("DeleteValue[Int, String]", BM_DeleteValue,
                               &is_map, is_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("DeleteValue[String, Int]", BM_DeleteValue,
                               &si_map, si_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RegisterBenchmark("DeleteValue[String, String]", BM_DeleteValue,
                               &ss_map, ss_elems)
      ->RangeMultiplier(MULTIPLIER)
      ->Range(1, MAX_ELEMENTS)
      ->Complexity(benchmark::oN)
      ->Threads(THREADS);

  benchmark::RunSpecifiedBenchmarks();

  return 0;
}