// Czy w aplikacji wystepuja wyscigi?
// Czy mozesz poprawić skalowalność aplikacji?

#include <memory>
#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
#include <random>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <atomic>
#include <chrono>

using namespace std;

using Data = int;  //zakladamy, ze Data jest to duzy obiekt
const Data EMPTY = -1; //reprezentuje brak danych

bool isProper(const Data& d) {
	//bardzo dlugi algorytm lokalny
	std::this_thread::sleep_for( chrono::milliseconds(10));
	return d == 0; //symuluje rozne wartosci
}

struct InData {
	InData(const vector<Data>& r) : records_(r), counter_(0) {}
	const vector<Data>& records_;
	mutex m_;
	int counter_;
};

struct Thread {
	Thread(InData& d) : data_(d), currentIdx_(0) {}

	void setIdx(int i) { currentIdx_ = i; }
	void operator()() {
		std::lock_guard<std::mutex> lock( data_.m_ );
		if(isProper(data_.records_[currentIdx_]) ) {
			data_.counter_ += 1;
		}
	}
private:
	int currentIdx_;
	InData& data_;
};

int countProper(const vector<Data>& records) {
	InData input(records);
	Thread t1(input);
	Thread t2(input);

	int i = 0; //indeks
	while( i < records.size() ) {
		t1.setIdx(i);
		++i;
		thread thrd1( ref(t1) );
		if( ++i < records.size() ) {
			t2.setIdx(i);
			++i;
			thread thrd2( ref(t2) );
			thrd2.join();
		}
		thrd1.join();
	}
	return input.counter_;
}

struct ThreadBetter {
	ThreadBetter(atomic<int>& c) : counter_(c), data_(nullptr) {}

	void setData(const Data* d) { data_ = d; }
	void operator()() {
		if(data_ && isProper(*data_) ) {
			counter_ += 1;
		}
	}
private:
	atomic<int>& counter_;
	const Data* data_;
};

int countProperBetter(const vector<Data>& records) {
	atomic<int> counter_(0);
	ThreadBetter t1(counter_);
	ThreadBetter t2(counter_);

	int i = 0; //indeks
	while( i < records.size() ) {
		t1.setData(&records[i]);
		++i;
		thread thrd1( ref(t1) );
		if( ++i < records.size() ) {
			t2.setData(&records[i]);
			++i;
			thread thrd2( ref(t2) );
			thrd2.join();
		}
		thrd1.join();
	}
	return counter_.load();
}

const int N = 100;
int main () {
    vector<Data> v(N); //wektor N liczb
    std::mt19937_64 gen(0); //generator losowy (Mersanne Twister, default seed)
    std::uniform_int_distribution<int> distr(-9, 9);
    generate( v.begin(), v.end(), [&](){ return distr(gen);}); //N liczb losowych od 1*10^12 do 9*10^12
    cout << "v size:" << v.size() << endl;
    copy( v.begin(), v.end(), ostream_iterator<int>(cout, " ") );
    cout << endl;
    auto start1 = std::chrono::high_resolution_clock::now();
	countProper(v);
    auto end1 = std::chrono::high_resolution_clock::now();
    std::cout << "Ver1 time "
              << std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count() << "ms"
              << std::endl;

    auto start2 = std::chrono::high_resolution_clock::now();
	countProperBetter(v);
    auto end2 = std::chrono::high_resolution_clock::now();
    std::cout << "Ver2 time "
              << std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count() << "ms"
              << std::endl;

    return 0;

}
