#include "IrcParser.hpp"
#include <cctype>

IrcParser::IrcParser(void) {}
IrcParser::IrcParser(const IrcParser &other) { (void)other; }
IrcParser &IrcParser::operator=(const IrcParser &other) { (void)other; return (*this); }
IrcParser::~IrcParser(void) {}

bool IrcParser::isSpace(char c)
{
	return (c == ' ' || c == '\t');
}

std::string IrcParser::trimLeft(const std::string &s)
{
	size_t i = 0;

	while (i < s.size() && isSpace(s[i]))
		i++;
	return (s.substr(i));
}

std::string IrcParser::trimRight(const std::string &s)
{
	size_t end = s.size();

	while (end > 0 && (s[end - 1] == '\r' || s[end - 1] == '\n' || isSpace(s[end - 1])))
		end--;
	return (s.substr(0, end));
}

std::string IrcParser::toUpper(const std::string &s)
{
	std::string out = s;

	for (size_t i = 0; i < out.size(); ++i)
		out[i] = static_cast<char>(std::toupper(static_cast<unsigned char>(out[i])));
	return (out);
}

bool IrcParser::isCommandChar(char c)
{
	return (std::isalpha(static_cast<unsigned char>(c)) || std::isdigit(static_cast<unsigned char>(c)));
}

bool IrcParser::isValidCommand(const std::string &command)
{
	if (command.empty())
		return (false);
	for (size_t i = 0; i < command.size(); ++i)
	{
		if (!isCommandChar(command[i]))
			return (false);
	}
	return (true);
}

/*
	IRC params rule:
	- parameters are separated by spaces
	- if a parameter starts with ':', it is the trailing parameter
	- the trailing parameter may contain spaces and is always the last param

	Example:
	PRIVMSG #chan :hello les gars
	params[0] = "#chan"
	params[1] = "hello les gars"
*/
void IrcParser::parseParams(const std::string &s, size_t pos, IrcMessage &msg)
{
	while (pos < s.size())
	{
		while (pos < s.size() && isSpace(s[pos]))
			pos++;
		if (pos >= s.size())
			break;

		if (s[pos] == ':')
		{
			msg.addParam(s.substr(pos + 1));
			break;
		}

		size_t start = pos;
		while (pos < s.size() && !isSpace(s[pos]))
			pos++;
		msg.addParam(s.substr(start, pos - start));
	}
}

IrcMessage IrcParser::parseLine(const std::string &line)
{
	IrcMessage	msg;
	std::string	s;
	size_t		pos;
	size_t		start;

	msg.setRaw(line);
	s = trimRight(trimLeft(line));

	if (s.empty())
	{
		msg.setError("empty line");
		return (msg);
	}

	/*
		Optional prefix:
		:nick!user@host COMMAND params...
		In ft_irc, clients usually do not send prefixes, but accepting it makes the parser robust.
	*/
	pos = 0;
	if (s[pos] == ':')
	{
		size_t prefixEnd = s.find(' ', pos);
		if (prefixEnd == std::string::npos)
		{
			msg.setError("prefix without command");
			return (msg);
		}
		msg.setPrefix(s.substr(1, prefixEnd - 1));
		pos = prefixEnd + 1;
		while (pos < s.size() && isSpace(s[pos]))
			pos++;
	}

	start = pos;
	while (pos < s.size() && !isSpace(s[pos]))
		pos++;

	msg.setCommand(toUpper(s.substr(start, pos - start)));

	if (!isValidCommand(msg.getCommand()))
	{
		msg.setError("invalid command");
		return (msg);
	}

	parseParams(s, pos, msg);
	msg.setValid(true);
	return (msg);
}
