#pragma warning(push)
#pragma warning(disable: 4365)
#include <vector>
#include <string>
#include <utility>
#pragma warning(pop)

using std::string;
using std::vector;
using std::pair;

enum class TokenType {
	number,
	name,
	add, substract, multiply, divide, modulo,
	factorial,
	leftParenthesis, rightParenthesis,
	equal,
	statementEnding,
	quit,
	let,
	bad,
	empty
};

class Token {
public:
	Token(TokenType type);
	Token(TokenType type, string value);

	double toNumeric();
	TokenType getType() const;
	string getValue() const;

	static bool hasCharacterValue(TokenType type);
	static string tokenTypeToValue(TokenType type);
	static TokenType characterToTokenType(char ch);
	static TokenType stringToTokenType(const string& str);

private:
	TokenType type;
	string value;
	static const vector<pair<TokenType, char>> tokenTypeToCharacter_pairs;
	static const vector<pair<TokenType, string>> tokenTypeToString_pairs;
};

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

//#pragma warning(push)
//#pragma warning(disable: 4365)
#include <string>
#include <stdexcept>
#include <cmath>
#include <utility>
#include <algorithm>
#include <climits>
//#pragma warning(pop)

using std::domain_error;
using std::find;
using std::find_if;
using std::string;
using std::stod;
using std::numeric_limits;

Token::Token(TokenType type)
	: type{ type } {
	value = tokenTypeToValue(type);
}

Token::Token(TokenType type, string value)
	: type{ type }, value{ value } {}

double Token::toNumeric() try {
	return stod(value);
}
catch (const std::exception&) {
	throw domain_error("\"" + value + "\" is not a real number record");
}

TokenType Token::getType() const {
	return type;
}

string Token::getValue() const {
	return value;
}

const vector<pair<TokenType, char>> Token::tokenTypeToCharacter_pairs = {
		{TokenType::add, '+'},
		{TokenType::substract, '-'},
		{TokenType::multiply, '*'},
		{TokenType::divide, '/'},
		{TokenType::modulo, '%'},
		{TokenType::factorial, '!'},
		{TokenType::leftParenthesis, '('},
		{TokenType::rightParenthesis, ')'},
		{TokenType::equal, '='},
		{TokenType::add, '+'},
		{TokenType::statementEnding, '='}
};

const vector<pair<TokenType, string>> Token::tokenTypeToString_pairs = {
	{TokenType::quit, "quit"},
	{TokenType::let, "let"},
	{TokenType::empty, "empty!"}
};

bool Token::hasCharacterValue(TokenType type) {
	auto searchAmongCharactersPredicate = [type](const pair<TokenType, char>& p) -> bool {
		return p.first == type;
	};
	auto itChar = find_if(tokenTypeToCharacter_pairs.begin(), tokenTypeToCharacter_pairs.end(), searchAmongCharactersPredicate);
	return itChar != tokenTypeToCharacter_pairs.end();
}

string Token::tokenTypeToValue(TokenType type) {
	auto searchAmongCharactersPredicate = [type](const pair<TokenType, char>& pair) -> bool {
		return pair.first == type;
	};
	auto itChar = find_if(tokenTypeToCharacter_pairs.begin(), tokenTypeToCharacter_pairs.end(), searchAmongCharactersPredicate);
	if (itChar != tokenTypeToCharacter_pairs.end()) {
		return string() + itChar->second;
	}

	auto searchAmongStringsPredicate = [type](const pair<TokenType, string>& pair) -> bool {
		return pair.first == type;
	};
	auto itString = find_if(tokenTypeToString_pairs.begin(), tokenTypeToString_pairs.end(), searchAmongStringsPredicate);
	if (itString != tokenTypeToString_pairs.end()) {
		return itString->second;
	}
	throw domain_error("given token type has no definite value");
}

TokenType Token::characterToTokenType(char ch) {
	auto searchPredicate =
		[ch](const pair<TokenType, char>& pair) -> bool {
		return pair.second == ch;
	};
	auto it = find_if(tokenTypeToCharacter_pairs.begin(), tokenTypeToCharacter_pairs.end(), searchPredicate);
	if (it == tokenTypeToCharacter_pairs.end()) {
		throw domain_error(string("there is not a token type that represents the character \"") + ch + "\"");
	}
	else {
		return it->first;
	}
}

TokenType Token::stringToTokenType(const string& str) {
	auto searchPredicate =
		[str](const pair<TokenType, string>& p) -> bool {
		return p.second == str;
	};
	auto it = find_if(tokenTypeToString_pairs.begin(), tokenTypeToString_pairs.end(), searchPredicate);
	if (it == tokenTypeToString_pairs.end()) {
		throw domain_error(string("there is not a token type that represents the string \"") + str + "\"");
	}
	else {
		return it->first;
	}
}

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

#include <iostream>

int main()
{
	auto result =  Token::tokenTypeToValue(TokenType::empty);
    std::cout << result; 
}