/*
  Copyright (C) 2018 David Robillard <d@drobilla.net>

  This program is free software: you can redistribute it and/or modify it under
  the terms of the GNU General Public License as published by the Free Software
  Foundation, either version 2 of the License, or (at your option) any later
  version.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  details.

  You should have received a copy of the GNU General Public License along with
  this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "bench_utils.hpp"
#include "test_utils.hpp"

#include "chilbert/BoundedBitVec.hpp" // IWYU pragma: keep
#include "chilbert/DynamicBitVec.hpp" // IWYU pragma: keep
#include "chilbert/SmallBitVec.hpp"
#include "chilbert/StaticBitVec.hpp" // IWYU pragma: keep
#include "chilbert/chilbert.hpp"

#include <array>
#include <cstddef>
#include <fstream>
#include <string>

template<class H, size_t M, size_t D>
struct BenchCoordsToIndex {
  Duration operator()(Context& ctx)
  {
    const auto p  = make_random_point<M, D>(ctx);
    H          ha = make_zero_bitvec<H, D * M>();

    return run_bench([&](auto) { chilbert::coords_to_index(p, M, D, ha); });
  }
};

template<class H, size_t M, size_t D>
struct BenchIndexToCoords {
  Duration operator()(Context& ctx)
  {
    auto    p  = make_random_point<M, D>(ctx);
    const H ha = make_random_bitvec<H, D * M>(ctx);

    return run_bench([&](auto) { chilbert::index_to_coords(p, M, D, ha); });
  }
};

/// Run benchmark for size N
template<template<class H, size_t M, size_t D> class Bench, size_t M, size_t D>
void
bench_row(Context& ctx, std::ostream& os)
{
  std::array<Duration, 4> results = {
    ((M * D <= chilbert::SmallBitVec::bits_per_rack)
       ? Bench<chilbert::SmallBitVec, M, D>{}(ctx)
       : Duration{}),
    Bench<chilbert::StaticBitVec<M * D>, M, D>{}(ctx),
    Bench<chilbert::BoundedBitVec<M * D>, M, D>{}(ctx),
    Bench<chilbert::DynamicBitVec, M, D>{}(ctx),
  };

  write_row(os, D, results);
}

/// Terminate recursion
template<template<class H, size_t M, size_t D> class Bench, size_t M>
void
bench_rec(Context&, std::ostream&)
{}

/// Run benchmark for sizes N, Ns... (recursive helper)
template<template<class H, size_t M, size_t D> class Bench,
         size_t M,
         size_t D,
         size_t... Ds>
void
bench_rec(Context& ctx, std::ostream& os)
{
  bench_row<Bench, M, D>(ctx, os);
  bench_rec<Bench, M, Ds...>(ctx, os);
}

/// Run benchmark
template<template<class H, size_t M, size_t D> class Bench>
void
bench(Context& ctx, const std::string& name)
{
  std::ofstream out("hilbert_" + name + ".txt");
  out << "d\tsmall\tstatic\tbounded\tdynamic\n";
  bench_rec<Bench, 8, 2, 4, 8, 16, 32, 64>(ctx, out);
}

int
main()
{
  Context ctx;

  bench<BenchCoordsToIndex>(ctx, "coords_to_index");
  bench<BenchIndexToCoords>(ctx, "index_to_coords");

  return 0;
}