#include "Board.hpp"
#include "Utils.hpp"
#include <iostream>
#include <string>
#include <thread>

void Board::initVariables() {

	// Ustaw wartości na domyślne.
	this->window = nullptr;

	// Wczytaj tło - bitmapę planszy.
	// loadFromFile() zwraca True w przypadku pomyślnego
	// wczytania tekstury.
	if (!this->boardTexture.loadFromFile("res/sprites/board2.png")) {

		// Jeśli nie uda nam się wczytać tekstury:
		std::cerr << "Uwaga: Nie udalo sie wczytac "
			"wymaganej tekstury planszy z \"res/sprites/board2.png\"!\n"
			"Upewnij sie, ze plik .exe jest we wlasciwym katalogu, "
			"a gra zostala w pelni wypakowana wraz z folderem \"res\".\n";

		this->smartSleep(10000);
		this->window->close();
	}

	// Podobnie, wczytaj czcionkę Ubuntu.
	if (!this->Ubuntu.loadFromFile("res/fonts/Ubuntu-Regular.ttf")) {

		// Jeśli nie uda nam się wczytać czcionki:
		std::cerr << "Uwaga: Nie udalo sie wczytac "
			"wymaganej czcionki Ubuntu z \"res/fonts/Ubuntu.ttf\"!\n"
			"Upewnij sie, ze plik .exe jest we wlasciwym katalogu, "
			"a gra zostala w pelni wypakowana wraz z folderem \"res\".\n";

		this->smartSleep(10000);
		this->window->close();

	}

}

void Board::initWindow() {

	// Dwuwymiarowy, wektor typu (u)nsigned int.
	sf::Vector2u textureSize = this->boardTexture.getSize();

	// Aby okno zmieściło się na większości wyświetlaczy,
	// sprawmy, aby zajmowało ono około 550x550 pikseli,
	// czyli dokładnie połowę z rozdzielczości tekstury planszy.
	this->videoMode.width    = textureSize.x / 2;
	this->videoMode.height   = textureSize.y / 2;

	// Stwórz okno.
	this->window = new sf::RenderWindow(
		this->videoMode,
		"Okno planszy - Chinczyk188",
		sf::Style::Default);

	// Ustaw rozsądny limit FPS
	this->window->setFramerateLimit(120);

}

void Board::initBoard() {

	// Wczytaj wymaganą teksturę planszy
	this->boardSprite.setTexture(this->boardTexture);

	// Ustaw pozycję grafiki.
	this->boardSprite.setPosition(0.0f, 0.0f);
}

void Board::initView() {

	// Ze względu na to, że długość krawędzi okna jest dwa razy mniejsza
	// od tekstury planszy, powinniśmy to zrównoważyć powiększając
	// widok o tą samą wartość.
	sf::Vector2f viewSize(
		static_cast<float>(2 * this->videoMode.width),
		static_cast<float>(2 * this->videoMode.height)
	);

	this->view.setSize(viewSize);
	this->view.setCenter(viewSize.x / 2.0f, viewSize.y / 2.0f);

	// Ustawiamy widok na wyznaczone wartości.
	this->window->setView(this->view);

}

void Board::handleResize(unsigned int newWidth, unsigned int newHeight) {

	// Aby zapobiec sytuacji, w której po zmianie rozmiaru
	// okno byłoby większe od rozdzielczości ekranu,
	// niech krawędź okna wynosi minimum z nowej szerokości i wysokości,
	// zachowując przy tym proporcję 1:1 planszy.
	unsigned int newSize = std::min(newWidth, newHeight);
	this->window->setSize(sf::Vector2u(newSize, newSize));

}

bool Board::running() const {

	// Akcesor dla isOpen()
	return this->window->isOpen();

}

void Board::pollEvents() {

	while (this->window->pollEvent(this->ev)) {

		switch (this->ev.type) {

			case sf::Event::Closed:
				this->window->close();
				break;

			case sf::Event::Resized:
				this->handleResize(this->ev.size.width, this->ev.size.height);
				break;

			case sf::Event::KeyPressed:
				if (this->ev.key.code == sf::Keyboard::Escape)
					this->window->close();
				break;

		}
	}

}

void Board::update() {

	// Obecnie wrapper dla metody 
	// pollEvents(), nasłuchiwanie wydarzeń.
	this->pollEvents();

}

bool Board::manualUpdate() {

	this->update();
	this->render();

	bool retval = this->window->pollEvent(this->ev);
	this->lastEvent = this->ev;

	return retval;
}

void Board::render() {

	// Mechanizm renderowania

	// Czyszczenie ekranu
	this->window->clear(sf::Color::White);
	// Rysowanie tła
	this->window->draw(this->boardSprite);

	for (sf::CircleShape pawn: pieces) {
		this->window->draw(pawn);
	}

	for (sf::Text pawnText: piecesText) {
		this->window->draw(pawnText);
	}

	// Mechanizm wyświetlania
	// W celu wyświetlenia na ekranie wyniku renderowania
	this->window->display();

}

void Board::updateAndRender() {

	this->update();
	this->render();

}

void Board::smartSleep(int millis) {

	#ifdef DEBUG
		millis /= 10;
	#endif

	for (int i = 0; i < millis / 10; i++) {
		sf::sleep(sf::milliseconds(10));
		this->update();
		this->render();
	}
}

std::string Board::asyncStrCin() {

	bool done = false;
	std::string text;
	// std::thread t(&Board::_thread_wait_for_str_cin, Board(), std::ref(done), std::ref(text));
	std::thread t(_thread_wait_for_str_cin, std::ref(done), std::ref(text));
	
	while(t.joinable()) {
		this->update();
		this->render();
		if (done) t.join();
	}

	return text;

}

std::string Board::asyncStrGetline() {

	bool done = false;
	std::string text;
	// std::thread t(&Board::_thread_wait_for_str_getline, Board(), std::ref(done), std::ref(text));
	std::thread t(_thread_wait_for_str_getline, std::ref(done), std::ref(text));
	
	while(t.joinable()) {
		this->update();
		this->render();
		if (done) t.join();
	}

	return text;

}

void Board::selectAField(short &pickStatus, short &field) {

	sf::Vector2i mousePosition;
	sf::Vector2f mapped;


	while (this->manualUpdate() && this->running()) {

		sf::Event currentEvent = this->lastEvent;
		switch (currentEvent.type) {

			case sf::Event::Closed:
				this->closeWindow();
				// Zamknięcie okna powinno wyjść z tej pętli
				return;

			case sf::Event::MouseButtonPressed:
				// Informacje musimy pobrać przy naciśnięciu,
				// ponieważ użytkownik może zwolnić przycisk poza oknem gry.
				mousePosition = sf::Mouse::getPosition(*this->window);
				mapped = this->window->mapPixelToCoords(mousePosition, this->view);
				field = static_cast<short>(mapped.y / 100) * 11 + static_cast<short>(mapped.x / 100);
				pickStatus = 4;
				break;

			case sf::Event::MouseButtonReleased:
				if (pickStatus == 4) {
					mousePosition = sf::Mouse::getPosition(*this->window);
					mapped = this->window->mapPixelToCoords(mousePosition, this->view);
					if (mapped.x >= 0 && mapped.x <= 1100 && mapped.y >= 0 && mapped.y <= 1100) {
						// Wyznacz ponownie field, na wypadek jeśli użytkownik zmienił zdanie
						field = XYToField(mapped.x, mapped.y);
					}
					pickStatus = 8;
				}
				break;

			case sf::Event::Resized:
				#ifdef DEBUG
					std::cout << "Resized event triggered: " << currentEvent.size.width << " " << currentEvent.size.height << "\n";
				#endif
				this->handleResize(currentEvent.size.width, currentEvent.size.height);
				break;

		}
	}

}

/**
 * @brief      Draws a pawn (or, optionally, multiple).
 *
 * @param[in]  color    Pawn color
 * @param[in]  field    Field in fields matrix to draw in
 * @param[in]  howMany  (Optional, default: 1) Piece count overlay
 */
void Board::drawPawn(short color, short field, short howMany) {

	sf::CircleShape circle(40.f);
	cords3 outlineColor = color2RGBOutline(color);
	cords3 fillerColor  =  color2RGBFiller(color);
	cords2 XY = fieldToXY(field);
	circle.setOutlineColor(sf::Color(outlineColor.x - 20, outlineColor.y - 20, outlineColor.z - 20));
	circle.setFillColor(   sf::Color( fillerColor.x - 20,  fillerColor.y - 20,  fillerColor.z - 20));
	circle.setPosition(XY.x - 50 + 10, XY.y - 50 + 10);
	circle.setOutlineThickness(5);
	this->pieces.push_back(circle);

	if (howMany > 1) {
		sf::Text number(std::to_string(howMany), Ubuntu);
		number.setCharacterSize(48);
		number.setFillColor(sf::Color::White);
		number.setPosition(XY.x - 12, XY.y - 30); // wartości dobrane empirycznie :)
		number.setOutlineColor (sf::Color::Black);
		number.setOutlineThickness(2);
		this->piecesText.push_back(number);
	}

	return;

}

void Board::drawPawns() {
	this->pieces.clear();
	this->piecesText.clear();
	for (int i = 0; i < 121; i++) {
		// Wyznacz kolor i zlicz pionki
		short color = -1;
		short pawnsFound = 0;
		for (int j = 0; j < 4; j++) {
			if (fields[i][j] != 0) {
				color = fields[i][j] - 4;
				pawnsFound++;
			}
		}
		if (pawnsFound != 0) {
			this->drawPawn(color, i, pawnsFound);
		}
	}
}

void Board::closeWindow() {

	this->window->close();

}

void Board::run() {

}