//
// 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/IMAPFolder.hpp"

#include "vmime/net/imap/IMAPStore.hpp"
#include "vmime/net/imap/IMAPParser.hpp"
#include "vmime/net/imap/IMAPMessage.hpp"
#include "vmime/net/imap/IMAPUtils.hpp"
#include "vmime/net/imap/IMAPConnection.hpp"
#include "vmime/net/imap/IMAPFolderStatus.hpp"
#include "vmime/net/imap/IMAPCommand.hpp"

#include "vmime/message.hpp"

#include "vmime/exception.hpp"

#include "vmime/utility/outputStreamAdapter.hpp"

#include <algorithm>
#include <sstream>


namespace vmime {
namespace net {
namespace imap {


IMAPFolder::IMAPFolder(
	const folder::path& path,
	const shared_ptr <IMAPStore>& store,
	const shared_ptr <folderAttributes>& attribs
)
	: m_store(store),
	  m_connection(store->connection()),
	  m_path(path),
	  m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()),
	  m_mode(-1),
	  m_open(false),
	  m_attribs(attribs) {

	store->registerFolder(this);

	m_status = make_shared <IMAPFolderStatus>();
}


IMAPFolder::~IMAPFolder() {

	try {

		shared_ptr <IMAPStore> store = m_store.lock();

		if (store) {

			if (m_open) {
				try {
					close(false);
				} catch (...) {
					// Ignore exception here to make sure unregisterFolder is called
				}
			}

			store->unregisterFolder(this);

		} else if (m_open) {

			m_connection = null;
			onClose();
		}

	} catch (...) {

		// Don't throw in destructor
	}
}


int IMAPFolder::getMode() const {

	if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	return m_mode;
}


const folderAttributes IMAPFolder::getAttributes() {

	// Root folder
	if (m_path.isEmpty()) {

		folderAttributes attribs;
		attribs.setType(folderAttributes::TYPE_CONTAINS_FOLDERS);
		attribs.setFlags(folderAttributes::FLAG_HAS_CHILDREN | folderAttributes::FLAG_NO_OPEN);

		return attribs;

	} else {

		if (!m_attribs) {
			testExistAndGetType();
		}

		return *m_attribs;
	}
}


const folder::path::component IMAPFolder::getName() const {

	return m_name;
}


const folder::path IMAPFolder::getFullPath() const {

	return m_path;
}


void IMAPFolder::open(const int mode, bool failIfModeIsNotAvailable) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	}

	// Ensure this folder is not already open in the same session
	for (std::list <IMAPFolder*>::iterator it = store->m_folders.begin() ;
	     it != store->m_folders.end() ; ++it) {

		if ((*it) != this && (*it)->getFullPath() == m_path) {
			throw exceptions::folder_already_open();
		}
	}

	// Open a connection for this folder
	shared_ptr <IMAPConnection> connection =
		make_shared <IMAPConnection>(store, store->getAuthenticator());

	try {

		connection->connect();

		// Emit the "SELECT" command
		//
		// Example:  C: A142 SELECT INBOX
		//           S: * 172 EXISTS
		//           S: * 1 RECENT
		//           S: * OK [UNSEEN 12] Message 12 is first unseen
		//           S: * OK [UIDVALIDITY 3857529045] UIDs valid
		//           S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
		//           S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited
		//           S: A142 OK [READ-WRITE] SELECT completed

		std::vector <string> selectParams;

		if (m_connection->hasCapability("CONDSTORE")) {
			selectParams.push_back("CONDSTORE");
		}

		IMAPCommand::SELECT(
			mode == MODE_READ_ONLY,
			IMAPUtils::pathToString(connection->hierarchySeparator(), getFullPath()),
			selectParams
		)->send(connection);

		// Read the response
		scoped_ptr <IMAPParser::response> resp(connection->readResponse());

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

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

		auto &respDataList = resp->continue_req_or_response_data;

		for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

			auto *responseData = (*it)->response_data.get();

			if (!responseData) {
				throw exceptions::command_error("SELECT", resp->getErrorLog(), "invalid response");
			}

			// OK Untagged responses: UNSEEN, PERMANENTFLAGS, UIDVALIDITY (optional)
			if (responseData->resp_cond_state) {

				auto *code = responseData->resp_cond_state->resp_text->resp_text_code.get();

				if (code) {

					switch (code->type) {

						case IMAPParser::resp_text_code::NOMODSEQ:

							connection->disableMODSEQ();
							break;

						default:

							break;
					}
				}

			// Untagged responses: FLAGS, EXISTS, RECENT (required)
			} else if (responseData->mailbox_data) {

				switch (responseData->mailbox_data->type) {

					default: break;

					case IMAPParser::mailbox_data::FLAGS: {

						if (!m_attribs) {
							m_attribs = make_shared <folderAttributes>();
						}

						IMAPUtils::mailboxFlagsToFolderAttributes(
							connection,
							m_path,
							*responseData->mailbox_data->mailbox_flag_list,
							*m_attribs
						);

						break;
					}
				}
			}
		}

		processStatusUpdate(resp.get());

		// Check for access mode (read-only or read-write)
		auto *respTextCode = resp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code.get();

		if (respTextCode) {

			const int openMode =
				(respTextCode->type == IMAPParser::resp_text_code::READ_WRITE)
					? MODE_READ_WRITE
					: MODE_READ_ONLY;

			if (failIfModeIsNotAvailable &&
			    mode == MODE_READ_WRITE && openMode == MODE_READ_ONLY) {

				throw exceptions::operation_not_supported();
			}
		}

		m_connection = connection;
		m_open = true;
		m_mode = mode;

	} catch (std::exception&) {

		throw;
	}
}


void IMAPFolder::close(const bool expunge) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		if (m_connection && m_connection->isConnected())
			m_connection->disconnect();
		m_connection = nullptr;
		m_open = false;
		throw exceptions::illegal_state("Store disconnected");
	}

	if (!m_connection || !m_connection->isConnected()) {
		throw exceptions::illegal_state("Folder not connected");
	}

	if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	shared_ptr <IMAPConnection> oldConnection = m_connection;

	// Emit the "CLOSE" command to expunge messages marked
	// as deleted (this is fastest than "EXPUNGE")
	if (expunge) {

		if (m_mode == MODE_READ_ONLY) {
			throw exceptions::operation_not_supported();
		}

		IMAPCommand::CLOSE()->send(oldConnection);
	}

	// Close this folder connection
	oldConnection->disconnect();

	// Now use default store connection
	m_connection = store->connection();

	m_open = false;
	m_mode = -1;

	m_status = make_shared <IMAPFolderStatus>();

	onClose();
}


void IMAPFolder::onClose() {

	for (std::vector <IMAPMessage*>::iterator it = m_messages.begin() ;
	     it != m_messages.end() ; ++it) {

		(*it)->onFolderClosed();
	}

	m_messages.clear();
}


void IMAPFolder::create(const folderAttributes& attribs) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (isOpen()) {
		throw exceptions::illegal_state("Folder is open");
	} else if (exists()) {
		throw exceptions::illegal_state("Folder already exists");
	} else if (!store->isValidFolderName(m_name)) {
		throw exceptions::invalid_folder_name();
	}

	// Emit the "CREATE" command
	//
	// Example:   C: A003 CREATE owatagusiam/
	//            S: A003 OK CREATE completed
	//            C: A004 CREATE owatagusiam/blurdybloop
	//            S: A004 OK CREATE completed

	string mailbox = IMAPUtils::pathToString
		(m_connection->hierarchySeparator(), getFullPath());

	if (attribs.getType() & folderAttributes::TYPE_CONTAINS_FOLDERS) {
		mailbox += m_connection->hierarchySeparator();
	}

	std::vector <string> createParams;

	if (attribs.getSpecialUse() != folderAttributes::SPECIALUSE_NONE) {

		if (!m_connection->hasCapability("CREATE-SPECIAL-USE")) {
			throw exceptions::operation_not_supported();
		}

		// C: t2 CREATE MySpecial (USE (\Drafts \Sent))
		std::ostringstream oss;
		oss << "USE (";

		switch (attribs.getSpecialUse()) {

			case folderAttributes::SPECIALUSE_NONE:      // should not happen
			case folderAttributes::SPECIALUSE_ALL:       oss << "\\All"; break;
			case folderAttributes::SPECIALUSE_ARCHIVE:   oss << "\\Archive"; break;
			case folderAttributes::SPECIALUSE_DRAFTS:    oss << "\\Drafts"; break;
			case folderAttributes::SPECIALUSE_FLAGGED:   oss << "\\Flagged"; break;
			case folderAttributes::SPECIALUSE_JUNK:      oss << "\\Junk"; break;
			case folderAttributes::SPECIALUSE_SENT:      oss << "\\Sent"; break;
			case folderAttributes::SPECIALUSE_TRASH:     oss << "\\Trash"; break;
			case folderAttributes::SPECIALUSE_IMPORTANT: oss << "\\Important"; break;
		}

		oss << ")";

		createParams.push_back(oss.str());
	}

	IMAPCommand::CREATE(mailbox, createParams)->send(m_connection);


	scoped_ptr <IMAPParser::response> resp(m_connection->readResponse());

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

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

	// Notify folder created
	shared_ptr <events::folderEvent> event =
		make_shared <events::folderEvent>(
			dynamicCast <folder>(shared_from_this()),
			events::folderEvent::TYPE_CREATED,
			m_path, m_path
		);

	notifyFolder(event);
}


void IMAPFolder::destroy() {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	}

	if (isOpen()) {
		throw exceptions::illegal_state("Folder is open");
	}

	const string mailbox = IMAPUtils::pathToString(
		m_connection->hierarchySeparator(), getFullPath()
	);

	IMAPCommand::DELETE(mailbox)->send(m_connection);


	scoped_ptr <IMAPParser::response> resp(m_connection->readResponse());

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

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

	// Notify folder deleted
	shared_ptr <events::folderEvent> event =
		make_shared <events::folderEvent>(
			dynamicCast <folder>(shared_from_this()),
			events::folderEvent::TYPE_DELETED,
			m_path, m_path
		);

	notifyFolder(event);
}


bool IMAPFolder::exists() {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!isOpen() && !store) {
		throw exceptions::illegal_state("Store disconnected");
	}

	return testExistAndGetType() != -1;
}


int IMAPFolder::testExistAndGetType() {

	// To test whether a folder exists, we simple list it using
	// the "LIST" command, and there should be one unique mailbox
	// with this name...
	//
	// Eg. Test whether '/foo/bar' exists
	//
	//     C: a005 list "" foo/bar
	//     S: * LIST (\NoSelect) "/" foo/bar
	//     S: a005 OK LIST completed
	//
	// ==> OK, exists
	//
	// Test whether '/foo/bar/zap' exists
	//
	//     C: a005 list "" foo/bar/zap
	//     S: a005 OK LIST completed
	//
	// ==> NO, does not exist

	IMAPCommand::LIST(
		"",
		IMAPUtils::pathToString(
			m_connection->hierarchySeparator(),
			getFullPath()
		)
	)->send(m_connection);


	scoped_ptr <IMAPParser::response> resp(m_connection->readResponse());

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

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

	// Check whether the result mailbox list contains this folder
	auto& respDataList = resp->continue_req_or_response_data;

	folderAttributes attribs;
	attribs.setType(-1);

	for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

		if (!(*it)->response_data) {
			throw exceptions::command_error("LIST", resp->getErrorLog(), "invalid response");
		}

		auto *mailboxData = (*it)->response_data->mailbox_data.get();

		// We are only interested in responses of type "LIST"
		if (mailboxData &&
		    mailboxData->type == IMAPParser::mailbox_data::LIST) {

			// Get the folder type/flags at the same time
			IMAPUtils::mailboxFlagsToFolderAttributes(
				m_connection,
				m_path,
				*mailboxData->mailbox_list->mailbox_flag_list,
				attribs
			);
		}
	}

	m_attribs = make_shared <folderAttributes>(attribs);

	return m_attribs->getType();
}


bool IMAPFolder::isOpen() const {

	return m_open;
}

shared_ptr <message> IMAPFolder::getMessage(const size_t num) {

	if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	if (num < 1 || num > m_status->getMessageCount()) {
		throw exceptions::message_not_found();
	}

	return make_shared <IMAPMessage>(dynamicCast <IMAPFolder>(shared_from_this()), num);
}


std::vector <shared_ptr <message> > IMAPFolder::getMessages(const messageSet& msgs) {

	if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	if (msgs.isEmpty()) {
		return std::vector <shared_ptr <message> >();
	}

	std::vector <shared_ptr <message> > messages;

	// Sequence number message set:
	//     C: . FETCH uuuu1,uuuu2,uuuu3 UID
	//     S: * nnnn1 FETCH (UID uuuu1)
	//     S: * nnnn2 FETCH (UID uuuu2)
	//     S: * nnnn3 FETCH (UID uuuu3)
	//     S: . OK FETCH completed

	// UID message set:
	//     C: . UID FETCH uuuu1,uuuu2,uuuu3 UID
	//     S: * nnnn1 FETCH (UID uuuu1)
	//     S: * nnnn2 FETCH (UID uuuu2)
	//     S: * nnnn3 FETCH (UID uuuu3)
	//     S: . OK UID FETCH completed

	std::vector <string> params;
	params.push_back("UID");

	IMAPCommand::FETCH(msgs, params)->send(m_connection);

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

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

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

	// Process the response
	auto &respDataList = resp->continue_req_or_response_data;

	for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

		if (!(*it)->response_data) {
			throw exceptions::command_error("UID FETCH ... UID", resp->getErrorLog(), "invalid response");
		}

		auto *messageData = (*it)->response_data->message_data.get();

		// We are only interested in responses of type "FETCH"
		if (!messageData || messageData->type != IMAPParser::message_data::FETCH) {
			continue;
		}

		// Find UID in message attributes
		const size_t msgNum = messageData->number;
		message::uid msgUID;

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

			if (att->type == IMAPParser::msg_att_item::UID) {
				msgUID = att->uniqueid->value;
				break;
			}
		}

		if (!msgUID.empty()) {
			shared_ptr <IMAPFolder> thisFolder = dynamicCast <IMAPFolder>(shared_from_this());
			messages.push_back(make_shared <IMAPMessage>(thisFolder, msgNum, msgUID));
		}
	}

	return messages;
}


size_t IMAPFolder::getMessageCount() {

	if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	return m_status->getMessageCount();
}


vmime_uint32 IMAPFolder::getUIDValidity() const {

	if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	return m_status->getUIDValidity();
}


vmime_uint64 IMAPFolder::getHighestModSequence() const {

	if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	return m_status->getHighestModSeq();
}


shared_ptr <folder> IMAPFolder::getFolder(const folder::path::component& name) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	}

	return make_shared <IMAPFolder>(m_path / name, store, shared_ptr <folderAttributes>());
}


std::vector <shared_ptr <folder> > IMAPFolder::getFolders(const bool recursive) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!isOpen() && !store) {
		throw exceptions::illegal_state("Store disconnected");
	}

	// Eg. List folders in '/foo/bar'
	//
	//     C: a005 list "foo/bar" *
	//     S: * LIST (\NoSelect) "/" foo/bar
	//     S: * LIST (\NoInferiors) "/" foo/bar/zap
	//     S: a005 OK LIST completed

	shared_ptr <IMAPCommand> cmd;

	const string pathString = IMAPUtils::pathToString(
		m_connection->hierarchySeparator(), getFullPath()
	);

	if (recursive) {

		cmd = IMAPCommand::LIST(pathString, "*");

	} else {

		cmd = IMAPCommand::LIST(
			pathString.empty()
				? ""
				: (pathString + m_connection->hierarchySeparator()),
			"%"
		);
	}

	cmd->send(m_connection);


	scoped_ptr <IMAPParser::response> resp(m_connection->readResponse());

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

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

	auto &respDataList = resp->continue_req_or_response_data;

	std::vector <shared_ptr <folder> > v;

	for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

		if (!(*it)->response_data) {
			throw exceptions::command_error("LIST", resp->getErrorLog(), "invalid response");
		}

		auto *mailboxData = (*it)->response_data->mailbox_data.get();

		if (!mailboxData || mailboxData->type != IMAPParser::mailbox_data::LIST) {
			continue;
		}

		// Get folder path
		auto &mailbox = mailboxData->mailbox_list->mailbox;

		folder::path path = IMAPUtils::stringToPath(
			mailboxData->mailbox_list->quoted_char, mailbox->name
		);

		if (recursive || m_path.isDirectParentOf(path)) {

			// Append folder to list
			shared_ptr <folderAttributes> attribs = make_shared <folderAttributes>();

			IMAPUtils::mailboxFlagsToFolderAttributes(
				m_connection,
				path,
				*mailboxData->mailbox_list->mailbox_flag_list,
				*attribs
			);

			v.push_back(make_shared <IMAPFolder>(path, store, attribs));
		}
	}

	return v;
}


void IMAPFolder::fetchMessages(
	std::vector <shared_ptr <message> >& msg,
	const fetchAttributes& options,
	utility::progressListener* progress
) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	if (msg.empty()) {
		return;
	}

	// Build message numbers list
	std::vector <size_t> list;
	list.reserve(msg.size());

	std::map <size_t, shared_ptr <IMAPMessage> > numberToMsg;

	for (std::vector <shared_ptr <message> >::iterator it = msg.begin() ; it != msg.end() ; ++it) {

		list.push_back((*it)->getNumber());
		numberToMsg[(*it)->getNumber()] = dynamicCast <IMAPMessage>(*it);
	}

	// Send the request
	IMAPUtils::buildFetchCommand(
		m_connection, messageSet::byNumber(list), options
	)->send(m_connection);

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

	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");
	}

	auto &respDataList = resp->continue_req_or_response_data;

	const size_t total = msg.size();
	size_t current = 0;

	if (progress) {
		progress->start(total);
	}

	try {

		for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

			if (!(*it)->response_data) {
				throw exceptions::command_error("FETCH", resp->getErrorLog(), "invalid response");
			}

			auto *messageData = (*it)->response_data->message_data.get();

			// We are only interested in responses of type "FETCH"
			if (!messageData || messageData->type != IMAPParser::message_data::FETCH) {
				continue;
			}

			// Process fetch response for this message
			const size_t num = messageData->number;

			std::map <size_t, shared_ptr <IMAPMessage> >::iterator msg = numberToMsg.find(num);

			if (msg != numberToMsg.end()) {

				(*msg).second->processFetchResponse(options, *messageData);

				if (progress) {
					progress->progress(++current, total);
				}
			}
		}

	} catch (...) {

		if (progress) {
			progress->stop(total);
		}

		throw;
	}

	if (progress) {
		progress->stop(total);
	}

	processStatusUpdate(resp.get());
}


void IMAPFolder::fetchMessage(const shared_ptr <message>& msg, const fetchAttributes& options) {

	std::vector <shared_ptr <message> > msgs;
	msgs.push_back(msg);

	fetchMessages(msgs, options, /* progress */ NULL);
}


std::vector <shared_ptr <message> > IMAPFolder::getAndFetchMessages(
	const messageSet& msgs,
	const fetchAttributes& attribs
) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	if (msgs.isEmpty()) {
		return std::vector <shared_ptr <message> >();
	}

	// Ensure we also get the UID for each message
	fetchAttributes attribsWithUID(attribs);
	attribsWithUID.add(fetchAttributes::UID);

	// Send the request
	IMAPUtils::buildFetchCommand(m_connection, msgs, attribsWithUID)->send(m_connection);

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

	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");
	}

	auto &respDataList = resp->continue_req_or_response_data;

	std::vector <shared_ptr <message> > messages;

	for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

		if (!(*it)->response_data) {
			throw exceptions::command_error("FETCH", resp->getErrorLog(), "invalid response");
		}

		auto *messageData = (*it)->response_data->message_data.get();

		// We are only interested in responses of type "FETCH"
		if (!messageData || messageData->type != IMAPParser::message_data::FETCH) {
			continue;
		}

		// Get message number
		const size_t msgNum = messageData->number;

		// Get message UID
		message::uid msgUID;

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

			if (att->type == IMAPParser::msg_att_item::UID) {
				msgUID = att->uniqueid->value;
				break;
			}
		}

		// Create a new message reference
		shared_ptr <IMAPFolder> thisFolder = dynamicCast <IMAPFolder>(shared_from_this());
		shared_ptr <IMAPMessage> msg = make_shared <IMAPMessage>(thisFolder, msgNum, msgUID);

		messages.push_back(msg);

		// Process fetch response for this message
		msg->processFetchResponse(attribsWithUID, *messageData);
	}

	processStatusUpdate(resp.get());

	return messages;
}


int IMAPFolder::getFetchCapabilities() const {

	return fetchAttributes::ENVELOPE | fetchAttributes::CONTENT_INFO |
	       fetchAttributes::STRUCTURE | fetchAttributes::FLAGS |
	       fetchAttributes::SIZE | fetchAttributes::FULL_HEADER |
	       fetchAttributes::UID | fetchAttributes::IMPORTANCE |
	       fetchAttributes::PEEK;
}


shared_ptr <folder> IMAPFolder::getParent() {

	if (m_path.isEmpty()) {

		return null;

	} else {

		return make_shared <IMAPFolder>(
			m_path.getParent(), m_store.lock(), shared_ptr <folderAttributes>()
		);
	}
}


shared_ptr <const store> IMAPFolder::getStore() const {

	return m_store.lock();
}


shared_ptr <store> IMAPFolder::getStore() {

	return m_store.lock();
}


void IMAPFolder::registerMessage(IMAPMessage* msg) {

	m_messages.push_back(msg);
}


void IMAPFolder::unregisterMessage(IMAPMessage* msg) {

	std::vector <IMAPMessage*>::iterator it =
		std::find(m_messages.begin(), m_messages.end(), msg);

	if (it != m_messages.end()) {
		m_messages.erase(it);
	}
}


void IMAPFolder::onStoreDisconnected() {

	m_store.reset();
}


void IMAPFolder::deleteMessages(const messageSet& msgs) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (msgs.isEmpty()) {
		throw exceptions::invalid_argument();
	}

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	} else if (m_mode == MODE_READ_ONLY) {
		throw exceptions::illegal_state("Folder is read-only");
	}

	// Send the request
	IMAPCommand::STORE(
		msgs, message::FLAG_MODE_ADD,
		IMAPUtils::messageFlagList(message::FLAG_DELETED)
	)->send(m_connection);

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

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

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

	processStatusUpdate(resp.get());
}


void IMAPFolder::setMessageFlags(
	const messageSet& msgs,
	const int flags,
	const int mode
) {

	const std::vector <string> flagList = IMAPUtils::messageFlagList(flags);

	if ((mode == message::FLAG_MODE_SET) || !flagList.empty()) {

		// Send the request
		IMAPCommand::STORE(msgs, mode, flagList)->send(m_connection);

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

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

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

		processStatusUpdate(resp.get());
	}
}


messageSet IMAPFolder::addMessage(
	const shared_ptr <vmime::message>& msg,
	const int flags,
	vmime::datetime* date,
	utility::progressListener* progress
) {

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

	msg->generate(ossAdapter);

	const string& str = oss.str();
	utility::inputStreamStringAdapter strAdapter(str);

	return addMessage(strAdapter, str.length(), flags, date, progress);
}


messageSet IMAPFolder::addMessage(
	utility::inputStream& is,
	const size_t size,
	const int flags,
	vmime::datetime* date,
	utility::progressListener* progress
) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	} else if (m_mode == MODE_READ_ONLY) {
		throw exceptions::illegal_state("Folder is read-only");
	}

	// Send the request
	IMAPCommand::APPEND(
		IMAPUtils::pathToString(m_connection->hierarchySeparator(), getFullPath()),
		IMAPUtils::messageFlagList(flags), date, size
	)->send(m_connection);

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

	bool ok = false;
	auto &respList = resp->continue_req_or_response_data;

	for (auto it = respList.begin() ; !ok && (it != respList.end()) ; ++it) {

		if ((*it)->continue_req) {
			ok = true;
		}
	}

	if (!ok) {
		throw exceptions::command_error("APPEND", resp->getErrorLog(), "bad response");
	}

	processStatusUpdate(resp.get());

	// Send message data
	const size_t total = size;
	size_t current = 0;

	if (progress) {
		progress->start(total);
	}

	const size_t blockSize = std::min(
		is.getBlockSize(),
		static_cast <size_t>(m_connection->getSocket()->getBlockSize())
	);

	std::vector <byte_t> vbuffer(blockSize);
	byte_t* buffer = &vbuffer.front();

	while (!is.eof()) {

		// Read some data from the input stream
		const size_t read = is.read(buffer, blockSize);
		current += read;

		// Put read data into socket output stream
		m_connection->sendRaw(buffer, read);

		// Notify progress
		if (progress) {
			progress->progress(current, total);
		}
	}

	m_connection->sendRaw(utility::stringUtils::bytesFromString("\r\n"), 2);

	if (m_connection->getTracer()) {
		m_connection->getTracer()->traceSendBytes(current);
	}

	if (progress) {
		progress->stop(total);
	}

	// Get the response
	scoped_ptr <IMAPParser::response> finalResp(m_connection->readResponse());

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

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

	processStatusUpdate(finalResp.get());

	auto *respTextCode =
		finalResp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code.get();

	if (respTextCode && respTextCode->type == IMAPParser::resp_text_code::APPENDUID) {
		return IMAPUtils::buildMessageSet(*respTextCode->uid_set);
	}

	return messageSet::empty();
}


void IMAPFolder::expunge() {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	} else if (m_mode == MODE_READ_ONLY) {
		throw exceptions::illegal_state("Folder is read-only");
	}

	// Send the request
	IMAPCommand::EXPUNGE()->send(m_connection);

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

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

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

	processStatusUpdate(resp.get());
}


void IMAPFolder::rename(const folder::path& newPath) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (m_path.isEmpty() || newPath.isEmpty()) {
		throw exceptions::illegal_operation("Cannot rename root folder");
	} else if (m_path.getSize() == 1 && m_name.getBuffer() == "INBOX") {
		throw exceptions::illegal_operation("Cannot rename 'INBOX' folder");
	} else if (!store->isValidFolderName(newPath.getLastComponent())) {
		throw exceptions::invalid_folder_name();
	}

	// Send the request
	IMAPCommand::RENAME(
		IMAPUtils::pathToString(m_connection->hierarchySeparator(), getFullPath()),
		IMAPUtils::pathToString(m_connection->hierarchySeparator(), newPath)
	)->send(m_connection);

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

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

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

	// Notify folder renamed
	folder::path oldPath(m_path);

	m_path = newPath;
	m_name = newPath.getLastComponent();

	shared_ptr <events::folderEvent> event =
		make_shared <events::folderEvent>(
			dynamicCast <folder>(shared_from_this()),
			events::folderEvent::TYPE_RENAMED,
			oldPath,
			newPath
		);

	notifyFolder(event);

	// Notify sub-folders
	for (std::list <IMAPFolder*>::iterator it = store->m_folders.begin() ;
	     it != store->m_folders.end() ; ++it) {

		if ((*it) != this && oldPath.isParentOf((*it)->getFullPath())) {

			folder::path oldPath((*it)->m_path);

			(*it)->m_path.renameParent(oldPath, newPath);

			shared_ptr <events::folderEvent> event =
				make_shared <events::folderEvent>(
					dynamicCast <folder>((*it)->shared_from_this()),
					events::folderEvent::TYPE_RENAMED,
					oldPath, (*it)->m_path
				);

			(*it)->notifyFolder(event);
		}
	}

	processStatusUpdate(resp.get());
}


messageSet IMAPFolder::copyMessages(const folder::path& dest, const messageSet& set) {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	}

	// Send the request
	IMAPCommand::COPY(
		set,
		IMAPUtils::pathToString(m_connection->hierarchySeparator(), dest)
	)->send(m_connection);

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

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

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

	processStatusUpdate(resp.get());

	auto *respTextCode =
		resp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code.get();

	if (respTextCode && respTextCode->type == IMAPParser::resp_text_code::COPYUID) {
		return IMAPUtils::buildMessageSet(*respTextCode->uid_set2);
	}

	return messageSet::empty();
}


void IMAPFolder::status(size_t& count, size_t& unseen) {

	count = 0;
	unseen = 0;

	shared_ptr <folderStatus> status = getStatus();

	count = status->getMessageCount();
	unseen = status->getUnseenCount();
}


shared_ptr <folderStatus> IMAPFolder::getStatus() {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	}

	// Build the attributes list
	std::vector <string> attribs;

	attribs.push_back("MESSAGES");
	attribs.push_back("UNSEEN");
	attribs.push_back("UIDNEXT");
	attribs.push_back("UIDVALIDITY");

	if (m_connection->hasCapability("CONDSTORE")) {
		attribs.push_back("HIGHESTMODSEQ");
	}

	// Send the request
	IMAPCommand::STATUS(
		IMAPUtils::pathToString(m_connection->hierarchySeparator(), getFullPath()),
		attribs
	)->send(m_connection);

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

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

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

	auto &respDataList = resp->continue_req_or_response_data;

	for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

		if ((*it)->response_data) {

			auto &responseData = (*it)->response_data;

			if (responseData->mailbox_data &&
				responseData->mailbox_data->type == IMAPParser::mailbox_data::STATUS) {

				shared_ptr <IMAPFolderStatus> status = make_shared <IMAPFolderStatus>();
				status->updateFromResponse(*responseData->mailbox_data);

				m_status->updateFromResponse(*responseData->mailbox_data);

				return status;
			}
		}
	}

	throw exceptions::command_error("STATUS", resp->getErrorLog(), "invalid response");
}


void IMAPFolder::noop() {

	shared_ptr <IMAPStore> store = m_store.lock();

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	}

	if (!m_connection || !m_connection->isConnected()) {
		throw exceptions::illegal_state("Store disconnected");
	}

	IMAPCommand::NOOP()->send(m_connection);

	scoped_ptr <IMAPParser::response> resp(m_connection->readResponse());

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

		throw exceptions::command_error("NOOP", resp->getErrorLog());
	}

	processStatusUpdate(resp.get());
}


std::vector <size_t> IMAPFolder::getMessageNumbersStartingOnUID(const message::uid& uid) {

	// Send the request
	std::ostringstream uidSearchKey;
	uidSearchKey.imbue(std::locale::classic());
	uidSearchKey << "UID " << uid << ":*";

	std::vector <string> searchKeys;
	searchKeys.push_back(uidSearchKey.str());

	IMAPCommand::SEARCH(searchKeys, /* charset */ NULL)->send(m_connection);

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

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

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

	auto& respDataList = resp->continue_req_or_response_data;

	std::vector <size_t> seqNumbers;

	for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

		if (!(*it)->response_data) {
			throw exceptions::command_error("SEARCH", resp->getErrorLog(), "invalid response");
		}

		auto *mailboxData = (*it)->response_data->mailbox_data.get();

		// We are only interested in responses of type "SEARCH"
		if (!mailboxData ||
		    mailboxData->type != IMAPParser::mailbox_data::SEARCH) {

			continue;
		}

		for (auto &nzn : mailboxData->search_nz_number_list) {
			seqNumbers.push_back(nzn->value);
		}
	}

	processStatusUpdate(resp.get());

	return seqNumbers;
}


std::vector <size_t> IMAPFolder::getMessageNumbersMatchingSearchAttributes(
	const IMAPSearchAttributes& sa,
	const vmime::charset* charset
) {

	auto searchKeys = sa.generate();

	IMAPCommand::SEARCH(searchKeys, charset)->send(m_connection);

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

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

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

	auto& respDataList = resp->continue_req_or_response_data;

	std::vector <size_t> seqNumbers;

	for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

		if (!(*it)->response_data) {
			throw exceptions::command_error("SEARCH", resp->getErrorLog(), "invalid response");
		}

		auto *mailboxData = (*it)->response_data->mailbox_data.get();

		// We are only interested in responses of type "SEARCH"
		if (!mailboxData ||
			mailboxData->type != IMAPParser::mailbox_data::SEARCH) {

			continue;
		}

		for (auto &nzn : mailboxData->search_nz_number_list) {
			seqNumbers.push_back(nzn->value);
		}
	}

	processStatusUpdate(resp.get());

	return seqNumbers;
}


std::vector <message::uid> IMAPFolder::getMessageUIDsMatchingSearchAttributes(
	const IMAPSearchAttributes& sa,
	const vmime::charset* charset
) {

	auto searchKeys = sa.generate();

	IMAPCommand::UIDSEARCH(searchKeys, charset)->send(m_connection);

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

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

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

	auto& respDataList = resp->continue_req_or_response_data;

	std::vector <message::uid> uidNumbers;

	for (auto it = respDataList.begin() ; it != respDataList.end() ; ++it) {

		if (!(*it)->response_data) {
			throw exceptions::command_error("UIDSEARCH", resp->getErrorLog(), "invalid response");
		}

		auto *mailboxData = (*it)->response_data->mailbox_data.get();

		// We are only interested in responses of type "SEARCH"
		if (!mailboxData ||
			mailboxData->type != IMAPParser::mailbox_data::SEARCH) {

			continue;
		}

		for (auto &nzn : mailboxData->search_nz_number_list) {
			uidNumbers.push_back(nzn->value);
		}
	}

	processStatusUpdate(resp.get());

	return uidNumbers;
}


void IMAPFolder::processStatusUpdate(const IMAPParser::response* resp) {

	std::vector <shared_ptr <events::event> > events;

	shared_ptr <IMAPFolderStatus> oldStatus = vmime::clone(m_status);
	int expungedMessageCount = 0;

	// Process tagged response
	if (resp->response_done &&
	    resp->response_done->response_tagged &&
	    resp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code) {

		m_status->updateFromResponse(
			*resp->response_done->response_tagged->resp_cond_state->resp_text->resp_text_code
		);
	}

	// Process untagged responses
	for (auto it = resp->continue_req_or_response_data.begin() ;
	     it != resp->continue_req_or_response_data.end() ; ++it) {

		if ((*it)->response_data &&
		    (*it)->response_data->resp_cond_state &&
		    (*it)->response_data->resp_cond_state->resp_text->resp_text_code) {

			m_status->updateFromResponse(
				*(*it)->response_data->resp_cond_state->resp_text->resp_text_code
			);

		} else if ((*it)->response_data &&
		           (*it)->response_data->mailbox_data) {

			m_status->updateFromResponse(*(*it)->response_data->mailbox_data);

			// Update folder attributes, if available
			if ((*it)->response_data->mailbox_data->type == IMAPParser::mailbox_data::LIST) {

				folderAttributes attribs;
				IMAPUtils::mailboxFlagsToFolderAttributes(
					m_connection,
					m_path,
					*(*it)->response_data->mailbox_data->mailbox_list->mailbox_flag_list,
					attribs
				);

				m_attribs = make_shared <folderAttributes>(attribs);
			}

		} else if ((*it)->response_data && (*it)->response_data->message_data) {

			auto* msgData = (*it)->response_data->message_data.get();
			const size_t msgNumber = msgData->number;

			if (msgData->type == IMAPParser::message_data::FETCH) {

				// Message changed
				for (std::vector <IMAPMessage*>::iterator mit =
				     m_messages.begin() ; mit != m_messages.end() ; ++mit) {

					if ((*mit)->getNumber() == msgNumber) {
						(*mit)->processFetchResponse(/* options */ 0, *msgData);
					}
				}

				events.push_back(
					make_shared <events::messageChangedEvent>(
						dynamicCast <folder>(shared_from_this()),
						events::messageChangedEvent::TYPE_FLAGS,
						std::vector <size_t>(1, msgNumber)
					)
				);

			} else if (msgData->type == IMAPParser::message_data::EXPUNGE) {

				// A message has been expunged, renumber messages
				for (std::vector <IMAPMessage*>::iterator jt =
				     m_messages.begin() ; jt != m_messages.end() ; ++jt) {

					if ((*jt)->getNumber() == msgNumber) {
						(*jt)->setExpunged();
					} else if ((*jt)->getNumber() > msgNumber) {
						(*jt)->renumber((*jt)->getNumber() - 1);
					}
				}

				events.push_back(
					make_shared <events::messageCountEvent>(
						dynamicCast <folder>(shared_from_this()),
						events::messageCountEvent::TYPE_REMOVED,
						std::vector <size_t>(1, msgNumber)
					)
				);

				expungedMessageCount++;
			}
		}
	}

	// New messages arrived
	if (m_status->getMessageCount() > oldStatus->getMessageCount() - expungedMessageCount) {

		std::vector <size_t> newMessageNumbers;

		for (size_t msgNumber = oldStatus->getMessageCount() - expungedMessageCount ;
		     msgNumber <= m_status->getMessageCount() ; ++msgNumber) {

			newMessageNumbers.push_back(msgNumber);
		}

		events.push_back(
			make_shared <events::messageCountEvent>(
				dynamicCast <folder>(shared_from_this()),
				events::messageCountEvent::TYPE_ADDED,
				newMessageNumbers
			)
		);
	}

	// Dispatch notifications
	for (std::vector <shared_ptr <events::event> >::iterator evit =
	     events.begin() ; evit != events.end() ; ++evit) {

		notifyEvent(*evit);
	}
}


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


#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP

