//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002 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/config.hpp"


#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP


#include "vmime/net/imap/IMAPParser.hpp"
#include "vmime/net/imap/IMAPMessage.hpp"
#include "vmime/net/imap/IMAPFolder.hpp"
#include "vmime/net/imap/IMAPFolderStatus.hpp"
#include "vmime/net/imap/IMAPStore.hpp"
#include "vmime/net/imap/IMAPConnection.hpp"
#include "vmime/net/imap/IMAPUtils.hpp"
#include "vmime/net/imap/IMAPMessageStructure.hpp"
#include "vmime/net/imap/IMAPMessagePart.hpp"
#include "vmime/net/imap/IMAPMessagePartContentHandler.hpp"

#include "vmime/messageId.hpp"
#include "vmime/messageIdSequence.hpp"
#include "vmime/utility/outputStreamAdapter.hpp"

#include <sstream>
#include <iterator>
#include <typeinfo>


namespace vmime {
namespace net {
namespace imap {


#ifndef VMIME_BUILDING_DOC

//
// IMAPMessage_literalHandler
//

class IMAPMessage_literalHandler : public IMAPParser::literalHandler {

public:

	IMAPMessage_literalHandler(utility::outputStream& os, utility::progressListener* progress) {

		m_target = shared_ptr <target>(new targetStream(progress, os));
	}

	shared_ptr <target> targetFor(const IMAPParser::component& comp, const int /* data */) {

		if (typeid(comp) == typeid(IMAPParser::msg_att_item)) {

			const int type = static_cast
				<const IMAPParser::msg_att_item&>(comp).type;

			if (type == IMAPParser::msg_att_item::BODY_SECTION ||
			    type == IMAPParser::msg_att_item::RFC822_TEXT) {

				return m_target;
			}
		}

		return shared_ptr <target>();
	}

	shared_ptr <target> getTarget() {

		return m_target;
	}

private:

	shared_ptr <target> m_target;
};

#endif // VMIME_BUILDING_DOC



//
// IMAPMessage
//


IMAPMessage::IMAPMessage(
	const shared_ptr <IMAPFolder>& folder,
	const size_t num
)
	: m_folder(folder),
	  m_num(num),
	  m_size(-1U),
	  m_flags(FLAG_UNDEFINED),
	  m_expunged(false),
	  m_modseq(0),
	  m_structure(null) {

	folder->registerMessage(this);
}


IMAPMessage::IMAPMessage(
	const shared_ptr <IMAPFolder>& folder,
	const size_t num,
	const uid& uid
)
	: m_folder(folder),
	  m_num(num),
	  m_size(-1),
	  m_flags(FLAG_UNDEFINED),
	  m_expunged(false),
	  m_uid(uid),
	  m_modseq(0),
	  m_structure(null) {

	folder->registerMessage(this);
}


IMAPMessage::~IMAPMessage() {

	try {

		shared_ptr <IMAPFolder> folder = m_folder.lock();

		if (folder) {
			folder->unregisterMessage(this);
		}

	} catch (...) {

		// Don't throw in destructor
	}
}


void IMAPMessage::onFolderClosed() {

	m_folder.reset();
}


size_t IMAPMessage::getNumber() const {

	return m_num;
}


const message::uid IMAPMessage::getUID() const {

	return m_uid;
}


vmime_uint64 IMAPMessage::getModSequence() const {

	return m_modseq;
}


size_t IMAPMessage::getSize() const {

	if (m_size == -1U) {
		throw exceptions::unfetched_object();
	}

	return m_size;
}


bool IMAPMessage::isExpunged() const {

	return m_expunged;
}


int IMAPMessage::getFlags() const {

	if (m_flags == FLAG_UNDEFINED) {
		throw exceptions::unfetched_object();
	}

	return m_flags;
}


shared_ptr <const messageStructure> IMAPMessage::getStructure() const {

	if (m_structure == NULL) {
		throw exceptions::unfetched_object();
	}

	return m_structure;
}


shared_ptr <messageStructure> IMAPMessage::getStructure() {

	if (m_structure == NULL) {
		throw exceptions::unfetched_object();
	}

	return m_structure;
}


shared_ptr <const header> IMAPMessage::getHeader() const {

	if (m_header == NULL) {
		throw exceptions::unfetched_object();
	}

	return m_header;
}


void IMAPMessage::extract(
	utility::outputStream& os,
	utility::progressListener* progress,
    const size_t start,
    const size_t length,
    const bool peek
) const {

	shared_ptr <const IMAPFolder> folder = m_folder.lock();

	if (!folder) {
		throw exceptions::folder_not_found();
	}

	extractImpl(
		null, os, progress, start, length,
		EXTRACT_HEADER | EXTRACT_BODY | (peek ? EXTRACT_PEEK : 0)
	);
}


void IMAPMessage::extractPart(
	const shared_ptr <const messagePart>& p,
	utility::outputStream& os,
	utility::progressListener* progress,
	const size_t start,
	const size_t length,
	const bool peek
) const {

	shared_ptr <const IMAPFolder> folder = m_folder.lock();

	if (!folder) {
		throw exceptions::folder_not_found();
	}

	extractImpl(
		p, os, progress, start, length,
		EXTRACT_HEADER | EXTRACT_BODY | (peek ? EXTRACT_PEEK : 0)
	);
}


void IMAPMessage::fetchPartHeader(const shared_ptr <messagePart>& p) {

	shared_ptr <IMAPFolder> folder = m_folder.lock();

	if (!folder) {
		throw exceptions::folder_not_found();
	}

	std::ostringstream oss;
	utility::outputStreamAdapter ossAdapter(oss);

	extractImpl(p, ossAdapter, NULL, 0, -1, EXTRACT_HEADER | EXTRACT_PEEK);

	dynamicCast <IMAPMessagePart>(p)->getOrCreateHeader().parse(oss.str());
}


void IMAPMessage::fetchPartHeaderForStructure(const shared_ptr <messageStructure>& str) {

	for (size_t i = 0, n = str->getPartCount() ; i < n ; ++i) {

		shared_ptr <messagePart> part = str->getPartAt(i);

		// Fetch header of current part
		fetchPartHeader(part);

		// Fetch header of sub-parts
		fetchPartHeaderForStructure(part->getStructure());
	}
}


size_t IMAPMessage::extractImpl(
	const shared_ptr <const messagePart>& p,
	utility::outputStream& os,
	utility::progressListener* progress,
	const size_t start,
	const size_t length,
	const int extractFlags
) const {

	shared_ptr <const IMAPFolder> folder = m_folder.lock();

	if (!folder) {
		throw exceptions::folder_not_found();
	}
	
	if (!folder->isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}
	
	IMAPMessage_literalHandler literalHandler(os, progress);

	if (length == 0) {
		return 0;
	}

	// Construct section identifier
	std::ostringstream section;
	section.imbue(std::locale::classic());

	if (p != NULL) {

		shared_ptr <const IMAPMessagePart> currentPart = dynamicCast <const IMAPMessagePart>(p);
		std::vector <size_t> numbers;

		numbers.push_back(currentPart->getNumber());
		currentPart = currentPart->getParent();

		while (currentPart != NULL) {
			numbers.push_back(currentPart->getNumber());
			currentPart = currentPart->getParent();
		}

		numbers.erase(numbers.end() - 1);

		for (std::vector <size_t>::reverse_iterator it = numbers.rbegin() ; it != numbers.rend() ; ++it) {
			if (it != numbers.rbegin()) section << ".";
			section << (*it + 1);
		}
	}

	// Build the body descriptor for FETCH
	/*
	   BODY[]               header + body
	   BODY.PEEK[]          header + body (peek)
	   BODY[HEADER]         header
	   BODY.PEEK[HEADER]    header (peek)
	   BODY[TEXT]           body
	   BODY.PEEK[TEXT]      body (peek)
	*/
	std::ostringstream bodyDesc;
	bodyDesc.imbue(std::locale::classic());

	bodyDesc << "BODY";

	if (extractFlags & EXTRACT_PEEK) {
		bodyDesc << ".PEEK";
	}

	bodyDesc << "[";

	if (section.str().empty()) {

		// header + body
		if ((extractFlags & EXTRACT_HEADER) && (extractFlags & EXTRACT_BODY)) {
			bodyDesc << "";
		// body only
		} else if (extractFlags & EXTRACT_BODY) {
			bodyDesc << "TEXT";
		// header only
		} else if (extractFlags & EXTRACT_HEADER) {
			bodyDesc << "HEADER";
		}

	} else {

		bodyDesc << section.str();

		// header + body
		if ((extractFlags & EXTRACT_HEADER) && (extractFlags & EXTRACT_BODY)) {

			// First, extract header
			std::ostringstream header;
			utility::outputStreamAdapter headerStream(header);

			const size_t headerLength = extractImpl(
				p, headerStream, /* progress */ NULL,
				/* start */ 0, /* length */ -1, extractFlags & ~EXTRACT_BODY
			);

			size_t s = start;
			size_t l = length;

			if (s < headerLength) {

				if (l == static_cast <size_t>(-1)) {

					os.write(header.str().data() + s, headerLength - s);

				} else {

					size_t headerCopyLength = l;

					if (start + headerCopyLength > headerLength) {
						headerCopyLength = headerLength - start;
					}

					os.write(header.str().data() + s, headerCopyLength);

					l -= headerCopyLength;
				}

				s = 0;

			} else {

				s -= headerLength;
			}

			// Then, extract body
			return extractImpl(p, os, progress, s, l, extractFlags & ~EXTRACT_HEADER);

		// header only
		} else if (extractFlags & EXTRACT_HEADER) {

			bodyDesc << ".MIME";   // "MIME" not "HEADER" for parts
		}
	}

	bodyDesc << "]";

	if (start != 0 || length != static_cast <size_t>(-1)) {

		if (length == static_cast <size_t>(-1)) {
			bodyDesc << "<" << start << "." << static_cast <unsigned int>(-1) << ">";
		} else {
			bodyDesc << "<" << start << "." << length << ">";
		}
	}

	std::vector <std::string> fetchParams;
	fetchParams.push_back(bodyDesc.str());

	// Send the request
	IMAPCommand::FETCH(
		m_uid.empty() ? messageSet::byNumber(m_num) : messageSet::byUID(m_uid),
		fetchParams
	)->send(folder->m_connection);

	// Get the response
	scoped_ptr <IMAPParser::response> resp(folder->m_connection->readResponse(&literalHandler));

	if (resp->isBad() || resp->response_done->response_tagged->
		resp_cond_state->status != IMAPParser::resp_cond_state::OK) {

		throw exceptions::command_error("FETCH", resp->getErrorLog(), "bad response");
	}


	if (extractFlags & EXTRACT_BODY) {
		// TODO: update the flags (eg. flag "\Seen" may have been set)
	}

	return literalHandler.getTarget()->getBytesWritten();
}

int IMAPMessage::processFetchResponse(
	const fetchAttributes& options,
	const IMAPParser::message_data& msgData
) {

	shared_ptr <IMAPFolder> folder = m_folder.lock();

	// Get message attributes
	int changes = 0;

	for (auto &att : msgData.msg_att->items) {

		switch (att->type) {

			case IMAPParser::msg_att_item::FLAGS: {

				int flags = IMAPUtils::messageFlagsFromFlags(*att->flag_list);

				if (m_flags != flags) {
					m_flags = flags;
					changes |= events::messageChangedEvent::TYPE_FLAGS;
				}

				break;
			}
			case IMAPParser::msg_att_item::UID: {

				m_uid = att->uniqueid->value;
				break;
			}
			case IMAPParser::msg_att_item::MODSEQ: {

				m_modseq = att->mod_sequence_value->value;
				break;
			}
			case IMAPParser::msg_att_item::ENVELOPE: {

				if (!options.has(fetchAttributes::FULL_HEADER)) {

					auto* env = att->envelope.get();
					shared_ptr <vmime::header> hdr = getOrCreateHeader();

					// Date
					hdr->Date()->setValue(env->env_date->value);

					// Subject
					text subject;
					text::decodeAndUnfold(env->env_subject->value, &subject);

					hdr->Subject()->setValue(subject);

					// From
					mailboxList from;
					IMAPUtils::convertAddressList(*(env->env_from), from);

					if (!from.isEmpty()) {
						hdr->From()->setValue(*(from.getMailboxAt(0)));
					}

					// To
					mailboxList to;
					IMAPUtils::convertAddressList(*(env->env_to), to);

					hdr->To()->setValue(to.toAddressList());

					// Sender
					mailboxList sender;
					IMAPUtils::convertAddressList(*(env->env_sender), sender);

					if (!sender.isEmpty()) {
						hdr->Sender()->setValue(*(sender.getMailboxAt(0)));
					}

					// Reply-to
					mailboxList replyTo;
					IMAPUtils::convertAddressList(*(env->env_reply_to), replyTo);

					if (!replyTo.isEmpty()) {
						hdr->ReplyTo()->setValue(replyTo.toAddressList());
					}

					// Cc
					mailboxList cc;
					IMAPUtils::convertAddressList(*(env->env_cc), cc);

					if (!cc.isEmpty()) {
						hdr->Cc()->setValue(cc.toAddressList());
					}

					// Bcc
					mailboxList bcc;
					IMAPUtils::convertAddressList(*(env->env_bcc), bcc);

					if (!bcc.isEmpty()) {
						hdr->Bcc()->setValue(bcc.toAddressList());
					}

					// Message-ID
					if (!env->env_message_id->isNIL) {
						hdr->MessageId()->setValue(messageId(env->env_message_id->value));
					}

					// In-Reply-To
					if (!env->env_in_reply_to->isNIL) {
						shared_ptr <messageId> mid = make_shared <messageId>(env->env_in_reply_to->value);
						messageIdSequence sequence;
						sequence.appendMessageId(mid);
						hdr->InReplyTo()->setValue(sequence);
					}
				}

				break;
			}
			case IMAPParser::msg_att_item::BODY_STRUCTURE: {

				m_structure = make_shared <IMAPMessageStructure>(att->body.get());
				break;
			}
			case IMAPParser::msg_att_item::RFC822_HEADER: {

				getOrCreateHeader()->parse(att->nstring->value);
				break;
			}
			case IMAPParser::msg_att_item::RFC822_SIZE: {

				m_size = static_cast <size_t>(att->number->value);
				break;
			}
			case IMAPParser::msg_att_item::BODY_SECTION: {

				if (!options.has(fetchAttributes::FULL_HEADER)) {

					if (att->section->section_text1 &&
					    att->section->section_text1->type
					        == IMAPParser::section_text::HEADER_FIELDS) {

						header tempHeader;
						tempHeader.parse(att->nstring->value);

						vmime::header& hdr = *getOrCreateHeader();

						for (auto& fld : tempHeader.getFieldList()) {
							hdr.appendField(vmime::clone(fld));
						}
					}
				}

				break;
			}
			case IMAPParser::msg_att_item::INTERNALDATE:
			case IMAPParser::msg_att_item::RFC822:
			case IMAPParser::msg_att_item::RFC822_TEXT:
			case IMAPParser::msg_att_item::BODY: {

				break;
			}

		}
	}

	return changes;
}


shared_ptr <header> IMAPMessage::getOrCreateHeader() {

	if (m_header != NULL) {
		return m_header;
	} else {
		return m_header = make_shared <header>();
	}
}


void IMAPMessage::setFlags(const int flags, const int mode) {

	shared_ptr <IMAPFolder> folder = m_folder.lock();

	if (!folder) {
		throw exceptions::folder_not_found();
	}

	if (!m_uid.empty()) {
		folder->setMessageFlags(messageSet::byUID(m_uid), flags, mode);
	} else {
		folder->setMessageFlags(messageSet::byNumber(m_num), flags, mode);
	}
}


void IMAPMessage::constructParsedMessage(
	const shared_ptr <bodyPart>& parentPart,
	const shared_ptr <messageStructure>& str,
	int level
) {

	if (level == 0) {

		shared_ptr <messagePart> part = str->getPartAt(0);

		// Copy header
		shared_ptr <const header> hdr = part->getHeader();
		parentPart->getHeader()->copyFrom(*hdr);

		// Initialize body
		parentPart->getBody()->setContents(
			make_shared <IMAPMessagePartContentHandler>(
				dynamicCast <IMAPMessage>(shared_from_this()),
				part, parentPart->getBody()->getEncoding()
			)
		);

		constructParsedMessage(parentPart, part->getStructure(), 1);

	} else {

		for (size_t i = 0, n = str->getPartCount() ; i < n ; ++i) {

			shared_ptr <messagePart> part = str->getPartAt(i);

			shared_ptr <bodyPart> childPart = make_shared <bodyPart>();

			// Copy header
			shared_ptr <const header> hdr = part->getHeader();
			childPart->getHeader()->copyFrom(*hdr);

			// Initialize body
			childPart->getBody()->setContents(
				make_shared <IMAPMessagePartContentHandler>(
					dynamicCast <IMAPMessage>(shared_from_this()),
					part, childPart->getBody()->getEncoding()
				)
			);

			// Add child part
			parentPart->getBody()->appendPart(childPart);

			// Construct sub parts
			constructParsedMessage(childPart, part->getStructure(), ++level);
		}
	}
}


shared_ptr <vmime::message> IMAPMessage::getParsedMessage() {

	// Fetch structure
	shared_ptr <messageStructure> structure;

	try {

		structure = getStructure();

	} catch (exceptions::unfetched_object&) {

		auto folder = m_folder.lock();

		if (!folder) {
			throw exceptions::folder_not_found();
		}

		std::vector <shared_ptr <message> > msgs;
		msgs.push_back(dynamicCast <IMAPMessage>(shared_from_this()));

		folder->fetchMessages(
			msgs, fetchAttributes(fetchAttributes::STRUCTURE), /* progress */ NULL
		);

		structure = getStructure();
	}

	// Fetch header for each part
	fetchPartHeaderForStructure(structure);

	// Construct message from structure
	shared_ptr <vmime::message> msg = make_shared <vmime::message>();

	constructParsedMessage(msg, structure);

	return msg;
}


void IMAPMessage::renumber(const size_t number) {

	m_num = number;
}


void IMAPMessage::setExpunged() {

	m_expunged = true;
}


} // imap
} // net
} // vmime


#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP

