// Copyright 2022, University of Freiburg,
// Chair of Algorithms and Data Structures.
// Author: Andre Schlegel (November of 2023,
// schlegea@informatik.uni-freiburg.de)

#pragma once

#include "../benchmark/infrastructure/BenchmarkMeasurementContainer.h"
#include "util/Exception.h"
#include "util/TypeTraits.h"

/*
For doing a column based operations, that is, on all the entries.
For example: Adding two columns together, calculating speed up between the
entries of two columns, etc.
*/
namespace ad_benchmark {

/*
Column number together with the type of value, that can be found inside the
column. Note, that **all** entries in the column must have the same type,
because of `ResultTable::getEntry`.
*/
CPP_template(typename Type)(
    requires ad_utility::SameAsAnyTypeIn<
        Type, ResultTable::EntryType>) struct ColumnNumWithType {
  using ColumnType = Type;
  const size_t columnNum_;
};

// clang-format off
CPP_variadic_template(typename ColumnReturnType, typename... ColumnInputTypes)
  (requires(sizeof...(ColumnInputTypes) > 0))
    // clang-format on
    void generateColumnWithColumnInput(
        ResultTable* const table,
        QL_CONCEPT_OR_NOTHING(
            ad_utility::InvocableWithSimilarReturnType<
                ColumnReturnType, const ColumnInputTypes&...>) auto&& generator,
        const ColumnNumWithType<ColumnReturnType>& columnToPutResultIn,
        const ColumnNumWithType<ColumnInputTypes>&... inputColumns) {
  // Using a column more than once is the sign of an error.
  std::array<size_t, sizeof...(ColumnInputTypes)> allColumnNums{
      {inputColumns.columnNum_...}};
  ql::ranges::sort(allColumnNums);
  AD_CONTRACT_CHECK(ql::ranges::adjacent_find(allColumnNums) ==
                    allColumnNums.end());

  // Fill the result column.
  for (size_t row = 0; row < table->numRows(); row++) {
    table->setEntry(
        row, columnToPutResultIn.columnNum_,
        std::invoke(generator, table->getEntry<ColumnInputTypes>(
                                   row, inputColumns.columnNum_)...));
  }
}

/*
@brief Vector addition with `ResultTable` columns.
*/
// clang-format off
CPP_variadic_template(typename ColumnReturnType,
          typename... ColumnInputTypes)
          (requires((ql::concepts::same_as<ColumnInputTypes, ColumnNumWithType<ColumnReturnType>> && ...)
            && sizeof...(ColumnInputTypes) > 1))
    // clang-format on
    void sumUpColumns(
        ResultTable* const table,
        const ColumnNumWithType<ColumnReturnType>& columnToPutResultIn,
        const ColumnInputTypes&... columnsToSumUp) {
  // We can simply pass this to `generateColumnWithColumnInput`.
  generateColumnWithColumnInput(
      table,
      [](const ColumnInputTypes::ColumnType&... values) -> ColumnReturnType {
        return (values + ...);
      },
      columnToPutResultIn, columnsToSumUp...);
}

/*
@brief Reads two floating point columns, calculates the relative speedup between
their entries and writes it in a third column.

@param columnToCalculateFor, columnToCompareAgainst The columns, with which
the question "How much faster than the entries of `columnToCompareAgainst`
are the entries of `columnToCalculateFor`?".
@param columnToPlaceResultIn This is where the speedup calculation results
will be placed in.
*/
inline void calculateSpeedupOfColumn(
    ResultTable* const table,
    const ColumnNumWithType<float>& columnToPlaceResultIn,
    const ColumnNumWithType<float>& columnToCalculateFor,
    const ColumnNumWithType<float>& columnToCompareAgainst) {
  // We can simply pass this to `generateColumnWithColumnInput`.
  generateColumnWithColumnInput(
      table,
      [](const float compareAgainst, const float calculateFor) -> float {
        /*
        Speedup calculations only makes sense, if ALL values are bigger than
        0.
        */
        AD_CONTRACT_CHECK(compareAgainst > 0.f && calculateFor > 0.f);
        return compareAgainst / calculateFor;
      },
      columnToPlaceResultIn, columnToCompareAgainst, columnToCalculateFor);
}
}  // namespace ad_benchmark
