/*
    Задача:

        В данном примере написана функция шаблонная getMax, которая находит максимум из чисел в некотором контейнере.
        При этом вычисления проходят однопоточно.


        Вам нужно написать шаблонную функцию     
            
            template <typename RandIt>
            RandIt getMax(int n, RandIt start, RandIt finish)

        которая будет делать то же самое, но только использовать для этого n потоков.
        Функция должна принимать на вход количество потоков и два итератора, задающих
        диапазон на котором нужно искать максимум.
        Известно, что оба итератора являются random access итераторами.



        Проверить, что эта функция будет работать и для других контейнеров и типов хранящихся в них данных.
        Например, для:

            std::deque<double> d {1.2, 5.1, 8.2, 1.0, 0.2, 5.0, 7.8};
*/

#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <list>
#include <random>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <deque>
using std::cout, std::endl, std::size_t;
using namespace std::chrono_literals;


template <typename RandIt>
RandIt getMax(RandIt start, RandIt finish)
{
    RandIt maxIt = start;
    for (auto it = start; it != finish; ++it)
    {
        if (*it > *maxIt)
            maxIt = it;
    }
    return maxIt;
}

template <typename RandIt>
struct maxBlock {
    void operator() (RandIt first, RandIt last, RandIt& res) {
        res = getMax(first, last); 
    }
};

template <typename RandIt>
RandIt getMax(int n, RandIt start, RandIt finish) {
    std::vector<RandIt> results(n);

    std::vector<std::thread> threads(n - 1);   
    unsigned long const block_size =  std::distance(start, finish) / n;

    RandIt block_start = start;

    for (int i = 0; i < n - 1; ++i) {
        RandIt block_end = block_start;
        std::advance(block_end, block_size);
        threads[i] = std::thread(maxBlock<RandIt>(), 
                        block_start, block_end, std::ref(results[i]));
        block_start = block_end;
    }
    maxBlock<RandIt>() (block_start, finish, results[n - 1]);

    std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));

    return *getMax(results.begin(), results.end());
}

int main()
{
    cout << "Generating numbers!" << endl;
    std::deque<uint64_t> numbers;
    
    numbers.push_back(1);

    for (size_t i = 0; i < 1e7; ++i)
    {
        numbers.push_back(numbers.back() + 1);
    }

    cout << "Numbers generated!" << endl;

    auto start = std::chrono::high_resolution_clock::now();
    auto it = getMax(numbers.begin(), numbers.end());
    cout << "Maximum = " << *it << endl;
    auto end = std::chrono::high_resolution_clock::now();
    cout << "Time to calclulate max = " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
         << " milliseconds." << endl;


    start = std::chrono::high_resolution_clock::now();
    it = getMax(4, numbers.begin(), numbers.end());
    cout << "Maximum = " << *it << endl;
    end = std::chrono::high_resolution_clock::now();
    cout << "Time to calclulate max = " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
         << " milliseconds." << endl;
}
