﻿//autor: Mateusz Gurdała

#include <iostream>
#include <expected>
#include <fstream>
#include <string>
#include <vector>

/*
	template<class T, class E> class expected
	Klasa która umożliwia przetrzymywanie jednej z dwóch wartości:

		oczekiwanej -> wartość zwracana przez operację która zakończy się sukcesem.
		Musi być 'Destructible' - typ musi zapewniać destruktor za pomocą którego wszystkie zasoby zajmowane przez typ są zwalniane bez błędów,
		lub być typu void.

		nieoczekiwanej (błędu) -> wartość która jest efektem wystąpienia błędu w programie
		Tak samo jak wartość oczekiwana musi spełniać warunki 'Destructible' oraz spełniać warunki template'a klasy std::unexpected.

	Znajduje wykorzystanie w funkcjach które oczekujemy że będą zwracały dany typ danych, ale jest możliwość wystąpnienia błędu w programie.
	W takim przypadku zwracany typ danych będzie typu wartości nieoczekiwanej.

	Klasa implementuje niektóre funkcjonalności klas std::variant (pozwala na przetrzymywanie jednej z N klas, w tym przypadku N == 2)
	oraz std::optional (pozwala na sprawdzanie czy posiada oczekiwaną wartość za pomocą if (result) lub result.has_value()).

	Do zwracania wartości nieoczekiwanej wykorzystywana jest klasa pomocnicza std::unexpected.

	Źródła:
	https://en.cppreference.com/w/cpp/utility/expected
	https://en.cppreference.com/w/cpp/utility/expected/unexpected
	https://en.cppreference.com/w/cpp/named_req/Destructible
	https://www.youtube.com/watch?v=PH4WBuE1BHI  // Video z konferencji omawiającej koncept std::expected

	Do kompilacji wykorzystałem MicrosoftVisualC++23.
*/

// Typy wyliczeniowe dobrze sprawdzają się jako typ wartości nieoczekiwanej w przypadku gdy funkcja jest podatna na wiele różnych błędów
enum class Error {
	FileNotFound,
	ParseError,
	UserNotFound,
	InvalidId
};

/*
	Prosta funkcja demonstracyjna pobierająca i parsująca jakieś dane z pliku - posiada wiele punktów które mogą generować błędy.
	Zdaję sobie sprawę że jest nieefektywna i nie tak powinno się ten problem rozwiązać,
	skupiłem się bardziej na prezentowaniu funkcjonalności omawianego zagadnienia.
*/
std::expected<int, Error> getUserId(std::string& userName, std::ifstream& file)
{
	int id;
	size_t pos;
	std::string line;
	bool found = false;

	if (file.is_open())
	{
		while (std::getline(file, line))
		{
			if ((pos = line.find(' ')) == std::string::npos)
			{
				// Wykorzystanie klasy pomocniczej std::unexpected żeby zwrócić wartość nieoczekiwaną.
				return std::unexpected(Error::ParseError);
			};

			if (line.substr(0, pos) == userName)
			{
				try
				{
					id = std::stoi(line.substr(pos + 1, line.length()));
					found = true;
					break;
				}
				catch (const std::exception&)
				{
					return std::unexpected(Error::InvalidId);
				}
			}
		}
	}
	else
	{
		return std::unexpected(Error::FileNotFound);
	}

	if (found)
	{
		return id; // Funkcja prawidłowo się wykonała, zwracana jest wartość oczekiwana
	}
	return std::unexpected(Error::UserNotFound);
}


int main()
{
	std::string userName = "MyUsername";
	std::ifstream file;

	file.open("valid_list.txt"); // W tym miejscu zmienić nazwę pliku z listą by otrzymać odpowiedni rezultat.

	auto userId = getUserId(userName, file);

	file.close();

	/*
		Sprawdzanie jaki typ wartości przechowuje zmienna userId i odpowiednia jej obsługa.
	*/
	if (userId) // Tożsame z userId.has_value()
	{
		//Dostęp do wartości oczekiwanej poprzez *userId lub userId.value()
		std::cout << userName << " has id: " << *userId << ".\n";
		return 0;
	}
	else
	{
		/*
			W przypadku gdy wartość nieoczekiwana jest typu wyliczeniowego można wygodnie
			obsługiwać odpowiednie typy błędów za pomocą switch case
		*/
		switch (userId.error()) // Dostęp do wartości nieoczekiwanej za pomocą metody .error()
		{
		case Error::FileNotFound:
			std::cout << "File not found.\n";
			break;
		case Error::ParseError:
			std::cout << "Parse error.\n";
			break;
		case Error::UserNotFound:
			std::cout << "User not found.\n";
			break;
		case Error::InvalidId:
			std::cout << "Invalid id.\n";
			break;
		default:
			break;
		}
		return 1;
	}
}
