#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/variance.hpp>

#include <chrono>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>

//avoid checking types that don't contain infinity.
namespace Detail {
template <typename T, bool>
struct has_infinity;

template <typename T>
struct has_infinity<T, true> {
  static bool isinf(T x)
  {
    return std::isinf(x);
  }
};

template <typename T>
struct has_infinity<T, false>
{ 
  static bool isinf(T)
  {
    return false;
  }
};

template <typename T>
bool isinf(T x)
{
  // Select the correct partially specialized type.
  return has_infinity<T, std::numeric_limits<T>::has_infinity>::isinf(x);
}
}



int main(int argc, char **argv)
{

  using namespace boost::accumulators;
  const std::size_t VectorSize = 500000000;
  const std::size_t iterations = 20;
  
  const int actualMin = 0;
  const int actualMax = VectorSize - 1;
  
  std::vector<int> numbers (VectorSize);
  std::iota(numbers.begin(), numbers.end(), 0);

  std::cout << "Using Detail::isinf\n";
  {
    accumulator_set<double, stats<tag::mean, tag::variance>> acc;
    for(auto i = 0;i<iterations;++i)
    {
      int maxValue = std::numeric_limits<int>::lowest();
      int minValue = std::numeric_limits<int>::max();
      auto start = std::chrono::high_resolution_clock::now();
      for(auto value : numbers)
      {
        if (!Detail::isinf(value))
        {
          minValue = std::min(minValue, value);
          maxValue = std::max(maxValue, value);
        }
      }

      auto end = std::chrono::high_resolution_clock::now();
      auto time = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);

      acc(time.count());
      
      if( minValue != actualMin) {
        std::cout << "minValue: " << minValue << " != " << actualMin << "!\n";
      }
      if( maxValue != actualMax) {
        std::cout << "maxValue: " << maxValue << " != " << actualMax << "!\n";
      }
  
    }

    std::cout << "Time (s): " << mean(acc) << '\n';
    auto stdev = std::sqrt(variance(acc)*(iterations)/(iterations-1.0));
    std::cout << "Standard Deviation: " << stdev << "\n\n";
  }
  
  std::cout << "Using std::isinf\n";
  {
    accumulator_set<double, stats<tag::mean, tag::variance>> acc;
    for(std::size_t i = 0;i < iterations;++i)
    {
      int maxValue = std::numeric_limits<int>::lowest();
      int minValue = std::numeric_limits<int>::max();
      auto start = std::chrono::high_resolution_clock::now();

      for(auto value : numbers)
      {
        if (!std::isinf(value))
        {
          minValue = std::min(minValue, value);
          maxValue = std::max(maxValue, value);
        }
      }

      auto end = std::chrono::high_resolution_clock::now();
      auto time = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);

      acc(time.count());

      if( minValue != actualMin) {
        std::cout << "minValue: " << minValue << " != " << actualMin << "!\n";
      }
      if( maxValue != actualMax) {
        std::cout << "maxValue: " << maxValue << " != " << actualMax << "!\n";
      }
    }
    std::cout << "Time (s): " << mean(acc) << '\n';
    auto stdev = std::sqrt(variance(acc)*(iterations)/(iterations-1.0));
    std::cout << "Standard Deviation: " << stdev << '\n';
  }
}
