//===-- rosa/support/math.hpp -----------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/support/math.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Math helpers.
///
//===----------------------------------------------------------------------===//

// !!!!!! Please check lines 60 - 180 forward !!!!!!!!!!!!!!

#ifndef ROSA_SUPPORT_MATH_HPP
#define ROSA_SUPPORT_MATH_HPP

#include "debug.hpp"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdarg>
#include <cstdlib>
#include <limits>
#include <type_traits>

namespace rosa {

/// Computes log base 2 of a number.
///
/// \param N the number to compute log base 2 for
///
/// \return log base 2 of \p N
constexpr size_t log2(const size_t N) {
  return ((N < 2) ? 1 : 1 + log2(N / 2));
}

/// Tells the next representable floating point value.
///
/// \tparam T type to operate on
///
/// \note The second type argument enforces \p T being a floating point type,
/// always use the default value!
///
/// \param V value to which find the next representable one
///
/// \return the next representable value of type \p T after value \p V
///
/// \pre Type \p T must be a floating point type, which is enforced by
/// `std::enable_if` in the second type argument.
template <typename T,
          typename = std::enable_if_t<std::is_floating_point<T>::value>>
T nextRepresentableFloatingPoint(const T V) {
  return std::nextafter(V, std::numeric_limits<T>::infinity());
}

#if false //can't compile original
// copied from the internet and adapted
//
(https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c)
/// Conjuncts two or more values with each other.
///
/// \param two or more values of the same datatype
///
/// \return the conjunction of the values given as parameter.
template <typename CONFDATATYPE>
CONFDATATYPE fuzzyAND(int n_args, ...) noexcept {
  // TODO: check datatype, if there are at least two arguments, and if they are
  // between 0 and 1
  // David suggests: nstead of a variadic argument, you could pass the values as
  // an std::array (with a template argument for the length). When you pass the
  // values as a container, you can simply use std::max_element and
  // std::min_element to have a one-liner implementation of the these fuzzy
  // functions.
  va_list ap;
  va_start(ap, n_args);
  CONFDATATYPE min = va_arg(ap, CONFDATATYPE);
  for (int i = 2; i <= n_args; i++) {
    CONFDATATYPE a = va_arg(ap, CONFDATATYPE);
    min = std::min(a, min);
  }
  va_end(ap);
  return min;
}
#else

template <typename CONFDATATYPE, std::size_t size>
CONFDATATYPE fuzzyAND(std::array<CONFDATATYPE, size> Data) noexcept {
  STATIC_ASSERT(std::is_arithmetic<CONFDATATYPE>::value,
                "Type of FuzzyAnd is not arithmetic");
  STATIC_ASSERT(size > 1, "Number of Arguments is to little");
  for (auto tmp : Data)
    ASSERT(tmp <= 1 && tmp >= 0);
  return *std::min_element(Data.begin(), Data.end());
}

#if false // safer
template <typename CONFDATATYPE, typename... _CONFDATATYPE>
std::enable_if_t<
    std::conjunction_v<std::is_same<CONFDATATYPE, _CONFDATATYPE>...>,
    CONFDATATYPE>
fuzzyAND(CONFDATATYPE Data, _CONFDATATYPE... Datan) noexcept {
  return fuzzyAND(
      std::array<CONFDATATYPE, sizeof...(Datan) + 1>{Data, Datan...});
}
#else
template <typename CONFDATATYPE, typename... _CONFDATATYPE>
CONFDATATYPE fuzzyAND(CONFDATATYPE Data, _CONFDATATYPE... Datan) noexcept {
  return fuzzyAND(
      std::array<CONFDATATYPE, sizeof...(Datan) + 1>{Data, Datan...});
}
#endif

#endif


#if false //can't compile original
/// Disjuncts two or more values with each other.
///
///
///
/// \return the disjunction of the values given as parameter.
// copied from the internet
// (https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c)
template <typename CONFDATATYPE>
CONFDATATYPE fuzzyOR(int n_args, ...) noexcept {
  // TODO: check datatype and if they are between 0 and 1
  // David suggests: nstead of a variadic argument, you could pass the values as
  // an std::array (with a template argument for the length). When you pass the
  // values as a container, you can simply use std::max_element and
  // std::min_element to have a one-liner implementation of the these fuzzy
  // functions.
  va_list ap;
  va_start(ap, n_args);
  CONFDATATYPE max = va_arg(ap, CONFDATATYPE);
  for (int i = 2; i <= n_args; i++) {
    CONFDATATYPE a = va_arg(ap, CONFDATATYPE);
    max = std::max(a, max);
  }
  va_end(ap);
  return max;
}
#else


template <typename CONFDATATYPE, std::size_t size>
CONFDATATYPE fuzzyOR(std::array<CONFDATATYPE, size> Data) noexcept {
  STATIC_ASSERT(std::is_arithmetic<CONFDATATYPE>::value,
                "Type of FuzzyAnd is not arithmetic");
  STATIC_ASSERT(size > 1, "Number of Arguments is to little");
  for (auto tmp : Data)
    ASSERT(tmp <= 1 && tmp >= 0);
  return *std::max_element(Data.begin(), Data.end());
}

#if false // safer
template <typename CONFDATATYPE, typename... _CONFDATATYPE>
std::enable_if_t<
    std::conjunction_v<std::is_same<CONFDATATYPE, _CONFDATATYPE>...>,
    CONFDATATYPE>
fuzzyOR(CONFDATATYPE Data, _CONFDATATYPE... Datan) noexcept {
  return fuzzyOR(
      std::array<CONFDATATYPE, sizeof...(Datan) + 1>{Data, Datan...});
}
#else
template <typename CONFDATATYPE, typename... _CONFDATATYPE>
CONFDATATYPE fuzzyOR(CONFDATATYPE Data, _CONFDATATYPE... Datan) noexcept {
  return fuzzyOR(
      std::array<CONFDATATYPE, sizeof...(Datan) + 1>{Data, Datan...});
}
#endif


#endif

template <typename INDATATYPE, typename PROCDATATYPE>
PROCDATATYPE relativeDistance(INDATATYPE NewValue,
                              INDATATYPE HistoryValue) noexcept {
  PROCDATATYPE Dist = HistoryValue - NewValue;

  if (Dist == 0) {
    return 0;
  } else {
    Dist = Dist / NewValue;
    if (Dist < 0) {
      // TODO: I guess this multiplication here should not be done because
      // it could be that the distance fuzzy functions are not symetrical
      //(negative and positive side)
      Dist = Dist * (-1);
    }
    return (Dist);
  }
}

} // End namespace rosa

#endif // ROSA_SUPPORT_MATH_HPP
