//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2013 Vincent Richard <vincent@vmime.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 3 of
// the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library.  Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//

#include "vmime/relay.hpp"
#include "vmime/text.hpp"
#include "vmime/parserHelpers.hpp"
#include "vmime/utility/outputStreamAdapter.hpp"

#include <sstream>


namespace vmime
{


relay::relay()
{
}


relay::relay(const relay& r)
	: headerFieldValue()
{
	copyFrom(r);
}


/*

 RFC #2822:

     received    =  "Received"    ":"            ; one per relay
                       ["from" domain]           ; sending host
                       ["by"   domain]           ; receiving host
                       ["via"  atom]             ; physical path
                      *("with" atom)             ; link/mail protocol
                       ["id"   msg-id]           ; receiver msg id
                       ["for"  addr-spec]        ; initial form
*/

void relay::parseImpl
	(const parsingContext& ctx, const string& buffer, const size_t position,
	 const size_t end, size_t* newPosition)
{
	const char* const pend = buffer.data() + end;
	const char* const pstart = buffer.data() + position;
	const char* p = pend - 1;

	// Find the beginning of the date part
	while (p >= pstart && *p != ';')
		--p;

	if (p >= pstart)
	{
		// Parse the date/time part
		m_date.parse(ctx, buffer, position + (p - pstart) + 1, end);

		// Parse the components
		std::istringstream iss(string
			(buffer.begin() + position, buffer.begin() + position + (p - pstart)));

		string word;
		std::vector <string> previous;

		enum Parts
		{
			Part_None,
			Part_From,              // The "from" part
			Part_By,                // The "by" part
			Part_Via,               // The "via" part
			Part_With,              // One "with" part
			Part_Id,                // The "id" part
			Part_For,               // The "for" part
			Part_End
		};

		Parts part = Part_None;
		bool cont = true;
		bool inComment = false;

		while (cont)
		{
			Parts newPart = Part_None;

			if ((cont = !(iss >> word).fail()))
			{
				// A little hack for handling comments
				if (inComment)
				{
					size_t par = word.find(')');

					if (par != string::npos)
					{
						previous.push_back(string(word.begin(), word.begin() + par + 1));
						word.erase(word.begin(), word.begin() + par + 1);
						inComment = false;
					}
				}

				bool keyword = false;

				if (!inComment)
				{
					if (utility::stringUtils::isStringEqualNoCase(word, "from", 4))
					{
						newPart = Part_From;
						keyword = true;
					}
					else if (utility::stringUtils::isStringEqualNoCase(word, "by", 2))
					{
						newPart = Part_By;
						keyword = true;
					}
					else if (utility::stringUtils::isStringEqualNoCase(word, "via", 2))
					{
						newPart = Part_Via;
						keyword = true;
					}
					else if (utility::stringUtils::isStringEqualNoCase(word, "with", 2))
					{
						newPart = Part_With;
						keyword = true;
					}
					else if (utility::stringUtils::isStringEqualNoCase(word, "id", 2))
					{
						newPart = Part_Id;
						keyword = true;
					}
					else if (utility::stringUtils::isStringEqualNoCase(word, "for", 2))
					{
						newPart = Part_For;
						keyword = true;
					}
				}

				if (!keyword)
				{
					if (word.find('(') != string::npos)
						inComment = true;

					previous.push_back(word);
				}
			}

			if (!cont || newPart != Part_None)
			{
				if (part != Part_None)
				{
					std::ostringstream value;

					for (std::vector <string>::const_iterator
						it = previous.begin() ; it != previous.end() ; ++it)
					{
						if (it != previous.begin()) value << " ";
						value << *it;
					}

					switch (part)
					{
					case Part_From: m_from = value.str(); break;
					case Part_By: m_by = value.str(); break;
					case Part_Via: m_via = value.str(); break;
					case Part_With: m_with.push_back(value.str()); break;
					case Part_Id: m_id = value.str(); break;
					case Part_For: m_for = value.str(); break;
					default: break; // Should never happen...
					}
				}

				previous.clear();
				part = newPart;
			}
		}
	}

	setParsedBounds(position, end);

	if (newPosition)
		*newPosition = end;
}


void relay::generateImpl
	(const generationContext& ctx, utility::outputStream& os,
	 const size_t curLinePos, size_t* newLinePos) const
{
	std::ostringstream oss;
	int count = 0;

	if (m_from.length()) oss << (count++ > 0 ? " " : "") << "from " << m_from;
	if (m_by.length()) oss << (count++ > 0 ? " " : "") << "by " << m_by;
	if (m_via.length()) oss << (count++ > 0 ? " " : "") << "via " << m_via;

	for (std::vector <string>::const_iterator
	     it = m_with.begin() ; it != m_with.end() ; ++it)
	{
		oss << (count++ > 0 ? " " : "") << "with " << *it;
	}

	if (m_id.length()) oss << (count++ > 0 ? " " : "") << "id " << m_id;
	if (m_for.length()) oss << (count++ > 0 ? " " : "") << "for " << m_for;

	oss << "; ";

	vmime::utility::outputStreamAdapter dos(oss);
	m_date.generate(ctx, dos, 0, NULL);

	text(oss.str()).encodeAndFold(ctx, os,
		curLinePos, newLinePos, text::FORCE_NO_ENCODING);
}


void relay::copyFrom(const component& other)
{
	const relay& r = dynamic_cast <const relay&>(other);

	m_from = r.m_from;
	m_via = r.m_via;
	m_by = r.m_by;
	m_id = r.m_id;
	m_for = r.m_for;

	m_with.resize(r.m_with.size());
	std::copy(r.m_with.begin(), r.m_with.end(), m_with.begin());

	m_date = r.m_date;
}


relay& relay::operator=(const relay& other)
{
	copyFrom(other);
	return (*this);
}


shared_ptr <component> relay::clone() const
{
	return make_shared <relay>(*this);
}


const string& relay::getFrom() const
{
	return (m_from);
}


void relay::setFrom(const string& from)
{
	m_from = from;
}


const string& relay::getVia() const
{
	return (m_via);
}


void relay::setVia(const string& via)
{
	m_via = via;
}


const string& relay::getBy() const
{
	return (m_by);
}


void relay::setBy(const string& by)
{
	m_by = by;
}


const string& relay::getId() const
{
	return (m_id);
}


void relay::setId(const string& id)
{
	m_id = id;
}


const string& relay::getFor() const
{
	return (m_for);
}


void relay::setFor(const string& for_)
{
	m_for = for_;
}


const datetime& relay::getDate() const
{
	return (m_date);
}


void relay::setDate(const datetime& date)
{
	m_date = date;
}


const std::vector <string>& relay::getWithList() const
{
	return (m_with);
}


std::vector <string>& relay::getWithList()
{
	return (m_with);
}


const std::vector <shared_ptr <component> > relay::getChildComponents()
{
	// TODO: should fields inherit from 'component'? (using typeAdapter)
	return std::vector <shared_ptr <component> >();
}


} // vmime
