//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2005 Vincent Richard <vincent@vincent-richard.net>
//
// 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA.
//

#include "vmime/net/maildir/maildirFolder.hpp"

#include "vmime/net/maildir/maildirStore.hpp"
#include "vmime/net/maildir/maildirMessage.hpp"
#include "vmime/net/maildir/maildirUtils.hpp"

#include "vmime/utility/smartPtr.hpp"

#include "vmime/message.hpp"

#include "vmime/exception.hpp"
#include "vmime/platformDependant.hpp"


namespace vmime {
namespace net {
namespace maildir {


maildirFolder::maildirFolder(const folder::path& path, weak_ref <maildirStore> store)
	: m_store(store), m_path(path),
	  m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()),
	  m_mode(-1), m_open(false), m_unreadMessageCount(0), m_messageCount(0)
{
	m_store->registerFolder(this);
}


maildirFolder::~maildirFolder()
{
	if (m_store)
	{
		if (m_open)
			close(false);

		m_store->unregisterFolder(this);
	}
	else if (m_open)
	{
		close(false);
	}
}


void maildirFolder::onStoreDisconnected()
{
	m_store = NULL;
}


const int maildirFolder::getMode() const
{
	if (!isOpen())
		throw exceptions::illegal_state("Folder not open");

	return (m_mode);
}


const int maildirFolder::getType()
{
	if (m_path.isEmpty())
		return (TYPE_CONTAINS_FOLDERS);
	else
		return (TYPE_CONTAINS_FOLDERS | TYPE_CONTAINS_MESSAGES);
}


const int maildirFolder::getFlags()
{
	int flags = 0;

	utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

	ref <utility::file> rootDir = fsf->create
		(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER));

	ref <utility::fileIterator> it = rootDir->getFiles();

	while (it->hasMoreElements())
	{
		ref <utility::file> file = it->nextElement();

		if (maildirUtils::isSubfolderDirectory(*file))
		{
			flags |= FLAG_CHILDREN; // Contains at least one sub-folder
			break;
		}
	}

	return (flags);
}


const folder::path::component maildirFolder::getName() const
{
	return (m_name);
}


const folder::path maildirFolder::getFullPath() const
{
	return (m_path);
}


void maildirFolder::open(const int mode, bool /* failIfModeIsNotAvailable */)
{
	if (!m_store)
		throw exceptions::illegal_state("Store disconnected");
	else if (isOpen())
		throw exceptions::illegal_state("Folder is already open");
	else if (!exists())
		throw exceptions::illegal_state("Folder does not exist");

	scanFolder();

	m_open = true;
	m_mode = mode;
}


void maildirFolder::close(const bool expunge)
{
	if (!m_store)
		throw exceptions::illegal_state("Store disconnected");

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

	if (expunge)
		this->expunge();

	m_open = false;
	m_mode = -1;

	onClose();
}


void maildirFolder::onClose()
{
	for (std::vector <maildirMessage*>::iterator it = m_messages.begin() ;
	     it != m_messages.end() ; ++it)
	{
		(*it)->onFolderClosed();
	}

	m_messages.clear();
}


void maildirFolder::registerMessage(maildirMessage* msg)
{
	m_messages.push_back(msg);
}


void maildirFolder::unregisterMessage(maildirMessage* msg)
{
	std::vector <maildirMessage*>::iterator it =
		std::find(m_messages.begin(), m_messages.end(), msg);

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


void maildirFolder::create(const int /* type */)
{
	if (!m_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 (!m_store->isValidFolderName(m_name))
		throw exceptions::invalid_folder_name();

	// Create directory on file system
	try
	{
		utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

		if (!fsf->isValidPath(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT)))
			throw exceptions::invalid_folder_name();

		ref <utility::file> rootDir = fsf->create
			(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT));

		ref <utility::file> newDir = fsf->create
			(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW));
		ref <utility::file> tmpDir = fsf->create
			(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_TMP));
		ref <utility::file> curDir = fsf->create
			(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CUR));

		rootDir->createDirectory(true);

		newDir->createDirectory(false);
		tmpDir->createDirectory(false);
		curDir->createDirectory(false);
	}
	catch (exceptions::filesystem_exception& e)
	{
		throw exceptions::command_error("CREATE", "", "File system exception", e);
	}

	// Notify folder created
	events::folderEvent event
		(thisRef().dynamicCast <folder>(),
		 events::folderEvent::TYPE_CREATED, m_path, m_path);

	notifyFolder(event);
}


const bool maildirFolder::exists()
{
	utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

	ref <utility::file> rootDir = fsf->create
		(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT));

	ref <utility::file> newDir = fsf->create
		(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_NEW));
	ref <utility::file> tmpDir = fsf->create
		(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_TMP));
	ref <utility::file> curDir = fsf->create
		(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CUR));

	return (rootDir->exists() && rootDir->isDirectory() &&
	        newDir->exists() && newDir->isDirectory() &&
	        tmpDir->exists() && tmpDir->isDirectory() &&
	        curDir->exists() && curDir->isDirectory());
}


const bool maildirFolder::isOpen() const
{
	return (m_open);
}


void maildirFolder::scanFolder()
{
	try
	{
		m_messageCount = 0;
		m_unreadMessageCount = 0;

		utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

		utility::file::path newDirPath = maildirUtils::getFolderFSPath
			(m_store, m_path, maildirUtils::FOLDER_PATH_NEW);
		ref <utility::file> newDir = fsf->create(newDirPath);

		utility::file::path curDirPath = maildirUtils::getFolderFSPath
			(m_store, m_path, maildirUtils::FOLDER_PATH_CUR);
		ref <utility::file> curDir = fsf->create(curDirPath);

		// New received messages (new/)
		ref <utility::fileIterator> nit = newDir->getFiles();
		std::vector <utility::file::path::component> newMessageFilenames;

		while (nit->hasMoreElements())
		{
			ref <utility::file> file = nit->nextElement();

			if (maildirUtils::isMessageFile(*file))
				newMessageFilenames.push_back(file->getFullPath().getLastComponent());
		}

		// Current messages (cur/)
		ref <utility::fileIterator> cit = curDir->getFiles();
		std::vector <utility::file::path::component> curMessageFilenames;

		while (cit->hasMoreElements())
		{
			ref <utility::file> file = cit->nextElement();

			if (maildirUtils::isMessageFile(*file))
				curMessageFilenames.push_back(file->getFullPath().getLastComponent());
		}

		// Update/delete existing messages (found in previous scan)
		for (unsigned int i = 0 ; i < m_messageInfos.size() ; ++i)
		{
			messageInfos& msgInfos = m_messageInfos[i];

			// NOTE: the flags may have changed (eg. moving from 'new' to 'cur'
			// may imply the 'S' flag) and so the filename. That's why we use
			// "maildirUtils::messageIdComparator" to compare only the 'unique'
			// portion of the filename...

			if (msgInfos.type == messageInfos::TYPE_CUR)
			{
				const std::vector <utility::file::path::component>::iterator pos =
					std::find_if(curMessageFilenames.begin(), curMessageFilenames.end(),
						maildirUtils::messageIdComparator(msgInfos.path));

				// If we cannot find this message in the 'cur' directory,
				// it means it has been deleted (and expunged).
				if (pos == curMessageFilenames.end())
				{
					msgInfos.type = messageInfos::TYPE_DELETED;
				}
				// Otherwise, update its information.
				else
				{
					msgInfos.path = *pos;
					curMessageFilenames.erase(pos);
				}
			}
		}

		m_messageInfos.reserve(m_messageInfos.size()
			+ newMessageFilenames.size() + curMessageFilenames.size());

		// Add new messages from 'new': we are responsible to move the files
		// from the 'new' directory to the 'cur' directory, and append them
		// to our message list.
		for (std::vector <utility::file::path::component>::const_iterator
		     it = newMessageFilenames.begin() ; it != newMessageFilenames.end() ; ++it)
		{
			const utility::file::path::component newFilename =
				maildirUtils::buildFilename(maildirUtils::extractId(*it), 0);

			// Move messages from 'new' to 'cur'
			ref <utility::file> file = fsf->create(newDirPath / *it);
			file->rename(curDirPath / newFilename);

			// Append to message list
			messageInfos msgInfos;
			msgInfos.path = newFilename;
			msgInfos.type = messageInfos::TYPE_CUR;

			m_messageInfos.push_back(msgInfos);
		}

		// Add new messages from 'cur': the files have already been moved
		// from 'new' to 'cur'. Just append them to our message list.
		for (std::vector <utility::file::path::component>::const_iterator
		     it = curMessageFilenames.begin() ; it != curMessageFilenames.end() ; ++it)
		{
			// Append to message list
			messageInfos msgInfos;
			msgInfos.path = *it;
			msgInfos.type = messageInfos::TYPE_CUR;

			m_messageInfos.push_back(msgInfos);
		}

		// Update message count
		int unreadMessageCount = 0;

		for (std::vector <messageInfos>::const_iterator
		     it = m_messageInfos.begin() ; it != m_messageInfos.end() ; ++it)
		{
			if ((maildirUtils::extractFlags((*it).path) & message::FLAG_SEEN) == 0)
				++unreadMessageCount;
		}

		m_unreadMessageCount = unreadMessageCount;
		m_messageCount = m_messageInfos.size();
	}
	catch (exceptions::filesystem_exception&)
	{
		// Should not happen...
	}
}


ref <message> maildirFolder::getMessage(const int num)
{
	if (!isOpen())
		throw exceptions::illegal_state("Folder not open");

	if (num < 1 || num > m_messageCount)
		throw exceptions::message_not_found();

	return vmime::create <maildirMessage>
		(thisWeakRef().dynamicCast <maildirFolder>(), num);
}


std::vector <ref <message> > maildirFolder::getMessages(const int from, const int to)
{
	const int to2 = (to == -1 ? m_messageCount : to);

	if (!isOpen())
		throw exceptions::illegal_state("Folder not open");
	else if (to2 < from || from < 1 || to2 < 1 || from > m_messageCount || to2 > m_messageCount)
		throw exceptions::message_not_found();

	std::vector <ref <message> > v;

	for (int i = from ; i <= to2 ; ++i)
	{
		v.push_back(vmime::create <maildirMessage>
			(thisWeakRef().dynamicCast <maildirFolder>(), i));
	}

	return (v);
}


std::vector <ref <message> > maildirFolder::getMessages(const std::vector <int>& nums)
{
	if (!isOpen())
		throw exceptions::illegal_state("Folder not open");

	std::vector <ref <message> > v;

	for (std::vector <int>::const_iterator it = nums.begin() ; it != nums.end() ; ++it)
	{
		v.push_back(vmime::create <maildirMessage>
			(thisWeakRef().dynamicCast <maildirFolder>(), *it));
	}

	return (v);
}


const int maildirFolder::getMessageCount()
{
	return (m_messageCount);
}


ref <folder> maildirFolder::getFolder(const folder::path::component& name)
{
	if (!m_store)
		throw exceptions::illegal_state("Store disconnected");

	return vmime::create <maildirFolder>(m_path / name, m_store);
}


std::vector <ref <folder> > maildirFolder::getFolders(const bool recursive)
{
	if (!isOpen() && !m_store)
		throw exceptions::illegal_state("Store disconnected");

	std::vector <ref <folder> > list;

	listFolders(list, recursive);

	return (list);
}


void maildirFolder::listFolders(std::vector <ref <folder> >& list, const bool recursive)
{
	try
	{
		utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

		ref <utility::file> rootDir = fsf->create
			(maildirUtils::getFolderFSPath(m_store, m_path,
				m_path.isEmpty() ? maildirUtils::FOLDER_PATH_ROOT
				                 : maildirUtils::FOLDER_PATH_CONTAINER));

		if (rootDir->exists())
		{
			ref <utility::fileIterator> it = rootDir->getFiles();

			while (it->hasMoreElements())
			{
				ref <utility::file> file = it->nextElement();

				if (maildirUtils::isSubfolderDirectory(*file))
				{
					const utility::path subPath =
						m_path / file->getFullPath().getLastComponent();

					ref <maildirFolder> subFolder =
						vmime::create <maildirFolder>(subPath, m_store);

					list.push_back(subFolder);

					if (recursive)
						subFolder->listFolders(list, true);
				}
			}
		}
		else
		{
			// No sub-folder
		}
	}
	catch (exceptions::filesystem_exception& e)
	{
		throw exceptions::command_error("LIST", "", "", e);
	}
}


void maildirFolder::rename(const folder::path& newPath)
{
	if (!m_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_store->isValidFolderName(newPath.getLastComponent()))
		throw exceptions::invalid_folder_name();

	// Rename the directory on the file system
	utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

	ref <utility::file> rootDir = fsf->create
		(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT));
	ref <utility::file> contDir = fsf->create
		(maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER));

	try
	{
		const utility::file::path newRootPath =
			maildirUtils::getFolderFSPath(m_store, newPath, maildirUtils::FOLDER_PATH_ROOT);
		const utility::file::path newContPath =
			maildirUtils::getFolderFSPath(m_store, newPath, maildirUtils::FOLDER_PATH_CONTAINER);

		rootDir->rename(newRootPath);

		// Container directory may not exist, so ignore error when trying to rename it
		try
		{
			contDir->rename(newContPath);
		}
		catch (exceptions::filesystem_exception& e)
		{
			// Ignore
		}
	}
	catch (exceptions::filesystem_exception& e)
	{
		// Revert to old location
		const utility::file::path rootPath =
			maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_ROOT);
		const utility::file::path contPath =
			maildirUtils::getFolderFSPath(m_store, m_path, maildirUtils::FOLDER_PATH_CONTAINER);

		try
		{
			rootDir->rename(rootPath);
			contDir->rename(contPath);
		}
		catch (exceptions::filesystem_exception& e)
		{
			// Ignore
		}

		throw exceptions::command_error("RENAME", "", "", e);
	}

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

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

	events::folderEvent event
		(thisRef().dynamicCast <folder>(),
		 events::folderEvent::TYPE_RENAMED, oldPath, newPath);

	notifyFolder(event);

	// Notify folders with the same path
	for (std::list <maildirFolder*>::iterator it = m_store->m_folders.begin() ;
	     it != m_store->m_folders.end() ; ++it)
	{
		if ((*it) != this && (*it)->getFullPath() == oldPath)
		{
			(*it)->m_path = newPath;
			(*it)->m_name = newPath.getLastComponent();

			events::folderEvent event
				((*it)->thisRef().dynamicCast <folder>(),
				 events::folderEvent::TYPE_RENAMED, oldPath, newPath);

			(*it)->notifyFolder(event);
		}
		else if ((*it) != this && oldPath.isParentOf((*it)->getFullPath()))
		{
			folder::path oldPath((*it)->m_path);

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

			events::folderEvent event
				((*it)->thisRef().dynamicCast <folder>(),
				 events::folderEvent::TYPE_RENAMED, oldPath, (*it)->m_path);

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


void maildirFolder::deleteMessage(const int num)
{
	// Mark messages as deleted
	setMessageFlags(num, num, message::FLAG_MODE_ADD, message::FLAG_DELETED);
}


void maildirFolder::deleteMessages(const int from, const int to)
{
	// Mark messages as deleted
	setMessageFlags(from, to, message::FLAG_MODE_ADD, message::FLAG_DELETED);
}


void maildirFolder::deleteMessages(const std::vector <int>& nums)
{
	// Mark messages as deleted
	setMessageFlags(nums, message::FLAG_MODE_ADD, message::FLAG_DELETED);
}


void maildirFolder::setMessageFlags
	(const int from, const int to, const int flags, const int mode)
{
	if (from < 1 || (to < from && to != -1))
		throw exceptions::invalid_argument();

	if (!m_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");

	// Construct the list of message numbers
	const int to2 = (to == -1) ? m_messageCount : to;
	const int count = to - from + 1;

	std::vector <int> nums;
	nums.resize(count);

	for (int i = from, j = 0 ; i <= to2 ; ++i, ++j)
		nums[j] = i;

	// Change message flags
	setMessageFlagsImpl(nums, flags, mode);

	// Update local flags
	switch (mode)
	{
	case message::FLAG_MODE_ADD:
	{
		for (std::vector <maildirMessage*>::iterator it =
		     m_messages.begin() ; it != m_messages.end() ; ++it)
		{
			if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 &&
			    (*it)->m_flags != message::FLAG_UNDEFINED)
			{
				(*it)->m_flags |= flags;
			}
		}

		break;
	}
	case message::FLAG_MODE_REMOVE:
	{
		for (std::vector <maildirMessage*>::iterator it =
		     m_messages.begin() ; it != m_messages.end() ; ++it)
		{
			if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 &&
			    (*it)->m_flags != message::FLAG_UNDEFINED)
			{
				(*it)->m_flags &= ~flags;
			}
		}

		break;
	}
	default:
	case message::FLAG_MODE_SET:
	{
		for (std::vector <maildirMessage*>::iterator it =
		     m_messages.begin() ; it != m_messages.end() ; ++it)
		{
			if ((*it)->getNumber() >= from && (*it)->getNumber() <= to2 &&
			    (*it)->m_flags != message::FLAG_UNDEFINED)
			{
				(*it)->m_flags = flags;
			}
		}

		break;
	}

	}

	// Notify message flags changed
	events::messageChangedEvent event
		(thisRef().dynamicCast <folder>(),
		 events::messageChangedEvent::TYPE_FLAGS, nums);

	notifyMessageChanged(event);

	// TODO: notify other folders with the same path
}


void maildirFolder::setMessageFlags
	(const std::vector <int>& nums, const int flags, const int mode)
{
	if (!m_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");

	// Sort the list of message numbers
	std::vector <int> list;

	list.resize(nums.size());
	std::copy(nums.begin(), nums.end(), list.begin());

	std::sort(list.begin(), list.end());

	// Change message flags
	setMessageFlagsImpl(list, flags, mode);

	// Update local flags
	switch (mode)
	{
	case message::FLAG_MODE_ADD:
	{
		for (std::vector <maildirMessage*>::iterator it =
		     m_messages.begin() ; it != m_messages.end() ; ++it)
		{
			if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) &&
			    (*it)->m_flags != message::FLAG_UNDEFINED)
			{
				(*it)->m_flags |= flags;
			}
		}

		break;
	}
	case message::FLAG_MODE_REMOVE:
	{
		for (std::vector <maildirMessage*>::iterator it =
		     m_messages.begin() ; it != m_messages.end() ; ++it)
		{
			if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) &&
			    (*it)->m_flags != message::FLAG_UNDEFINED)
			{
				(*it)->m_flags &= ~flags;
			}
		}

		break;
	}
	default:
	case message::FLAG_MODE_SET:
	{
		for (std::vector <maildirMessage*>::iterator it =
		     m_messages.begin() ; it != m_messages.end() ; ++it)
		{
			if (std::binary_search(list.begin(), list.end(), (*it)->getNumber()) &&
			    (*it)->m_flags != message::FLAG_UNDEFINED)
			{
				(*it)->m_flags = flags;
			}
		}

		break;
	}

	}

	// Notify message flags changed
	events::messageChangedEvent event
		(thisRef().dynamicCast <folder>(),
		 events::messageChangedEvent::TYPE_FLAGS, nums);

	notifyMessageChanged(event);

	// TODO: notify other folders with the same path
}


void maildirFolder::setMessageFlagsImpl
	(const std::vector <int>& nums, const int flags, const int mode)
{
	utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

	utility::file::path curDirPath = maildirUtils::getFolderFSPath
		(m_store, m_path, maildirUtils::FOLDER_PATH_CUR);

	for (std::vector <int>::const_iterator it =
	     nums.begin() ; it != nums.end() ; ++it)
	{
		const int num = *it - 1;

		try
		{
			const utility::file::path::component path = m_messageInfos[num].path;
			ref <utility::file> file = fsf->create(curDirPath / path);

			int newFlags = maildirUtils::extractFlags(path);

			switch (mode)
			{
			case message::FLAG_MODE_ADD:    newFlags |= flags; break;
			case message::FLAG_MODE_REMOVE: newFlags &= ~flags; break;
			default:
			case message::FLAG_MODE_SET:    newFlags = flags; break;
			}

			const utility::file::path::component newPath = maildirUtils::buildFilename
				(maildirUtils::extractId(path), newFlags);

			file->rename(curDirPath / newPath);

			if (flags & message::FLAG_DELETED)
				m_messageInfos[num].type = messageInfos::TYPE_DELETED;
			else
				m_messageInfos[num].type = messageInfos::TYPE_CUR;

			m_messageInfos[num].path = newPath;
		}
		catch (exceptions::filesystem_exception& e)
		{
			// Ignore (not important)
		}
	}
}


void maildirFolder::addMessage(ref <vmime::message> msg, const int flags,
	vmime::datetime* date, utility::progressionListener* progress)
{
	std::ostringstream oss;
	utility::outputStreamAdapter ossAdapter(oss);

	msg->generate(ossAdapter);

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

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


void maildirFolder::addMessage(utility::inputStream& is, const int size,
	const int flags, vmime::datetime* /* date */, utility::progressionListener* progress)
{
	if (!m_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");

	utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

	utility::file::path tmpDirPath = maildirUtils::getFolderFSPath
		(m_store, m_path, maildirUtils::FOLDER_PATH_TMP);
	utility::file::path curDirPath = maildirUtils::getFolderFSPath
		(m_store, m_path, maildirUtils::FOLDER_PATH_CUR);

	const utility::file::path::component filename =
		maildirUtils::buildFilename(maildirUtils::generateId(),
			((flags == message::FLAG_UNDEFINED) ? 0 : flags));

	try
	{
		ref <utility::file> tmpDir = fsf->create(tmpDirPath);
		tmpDir->createDirectory(true);
	}
	catch (exceptions::filesystem_exception&)
	{
		// Don't throw now, it will fail later...
	}

	try
	{
		ref <utility::file> curDir = fsf->create(curDirPath);
		curDir->createDirectory(true);
	}
	catch (exceptions::filesystem_exception&)
	{
		// Don't throw now, it will fail later...
	}

	// Actually add the message
	copyMessageImpl(tmpDirPath, curDirPath, filename, is, size, progress);

	// Append the message to the cache list
	messageInfos msgInfos;
	msgInfos.path = filename;
	msgInfos.type = messageInfos::TYPE_CUR;

	m_messageInfos.push_back(msgInfos);
	m_messageCount++;

	if ((flags == message::FLAG_UNDEFINED) || !(flags & message::FLAG_SEEN))
		m_unreadMessageCount++;

	// Notification
	std::vector <int> nums;
	nums.push_back(m_messageCount);

	events::messageCountEvent event
		(thisRef().dynamicCast <folder>(),
		 events::messageCountEvent::TYPE_ADDED, nums);

	notifyMessageCount(event);

	// Notify folders with the same path
	for (std::list <maildirFolder*>::iterator it = m_store->m_folders.begin() ;
	     it != m_store->m_folders.end() ; ++it)
	{
		if ((*it) != this && (*it)->getFullPath() == m_path)
		{
			(*it)->m_messageCount = m_messageCount;
			(*it)->m_unreadMessageCount = m_unreadMessageCount;

			(*it)->m_messageInfos.resize(m_messageInfos.size());
			std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin());

			events::messageCountEvent event
				((*it)->thisRef().dynamicCast <folder>(),
				 events::messageCountEvent::TYPE_ADDED, nums);

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


void maildirFolder::copyMessageImpl(const utility::file::path& tmpDirPath,
	const utility::file::path& curDirPath, const utility::file::path::component& filename,
	utility::inputStream& is, const utility::stream::size_type size,
	utility::progressionListener* progress)
{
	utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

	ref <utility::file> file = fsf->create(tmpDirPath / filename);

	if (progress)
		progress->start(size);

	// First, write the message into 'tmp'...
	try
	{
		file->createFile();

		ref <utility::fileWriter> fw = file->getFileWriter();
		ref <utility::outputStream> os = fw->getOutputStream();

		utility::stream::value_type buffer[65536];
		utility::stream::size_type total = 0;

		while (!is.eof())
		{
			const utility::stream::size_type read = is.read(buffer, sizeof(buffer));

			if (read != 0)
			{
				os->write(buffer, read);
				total += read;
			}

			if (progress)
				progress->progress(total, size);
		}
	}
	catch (exception& e)
	{
		if (progress)
			progress->stop(size);

		// Delete temporary file
		try
		{
			ref <utility::file> file = fsf->create(tmpDirPath / filename);
			file->remove();
		}
		catch (exceptions::filesystem_exception&)
		{
			// Ignore
		}

		throw exceptions::command_error("ADD", "", "", e);
	}

	// ...then, move it to 'cur'
	try
	{
		file->rename(curDirPath / filename);
	}
	catch (exception& e)
	{
		if (progress)
			progress->stop(size);

		// Delete temporary file
		try
		{
			ref <utility::file> file = fsf->create(tmpDirPath / filename);
			file->remove();
		}
		catch (exceptions::filesystem_exception&)
		{
			// Ignore
		}

		throw exceptions::command_error("ADD", "", "", e);
	}

	if (progress)
		progress->stop(size);
}


void maildirFolder::copyMessage(const folder::path& dest, const int num)
{
	if (!m_store)
		throw exceptions::illegal_state("Store disconnected");
	else if (!isOpen())
		throw exceptions::illegal_state("Folder not open");

	copyMessages(dest, num, num);
}


void maildirFolder::copyMessages(const folder::path& dest, const int from, const int to)
{
	if (!m_store)
		throw exceptions::illegal_state("Store disconnected");
	else if (!isOpen())
		throw exceptions::illegal_state("Folder not open");
	else if (from < 1 || (to < from && to != -1))
		throw exceptions::invalid_argument();

	// Construct the list of message numbers
	const int to2 = (to == -1) ? m_messageCount : to;
	const int count = to - from + 1;

	std::vector <int> nums;
	nums.resize(count);

	for (int i = from, j = 0 ; i <= to2 ; ++i, ++j)
		nums[j] = i;

	// Copy messages
	copyMessagesImpl(dest, nums);
}


void maildirFolder::copyMessages(const folder::path& dest, const std::vector <int>& nums)
{
	if (!m_store)
		throw exceptions::illegal_state("Store disconnected");
	else if (!isOpen())
		throw exceptions::illegal_state("Folder not open");

	// Copy messages
	copyMessagesImpl(dest, nums);
}


void maildirFolder::copyMessagesImpl(const folder::path& dest, const std::vector <int>& nums)
{
	utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

	utility::file::path curDirPath = maildirUtils::getFolderFSPath
		(m_store, m_path, maildirUtils::FOLDER_PATH_CUR);

	utility::file::path destCurDirPath = maildirUtils::getFolderFSPath
		(m_store, dest, maildirUtils::FOLDER_PATH_CUR);
	utility::file::path destTmpDirPath = maildirUtils::getFolderFSPath
		(m_store, dest, maildirUtils::FOLDER_PATH_TMP);

	// Create destination directories
	try
	{
		ref <utility::file> destTmpDir = fsf->create(destTmpDirPath);
		destTmpDir->createDirectory(true);
	}
	catch (exceptions::filesystem_exception&)
	{
		// Don't throw now, it will fail later...
	}

	try
	{
		ref <utility::file> destCurDir = fsf->create(destCurDirPath);
		destCurDir->createDirectory(true);
	}
	catch (exceptions::filesystem_exception&)
	{
		// Don't throw now, it will fail later...
	}

	// Copy messages
	try
	{
		for (std::vector <int>::const_iterator it =
		     nums.begin() ; it != nums.end() ; ++it)
		{
			const int num = *it;
			const messageInfos& msg = m_messageInfos[num - 1];
			const int flags = maildirUtils::extractFlags(msg.path);

			const utility::file::path::component filename =
				maildirUtils::buildFilename(maildirUtils::generateId(), flags);

			ref <utility::file> file = fsf->create(curDirPath / msg.path);
			ref <utility::fileReader> fr = file->getFileReader();
			ref <utility::inputStream> is = fr->getInputStream();

			copyMessageImpl(destTmpDirPath, destCurDirPath,
				filename, *is, file->getLength(), NULL);
		}
	}
	catch (exception& e)
	{
		notifyMessagesCopied(dest);
		throw exceptions::command_error("COPY", "", "", e);
	}

	notifyMessagesCopied(dest);
}


void maildirFolder::notifyMessagesCopied(const folder::path& dest)
{
	for (std::list <maildirFolder*>::iterator it = m_store->m_folders.begin() ;
	     it != m_store->m_folders.end() ; ++it)
	{
		if ((*it) != this && (*it)->getFullPath() == dest)
		{
			// We only need to update the first folder we found as calling
			// status() will notify all the folders with the same path.
			int count, unseen;
			(*it)->status(count, unseen);

			return;
		}
	}
}


void maildirFolder::status(int& count, int& unseen)
{
	const int oldCount = m_messageCount;

	scanFolder();

	count = m_messageCount;
	unseen = m_unreadMessageCount;

	// Notify message count changed (new messages)
	if (count > oldCount)
	{
		std::vector <int> nums;
		nums.reserve(count - oldCount);

		for (int i = oldCount + 1, j = 0 ; i <= count ; ++i, ++j)
			nums[j] = i;

		events::messageCountEvent event
			(thisRef().dynamicCast <folder>(),
			 events::messageCountEvent::TYPE_ADDED, nums);

		notifyMessageCount(event);

		// Notify folders with the same path
		for (std::list <maildirFolder*>::iterator it = m_store->m_folders.begin() ;
		     it != m_store->m_folders.end() ; ++it)
		{
			if ((*it) != this && (*it)->getFullPath() == m_path)
			{
				(*it)->m_messageCount = m_messageCount;
				(*it)->m_unreadMessageCount = m_unreadMessageCount;

				(*it)->m_messageInfos.resize(m_messageInfos.size());
				std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin());

				events::messageCountEvent event
					((*it)->thisRef().dynamicCast <folder>(),
					 events::messageCountEvent::TYPE_ADDED, nums);

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


void maildirFolder::expunge()
{
	if (!m_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");

	utility::fileSystemFactory* fsf = platformDependant::getHandler()->getFileSystemFactory();

	utility::file::path curDirPath = maildirUtils::getFolderFSPath
		(m_store, m_path, maildirUtils::FOLDER_PATH_CUR);

	std::vector <int> nums;
	int unreadCount = 0;

	for (int num = 1 ; num <= m_messageCount ; ++num)
	{
		messageInfos& infos = m_messageInfos[num - 1];

		if (infos.type == messageInfos::TYPE_DELETED)
		{
			nums.push_back(num);

			for (std::vector <maildirMessage*>::iterator it =
			     m_messages.begin() ; it != m_messages.end() ; ++it)
			{
				if ((*it)->m_num == num)
					(*it)->m_expunged = true;
				else if ((*it)->m_num > num)
					(*it)->m_num--;
			}

			if (maildirUtils::extractFlags(infos.path) & message::FLAG_SEEN)
				++unreadCount;

			// Delete file from file system
			try
			{
				ref <utility::file> file = fsf->create(curDirPath / infos.path);
				file->remove();
			}
			catch (exceptions::filesystem_exception& e)
			{
				// Ignore (not important)
			}
		}
	}

	if (!nums.empty())
	{
		for (int i = nums.size() - 1 ; i >= 0 ; --i)
			m_messageInfos.erase(m_messageInfos.begin() + i);
	}

	m_messageCount -= nums.size();
	m_unreadMessageCount -= unreadCount;

	// Notify message expunged
	events::messageCountEvent event
		(thisRef().dynamicCast <folder>(),
		 events::messageCountEvent::TYPE_REMOVED, nums);

	notifyMessageCount(event);

	// Notify folders with the same path
	for (std::list <maildirFolder*>::iterator it = m_store->m_folders.begin() ;
	     it != m_store->m_folders.end() ; ++it)
	{
		if ((*it) != this && (*it)->getFullPath() == m_path)
		{
			(*it)->m_messageCount = m_messageCount;
			(*it)->m_unreadMessageCount = m_unreadMessageCount;

			(*it)->m_messageInfos.resize(m_messageInfos.size());
			std::copy(m_messageInfos.begin(), m_messageInfos.end(), (*it)->m_messageInfos.begin());

			events::messageCountEvent event
				((*it)->thisRef().dynamicCast <folder>(),
				 events::messageCountEvent::TYPE_REMOVED, nums);

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


ref <folder> maildirFolder::getParent()
{
	if (m_path.isEmpty())
		return NULL;
	else
		return vmime::create <maildirFolder>(m_path.getParent(), m_store);
}


weak_ref <const store> maildirFolder::getStore() const
{
	return (m_store);
}


weak_ref <store> maildirFolder::getStore()
{
	return (m_store);
}


void maildirFolder::fetchMessages(std::vector <ref <message> >& msg,
	const int options, utility::progressionListener* progress)
{
	if (!m_store)
		throw exceptions::illegal_state("Store disconnected");
	else if (!isOpen())
		throw exceptions::illegal_state("Folder not open");

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

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

	weak_ref <maildirFolder> _this = thisWeakRef().dynamicCast <maildirFolder>();

	for (std::vector <ref <message> >::iterator it = msg.begin() ;
	     it != msg.end() ; ++it)
	{
		(*it).dynamicCast <maildirMessage>()->fetch(_this, options);

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

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


void maildirFolder::fetchMessage(ref <message> msg, const int options)
{
	if (!m_store)
		throw exceptions::illegal_state("Store disconnected");
	else if (!isOpen())
		throw exceptions::illegal_state("Folder not open");

	msg.dynamicCast <maildirMessage>()->fetch
		(thisWeakRef().dynamicCast <maildirFolder>(), options);
}


const int maildirFolder::getFetchCapabilities() const
{
	return (FETCH_ENVELOPE | FETCH_STRUCTURE | FETCH_CONTENT_INFO |
	        FETCH_FLAGS | FETCH_SIZE | FETCH_FULL_HEADER | FETCH_UID |
	        FETCH_IMPORTANCE);
}


const utility::file::path maildirFolder::getMessageFSPath(const int number) const
{
	utility::file::path curDirPath = maildirUtils::getFolderFSPath
		(m_store, m_path, maildirUtils::FOLDER_PATH_CUR);

	return (curDirPath / m_messageInfos[number - 1].path);
}


} // maildir
} // net
} // vmime