
#ifndef BENCHMARK_RUNNER_H
#define BENCHMARK_RUNNER_H

#include <string>
#include <cstdlib>
#include <vector>
#include <chrono>
#include <numeric>
#include <iostream>
#include <fstream>
#include <bits/stdc++.h>

using namespace std;

class benchmark_runner {
 private:
  string csv_path_;
  string csv_name_;

  chrono::steady_clock::time_point last_start_time_;
  vector<long> times_;

  void print_statistics() {
    long time_sum = std::accumulate(times_.begin(), times_.end(), 0l);
    cout << "Average Runtime (us): " << (time_sum / times_.size()) << endl;
  }

  inline bool file_exists(const std::string &name) {
    ifstream f(name);
    return f.good();
  }

 public:
  benchmark_runner(string csv_path, string csv_name, int num_measurements = 10000) : csv_path_{std::move(csv_path)},
                                                                                     csv_name_{std::move(csv_name)},
                                                                                     times_{} {
    string command = "mkdir -p " + csv_path_;
    int res = system(command.c_str());
    if (res) {
      cout << "Error while creating directory!" << endl;
      exit(1);
    }

    times_.reserve(num_measurements);
  }

  static void read_args(int argc, char **argv, int &num_threads, string &path) {
    if (argc < 3) {
      cout << "Must Specifiy concurrency and output directory! (usage: `benchmark <output_directory> <num_threads>`)"
           << endl;
      exit(1);
    }

    string tmp = argv[1];
    path = tmp;
    num_threads = atoi(argv[2]);
  }

  void start_iteration() {
    last_start_time_ = chrono::steady_clock::now();
  }

  void end_iteration() {
    auto end_time = chrono::steady_clock::now();
    long time = chrono::duration_cast<chrono::microseconds>(end_time - last_start_time_).count();
    times_.emplace_back(time);
  }

  void run_iterations(int count,
                      const function<void(void)> measure,
                      int warmup_count,
                      const function<void(void)> prepare = []() {}) {
    for (int i = 0; i < warmup_count; i++) {
      prepare();
      measure();
    }

    for (int i = 0; i < count; i++) {
      prepare();
      start_iteration();
      measure();
      end_iteration();
    }
  }

  void commit_results(bool print_stats) {
    if (print_stats) {
      print_statistics();
    }

    string full_filename = csv_path_ + csv_name_;
    bool write_header = !file_exists(full_filename);

    { // Scope for output file
      ofstream o(full_filename, std::fstream::out | std::fstream::app);
      if (write_header) {
        o << "runtime_us" << endl;
      }
      for (auto time : times_) {
        o << time << endl;
      }
    } // End Scope for output file

    times_.clear();
  }
};

#endif //BENCHMARK_RUNNER_H
