//--------ZAD2--------------------------------------------------------------------------
// Program oblicza ilosc liczb pierwszych, wykorzystujac wiele watkow. W aplikacji występuje wyscig.
// Prosze to wyeliminowac, dbajac o skalowalnosc.

// W funkcji 'main' występują obiekty 'ref(x)'. Są to obiekty pomocnicze, które zapobiegają tworzeniu kopii obiektu, więc np. 'thread thrd1( ref(p1) );' oznacza,
// że funkcja wątku obliczeniowego to będzie 'operator()' obiektu 'p1' (a nie jego kopii). Nie jest to kluczowe do znalezienia rozwiązania.


#include <thread>
#include <mutex>
#include <iostream>
#include <atomic>

using namespace std;

//bada, czy n jest liczba pierwsza (nie zmieniamy)
bool isPrime(long n) {
    if( n < 2 ) return false; // '0' and '1' is not prime
    if( n < 4 ) return true; // '2' and '3' is prime
    if( n % 2 == 0) return false;
    for(long i = 3; i * i <= n; i = i + 2)
        if(n % i == 0) return false;
    return true;
}

class ThreadPrime {
public:
    ThreadPrime(int n, atomic<int>& num) : n_max(n), num_primes(num) { }
    void operator()() {
        for(int i = 1; i < n_max; ++i ) {
            if(isPrime(i) )
                ++num_primes;
        }
    }
private:
    int n_max;
    atomic<int>& num_primes;
};

class ThreadPrime2 {
public:
    ThreadPrime2(int n, atomic<int>& curr, atomic<int>& num) : n_max(n), current(curr), num_primes(num) { }

    void operator()() {
        int local_num_primes = 0;
        for(int c = ++current; c < n_max; c = ++current) {
            if(isPrime(c) )
                ++local_num_primes;
        }
        num_primes += local_num_primes;
    }
private:
    int n_max;
    atomic<int>& current;
    atomic<int>& num_primes;
};


int main () {
    cout << "Program podaje ilosc liczb pierwszych mniejszych niz N" << endl;
    cout << "podaj N:";
    int n_max = 0;
    cin >> n_max;

    atomic<int> num_primes = 0;
    atomic<int> current = 0;

    ThreadPrime2 t1(n_max, current, num_primes);
    ThreadPrime2 t2(n_max, current, num_primes);
    ThreadPrime2 t3(n_max, current, num_primes);
    // ThreadPrime t1(n_max, num_primes);
    // ThreadPrime t2(n_max, num_primes);
    // ThreadPrime t3(n_max, num_primes);

    thread thrd1( ref(t1) );
    thread thrd2( ref(t2) );
    thread thrd3( ref(t3) );

    thrd1.join();
    thrd2.join();
    thrd3.join();

    cout << "num primes:" << num_primes << endl;
    cout << endl;

    return 0;
}
