//Autorka: Julia Parobczy

#include <iostream>
#include <future> /*Obsluga operacji asynchronicznych*/
#include <thread> /*Zarzadzanie wyjatkami*/
#include <chrono> /*Obsluga czasu*/

using namespace std::chrono_literals;

/*Funkcja pomocnicza wyswietlajaca informacje o watku i iteracji*/
void count(const char* thread) {
	for (int i = 0; i < 3; i++) {
		std::cout << thread << " " << i << std::endl;
		std::this_thread::sleep_for(100ms);
	}
}

class Object {
public:
	/*Funkcja wyswietlajaca informacje o watku i iteracji
	dla instancji obiektu tej klasy*/
	void print(const char* thread) {
		count(thread);
	}
};

/*Funkcja, ktora uruchamia dwie funkcje count
asynchronicznie w osobnych watkach (std::launch::async)*/
void asynchronous() {
	std::cout << "\nstd::launch::async\n";
	auto t1 = std::async(std::launch::async, count, "t1");
	auto t2 = std::async(std::launch::async, count, "t2");
	std::this_thread::sleep_for(1000ms);
	// funkcje wykonaly sie asynchronicznie od razu po wywolaniu
	std::cout << "\nget()\n";
	t1.get();
	t2.get();
}

/*Funkcja, ktora opoznia wykonanie funkcji count do momentu,
gdy zostanie wywolane get() (std::launch::deferred)*/
void deferred() {
	std::cout << "\nstd::launch::deferred\n";
	auto t1 = std::async(std::launch::deferred, count, "t1");
	auto t2 = std::async(std::launch::deferred, count, "t2");
	std::this_thread::sleep_for(1000ms);
	// funkcje nie wykonaly sie w tym miejscu
	std::cout << "\nget()\n";
	t1.get();
	t2.get();
	//funkcje wywolaly sie synchronicznie po wywolaniu get()
}

/*Funkcja, ktora uruchamia funkcje count asynchronicznie,
ale moze rowniez zdecydować sie na opoznienie wykonania
w zaleznosci od implementacji i dostepnosci watkow.
(std::launch::async | std::launch::deferred)*/
void both() {
	std::cout << "\nstd::launch::async | std::launch::deferred\n";
	auto t1 = std::async(std::launch::async | std::launch::deferred, count, "t1");
	auto t2 = std::async(std::launch::async | std::launch::deferred, count, "t2");
	std::this_thread::sleep_for(1000ms);
	std::cout << "\nget()\n";
	//zalezne od implementacji
	t1.get();
	t2.get();
	//zalezne od implementacji
}

/*Funkcja, ktora uruchamia funkcje count w taki sposob,
jak jest to zaimplementowane w kompilatorze (najczesciej
beda to obie polityki wywolania)*/
void no_options() {
	std::cout << "\n<implementation dependent>\n";
	auto t1 = std::async(count, "t1");
	auto t2 = std::async(count, "t2");
	std::this_thread::sleep_for(1000ms);
	std::cout << "\nget()\n";
	//zalezne od implementacji
	t1.get();
	t2.get();
	//zalezne od implementacji
}

/*Funkcja prezentuje przyklad wywolania asynchronicznego
dla obiektow danej klasy*/
void calling_object() {
	Object o1, o2;
	std::cout << "\ncalling objects\n";
	auto t1 = std::async(std::launch::async, &Object::print, &o1, "t1");
	auto t2 = std::async(std::launch::async, &Object::print, &o2, "t2");
}

/*Uwaga: jesli zwracana wartosc std::future nie zostanie
przeniesiona lub przypisana, destruktor tej wartosci zablokuje
sie na koncu wywolanej funkcji (jeszcze zanim zostanie ukonczona),
powodujac jednoczesnie, ze kod wykona sie synchronicznie. Z tego
powodu, kazda ze zwracanych wartosci std::future jest przypisywana
do zmiennej lokalnej*/

int main() {
	asynchronous();
	deferred();
	both();
	no_options();
	calling_object();
}

/*Zrodla:
https://en.cppreference.com/w/cpp/thread/async
https://ddanilov.me/std-async-implementations/
*/
