//
// 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_POP3


#include "vmime/net/pop3/POP3Folder.hpp"

#include "vmime/net/pop3/POP3Store.hpp"
#include "vmime/net/pop3/POP3Message.hpp"
#include "vmime/net/pop3/POP3Command.hpp"
#include "vmime/net/pop3/POP3Response.hpp"
#include "vmime/net/pop3/POP3FolderStatus.hpp"

#include "vmime/net/pop3/POP3Utils.hpp"

#include "vmime/exception.hpp"


namespace vmime {
namespace net {
namespace pop3 {


POP3Folder::POP3Folder(
	const folder::path& path,
	const shared_ptr <POP3Store>& store
)
	: m_store(store),
	  m_path(path),
	  m_name(path.isEmpty() ? folder::path::component("") : path.getLastComponent()),
	  m_mode(-1),
	  m_open(false) {

	store->registerFolder(this);
}


POP3Folder::~POP3Folder() {

	try {

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

		if (store) {

			if (m_open) {
				close(false);
			}

			store->unregisterFolder(this);

		} else if (m_open) {

			onClose();
		}

	} catch (...) {

		// Don't throw in destructor
	}
}


int POP3Folder::getMode() const {

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

	return m_mode;
}


const folderAttributes POP3Folder::getAttributes() {

	folderAttributes attribs;

	if (m_path.isEmpty()) {
		attribs.setType(folderAttributes::TYPE_CONTAINS_FOLDERS);
	} else if (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX") {
		attribs.setType(folderAttributes::TYPE_CONTAINS_MESSAGES);
		attribs.setSpecialUse(folderAttributes::SPECIALUSE_INBOX);
	} else {
		throw exceptions::folder_not_found();
	}

	attribs.setFlags(0);

	return attribs;
}


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

	return m_name;
}


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

	return m_path;
}


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

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

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

	if (m_path.isEmpty()) {

		if (mode != MODE_READ_ONLY && failIfModeIsNotAvailable) {
			throw exceptions::operation_not_supported();
		}

		m_open = true;
		m_mode = mode;

		m_messageCount = 0;

	} else if (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX") {

		POP3Command::STAT()->send(store->getConnection());

		shared_ptr <POP3Response> response = POP3Response::readResponse(store->getConnection());

		if (!response->isSuccess()) {
			throw exceptions::command_error("STAT", response->getFirstLine());
		}

		std::istringstream iss(response->getText());
		iss.imbue(std::locale::classic());
		iss >> m_messageCount;

		if (iss.fail()) {
			throw exceptions::invalid_response("STAT", response->getFirstLine());
		}

		m_open = true;
		m_mode = mode;

	} else {

		throw exceptions::folder_not_found();
	}
}


void POP3Folder::close(const bool expunge) {

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

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

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

	if (!expunge) {
		POP3Command::RSET()->send(store->getConnection());
		POP3Response::readResponse(store->getConnection());
	}

	m_open = false;
	m_mode = -1;

	onClose();
}


void POP3Folder::onClose() {

	for (MessageMap::iterator it = m_messages.begin() ; it != m_messages.end() ; ++it) {
		(*it).first->onFolderClosed();
	}

	m_messages.clear();
}


void POP3Folder::create(const folderAttributes& /* attribs */) {

	throw exceptions::operation_not_supported();
}


void POP3Folder::destroy() {

	throw exceptions::operation_not_supported();
}


bool POP3Folder::exists() {

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

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

	return m_path.isEmpty() || (m_path.getSize() == 1 && m_path[0].getBuffer() == "INBOX");
}


bool POP3Folder::isOpen() const {

	return m_open;
}


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

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

	if (!store) {
		throw exceptions::illegal_state("Store disconnected");
	} else if (!isOpen()) {
		throw exceptions::illegal_state("Folder not open");
	} else if (num < 1 || num > m_messageCount) {
		throw exceptions::message_not_found();
	}

	return make_shared <POP3Message>(dynamicCast <POP3Folder>(shared_from_this()), num);
}


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

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

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

	if (msgs.isNumberSet()) {

		const std::vector <size_t> numbers = POP3Utils::messageSetToNumberList(msgs, m_messageCount);

		std::vector <shared_ptr <message> > messages;
		shared_ptr <POP3Folder> thisFolder(dynamicCast <POP3Folder>(shared_from_this()));

		for (std::vector <size_t>::const_iterator it = numbers.begin() ; it != numbers.end() ; ++it) {

			if (*it < 1|| *it > m_messageCount) {
				throw exceptions::message_not_found();
			}

			messages.push_back(make_shared <POP3Message>(thisFolder, *it));
		}

		return messages;

	} else {

		throw exceptions::operation_not_supported();
	}
}


size_t POP3Folder::getMessageCount() {

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

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

	return m_messageCount;
}


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

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

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

	return shared_ptr <POP3Folder>(new POP3Folder(m_path / name, store));
}


std::vector <shared_ptr <folder> > POP3Folder::getFolders(const bool /* recursive */) {

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

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

	if (m_path.isEmpty()) {

		std::vector <shared_ptr <folder> > v;
		v.push_back(shared_ptr <POP3Folder>(new POP3Folder(folder::path::component("INBOX"), store)));
		return v;

	} else {

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


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

	shared_ptr <POP3Store> 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;
	}

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

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

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

		dynamicCast <POP3Message>(*it)->fetch(
			dynamicCast <POP3Folder>(shared_from_this()),
			options
		);

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

	if (options.has(fetchAttributes::SIZE)) {

		// Send the "LIST" command
		POP3Command::LIST()->send(store->getConnection());

		// Get the response
		shared_ptr <POP3Response> response =
			POP3Response::readMultilineResponse(store->getConnection());

		if (response->isSuccess()) {

			// C: LIST
			// S: +OK
			// S: 1 47548
			// S: 2 12653
			// S: .
			std::map <size_t, string> result;
			POP3Utils::parseMultiListOrUidlResponse(response, result);

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

				shared_ptr <POP3Message> m = dynamicCast <POP3Message>(*it);

				std::map <size_t, string>::const_iterator x = result.find(m->m_num);

				if (x != result.end()) {

					size_t size = 0;

					std::istringstream iss((*x).second);
					iss.imbue(std::locale::classic());
					iss >> size;

					m->m_size = size;
				}
			}
		}
	}

	if (options.has(fetchAttributes::UID)) {

		// Send the "UIDL" command
		POP3Command::UIDL()->send(store->getConnection());

		// Get the response
		shared_ptr <POP3Response> response =
			POP3Response::readMultilineResponse(store->getConnection());

		if (response->isSuccess()) {

			// C: UIDL
			// S: +OK
			// S: 1 whqtswO00WBw418f9t5JxYwZ
			// S: 2 QhdPYR:00WBw1Ph7x7
			// S: .
			std::map <size_t, string> result;
			POP3Utils::parseMultiListOrUidlResponse(response, result);

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

				shared_ptr <POP3Message> m = dynamicCast <POP3Message>(*it);

				std::map <size_t, string>::const_iterator x = result.find(m->m_num);

				if (x != result.end()) {
					m->m_uid = (*x).second;
				}
			}
		}
	}

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


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

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

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

	dynamicCast <POP3Message>(msg)->fetch(
		dynamicCast <POP3Folder>(shared_from_this()),
		options
	);

	if (options.has(fetchAttributes::SIZE)) {

		// Send the "LIST" command
		POP3Command::LIST(msg->getNumber())->send(store->getConnection());

		// Get the response
		shared_ptr <POP3Response> response =
			POP3Response::readResponse(store->getConnection());

		if (response->isSuccess()) {

			string responseText = response->getText();

			// C: LIST 2
			// S: +OK 2 4242
			string::iterator it = responseText.begin();

			while (it != responseText.end() && (*it == ' ' || *it == '\t')) ++it;
			while (it != responseText.end() && !(*it == ' ' || *it == '\t')) ++it;
			while (it != responseText.end() && (*it == ' ' || *it == '\t')) ++it;

			if (it != responseText.end()) {

				size_t size = 0;

				std::istringstream iss(string(it, responseText.end()));
				iss.imbue(std::locale::classic());
				iss >> size;

				dynamicCast <POP3Message>(msg)->m_size = size;
			}
		}
	}

	if (options.has(fetchAttributes::UID)) {

		// Send the "UIDL" command
		POP3Command::UIDL(msg->getNumber())->send(store->getConnection());

		// Get the response
		shared_ptr <POP3Response> response =
			POP3Response::readResponse(store->getConnection());

		if (response->isSuccess()) {

			string responseText = response->getText();

			// C: UIDL 2
			// S: +OK 2 QhdPYR:00WBw1Ph7x7
			string::iterator it = responseText.begin();

			while (it != responseText.end() && (*it == ' ' || *it == '\t')) ++it;
			while (it != responseText.end() && !(*it == ' ' || *it == '\t')) ++it;
			while (it != responseText.end() && (*it == ' ' || *it == '\t')) ++it;

			if (it != responseText.end()) {
				dynamicCast <POP3Message>(msg)->m_uid = string(it, responseText.end());
			}
		}
	}
}


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

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

	std::vector <shared_ptr <message> > messages = getMessages(msgs);
	fetchMessages(messages, attribs);

	return messages;
}


int POP3Folder::getFetchCapabilities() const {

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


shared_ptr <folder> POP3Folder::getParent() {

	if (m_path.isEmpty()) {
		return null;
	} else {
		return shared_ptr <POP3Folder>(new POP3Folder(m_path.getParent(), m_store.lock()));
	}
}


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

	return m_store.lock();
}


shared_ptr <store> POP3Folder::getStore() {

	return m_store.lock();
}


void POP3Folder::registerMessage(POP3Message* msg) {

	m_messages.insert(MessageMap::value_type(msg, msg->getNumber()));
}


void POP3Folder::unregisterMessage(POP3Message* msg) {

	m_messages.erase(msg);
}


void POP3Folder::onStoreDisconnected() {

	m_store.reset();
}


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

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

	const std::vector <size_t> nums = POP3Utils::messageSetToNumberList(msgs, m_messageCount);

	if (nums.empty()) {
		throw exceptions::invalid_argument();
	}

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

	for (std::vector <size_t>::const_iterator
	     it = nums.begin() ; it != nums.end() ; ++it) {

		POP3Command::DELE(*it)->send(store->getConnection());

		shared_ptr <POP3Response> response =
			POP3Response::readResponse(store->getConnection());

		if (!response->isSuccess()) {
			throw exceptions::command_error("DELE", response->getFirstLine());
		}
	}

	// Sort message list
	std::vector <size_t> list;

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

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

	// Update local flags
	for (std::map <POP3Message*, size_t>::iterator it =
	     m_messages.begin() ; it != m_messages.end() ; ++it) {

		POP3Message* msg = (*it).first;

		if (std::binary_search(list.begin(), list.end(), msg->getNumber())) {
			msg->m_deleted = true;
		}
	}

	// Notify message flags changed
	shared_ptr <events::messageChangedEvent> event =
		make_shared <events::messageChangedEvent>(
			dynamicCast <folder>(shared_from_this()),
			events::messageChangedEvent::TYPE_FLAGS,
			list
		);

	notifyMessageChanged(event);
}


void POP3Folder::setMessageFlags(
	const messageSet& /* msgs */,
	const int /* flags */,
	const int /* mode */
) {

	throw exceptions::operation_not_supported();
}


void POP3Folder::rename(const folder::path& /* newPath */) {

	throw exceptions::operation_not_supported();
}


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

	throw exceptions::operation_not_supported();
}


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

	throw exceptions::operation_not_supported();
}


messageSet POP3Folder::copyMessages(
	const folder::path& /* dest */,
	const messageSet& /* msgs */
) {

	throw exceptions::operation_not_supported();
}


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

	count = 0;
	unseen = 0;

	shared_ptr <folderStatus> status = getStatus();

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

	m_messageCount = count;
}


shared_ptr <folderStatus> POP3Folder::getStatus() {

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

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

	POP3Command::STAT()->send(store->getConnection());

	shared_ptr <POP3Response> response =
		POP3Response::readResponse(store->getConnection());

	if (!response->isSuccess()) {
		throw exceptions::command_error("STAT", response->getFirstLine());
	}


	size_t count = 0;

	std::istringstream iss(response->getText());
	iss.imbue(std::locale::classic());
	iss >> count;

	shared_ptr <POP3FolderStatus> status = make_shared <POP3FolderStatus>();

	status->setMessageCount(count);
	status->setUnseenCount(count);

	// Update local message count
	if (m_messageCount != count) {

		const size_t oldCount = m_messageCount;

		m_messageCount = count;

		if (count > oldCount) {

			std::vector <size_t> nums;
			nums.resize(count - oldCount);

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

			// Notify message count changed
			shared_ptr <events::messageCountEvent> event =
				make_shared <events::messageCountEvent>(
					dynamicCast <folder>(shared_from_this()),
					events::messageCountEvent::TYPE_ADDED,
					nums
				);

			notifyMessageCount(event);

			// Notify folders with the same path
			for (std::list <POP3Folder*>::iterator it = store->m_folders.begin() ;
			     it != store->m_folders.end() ; ++it) {

				if ((*it) != this && (*it)->getFullPath() == m_path) {

					(*it)->m_messageCount = count;

					shared_ptr <events::messageCountEvent> event =
						make_shared <events::messageCountEvent>(
							dynamicCast <folder>((*it)->shared_from_this()),
							events::messageCountEvent::TYPE_ADDED,
							nums
						);

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

	return status;
}


void POP3Folder::expunge() {

	// Not supported by POP3 protocol (deleted messages are automatically
	// expunged at the end of the session...).
}


std::vector <size_t> POP3Folder::getMessageNumbersStartingOnUID(const message::uid& /* uid */) {

	throw exceptions::operation_not_supported();
}


} // pop3
} // net
} // vmime


#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3

