// Local configuration
#include "config.h"

// Implementation
#include "Cookie.hpp"

// C++ Libraries
#include <ui-utilcpp/Text.hpp>

namespace UI {
namespace Util {
namespace Http {

// Cookie
Cookie::Cookie(std::string const & name, std::string const & value)
	:std::pair<std::string, std::string>(name, value)
{}

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


// Cookies
Cookies::Cookies(std::string const & line, std::string const & prefix)
{
	addLine(line, prefix);
}

Cookies::Cookies(HeaderField const & field, std::string const & prefix)
{
	if (field.getName() != "Cookie")
	{
		UI_THROW("Not a \"Cookie\" header field: " + field.getName());
	}
	addLine(field.getValue(), prefix);
}

Cookies::Cookies(Header const & header, std::string const & prefix)
{
	add(header, prefix);
}

Cookies & Cookies::add(std::string const & name, std::string const & value)
{
	push_back(Cookie(name, value));
	return *this;
}

Cookies & Cookies::addLine(std::string const & line, std::string const & prefix)
{
	std::string::size_type const size(line.size());
	if (size)
	{
		std::string::size_type p0(0);
		while (p0 != std::string::npos)
		{
			std::string::size_type const p1(line.find_first_of('=', p0));
			if (p1 == std::string::npos)
			{
				break;  // no name=value pair left
			}

			std::string::size_type p2(line.find_first_of(';', p1));
			p2 = p2 == std::string::npos ? size : p2;

			// accept only cookie names and values not containing " ,;="
			std::string const name(line.substr(p0, p1-p0));
			std::string const value(line.substr(p1+1, p2-p1-1));
			if (name.find_first_of(" ,;")  == std::string::npos &&
			    value.find_first_of(" ,=") == std::string::npos)
			{
				if (prefix.empty())
				{
					push_back(Cookie(name, value));
				}
				else if (name.compare(0, prefix.size(), prefix) == 0)
				{
					push_back(Cookie(name.substr(prefix.size()), value));
				}
			}

			p0 = p2 == size ? std::string::npos : line.find_first_not_of(" \t", p2+1);
		}
	}
	return *this;
}

Cookies & Cookies::add(Header const & header, std::string const & prefix)
{
	return addLine(header.get("Cookie", false), prefix);
}


Cookies::const_iterator Cookies::find(std::string const & name) const
{
	const_iterator i;
	for (i = begin(); i != end() && i->getName() != name; ++i) {}
	return i;
}

bool Cookies::exists(std::string const & name) const
{
	return find(name) != end();
}

std::string const & Cookies::get(std::string const & name, bool doThrow) const
{
	const_iterator i(find(name));
	if (i == end() && doThrow)
	{
		UI_THROW("Cookie not found: " + name);
	}
	return (i == end() ? EmptyString_ : i->getValue());
}

std::string Cookies::getLine(std::string const & prefix) const
{
	std::string result;
	for (const_iterator i(begin()); i != end(); ++i)
	{
		result += prefix + i->getName() + "=" + i->getValue() + ((i+1) == end() ? "" : "; ");
	}
	return result;
}


// SetCookie
SetCookie::SetCookie(HeaderField const & field)
{
	if (field.getName() != "SetCookie")
	{
		UI_THROW("Not a \"SetCookie\" header field: " + field.getName());
	}
	setLine(field.getValue());
}

SetCookie::SetCookie(std::string const & name, std::string const & value)
{
	setName(name);
	setValue(value);
	setExpires("");
	setDomain("");
	setPath("");
	setSecure(false);
}

SetCookie & SetCookie::setName(std::string const & name)
{
	if (!name.size())
	{
		UI_THROW("Empty name for SetCookie");
	}
	name_ = name;
	return *this;
}

SetCookie & SetCookie::setValue(std::string const & value)
{
	value_ = value;
	return *this;
}

SetCookie & SetCookie::setExpires(std::string const & expires)
{
	expires_ = expires;
	return *this;
}

SetCookie & SetCookie::setExpires(time_t const timestamp)
{
	char buf[30];
	struct tm t;
	gmtime_r(&timestamp, &t);
	strftime(buf, 30, "%a, %d-%b-%Y %H:%M:%S %Z", &t);
	expires_ = buf;
	return *this;
}

SetCookie & SetCookie::setPath(std::string const & path)
{
	path_ = path;
	return *this;
}

SetCookie & SetCookie::setDomain(std::string const & domain)
{
	domain_ = domain;
	return *this;
}

SetCookie & SetCookie::setSecure(bool const & secure)
{
	secure_ = secure;
	return *this;
}

SetCookie & SetCookie::setLine(std::string const & line)
{
	// Defaults for new values
	std::string newName("");
	std::string newValue("");
	std::string newExpires("");
	std::string newPath("");
	std::string newDomain("");
	bool newSecure(false);

	std::string::size_type const size(line.size());
	if (size)
	{
		std::string::size_type p0(0);
		while (p0 != std::string::npos)
		{
			std::string::size_type p1(line.find_first_of("=;", p0));
			p1 = p1 == std::string::npos ? size : p1;

			std::string const name(line.substr(p0, p1-p0));
			if (name.find_first_of(" ,") != std::string::npos)
			{
				// don't care for names with evil characters in them
				p0 = line.find_first_of(';', p1);
				continue;
			}

			std::string::size_type p2(p1);
			if (p1 != size && line.at(p1) == '=')
			{
				p2 = line.find_first_of(';', p1);
				p2 = p2 == std::string::npos ? size : p2;

				std::string const value(line.substr(p1+1, p2-p1-1));
				if      (name == "expires") newExpires = value;
				else if (name == "domain")  newDomain  = value;
				else if (name == "path")    newPath    = value;
				else if (value.find(" ,") == std::string::npos)
				{
					newName  = name;
					newValue = value;
				}
			}
			else if (name == "secure")
			{
				newSecure = true;
			}

			p0 = p2 == size ? std::string::npos : line.find_first_not_of(" \t", p2+1);
		}
	}

	// Set new values
	setName(newName);
	setValue(newValue);
	setExpires(newExpires);
	setPath(newPath);
	setDomain(newDomain);
	setSecure(newSecure);

	return *this;
}

std::string const & SetCookie::getName()    const { return name_; }
std::string const & SetCookie::getValue()   const { return value_; }
std::string const & SetCookie::getExpires() const { return expires_; }
std::string const & SetCookie::getPath()    const { return path_; }
std::string const & SetCookie::getDomain()  const { return domain_; }

void SetCookie::addTok(std::string & line, std::string const & name, std::string const & value) const
{
	line += "; " + name + "=" + value;
}

void SetCookie::addTokDef(std::string & line, std::string const & name, std::string const & value, std::string const & defaultValue) const
{
	if      (value.size())        { addTok(line, name, value); }
	else if (defaultValue.size()) { addTok(line, name, defaultValue); }
}

std::string SetCookie::getLine(std::string const & prefix,
                               std::string const & expiresDefault,
                               std::string const & pathDefault,
                               std::string const & domainDefault) const
{
	std::string result(prefix + name_ + "=" + value_);
	addTokDef(result, "expires", expires_, expiresDefault);
	addTokDef(result, "path",    path_,    pathDefault);
	addTokDef(result, "domain",  domain_,  domainDefault);
	if (secure_) { result += "; secure"; };
	return result;
}


// SetCookies
SetCookies::SetCookies()
{}

SetCookies::SetCookies(Header const & header)
{
	for (Header::const_iterator i(header.begin()); i != header.end(); ++i)
	{
		if (i->getName() == "Set-Cookie")
		{
			push_back(SetCookie(*i));
		}
	}
}

SetCookie & SetCookies::add(std::string const & name, std::string const & value)
{
	push_back(SetCookie(name, value));
	return back();
}

SetCookies::const_iterator SetCookies::find(std::string const & name) const
{
	const_iterator i;
	for (i = begin(); i != end() && (*i).getName() != name; ++i) {}
	return i;
}

bool SetCookies::exists(std::string const & name) const
{
	return find(name) != end();
}

SetCookie const & SetCookies::get(std::string const & name) const
{
	const_iterator i(find(name));
	if (i == end())
	{
		UI_THROW("SetCookie not found : " + name);
	}
	return *i;
}

Header SetCookies::getHeader(std::string const & prefix,
                             std::string const & expiresDefault,
                             std::string const & pathDefault,
                             std::string const & domainDefault) const
{
	Header result;
	for (const_iterator i(begin()); i != end(); ++i)
	{
		result.add("Set-Cookie", i->getLine(prefix, expiresDefault, pathDefault, domainDefault));
	}
	return result;
}

}}}
