// Local configuration
#include "config.h"

// Implementation
#include "Header.hpp"

// STDC++
#include <sstream>

// Local
#include "Connection.hpp"

namespace UI {
namespace Util {
namespace Http {

Exception::Exception(std::string const & what, std::string const & debug)
	:Util::Exception("HTTP error: " + what, debug)
{}

bool verifyVersion(std::string const & version, bool const & doThrow)
{
	bool const result(isToken(version, "HTTP/1.1,HTTP/1.0"));
	if (!result && doThrow)
	{
		UI_THROW("Unsupported version: " + version);
	}
	return result;
}


// RequestLine
RequestLine::MethodMap RequestLine::generateMethodMap()
{
	MethodMap result;
	result[Options_] = "OPTIONS";
	result[Get_]     = "GET";
	result[Head_]    = "HEAD";
	result[Post_]    = "POST";
	result[Put_]     = "PUT";
	result[Delete_]  = "DELETE";
	result[Trace_]   = "TRACE";
	result[Connect_] = "CONNECT";
	return result;
}

RequestLine::MethodMap const RequestLine::MethodMap_(RequestLine::generateMethodMap());

RequestLine::Method RequestLine::str2Method(std::string const & method)
{
	for (MethodMap::const_iterator i(MethodMap_.begin()); i != MethodMap_.end(); ++i)
	{
		if (i->second == method) return i->first;
	}
	UI_THROW("Unsupported method: " + method);
}

RequestLine::RequestLine(Method const & method, std::string const & uri, std::string const & version)
{
	setMethod(method);
	setURI(uri);
	setVersion(version);
}

RequestLine::RequestLine(std::string const & line)
{
	std::vector<std::string> tokens(strtoks(line, " \t"));
	if (tokens.size() != 3)
	{
		UI_THROW("Parsing request line failed: " + line);
	}
	setMethod(tokens[0]);
	setURI(tokens[1]);
	setVersion(tokens[2]);
}

RequestLine & RequestLine::setMethod(Method const & method)
{
	method_ = method;
	return *this;
}

RequestLine & RequestLine::setMethod(std::string const & method)
{
	return setMethod(str2Method(method));
}

RequestLine & RequestLine::setURI(std::string const & uri)
{
	uri_ = uri;
	return *this;
}

RequestLine & RequestLine::setVersion(std::string const & version)
{
	verifyVersion(version);
	version_ = version;
	return *this;
}

RequestLine::Method const & RequestLine::getMethod()    const { return method_; }
std::string         const & RequestLine::getMethodStr() const { return MethodMap_.find(method_)->second; }
std::string         const & RequestLine::getURI()       const { return uri_; }
std::string         const & RequestLine::getVersion()   const { return version_; }

std::string RequestLine::get() const
{
	return getMethodStr() + " " + getURI() + " " + getVersion();
}


// StatusLine
StatusLine::StatusLine(Code const & code, std::string const & reason, std::string const & version)
{
	setCode(code);
	setReason(reason);
	setVersion(version);
}

StatusLine::StatusLine(std::string const & line)
{
	std::vector<std::string> tokens(strtoks(line, " \t"));
	if (tokens.size() < 2)
	{
		UI_THROW("Parsing status line failed: " + line);
	}
	setCode(ato<Code>(tokens[1]));
	setVersion(tokens[0]);

	std::string reason;
	for (unsigned int i(2); i < tokens.size(); ++i)
	{
		reason += (i == 2 ? "" : " ") + tokens[i];
	}
	setReason(reason);
}

StatusLine & StatusLine::setCode(Code const & code)
{
	if (code < 100 || code >= 600)
	{
		UI_THROW("Invalid status code: " + tos(code));
	}
	code_ = code;
	return *this;
}
StatusLine & StatusLine::setCode(std::string const & code)
{
	return setCode(ato<Code>(code));
}
StatusLine & StatusLine::setReason(std::string const & reason)
{
	reason_ = reason;
	return *this;
}
StatusLine & StatusLine::setVersion(std::string const & version)
{
	verifyVersion(version);
	version_ = version;
	return *this;
}

StatusLine::Code const & StatusLine::getCode()    const { return code_; }
std::string              StatusLine::getCodeStr() const { return tos(code_); }
std::string      const & StatusLine::getReason()  const { return reason_; }
std::string      const & StatusLine::getVersion() const { return version_; }

std::string StatusLine::get() const { return getVersion() + " " + getCodeStr() + " " + getReason(); }


// HeaderField
HeaderField::HeaderField(std::string const & name, std::string const & value)
	:std::pair<std::string, std::string>(name, value)
{
	str2Ascii(second);
}

HeaderField::HeaderField(std::string const & line)
{
	std::string::size_type const colonPos(line.find(':'));
	/** @todo: Is it ok to allow empty names? */
	if (colonPos == std::string::npos)
	{
		UI_THROW("Not a header field: " + line);
	}
	else
	{
		/** todo: What about spaces before colon (e.g. "Content-Length  : 34")? */
		first = line.substr(0, colonPos);

		std::string::size_type const valuePos(line.find_first_not_of(" \t", colonPos+1));
		second = valuePos == std::string::npos ? "" : line.substr(valuePos);
	}
	str2Ascii(second);
}

std::string const & HeaderField::getName()  const { return first; }
std::string const & HeaderField::getValue() const { return second; }

std::string HeaderField::get() const { return first + ": " + second; }


// Header
Header::const_iterator Header::find(std::string const & name) const
{
	const_iterator i;
	for (i = begin(); i != end() && (*i).getName() != name; ++i)
	{}
	return i;
}
bool Header::exists(std::string const & name) const
{
	return find(name) != end();
}
std::string const & Header::get(std::string const & name, bool doThrow) const
{
	const_iterator i(find(name));
	if (i == end() && doThrow)
	{
		UI_THROW("Header field not found: " + name);
	}
	return (i == end() ? EmptyString_ : i->getValue());
}

Header & Header::add(std::string const & key, std::string const & value)
{
	push_back(HeaderField(key, value));
	return *this;
}
Header & Header::add(std::string const & line)
{
	push_back(HeaderField(line));
	return *this;
}
Header Header::getMulti(std::string const & name) const
{
	Header result;
	for (Header::const_iterator i(begin()); i != end(); ++i)
	{
		if (i->getName() == name)
		{
			result.push_back(HeaderField(i->getName(), i->getValue()));
		}
	}
	return result;
}
Header Header::getPrefix(std::string const & prefix) const
{
	Header result;
	for (Header::const_iterator i(begin()); i != end(); ++i)
	{
		if (i->getName().compare(0, prefix.size(), prefix) == 0)
		{
			result.push_back(HeaderField(i->getName().substr(prefix.size()), i->getValue()));
		}
	}
	return result;
}

std::string Header::get() const
{
	std::stringstream s;
	Connection(s).write(*this);
	return s.str();
}

}}}
