/**
 *  Triangles
 *  Copyright (C) 2016 POSITIVE MENTAL ATTITUDE
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, version 3 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <sstream>
#include <thread>
#include <fstream>
#include <iomanip>
#include "Triangles.hpp"
#include "IngameState.hpp"
#include "MenuState.hpp"
#include "Utility.hpp"

Triangles::Triangles(int argc, char** argv):
	_returnCode(0),
	_window("Triangles", sf::Vector2u(640u, 480u)),
	_current(nullptr),
	_initialState(State::Menu)
{
	_variables.fullscreen = false;
	_variables.borderless = false;
	_variables.vsync = false;
	_variables.running = true;
	_variables.needsUpdate = false;
	_variables.shaders = true;
	_variables.window = &_window; 
	_variables.assets = &_assets;
	_variables.core = this;
	_variables.foreground = nullptr;
	
	passArguments(argc, argv);
	
	if(_variables.running)
	{
		init();
		refresh();
		load(_initialState);
	}
}

void Triangles::init()
{
	_context = _variables;
	_window.setContext(&_context);
	_window.recreate();

	_fps.setFont(_assets.loadFont("data/ttf/canonical/Ubuntu-L.ttf"));
	_fps.setCharacterSize(12);
	_fps.setColor(sf::Color::Black);
	_fps.setPosition(10, 10);
	_fps.setString("TRIANGLES");
	_fpsRect.setPosition(0, 0);
	_fpsRect.setSize(sf::Vector2f(375.f, 60.f));
	_fpsRect.setFillColor(sf::Color(255, 255, 255, 120));
	
	_loadingText.setString("Loading");
	_loadingHint.setFont(_assets.loadFont("data/ttf/canonical/Ubuntu-L.ttf"));
	_loadingText.setFont(_assets.loadFont("data/ttf/canonical/Ubuntu-L.ttf"));
	_loadingHint.setColor(sf::Color::White);
	_loadingText.setColor(sf::Color::White);
	_loadingTriangle.setColor(sf::Color::White);
	
	std::ifstream ifs;
	ifs.open("data/hints.txt");
	while(ifs.good())
	{
		std::string s;
		std::getline(ifs, s);
		if(s.size() > 1)
			_loadingHints.push_back(sf::String(s));
	}
	ifs.close();
		
	if(_loadingHints.empty())
		_loadingHints.push_back(sf::String(""));
}

void Triangles::passArguments(int argc, char** argv)
{
	std::string argvStr[argc];
	for(int i = 0; i < argc; ++i)
	{
		argvStr[i] = argv[i];
		if(argvStr[i].size() < 2)
			argvStr[i] = "--kaczka";
	}
	searchArgument(argc, argvStr, 'f', "fullscreen", [this]()
	{
		_variables.fullscreen = true;
	});
	searchArgument(argc, argvStr, 'w', "window", [this]()
	{
		_variables.fullscreen = false;
	});
	searchArgument(argc, argvStr, 'b', "borderlesswindow", [this]()
	{
		_variables.borderless = true;
	});
	searchArgument(argc, argvStr, 'v', "vsync", [this]()
	{
		_variables.vsync = true;
	});
	searchArgument(argc, argvStr, 's', "skip", [this]()
	{
		_initialState = State::Ingame;
	});
	searchArgument(argc, argvStr, 'r', "resolution", [this](std::string param)
	{
		if(param == "640x480")
		{
			std::cerr << "What year is it?\n";
			_returnCode = 1;
			_variables.running = false;
		}
		std::size_t found = param.rfind('x');
		if(found == std::string::npos)
			return;
		
		std::string sub = param;
		std::string sup = param;

		sub.erase(sub.begin() + found, sub.end());
		sup.erase(sup.begin(), sup.begin() + found + 1);
		
		unsigned w, h;
		
		std::stringstream ss;
		ss << sub;
		ss >> w;
		ss.str(std::string());
		ss.clear();
		ss << sup;
		ss >> h;
		
		_window.setSize(w, h);
	});
	searchArgument(argc, argvStr, 'h', "help", [this]()
	{
		std::cout << "triangles: triangles [OPTION]..." << std::endl;
		std::cout << "A pseudogame not meant to be run by sane people." << std::endl << std::endl;
		std::cout << "Copyright (C) 2016 POSITIVE MENTAL ATTITUDE" << std::endl;
		std::cout << "This program comes with ABSOLUTELY NO WARRANTY;" << std::endl;
		std::cout << "This is free software, and you are welcome to redistribute it" << std::endl;
		std::cout << "under certain conditions; see LICENSE.md" << std::endl << std::endl;
		std::cout << std::setw(8) << "-f" << " or " << std::setw(16) << "--fullscreen" << "    prevents from playing in windowed mode; try F11" << std::endl;
		std::cout << std::setw(8) << "-w" << " or " << std::setw(16) << "--window" << "    prevents from playing in fullscreen mode; try F11" << std::endl;
		std::cout << std::setw(8) << "-v" << " or " << std::setw(16) << "--vsync" << "    prevents from playing without vertical synchronization; try F10" << std::endl;
		std::cout << std::setw(8) << "-s" << " or " << std::setw(16) << "--skip" << "    prevents from going to the main menu; try Escape" << std::endl;
		std::cout << std::setw(8) << "-h" << " or " << std::setw(16) << "--help" << "    prevents from running the actual game" << std::endl;
		std::cout << std::setw(8) << "-r WxH" << " or " << std::setw(16) << "--resolution=WxH" << "    prevents from running at 640x480" << std::endl;
		_variables.running = false;
	});
}

void Triangles::searchArgument(int argc, std::string argvStr[], char callShort, std::string callLong, std::function<void()> lambda)
{
	for(int i = 1; i < argc; ++i)
	{
		if((argvStr[i][0] == '-' and argvStr[i][1] == callShort) or argvStr[i] == "--" + callLong)
		{
			lambda();
			break;
		}
	}
}
void Triangles::searchArgument(int argc, std::string argvStr[], char callShort, std::string callLong, std::function<void(std::string param)> lambda)
{
	for(int i = 1; i < argc; ++i)
	{
		if(argvStr[i][0] == '-' and argvStr[i][1] == callShort and i < argc - 1)
		{
			lambda(argvStr[i + 1]);
			break;
		}
		
		std::size_t found = argvStr[i].rfind('=');
		if(found == std::string::npos)
			continue;
		
		std::string sub = argvStr[i];
		std::string sup = argvStr[i];

		sub.erase(sub.begin() + found, sub.end());
		sup.erase(sup.begin(), sup.begin() + found + 1);
		
		if(sub == "--" + callLong)
		{
			lambda(sup);
			break;
		}
	}
}

void Triangles::refresh()
{
	float u = _window.getSize().x >= 1024 ? 1.f : 0.75f;
	_loadingHint.setCharacterSize(20 * u);
	_loadingText.setCharacterSize(20 * u);
	_loadingTriangle.setSize(12.f * u);
	_loadingTriangle.setPosition(_window.getSize().x - 140.f, _window.getSize().y - (u == 1.f ? 45.f : 50.f));
	_loadingText.setPosition(_window.getSize().x - 120.f, _window.getSize().y - 60.f);
}

void Triangles::load(int stateType)
{
	Echo::debug("Deleting current state.");

	if(_current)
		delete _current;
		
	Echo::debug("Loading state ID: ", stateType);
	
	switch(stateType)
	{
		case State::Menu:
			_current = new MenuState;
			break;
		case State::Ingame:
			_current = new IngameState;
			break;
		default:
			return;
	}
	
	_current->setContext(&_context);
	
	_loadingHint.setString(_loadingHints[rand() % _loadingHints.size()]);
	_loadingHint.setPosition(_window.getSize().x / 2.f - _loadingHint.getLocalBounds().width / 2.f, _window.getSize().y - 60.f);
	
	_window.lockFramerate(true);
	_window.setActive(false);

	std::thread t1(&Triangles::loadingRender, this);
	std::thread t2(&State::init, _current);
	
	t1.join();
	t2.join();
	
	_window.lockFramerate(false);
	
	_current->refresh();
	
	Echo::debug("Done.");
}

int Triangles::run()
{
	sf::Clock clock;
	sf::Time delta, epsilon = sf::seconds(1.f / 30.f);
	
	if(_context.running)
	{
		Echo::out(Echo::Empty, "Triangles version -0.8.9");
		Echo::out(Echo::Empty, "Copyright (C) 2016 POSITIVE MENTAL ATTITUDE");
		Echo::out(Echo::Empty, "This program comes with ABSOLUTELY NO WARRANTY;");
		Echo::out(Echo::Empty, "This is free software, and you are welcome to redistribute it");
		Echo::out(Echo::Empty, "under certain conditions; see LICENSE file");
	}
	
	/**
	 *  Main loop
	 */
	while(_context.running)
	{
		if(_context.needsUpdate)
		{
			refresh();
			_current->refresh();
			_context.needsUpdate = false;
			_variables = _context;
		}
		if(_current->status() != State::Ongoing)
		{
			load(_current->status());
		}
		
		coreThink();
		coreInput();
		delta = clock.restart();
		fpsCalc(delta);

		while(delta > epsilon)
		{
			delta -= epsilon;
			coreUpdate(epsilon);
		}

		coreUpdate(delta);
		coreRender();
	}
	
	return _returnCode;
}

void Triangles::coreThink()
{
	sf::Event event;
	while(_window.pollEvent(event))
	{
		if(_current->status())
			_current->coreThink(event);
		_window.think(event);
		if(event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::F11)
			_context.fullscreen = !_context.fullscreen, _variables.fullscreen = _context.fullscreen, _window.recreate();
		if(event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::F10)
			_context.vsync = !_context.vsync, _variables.vsync = _context.vsync, _window.recreate();
	}
}

void Triangles::coreInput()
{
	_current->coreInput();
	_window.think();
}

void Triangles::fpsCalc(sf::Time delta)
{
	static sf::Clock clock;
	static float avg = 0.f, avgL = 0.f;
	static float min = 10000.f, max = 0.f;
	static float minL = 0.f, maxL = 0.f;
	static unsigned avgC = 0;

	float curr = 1.f / delta.asSeconds();
	if(avgC == 0)
	{
		avg = curr, avgC = 1;
		min = curr;
		max = curr;
	}
	else
	{
		avg = (avg * avgC + curr) / (avgC + 1);
		++avgC; // Called explicitly in order to avoid undefined behaviour.
		min = std::min(min, curr);
		max = std::max(max, curr);
	}
		
	if(clock.getElapsedTime() >= sf::seconds(1.f))
	{
		minL = min;
		maxL = max;
		avgL = avg;
		avgC = 0;
		clock.restart();
	}
		
	std::ostringstream oss;
	oss << "Build time: " << __DATE__ << ' ' << __TIME__ << '\n';
	oss << "Framerate: (min/max/avg/curr): " << minL << '/' << maxL << '/' << avgL << '/' << curr << '\n';
	oss << "Settings: " << (_variables.vsync ? "vsync " : "") << (_variables.fullscreen ? "fullscreen " : "") << (_variables.shaders ? "shaders " : "") << '\n';
	_fps.setString(oss.str());
}

void Triangles::coreUpdate(sf::Time delta)
{
	_current->coreUpdate(delta);
}

void Triangles::loadingRender()
{
	_window.setActive(true);
	while(!_current->status() && _context.running)
	{
		_loadingTriangle.rotate(8.f);
		coreThink();
		_window.clear();
		_window.draw(_loadingHint);
		_window.draw(_loadingText);
		_window.draw(_loadingTriangle);
		_window.render();
	}
}

void Triangles::coreRender()
{
	_window.clear();
	_current->coreRender(_variables.shaders);
	#ifdef DEBUG
	_window.draw(_fpsRect);
	_window.draw(_fps);
	#endif
	_window.render();
}