#include <iostream>
#include <array>
#include <random>
#include <numeric>
#include <algorithm>
#include <iterator>

template <class Result, class It>
Result avg(It begin,
           It end)
{
  auto sum = std::accumulate(begin,
                             end,
                             Result{});
  auto size = std::distance(begin, end);
  return sum / size;
}

thread_local std::mt19937 gen{std::random_device{}()};

// https://d...content-available-to-author-only...q.cc/post/180/nie-uzywaj-rand-cxx-ma-random/
template<typename T>
T random(T min, T max) {
    using dist = std::conditional_t<
        std::is_integral<T>::value,
        std::uniform_int_distribution<T>,
        std::uniform_real_distribution<T>
    >;
    return dist{min, max}(gen);
}

template <class It, class T = typename std::iterator_traits<It>::value_type>
void fillWithRandomData(It begin,
                        It end,
                        T min,
                        T max)
{
  std::generate(begin, end, [&]() {
                  return random(min, max);
                });
}

template <class It, class UnaryPredicate>
void print(It begin,
           It end,
           UnaryPredicate pred)
{
  std::copy_if(begin,
               end,
               std::ostream_iterator<typename std::iterator_traits<It>::value_type>(std::cout, "\n"),
               pred);
}

template <class It>
void printMinMax(It begin,
                 It end)
{
  auto minmax = std::minmax_element(begin, end);
  std::cout
    << "Min: " << *(minmax.first) << "\n"
    << "Max: " << *(minmax.second) << "\n";

}

template <class It>
void printMatrixMinMax(It begin,
                       It end)
{
  using Type = typename std::iterator_traits<It>::value_type;
  std::vector<typename Type::value_type> buffer;
  std::for_each(begin,
                end,
                [&](auto elem) {
                  auto minmax = std::minmax_element(elem.begin(), elem.end());
                  buffer.push_back(*(minmax.first));
                  buffer.push_back(*(minmax.second));
                });

  auto minmax = std::minmax_element(buffer.begin(), buffer.end());
  std::cout
    << "Min = " << *(minmax.first) << "\n"
    << "Max = " << *(minmax.second) << "\n";
}

template <class Result, class It>
Result sumDiagonalMatrix(It begin,
                         It end)
{
  Result result{};
  auto size = std::distance(begin, end);
  auto iter = begin;
  for (auto i = 0; i < size; ++i) {
    auto tmp = *iter;
    result += *(std::next(tmp.begin(), i));
    std::advance(iter, 1);
  }
  return result;
}

int main()
{
  // 1.
  std::array<int, 8> foo {1, 2, 3, 4, 5, 6, 7, 8};
  auto average = avg<double>(foo.begin(), foo.end());
  std::cout << "Avg = " << average << "\n";
  if (average < 5.0) {
    std::cout << "< 5.0\n";
  } else if (average > 5.0) {
    std::cout << "> 5.0\n";
  } else {
    std::cout << "== 5.0\n";
  }

  // 2.
  std::array<int, 10> bar;
  fillWithRandomData(bar.begin(),
                     bar.end(),
                     1,
                     500);
  print(bar.begin(),
        bar.end(),
        [](auto elem) {
          return elem % 6 == 0;
        });

  // 3.
  std::array<int, 20> simple;
  fillWithRandomData(simple.begin(),
                     simple.end(),
                     10,
                     50);
  printMinMax(simple.begin(), simple.end());

  // 5.
  std::array<std::array<int, 3>, 3> matrix;

  for (auto& elem : matrix) {
    fillWithRandomData(elem.begin(),
                       elem.end(),
                       0,
                       100);
  }

  printMatrixMinMax(matrix.begin(), matrix.end());

  // 6.
  std::array<std::array<int, 3>, 3> matrix2;

  for (auto& elem : matrix2) {
    fillWithRandomData(elem.begin(),
                       elem.end(),
                       0,
                       100);
  }

  auto sum = sumDiagonalMatrix<int>(matrix2.begin(),
                                    matrix2.end());
  std::cout << "Diagonal sum = " << sum << "\n";
}