// Stage 08: optional preview after chapter 14; concurrency is taught in chapters 21-27.
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <span>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
std::pair<std::size_t,std::size_t> partition(std::size_t n,std::size_t p,std::size_t id) {
    if(p==0 || id>=p) throw std::invalid_argument("partition");
    const auto q=n/p,r=n%p,begin=id*q+std::min(id,r);
    return {begin,begin+q+(id<r?1:0)};
}
long long parallel_total(std::span<const int> input,std::size_t p) {
    if(p==0 || p>8 || input.size()>1024) throw std::invalid_argument("bounded workload");
    for(const int value:input)
        if(value < -1000000 || value > 1000000) throw std::invalid_argument("reading range");
    std::vector<long long> partial(p,0);
    std::vector<std::thread> workers;workers.reserve(p);
    struct JoinAll {
        std::vector<std::thread>& threads;
        ~JoinAll(){for(auto& thread:threads) if(thread.joinable()) thread.join();}
    } join_all{workers};
    for(std::size_t id=0;id<p;++id) {
        const auto [begin,end]=partition(input.size(),p,id);
        workers.emplace_back([&,id,begin,end]{
            long long local=0;
            for(auto i=begin;i<end;++i) local+=input[i];
            partial[id]=local; // Each thread writes its own existing slot once.
        });
    }
    for(auto& thread:workers) thread.join();
    long long total=0;for(const auto value:partial) total+=value;
    return total;
}
int main() {
    const std::vector<int> readings{3,1,4,1,5},single{5};
    for(std::size_t p=1;p<=8;++p) {
        if(parallel_total(readings,p)!=14 || parallel_total({},p)!=0 || parallel_total(single,p)!=5) return 1;
        std::size_t previous=0;
        for(std::size_t id=0;id<p;++id) {
            const auto [begin,end]=partition(readings.size(),p,id);
            if(begin!=previous || end<begin || end>readings.size()) return 2;
            previous=end;
        }
        if(previous!=readings.size()) return 3;
    }
    int rejected=0;
    for(const std::size_t p:{0U,9U})
        try{(void)parallel_total(readings,p);}catch(const std::invalid_argument&){++rejected;}
    if(rejected!=2) return 4;
    std::cout<<"parallel_total="<<parallel_total(readings,3)<<" worker_cases=8 rejected=2\n";
    std::cout<<"ranges=0:2,2:4,4:5\n";
}
