#include "animone/player.h"
#include "animone/util.h"

#include <map>
#include <sstream>
#include <string>
#include <vector>
#include <optional>

#include <iostream>

namespace animone {

namespace internal::parser {

struct State {
	enum class Name {
		ExpectPlayerName,
		ExpectSection,
		ExpectWindowPlatform,
		ExpectExecutablePlatform,
		ExpectWindow,
		ExpectExecutable,
		ExpectStrategy,
		ExpectType,
		ExpectWindowTitle,
	};

	Name state = Name::ExpectPlayerName;
	WindowPlatform window_platform = WindowPlatform::Unknown;
	ExecutablePlatform executable_platform = ExecutablePlatform::Unknown;
};

size_t GetIndentation(const std::string& line) {
	return line.find_first_not_of('\t');
}

bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) {
	// Each state has a definitive expected indentation
	const auto expected = [](const State::Name& state) -> size_t {
		switch (state) {
			default:
			case State::Name::ExpectPlayerName: return 0;
			case State::Name::ExpectSection: return 1;
			case State::Name::ExpectWindowPlatform:
			case State::Name::ExpectExecutablePlatform:
			case State::Name::ExpectStrategy:
			case State::Name::ExpectType: return 2;
			case State::Name::ExpectWindow:
			case State::Name::ExpectExecutable:
			case State::Name::ExpectWindowTitle: return 3;
		}
	}(state.state);

	if (current > expected) {
		std::cerr << "animone: excessive indentation found" << std::endl;
		return false; // Disallow excessive indentation
	}

	if (current < expected) {
		const std::optional<State::Name> st = [current, state]() -> std::optional<State::Name> {
			switch (current) {
				case 0: return State::Name::ExpectPlayerName;
				default:
				case 1: return State::Name::ExpectSection;
				case 2:
					switch (state.state) {
						case State::Name::ExpectWindow: return State::Name::ExpectWindowPlatform;
						case State::Name::ExpectExecutable: return State::Name::ExpectExecutablePlatform;
						default: return std::nullopt;
					}
			}
		}();
		if (!st.has_value())
			return false;

		switch (state.state) {
			case State::Name::ExpectWindow:
				if (players.back().windows.empty())
					return false;
				state.state = st.value();
				break;
			case State::Name::ExpectExecutable:
				if (players.back().executables.empty())
					return false;
				state.state = st.value();
				break;
			case State::Name::ExpectStrategy:
				if (players.back().strategies.empty())
					return false;
				state.state = st.value();
				break;
			case State::Name::ExpectType:
				state.state = st.value();
				break;
			case State::Name::ExpectWindowTitle:
				return false;
		}
	}

	return true;
}

bool HandleState(std::string& line, std::vector<Player>& players, State& state) {
	switch (state.state) {
		case State::Name::ExpectPlayerName:
			players.push_back(Player());
			players.back().name = line;
			state.state = State::Name::ExpectSection;
			break;

		case State::Name::ExpectSection: {
			static const std::map<std::string, State::Name> sections = {
			    {"windows",     State::Name::ExpectWindowPlatform},
			    {"executables", State::Name::ExpectExecutablePlatform},
			    {"strategies",  State::Name::ExpectStrategy  },
			    {"type",        State::Name::ExpectType      },
			};
			util::TrimRight(line, ":");
			const auto it = sections.find(line);
			if (it == sections.end())
				return false;
			state.state = it->second;
			break;
		}

		case State::Name::ExpectWindowPlatform: {
			static const std::map<std::string, WindowPlatform> platforms = {
				{"quartz", WindowPlatform::Quartz},
				{"win32",  WindowPlatform::Win32},
				{"x11",    WindowPlatform::X11},
			};
			util::TrimRight(line, ":");
			const auto it = platforms.find(line);
			if (it == platforms.end())
				return false;
			state.state = State::Name::ExpectWindow;
			state.window_platform = it->second;
			break;
		}

		case State::Name::ExpectExecutablePlatform: {
			static const std::map<std::string, ExecutablePlatform> platforms = {
				{"posix",   ExecutablePlatform::Posix},
				{"win32",   ExecutablePlatform::Win32},
				{"macosx",  ExecutablePlatform::Xnu},
			};
			util::TrimRight(line, ":");
			const auto it = platforms.find(line);
			if (it == platforms.end())
				return false;
			state.state = State::Name::ExpectExecutable;
			state.executable_platform = it->second;
			break;
		}

		case State::Name::ExpectWindow: players.back().windows[state.window_platform].push_back(line); break;

		case State::Name::ExpectExecutable: players.back().executables[state.executable_platform].push_back(line); break;

		case State::Name::ExpectStrategy: {
			static const std::map<std::string, Strategy> strategies = {
			    {"window_title",  Strategy::WindowTitle },
			    {"open_files",    Strategy::OpenFiles   },
			    {"ui_automation", Strategy::UiAutomation},
			};
			util::TrimRight(line, ":");
			const auto it = strategies.find(line);
			if (it == strategies.end())
				return false;
			const auto strategy = it->second;
			players.back().strategies.push_back(strategy);
			switch (strategy) {
				case Strategy::WindowTitle: state.state = State::Name::ExpectWindowTitle; break;
			}
			break;
		}

		case State::Name::ExpectType: {
			static const std::map<std::string, PlayerType> types = {
			    {"default",     PlayerType::Default   },
			    {"web_browser", PlayerType::WebBrowser},
			};
			const auto it = types.find(line);
			if (it == types.end())
				return false;
			players.back().type = it->second;
			break;
		}

		case State::Name::ExpectWindowTitle:
			players.back().window_title_format = line;
			state.state = State::Name::ExpectStrategy;
			break;
	}

	return true;
}

} // namespace internal::parser

////////////////////////////////////////////////////////////////////////////////

bool ParsePlayersData(const std::string& data, std::vector<Player>& players) {
	if (data.empty())
		return false;

	std::istringstream stream(data);
	std::string line;
	size_t indentation = 0;
	internal::parser::State state;

	int ln = 1;
	for (; std::getline(stream, line, '\n'); ln++) {
		if (line.empty())
			continue; // Ignore empty lines

		indentation = internal::parser::GetIndentation(line);

		internal::util::TrimLeft(line, "\t");
		internal::util::TrimRight(line, "\n\r");

		if (line.empty() || line.front() == '#')
			continue; // Ignore empty lines and comments

		if (!internal::parser::HandleIndentation(indentation, players, state)) {
			std::cerr << "animone: indentation: failed on line " << ln << std::endl;
			return false;
		}

		if (!internal::parser::HandleState(line, players, state)) {
			std::cerr << "animone: state: failed on line " << ln << std::endl;
			return false;
		}
	}

	return !players.empty();
}

bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) {
	std::string data;

	if (!internal::util::ReadFile(path, data))
		return false;

	return ParsePlayersData(data, players);
}

} // namespace animone
