#include "mylib.h"
#include <algorithm>
#include <cmath>
#include <execution>
#include <iostream>
#include <numeric>
#include <omp.h>

using namespace std;


void means_omp(const std::vector<size_t> numbers, double &am, double &gm, double &hm)
{
    size_t const n = numbers.size();

    am = 0.;
    gm = 0.;
    hm = 0.;

#pragma omp parallel for shared(numbers, n, cout) reduction(+:am, gm, hm)
    for (size_t i = 0; i < n; ++i)
    {
        am += numbers[i];
        gm += log(numbers[i]);
        hm += 1.0/numbers[i];

        // #pragma omp critical
        // {
        //     cout << "Thread number " << omp_get_thread_num() << " processes value " << numbers[i] << endl;
        // }
    }

    am /= n;
    gm = exp(gm/n);
    hm = n/hm;
}


void minmax_omp(const std::vector<size_t> numbers, size_t &global_min, size_t &global_max)
{
    size_t const n = numbers.size();

    global_min = -1; // gives the maximum size_t value
    global_max = 0;

#pragma omp parallel shared(numbers, n, global_min, global_max)
    {
        const size_t nthreads = omp_get_num_threads();
        const size_t threadnum = omp_get_thread_num();
        const size_t chunksize = n/nthreads;


        size_t start = threadnum*chunksize;
        size_t end = start + chunksize;
        if (threadnum == nthreads - 1)
            end = n;


        size_t local_min = -1;
        size_t local_max = 0;
        for (size_t i = start; i < end ; ++i)
        {
            if (numbers[i] < local_min)
                local_min = numbers[i];

            if (numbers[i] > local_max)
                local_max = numbers[i];
        }

        #pragma omp critical
        {
            if (local_min < global_min)
                global_min = local_min;
            
            if (local_max > global_max)
                global_max = local_max;
        }
    }
}


void means_cpp(const std::vector<size_t> numbers, double &am, double &gm, double &hm)
{
    size_t const n = numbers.size();

    am = reduce(std::execution::par, numbers.begin(), numbers.end());
    gm = transform_reduce(std::execution::par, numbers.begin(), numbers.end(), 0.0, plus{}, [] (size_t x) -> double { return log(x); } );
    hm = transform_reduce(std::execution::par, numbers.begin(), numbers.end(), 0.0, plus{}, [] (size_t x) -> double { return 1.0/x; });

    am /= n;
    gm = exp(gm/n);
    hm = n/hm;
}


void minmax_cpp(const std::vector<size_t> numbers, size_t &global_min, size_t &global_max)
{
    auto min_it = min_element(std::execution::par, numbers.begin(), numbers.end());
    auto max_it = max_element(std::execution::par, numbers.begin(), numbers.end());
    
    global_min = *min_it;
    global_max = *max_it;
}