#include "Game.hpp"
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <filesystem> // sprawdzenie, czy plik istnieje
#include "ssp.hpp"    // nagłówkowy parser plików .csv
#include "Engine.hpp"
#include "Utils.hpp"

void Game::printGameWelcomeText() const {

	#ifdef DEBUG
		std::cout << "Uruchamiasz wersje DEBUG!\n\n";
	#endif

	std::cout << "\n"
				 " .o88b. db   db d888888b d8b   db  .o88b. d88888D db    db db   dD  db .d888b. .d888b.\n"
				 "d8P  Y8 88   88   `88'   888o  88 d8P  Y8 YP  d8' `8b  d8' 88 ,8P' o88 88   8D 88   8D\n"
				 "8P      88ooo88    88    88V8o 88 8P         d8'   `8bd8'  88,8P    88 `VoooY' `VoooY'\n"
				 "8b      88~~~88    88    88 V8o88 8b        d8'      88    88`8b    88 .d~~~b. .d~~~b.\n"
				 "Y8b  d8 88   88   .88.   88  V888 Y8b  d8  d8' db    88    88 `88.  88 88   8D 88   8D\n"
				 " `Y88P' YP   YP Y888888P VP   V8P  `Y88P' d88888P    YP    YP   YD  VP `Y888P' `Y888P'\n"
				 "\n";

	std::cout << "Witaj w Chinczyku!\nAutor: B.Z. (2 EF-DI, L01, 177188)\n";
	#ifdef CHINCZYK188_IGNORE_USER_SEED
		std::cout << "Wersja gry bez losowania deterministycznym ziarnem (z -DCHINCZYK188_IGNORE_USER_SEED).\n";
	#endif
	#ifndef CHINCZYK188_IGNORE_USER_SEED
		std::cout << "Wersja gry z losowaniem deterministycznym ziarnem (bez -DCHINCZYK188_IGNORE_USER_SEED).\n";
	#endif
	std::cout << "Kod zrodlowy: https://gitea.7o7.cx/sherl/Chinczyk188 na licencji GPLv3\n";
	std::cout << "Instrukcja gry powinna zostac dolaczona do tej kopii gry, w przeciwnym razie\n"
				 "jest ona dostepna w repozytorium git.\n\n";

}

void Game::readLeaderboards(std::string leaderboardLocation) {

	try {

		// Spróbuj wczytać plik z zapisanymi wynikami
		ss::parser<ss::throw_on_error> p{leaderboardLocation, " "};

		for (const auto& [csvName, csvWins]: p.iterate<std::string, unsigned int>()) {
			this->leaderboardName.push_back(csvName);
			this->leaderboardWins.push_back(csvWins);
		}

	}
	catch (ss::exception& e) {

		// Jeżeli nie udało się wczytać pliku, spróbuj wczytać kopię zapasową
		if (std::filesystem::exists("." + leaderboardLocation + ".old")) {

			ss::parser<ss::throw_on_error> p{"." + leaderboardLocation + ".old", " "};

			for (const auto& [csvName, csvWins]: p.iterate<std::string, unsigned int>()) {
				this->leaderboardName.push_back(csvName);
				this->leaderboardWins.push_back(csvWins);
			}

			// Przekopiuj po cichu (verbose=false) dane do pliku głównego
			this->dumpLeaderboards(leaderboardLocation, false);

			return;

		}

		std::cerr << "Wystapil blad przy probie wczytania listy wynikow. \n"
					 "Gra sprobuje naprawic blad poprzez utworzenie nowego pliku z lista wynikow.\n"
					 "Nacisnij CTRL+C w przeciagu 10 sekund aby anulowac i wyjsc z programu.\n";
		sf::sleep(sf::seconds(15));

		// Utwórz plik
		std::ofstream leaderboardFile;
		leaderboardFile.open(leaderboardLocation);
		leaderboardFile << "\n";
		leaderboardFile.close();

		// Przykładowe dane
		this->leaderboardName.push_back("Anonymous");
		this->leaderboardWins.push_back(1);

	}

}

void Game::dumpLeaderboards(std::string leaderboardLocation, bool verbose) const {

	// Otwórz strumień wyjściowy do ścieżki leaderboardLocation
	std::ofstream leaderboardFile;
	leaderboardFile.open(leaderboardLocation);
	for (int i = 0; i < (this->leaderboardName.size() & this->leaderboardWins.size()); i++) {
		// Zapisz dane z wektora w formacie csv, spacja to separator
		leaderboardFile << this->leaderboardName[i] << " " << this->leaderboardWins[i] << "\n";
	}
	leaderboardFile.close();

	// Zmienna verbose decyduje o tym, czy chcemy wyświetlić komunikat o zapisie
	if (verbose) std::cout << "Zapisano dane do pliku " << leaderboardLocation << ".\n";

}

unsigned int Game::updateLeaderboards(std::string winnerNickname) {

	bool updated = false;
	unsigned int totalWins = 0;

	for (int i = 0; i < (this->leaderboardName.size() & this->leaderboardWins.size()); i++) {

		// Szukaj nick we wczytanych danych z pliku CSV
		if (this->leaderboardName[i] == winnerNickname) {

			totalWins = ++this->leaderboardWins[i];
			updated = true;
			break;

		}

	}

	if (!updated) {
		// Jeżeli to pierwsza wygrana tego gracza, to dodaj go do wektora
		this->leaderboardName.push_back(winnerNickname);
		this->leaderboardWins.push_back(1);
		totalWins = 1;
	}

	// Zapisz po cichu zmiany do obu plików CSV
	this->dumpLeaderboards(LEADERBOARD_FILE, false);
	this->dumpLeaderboards(std::string(".") + LEADERBOARD_FILE + std::string(".old"), false);

	return totalWins;
}

void Game::printLeaderboards() const {

	// Zamienia dwa wektory w parę wektorów
	// https://stackoverflow.com/a/18479184
	std::vector<std::pair<std::string, int>> vectorPair(this->leaderboardName.size());
	for (unsigned int i = 0; i < vectorPair.size(); i++) {
		vectorPair[i] = std::make_pair(this->leaderboardName[i], this->leaderboardWins[i]);
	}

	// Sortuje malejąco parę wektorów biorąc pod uwagę wartości w tym drugim
	// https://stackoverflow.com/a/279878
	std::sort(vectorPair.begin(), vectorPair.end(), [](auto &left, auto &right) {
		return left.second > right.second;
	});

	for (const auto& [name, wins]: vectorPair) {
		std::cout << "- " << name << ", " << wins << " wygranych\n";
	}

}

void Game::playStartTune() {

	// Jeżli plik welcome_alt.ogg istnieje, załaduj go, a potem odtwórz.
	if (!this->soundBuffer1.openFromFile("res/audio/welcome_alt.ogg"))
		return;

	// Aby dało się usłyszeć dźwięk, nie może on wyjść poza zakres (out of scope).
	// Z tego powodu zapisujemy odnośnik do bufora w pamięci programu (w klasie Game).
	this->soundBuffer1.play();

}

void Game::initVariables() {

	// Ustaw wartości na domyślne.
	this->leaderboardLocation = LEADERBOARD_FILE;

	// Spróbuj wczytać listę wyników z pliku
	this->readLeaderboards(leaderboardLocation);
	// Zapisz po cichu (stąd false) kopię zapasową listy wyników
	this->dumpLeaderboards("." + leaderboardLocation + ".old", false);

}

Game::Game() {

	// Konstruktor klasy. Klasę Game tworzymy poprzez
	// ustawienie domyślnych wartości i stworzenie okna.

	// W Windows Terminal na Windowsie, oraz większości terminali Linuxowych
	// ta sekwencja ustawia tytuł okna/zakładki terminala.
	std::cout << "\033]0;" << "Chinczyk188" 
	#ifdef DEBUG
			  << " DEBUG"
	#endif
			  << "\007" << "\n";

	this->initVariables();
	this->board.initVariables();
	this->board.initWindow();
	this->board.initBoard();
	this->board.initView(); // widok musi być zainicjalizowany po oknie

	// Puść melodyjkę
	this->playStartTune();

	// Wyświetl witający tekst
	this->printGameWelcomeText();

}

Game::~Game() {

	// W destruktorze zapisujemy dane i usuwamy okno.
	this->dumpLeaderboards();

}

const bool Game::running() const {

	// Akcesor dla isOpen()
	return this->board.running();

}

void Game::run() {

	Engine engine;
	board.updateAndRender();

	int numPlayers = 2;
	std::cout << "Podaj prosze liczbe graczy (2-4):\n> ";
	numPlayers = stoi(removeWhitespace(board.asyncStrGetline()));
	std::cout << "\n";

	if (numPlayers < 2) numPlayers = 2;
	if (numPlayers > 4) numPlayers = 4;

	const char* colorNames[] = {"czerwony", "niebieski", "zolty", "zielony"};

	// Uzyskaj dane o użytkownikach
	for (int i = 0; i < numPlayers; i++) {

		std::string name;
		std::string str_seed = std::to_string(std::time(nullptr)); // czas

		std::cout << "Wpisz nazwe gracza " << (i + 1) << " (" << colorNames[i] << "):  ";
		name = board.asyncStrGetline();
		name = removeWhitespace(name);

		#ifndef CHINCZYK188_IGNORE_USER_SEED
			// Jeżeli nie została zdefiniowana flaga do ignorowania ziarna użytkownika
			// (deterministyczne losowanie), to pozwól na wprowadzanie ziaren.
			std::cout << "Wpisz ziarno gracza " << (i + 1) << " (" << colorNames[i] << "): ";
			str_seed = board.asyncStrGetline();
		#endif
		std::cout << "\n";

		short color       = static_cast<short>(i);
		unsigned int seed = std::hash<std::string_view>{}(str_seed);
		engine.addPlayer(name, seed, color);

		board.updateAndRender();

	}

	// Przekaż obiekt board do silnika gry
	engine.startGame(this->board);

	// Główna pętla gry
	while (board.running()) {

		board.updateAndRender();
		// this->update(); // do threadów
		//this->render();

		if (!engine.isGameOver()) {
			engine.nextTurn();
		} else {

			// updateLeaderboard() inkrementuje i zwraca liczbę wygranych przekazanego gracza
			std::cout << "To jego(/jej) " 
					  << updateLeaderboards(engine.winnerNickname) << ". wygrana!\n";

			std::cout << "--------------------\n\n"
					  << "Statystyki:\n";
			this->printLeaderboards();
			std::cout << "\n";

			// Zapisz do pliku CSV
			this->dumpLeaderboards(this->leaderboardLocation);
			// this->dumpLeaderboards("." + this->leaderboardLocation + ".old", false);

			this->board.smartSleep(20000);

			board.closeWindow();

		}

	}
}