From 7d0cd94de71d9e55e573e28206470439ecde3db5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C4=83t=C4=83lin=20Badea?= <catalin.badea392@gmail.com>
Date: Tue, 21 Aug 2012 22:06:05 +0300
Subject: History dialog

Add history dialog as an experimental feature.

License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.

diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot
index d6977fe..917935d 100644
--- a/BuildTools/SCons/SConscript.boot
+++ b/BuildTools/SCons/SConscript.boot
@@ -180,7 +180,7 @@ if not env["assertions"] :
 	env.Append(CPPDEFINES = ["NDEBUG"])
 
 if env["experimental"] :
-	env.Append(CPPDEFINES = ["SWIFT_EXPERIMENTAL_FT"])
+	env.Append(CPPDEFINES = ["SWIFT_EXPERIMENTAL_FT", "SWIFT_EXPERIMENTAL_HISTORY"])
 
 # If we build shared libs on AMD64, we need -fPIC.
 # This should have no performance impact om AMD64
diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct
index cd7a25a..42dc36a 100644
--- a/BuildTools/SCons/SConstruct
+++ b/BuildTools/SCons/SConstruct
@@ -358,21 +358,21 @@ else :
 	env["LIBNATPMP_FLAGS"] = {}
 
 # SQLite
-#sqlite_conf_env = conf_env.Clone()
-#sqlite_flags = {}
-#if env.get("sqlite_libdir", None) :
-#	sqlite_flags["LIBPATH"] = [env["sqlite_libdir"]]
-#if env.get("sqlite_includedir", None) :
-#	sqlite_flags["CPPPATH"] = [env["sqlite_includedir"]]
-#sqlite_conf_env.MergeFlags(sqlite_flags)
-#conf = Configure(sqlite_conf_env)
-#if conf.CheckCHeader("sqlite3.h") and conf.CheckLib(env["sqlite_libname"]) :
-#	env["HAVE_SQLITE"] = 1
-#	env["SQLITE_FLAGS"] = { "LIBS": [env["sqlite_libname"]] }
-#	env["SQLITE_FLAGS"].update(sqlite_flags)
-#else :
-#	env["SQLITE_BUNDLED"] = 1
-#conf.Finish()
+sqlite_conf_env = conf_env.Clone()
+sqlite_flags = {}
+if env.get("sqlite_libdir", None) :
+	sqlite_flags["LIBPATH"] = [env["sqlite_libdir"]]
+if env.get("sqlite_includedir", None) :
+	sqlite_flags["CPPPATH"] = [env["sqlite_includedir"]]
+sqlite_conf_env.MergeFlags(sqlite_flags)
+conf = Configure(sqlite_conf_env)
+if conf.CheckCHeader("sqlite3.h") and conf.CheckLib(env["sqlite_libname"]) :
+	env["HAVE_SQLITE"] = 1
+	env["SQLITE_FLAGS"] = { "LIBS": [env["sqlite_libname"]] }
+	env["SQLITE_FLAGS"].update(sqlite_flags)
+else :
+	env["SQLITE_BUNDLED"] = 1
+conf.Finish()
 
 # Lua
 env["LUA_BUNDLED"] = 1
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 2fa4559..f40f704 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -37,8 +37,8 @@ namespace Swift {
 /**
  * The controller does not gain ownership of the stanzaChannel, nor the factory.
  */
-ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings)
-	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings) {
+ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry)
+	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings) {
 	isInMUC_ = isInMUC;
 	lastWasPresence_ = false;
 	chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -353,4 +353,18 @@ boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(bo
 	return message->getTimestamp();
 }
 
+void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) {
+	HistoryMessage::Type type;
+	if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) {
+		type = HistoryMessage::PrivateMessage;
+	}
+	else {
+		type = HistoryMessage::Chat;
+	}
+
+	if (historyController_) {
+		historyController_->addMessage(message, fromJID, toJID, type, timeStamp);
+	}
+}
+
 }
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 7043231..a873ae9 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -21,10 +21,11 @@ namespace Swift {
 	class EntityCapsProvider;
 	class FileTransferController;
 	class SettingsProvider;
+	class HistoryController;
 
 	class ChatController : public ChatControllerBase {
 		public:
-			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings);
+			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry);
 			virtual ~ChatController();
 			virtual void setToJID(const JID& jid);
 			virtual void setOnline(bool online);
@@ -34,6 +35,7 @@ namespace Swift {
 		protected:
 			void cancelReplaces();
 			JID getBaseJID();
+			void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming);
 
 		private:
 			void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 2bfff4f..b5fe0c0 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -33,7 +33,7 @@
 
 namespace Swift {
 
-ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider) {
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry) {
 	chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
 	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
 	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
@@ -133,8 +133,9 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool
 		}
 	}
 	preSendMessageRequest(message);
+
+	boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
 	if (useDelayForLatency_) {
-		boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
 		message->addPayload(boost::make_shared<Delay>(now, selfJID_));
 	}
 	if (isCorrectionMessage) {
@@ -144,6 +145,10 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool
 	stanzaChannel_->sendMessage(message);
 	postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message));
 	onActivity(message->getBody());
+
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	logMessage(body, selfJID_, toJID_, now, false);
+#endif
 }
 
 void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) {
@@ -251,6 +256,8 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 		else {
 			lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp);
 		}
+
+		logMessage(body, from, selfJID_, timeStamp, true);
 	}
 	chatWindow_->show();
 	chatWindow_->setUnreadMessageCount(unreadMessages_.size());
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 8aed069..b26af02 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -27,6 +27,8 @@
 #include "Swiften/Presence/PresenceOracle.h"
 #include "Swiften/Queries/IQRouter.h"
 #include "Swiften/Base/IDGenerator.h"
+#include <Swift/Controllers/HistoryController.h>
+#include <Swiften/MUC/MUCRegistry.h>
 
 namespace Swift {
 	class IQRouter;
@@ -58,7 +60,7 @@ namespace Swift {
 			void handleCapsChanged(const JID& jid);
 
 		protected:
-			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider);
+			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry);
 
 			/**
 			 * Pass the Message appended, and the stanza used to send it.
@@ -78,6 +80,7 @@ namespace Swift {
 			virtual void cancelReplaces() = 0;
 			/** JID any iq for account should go to - bare except for PMs */
 			virtual JID getBaseJID();
+			virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0;
 
 		private:
 			IDGenerator idGenerator_;
@@ -111,5 +114,7 @@ namespace Swift {
 			TimerFactory* timerFactory_;
 			EntityCapsProvider* entityCapsProvider_;
 			SecurityLabelsCatalog::Item lastLabel_; 
+			HistoryController* historyController_;
+			MUCRegistry* mucRegistry_;
 	};
 }
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 4fc3752..6b51df6 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -71,7 +71,8 @@ ChatsManager::ChatsManager(
 		FileTransferOverview* ftOverview,
 		XMPPRoster* roster,
 		bool eagleMode,
-		SettingsProvider* settings) :
+		SettingsProvider* settings,
+		HistoryController* historyController) :
 			jid_(jid), 
 			joinMUCWindowFactory_(joinMUCWindowFactory), 
 			useDelayForLatency_(useDelayForLatency), 
@@ -81,7 +82,8 @@ ChatsManager::ChatsManager(
 			ftOverview_(ftOverview),
 			roster_(roster),
 			eagleMode_(eagleMode),
-			settings_(settings) {
+			settings_(settings),
+			historyController_(historyController) {
 	timerFactory_ = timerFactory;
 	eventController_ = eventController;
 	stanzaChannel_ = stanzaChannel;
@@ -512,7 +514,7 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact)
 
 ChatController* ChatsManager::createNewChatController(const JID& contact) {
 	assert(chatControllers_.find(contact) == chatControllers_.end());
-	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_);
+	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_);
 	chatControllers_[contact] = controller;
 	controller->setAvailableServerFeatures(serverDiscoInfo_);
 	controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
@@ -585,7 +587,7 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
 		if (createAsReservedIfNew) {
 			muc->setCreateAsReservedIfNew();
 		}
-		MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_);
+		MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_);
 		mucControllers_[mucJID] = controller;
 		controller->setAvailableServerFeatures(serverDiscoInfo_);
 		controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index a8c69c4..94efde1 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -47,10 +47,11 @@ namespace Swift {
 	class FileTransferController;
 	class XMPPRoster;
 	class SettingsProvider;
+	class HistoryController;
 	
 	class ChatsManager {
 		public:
-			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings);
+			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_);
 			virtual ~ChatsManager();
 			void setAvatarManager(AvatarManager* avatarManager);
 			void setOnline(bool enabled);
@@ -129,5 +130,6 @@ namespace Swift {
 			bool eagleMode_;
 			bool userWantsReceipts_;
 			SettingsProvider* settings_;
+			HistoryController* historyController_;
 	};
 }
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 0469cc6..fcd1f04 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -59,8 +59,10 @@ MUCController::MUCController (
 		TimerFactory* timerFactory,
 		EventController* eventController,
 		EntityCapsProvider* entityCapsProvider,
-		XMPPRoster* roster) :
-			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password) {
+		XMPPRoster* roster,
+		HistoryController* historyController,
+		MUCRegistry* mucRegistry) :
+			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry), muc_(muc), nick_(nick), desiredNick_(nick), password_(password) {
 	parting_ = true;
 	joined_ = false;
 	lastWasPresence_ = false;
@@ -193,11 +195,14 @@ void MUCController::rejoin() {
 			muc_->setPassword(*password_);
 		}
 		//FIXME: check for received activity
-		if (lastActivity_ == boost::posix_time::not_a_date_time) {
-			muc_->joinAs(nick_);
-		} else {
-			muc_->joinWithContextSince(nick_, lastActivity_);
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+		if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) {
+			lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_);
 		}
+		muc_->joinWithContextSince(nick_, lastActivity_);
+#else
+		muc_->joinAs(nick_);
+#endif
 	}
 }
 
@@ -273,6 +278,11 @@ void MUCController::handleJoinComplete(const std::string& nick) {
 	std::string joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
 	nick_ = nick;
 	chatWindow_->addSystemMessage(joinMessage);
+
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	addRecentLogs();
+#endif
+
 	clearPresenceQueue();
 	shouldJoinOnReconnect_ = true;
 	setEnabled(true);
@@ -430,6 +440,7 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes
 	}
 
 	if (!doneGettingHistory_) {
+		checkDuplicates(message);
 		messageEvent->conclude();
 	}
 }
@@ -779,4 +790,51 @@ void MUCController::handleAffiliationListReceived(MUCOccupant::Affiliation affil
 	chatWindow_->setAffiliations(affiliation, jids);
 }
 
+void MUCController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) {
+	// log only incoming messages
+	if (isIncoming && historyController_) {
+		historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp);
+	}
+}
+
+void MUCController::addRecentLogs() {
+	if (!historyController_) {
+		return;
+	}
+
+	joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_);
+
+	foreach (const HistoryMessage& message, joinContext_) {
+		bool senderIsSelf = nick_ == message.getFromJID().getResource();
+
+		// the chatWindow uses utc timestamps
+		addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), std::string(avatarManager_->getAvatarPath(message.getFromJID()).string()), message.getTime() - boost::posix_time::hours(message.getOffset()));
+	}
+}
+
+void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) {
+	std::string body = newMessage->getBody();
+	JID jid = newMessage->getFrom();
+	boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp();
+
+	reverse_foreach (const HistoryMessage& message, joinContext_) {
+		boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset());
+		if (time && time < messageTime) {
+			break;
+		}
+		if (time && time != messageTime) {
+			continue;
+		}
+		if (message.getFromJID() != jid) {
+			continue;
+		}
+		if (message.getMessage() != body) {
+			continue;
+		}
+
+		// Mark the message as unreadable
+		newMessage->setBody("");
+	}
+}
+
 }
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 6bf056b..63893d0 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -44,7 +44,7 @@ namespace Swift {
 
 	class MUCController : public ChatControllerBase {
 		public:
-			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster);
+			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry);
 			~MUCController();
 			boost::signal<void ()> onUserLeft;
 			boost::signal<void ()> onUserJoined;
@@ -64,6 +64,7 @@ namespace Swift {
 			void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>);
 			void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>);
 			void cancelReplaces();
+			void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming);
 
 		private:
 			void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role);
@@ -105,6 +106,8 @@ namespace Swift {
 			void handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes);
 			void handleInviteToMUCWindowDismissed();
 			void handleInviteToMUCWindowCompleted();
+			void addRecentLogs();
+			void checkDuplicates(boost::shared_ptr<Message> newMessage);
 
 		private:
 			MUC::ref muc_;
@@ -126,6 +129,7 @@ namespace Swift {
 			boost::optional<std::string> password_;
 			InviteToChatWindow* inviteWindow_;
 			XMPPRoster* xmppRoster_;
+			std::vector<HistoryMessage> joinContext_;
 	};
 }
 
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 11d0ce2..294dcb8 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -102,7 +102,7 @@ public:
 		ftOverview_ = new FileTransferOverview(ftManager_);
 
 		mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
-		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_);
+		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL);
 
 		avatarManager_ = new NullAvatarManager();
 		manager_->setAvatarManager(avatarManager_);
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 2cc62bc..4f37229 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -64,7 +64,7 @@ public:
 		entityCapsProvider_ = new DummyEntityCapsProvider();
 		muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_);
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
-		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL);
+		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_);
 	};
 
 	void tearDown() {
diff --git a/Swift/Controllers/HistoryController.cpp b/Swift/Controllers/HistoryController.cpp
new file mode 100644
index 0000000..d730236
--- /dev/null
+++ b/Swift/Controllers/HistoryController.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/HistoryController.h>
+#include <Swiften/History/HistoryStorage.h>
+#include <Swiften/History/HistoryMessage.h>
+#include <boost/date_time/c_local_time_adjustor.hpp>
+
+namespace Swift {
+
+HistoryController::HistoryController(HistoryStorage* localHistoryStorage) : localHistory_(localHistoryStorage) {
+}
+
+HistoryController::~HistoryController() {
+}
+
+void HistoryController::addMessage(const std::string& message, const JID& fromJID, const JID& toJID, HistoryMessage::Type type, const boost::posix_time::ptime& timeStamp) {
+	// note: using localtime timestamps
+	boost::posix_time::ptime localTime = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local(timeStamp);
+	int offset = (localTime - timeStamp).hours();
+
+	HistoryMessage historyMessage(message, fromJID, toJID, type, localTime, offset);
+
+	localHistory_->addMessage(historyMessage);
+	onNewMessage(historyMessage);
+}
+
+std::vector<HistoryMessage> HistoryController::getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const {
+	return localHistory_->getMessagesFromDate(selfJID, contactJID, type, date);
+}
+
+std::vector<HistoryMessage> HistoryController::getMUCContext(const JID& selfJID, const JID& mucJID, const boost::posix_time::ptime& timeStamp) const {
+	boost::posix_time::ptime localTime = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local(timeStamp);
+	return getMessagesFromDate(selfJID, mucJID, HistoryMessage::Groupchat, localTime.date());
+}
+
+std::vector<HistoryMessage> HistoryController::getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const {
+	return localHistory_->getMessagesFromPreviousDate(selfJID, contactJID, type, date);
+}
+
+std::vector<HistoryMessage> HistoryController::getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const {
+	return localHistory_->getMessagesFromNextDate(selfJID, contactJID, type, date);
+}
+
+ContactsMap HistoryController::getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const {
+	return localHistory_->getContacts(selfJID, type, keyword);
+}
+
+boost::posix_time::ptime HistoryController::getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) {
+	return localHistory_->getLastTimeStampFromMUC(selfJID, mucJID);
+}
+
+}
diff --git a/Swift/Controllers/HistoryController.h b/Swift/Controllers/HistoryController.h
new file mode 100644
index 0000000..8c86409
--- /dev/null
+++ b/Swift/Controllers/HistoryController.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/JID/JID.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <vector>
+#include <set>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/History/HistoryMessage.h>
+#include <Swiften/History/HistoryStorage.h>
+
+namespace Swift {
+	class JID;
+
+	class HistoryController {
+		public:
+			HistoryController(HistoryStorage* localHistoryStorage);
+			~HistoryController();
+
+			void addMessage(const std::string& message, const JID& fromJID, const JID& toJID, HistoryMessage::Type type, const boost::posix_time::ptime& timeStamp);
+			std::vector<HistoryMessage> getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const;
+			std::vector<HistoryMessage> getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const;
+			std::vector<HistoryMessage> getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const;
+			ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword = std::string()) const;
+			std::vector<HistoryMessage> getMUCContext(const JID& selfJID, const JID& mucJID, const boost::posix_time::ptime& timeStamp) const;
+
+			boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID);
+
+			boost::signal<void (const HistoryMessage&)> onNewMessage;
+
+		private:
+			HistoryStorage* localHistory_;
+			bool remoteArchiveSupported_;
+	};
+}
diff --git a/Swift/Controllers/HistoryViewController.cpp b/Swift/Controllers/HistoryViewController.cpp
new file mode 100644
index 0000000..9343017
--- /dev/null
+++ b/Swift/Controllers/HistoryViewController.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/HistoryViewController.h>
+
+#include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h>
+#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h>
+#include <Swift/Controllers/HistoryController.h>
+#include <Swiften/History/HistoryMessage.h>
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swift/Controllers/Roster/SetPresence.h>
+#include <Swift/Controllers/Roster/SetAvatar.h>
+
+namespace Swift {
+	static const std::string category[] = { "Contacts", "MUC", "Contacts" };
+
+HistoryViewController::HistoryViewController(
+		const JID& selfJID,
+		UIEventStream* uiEventStream,
+		HistoryController* historyController,
+		NickResolver* nickResolver,
+		AvatarManager* avatarManager,
+		PresenceOracle* presenceOracle,
+		HistoryWindowFactory* historyWindowFactory) :
+			selfJID_(selfJID),
+			uiEventStream_(uiEventStream),
+			historyController_(historyController),
+			nickResolver_(nickResolver),
+			avatarManager_(avatarManager),
+			presenceOracle_(presenceOracle),
+			historyWindowFactory_(historyWindowFactory),
+			historyWindow_(NULL),
+			selectedItem_(NULL),
+			currentResultDate_(boost::gregorian::not_a_date_time) {
+	uiEventStream_->onUIEvent.connect(boost::bind(&HistoryViewController::handleUIEvent, this, _1));
+
+	roster_ = new Roster(false, true);
+}
+
+HistoryViewController::~HistoryViewController() {
+	uiEventStream_->onUIEvent.disconnect(boost::bind(&HistoryViewController::handleUIEvent, this, _1));
+	if (historyWindow_) {
+		historyWindow_->onSelectedContactChanged.disconnect(boost::bind(&HistoryViewController::handleSelectedContactChanged, this, _1));
+		historyWindow_->onReturnPressed.disconnect(boost::bind(&HistoryViewController::handleReturnPressed, this, _1));
+		historyWindow_->onScrollReachedTop.disconnect(boost::bind(&HistoryViewController::handleScrollReachedTop, this, _1));
+		historyWindow_->onScrollReachedBottom.disconnect(boost::bind(&HistoryViewController::handleScrollReachedBottom, this, _1));
+		historyWindow_->onPreviousButtonClicked.disconnect(boost::bind(&HistoryViewController::handlePreviousButtonClicked, this));
+		historyWindow_->onNextButtonClicked.disconnect(boost::bind(&HistoryViewController::handleNextButtonClicked, this));
+		historyWindow_->onCalendarClicked.disconnect(boost::bind(&HistoryViewController::handleCalendarClicked, this, _1));
+		historyController_->onNewMessage.disconnect(boost::bind(&HistoryViewController::handleNewMessage, this, _1));
+
+		presenceOracle_->onPresenceChange.disconnect(boost::bind(&HistoryViewController::handlePresenceChanged, this, _1));
+		avatarManager_->onAvatarChanged.disconnect(boost::bind(&HistoryViewController::handleAvatarChanged, this, _1));
+
+		delete historyWindow_;
+	}
+	delete roster_;
+}
+
+void HistoryViewController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) {
+	boost::shared_ptr<RequestHistoryUIEvent> event = boost::dynamic_pointer_cast<RequestHistoryUIEvent>(rawEvent);
+	if (event != NULL) {
+		if (historyWindow_ == NULL) {
+			historyWindow_ = historyWindowFactory_->createHistoryWindow(uiEventStream_);
+			historyWindow_->onSelectedContactChanged.connect(boost::bind(&HistoryViewController::handleSelectedContactChanged, this, _1));
+			historyWindow_->onReturnPressed.connect(boost::bind(&HistoryViewController::handleReturnPressed, this, _1));
+			historyWindow_->onScrollReachedTop.connect(boost::bind(&HistoryViewController::handleScrollReachedTop, this, _1));
+			historyWindow_->onScrollReachedBottom.connect(boost::bind(&HistoryViewController::handleScrollReachedBottom, this, _1));
+			historyWindow_->onPreviousButtonClicked.connect(boost::bind(&HistoryViewController::handlePreviousButtonClicked, this));
+			historyWindow_->onNextButtonClicked.connect(boost::bind(&HistoryViewController::handleNextButtonClicked, this));
+			historyWindow_->onCalendarClicked.connect(boost::bind(&HistoryViewController::handleCalendarClicked, this, _1));
+			historyController_->onNewMessage.connect(boost::bind(&HistoryViewController::handleNewMessage, this, _1));
+
+			presenceOracle_->onPresenceChange.connect(boost::bind(&HistoryViewController::handlePresenceChanged, this, _1));
+			avatarManager_->onAvatarChanged.connect(boost::bind(&HistoryViewController::handleAvatarChanged, this, _1));
+
+			historyWindow_->setRosterModel(roster_);
+		}
+
+		// populate roster by doing an empty search
+		handleReturnPressed(std::string());
+
+		historyWindow_->activate();
+	}
+}
+
+void HistoryViewController::handleSelectedContactChanged(RosterItem* newContact) {
+	// FIXME: signal is triggerd twice.
+	ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(newContact);
+
+	if (contact && selectedItem_ != contact) {
+		selectedItem_ = contact;
+		historyWindow_->resetConversationView();
+	}
+	else {
+		return;
+	}
+
+	JID contactJID = contact->getJID();
+
+	std::vector<HistoryMessage> messages;
+	for (int it = HistoryMessage::Chat; it <= HistoryMessage::PrivateMessage; it++) {
+		HistoryMessage::Type type = static_cast<HistoryMessage::Type>(it);
+
+		if (contacts_[type].count(contactJID)) {
+			currentResultDate_ = *contacts_[type][contactJID].rbegin();
+			selectedItemType_ = type;
+			messages = historyController_->getMessagesFromDate(selfJID_, contactJID, type, currentResultDate_);
+		}
+	}
+
+	historyWindow_->setDate(currentResultDate_);
+
+	foreach (const HistoryMessage& message, messages) {
+		addNewMessage(message, false);
+	}
+}
+
+void HistoryViewController::handleNewMessage(const HistoryMessage& message) {
+	JID contactJID = message.getFromJID().toBare() == selfJID_ ? message.getToJID() : message.getFromJID();
+
+	JID displayJID;
+	if (message.getType() == HistoryMessage::PrivateMessage) {
+		displayJID = contactJID;
+	}
+	else {
+		displayJID = contactJID.toBare();
+	}
+
+	// check current conversation
+	if (selectedItem_ && selectedItem_->getJID() == displayJID) {
+		if (historyWindow_->getLastVisibleDate() == message.getTime().date()) {
+			addNewMessage(message, false);
+		}
+	}
+
+	// check if the new message matches the query
+	if (message.getMessage().find(historyWindow_->getSearchBoxText()) == std::string::npos) {
+		return;
+	}
+
+	// update contacts
+	if (!contacts_[message.getType()].count(displayJID)) {
+		roster_->addContact(displayJID, displayJID, nickResolver_->jidToNick(displayJID), category[message.getType()], avatarManager_->getAvatarPath(displayJID).string());
+	}
+
+	contacts_[message.getType()][displayJID].insert(message.getTime().date());
+}
+
+void HistoryViewController::addNewMessage(const HistoryMessage& message, bool addAtTheTop) {
+	bool senderIsSelf = message.getFromJID().toBare() == selfJID_;
+	std::string avatarPath = avatarManager_->getAvatarPath(message.getFromJID()).string();
+
+	std::string nick = message.getType() != HistoryMessage::Groupchat ? nickResolver_->jidToNick(message.getFromJID()) : message.getFromJID().getResource();
+	historyWindow_->addMessage(message.getMessage(), nick, senderIsSelf, avatarPath, message.getTime(), addAtTheTop);
+}
+
+void HistoryViewController::handleReturnPressed(const std::string& keyword) {
+	reset();
+
+	for (int it = HistoryMessage::Chat; it <= HistoryMessage::PrivateMessage; it++) {
+		HistoryMessage::Type type = static_cast<HistoryMessage::Type>(it);
+
+		contacts_[type] = historyController_->getContacts(selfJID_, type, keyword);
+
+		for (ContactsMap::const_iterator contact = contacts_[type].begin(); contact != contacts_[type].end(); contact++) {
+			const JID& jid = contact->first;
+			std::string nick;
+			if (type == HistoryMessage::PrivateMessage) {
+				nick = jid.toString();
+			}
+			else {
+				nick = nickResolver_->jidToNick(jid);
+			}
+			roster_->addContact(jid, jid, nick, category[type], avatarManager_->getAvatarPath(jid).string());
+
+			Presence::ref presence = getPresence(jid, type == HistoryMessage::Groupchat);
+
+			if (presence.get()) {
+				roster_->applyOnItem(SetPresence(presence, JID::WithoutResource), jid);
+			}
+		}
+	}
+}
+
+void HistoryViewController::handleScrollReachedTop(const boost::gregorian::date& date) {
+	if (!selectedItem_) {
+		return;
+	}
+
+	std::vector<HistoryMessage> messages = historyController_->getMessagesFromPreviousDate(selfJID_, selectedItem_->getJID(), selectedItemType_, date);
+
+	foreach (const HistoryMessage& message, messages) {
+		addNewMessage(message, true);
+	}
+	historyWindow_->resetConversationViewTopInsertPoint();
+}
+
+void HistoryViewController::handleScrollReachedBottom(const boost::gregorian::date& date) {
+	if (!selectedItem_) {
+		return;
+	}
+
+	std::vector<HistoryMessage> messages = historyController_->getMessagesFromNextDate(selfJID_, selectedItem_->getJID(), selectedItemType_, date);
+
+	foreach (const HistoryMessage& message, messages) {
+		addNewMessage(message, false);
+	}
+}
+
+void HistoryViewController::handleNextButtonClicked() {
+	if (!selectedItem_) {
+		return;
+	}
+
+	std::set<boost::gregorian::date>::iterator date = contacts_[selectedItemType_][selectedItem_->getJID()].find(currentResultDate_);
+
+	if (*date == *contacts_[selectedItemType_][selectedItem_->getJID()].rbegin()) {
+		return;
+	}
+
+	historyWindow_->resetConversationView();
+	currentResultDate_ = *(++date);
+	std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_);
+	historyWindow_->setDate(currentResultDate_);
+
+	foreach (const HistoryMessage& message, messages) {
+		addNewMessage(message, false);
+	}
+}
+
+void HistoryViewController::handlePreviousButtonClicked() {
+	if (!selectedItem_) {
+		return;
+	}
+
+	std::set<boost::gregorian::date>::iterator date = contacts_[selectedItemType_][selectedItem_->getJID()].find(currentResultDate_);
+
+	if (date == contacts_[selectedItemType_][selectedItem_->getJID()].begin()) {
+		return;
+	}
+
+	historyWindow_->resetConversationView();
+	currentResultDate_ = *(--date);
+	std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_);
+	historyWindow_->setDate(currentResultDate_);
+
+	foreach (const HistoryMessage& message, messages) {
+		addNewMessage(message, false);
+	}
+}
+
+void HistoryViewController::reset() {
+	roster_->removeAll();
+	contacts_.clear();
+	selectedItem_ = NULL;
+	historyWindow_->resetConversationView();
+}
+
+void HistoryViewController::handleCalendarClicked(const boost::gregorian::date& date) {
+	if (!selectedItem_) {
+		return;
+	}
+
+	boost::gregorian::date newDate;
+	if (contacts_[selectedItemType_][selectedItem_->getJID()].count(date)) {
+		newDate = date;
+	}
+	else if (date < currentResultDate_) {
+		foreach(const boost::gregorian::date& current, contacts_[selectedItemType_][selectedItem_->getJID()]) {
+			if (current > date) {
+				newDate = current;
+				break;
+			}
+		}
+	}
+	else {
+		reverse_foreach(const boost::gregorian::date& current, contacts_[selectedItemType_][selectedItem_->getJID()]) {
+			if (current < date) {
+				newDate = current;
+				break;
+			}
+		}
+	}
+
+	historyWindow_->setDate(newDate);
+	if (newDate == currentResultDate_) {
+		return;
+	}
+	currentResultDate_ = newDate;
+	historyWindow_->resetConversationView();
+
+	std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_);
+	historyWindow_->setDate(currentResultDate_);
+
+	foreach (const HistoryMessage& message, messages) {
+		addNewMessage(message, false);
+	}
+}
+
+void HistoryViewController::handlePresenceChanged(Presence::ref presence) {
+	JID jid = presence->getFrom();
+
+	if (contacts_[HistoryMessage::Chat].count(jid.toBare())) {
+		roster_->applyOnItems(SetPresence(presence, JID::WithoutResource));
+		return;
+	}
+
+	if (contacts_[HistoryMessage::Groupchat].count(jid.toBare())) {
+		Presence::ref availablePresence = boost::make_shared<Presence>(Presence());
+		availablePresence->setFrom(jid.toBare());
+		roster_->applyOnItems(SetPresence(availablePresence, JID::WithResource));
+	}
+
+	if (contacts_[HistoryMessage::PrivateMessage].count(jid)) {
+		roster_->applyOnItems(SetPresence(presence, JID::WithResource));
+	}
+}
+
+void HistoryViewController::handleAvatarChanged(const JID& jid) {
+	std::string path = avatarManager_->getAvatarPath(jid).string();
+	roster_->applyOnItems(SetAvatar(jid, path));
+}
+
+Presence::ref HistoryViewController::getPresence(const JID& jid, bool isMUC) {
+	if (jid.isBare() && !isMUC) {
+		return presenceOracle_->getHighestPriorityPresence(jid);
+	}
+
+	std::vector<Presence::ref> mucPresence = presenceOracle_->getAllPresence(jid.toBare());
+
+	if (isMUC && !mucPresence.empty()) {
+		Presence::ref presence = boost::make_shared<Presence>(Presence());
+		presence->setFrom(jid);
+		return presence;
+	}
+
+	foreach (Presence::ref presence, mucPresence) {
+		if (presence.get() && presence->getFrom() == jid) {
+			return presence;
+		}
+	}
+
+	return Presence::create();
+}
+
+}
diff --git a/Swift/Controllers/HistoryViewController.h b/Swift/Controllers/HistoryViewController.h
new file mode 100644
index 0000000..f44c968
--- /dev/null
+++ b/Swift/Controllers/HistoryViewController.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/History/HistoryStorage.h>
+#include <set>
+
+namespace Swift {
+	class HistoryWindowFactory;
+	class HistoryWindow;
+	class Roster;
+	class RosterItem;
+	class ContactRosterItem;
+	class HistoryController;
+	class NickResolver;
+	class AvatarManager;
+
+	class HistoryViewController {
+		public:
+			HistoryViewController(const JID& selfJID, UIEventStream* uiEventStream, HistoryController* historyController, NickResolver* nickResolver, AvatarManager* avatarManager, PresenceOracle* presenceOracle, HistoryWindowFactory* historyWindowFactory);
+			~HistoryViewController();
+
+		private:
+			void handleUIEvent(boost::shared_ptr<UIEvent> event);
+			void handleSelectedContactChanged(RosterItem* item);
+			void handleNewMessage(const HistoryMessage& message);
+			void handleReturnPressed(const std::string& keyword);
+			void handleScrollReachedTop(const boost::gregorian::date& date);
+			void handleScrollReachedBottom(const boost::gregorian::date& date);
+			void handlePreviousButtonClicked();
+			void handleNextButtonClicked();
+			void handleCalendarClicked(const boost::gregorian::date& date);
+			void handlePresenceChanged(Presence::ref presence);
+			void handleAvatarChanged(const JID& jid);
+
+			void addNewMessage(const HistoryMessage& message, bool addAtTheTop);
+			void reset();
+			Presence::ref getPresence(const JID& jid, bool isMUC);
+
+		private:
+			JID selfJID_;
+			UIEventStream* uiEventStream_;
+			HistoryController* historyController_;
+			NickResolver* nickResolver_;
+			AvatarManager* avatarManager_;
+			PresenceOracle* presenceOracle_;
+			HistoryWindowFactory* historyWindowFactory_;
+			HistoryWindow* historyWindow_;
+			Roster* roster_;
+
+			std::map<HistoryMessage::Type, ContactsMap> contacts_;
+			ContactRosterItem* selectedItem_;
+			HistoryMessage::Type selectedItemType_;
+			boost::gregorian::date currentResultDate_;
+	};
+}
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index f124298..2c02ba8 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -37,6 +37,8 @@
 #include "Swift/Controllers/SystemTray.h"
 #include "Swift/Controllers/SystemTrayController.h"
 #include "Swift/Controllers/XMLConsoleController.h"
+#include <Swift/Controllers/HistoryController.h>
+#include <Swift/Controllers/HistoryViewController.h>
 #include "Swift/Controllers/FileTransferListController.h"
 #include "Swift/Controllers/UIEvents/UIEventStream.h"
 #include "Swift/Controllers/PresenceNotifier.h"
@@ -112,6 +114,7 @@ MainController::MainController(
 	eventNotifier_ = NULL;
 	rosterController_ = NULL;
 	chatsManager_ = NULL;
+	historyViewController_ = NULL;
 	eventWindowController_ = NULL;
 	profileController_ = NULL;
 	contactEditController_ = NULL;
@@ -218,6 +221,12 @@ void MainController::resetClient() {
 	eventWindowController_ = NULL;
 	delete chatsManager_;
 	chatsManager_ = NULL;
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	delete historyController_;
+	historyController_ = NULL;
+	delete historyViewController_;
+	historyViewController_ = NULL;
+#endif
 	delete ftOverview_;
 	ftOverview_ = NULL;
 	delete rosterController_;
@@ -295,7 +304,13 @@ void MainController::handleConnected() {
 		 * be before they receive stanzas that need it (e.g. bookmarks).*/
 		client_->getVCardManager()->requestOwnVCard();
 
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_);
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+		historyController_ = new HistoryController(storages_->getHistoryStorage());
+		historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_);
+#else
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL);
+#endif
 		
 		client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
 		chatsManager_->setAvatarManager(client_->getAvatarManager());
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index eeba9f3..8f04f6c 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -52,6 +52,8 @@ namespace Swift {
 	class SoundEventController;
 	class SoundPlayer;
 	class XMLConsoleController;
+	class HistoryViewController;
+	class HistoryController;
 	class FileTransferListController;
 	class UIEventStream;
 	class EventWindowFactory;
@@ -143,6 +145,8 @@ namespace Swift {
 			LoginWindow* loginWindow_;
 			UIEventStream* uiEventStream_;
 			XMLConsoleController* xmlConsoleController_;
+			HistoryViewController* historyViewController_;
+			HistoryController* historyController_;
 			FileTransferListController* fileTransferListController_;
 			ChatsManager* chatsManager_;
 			ProfileController* profileController_;
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index eca0d38..b6f81b3 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -45,6 +45,8 @@ if env["SCONS_STAGE"] == "build" :
 			"SoundEventController.cpp",
 			"SystemTrayController.cpp",
 			"XMLConsoleController.cpp",
+			"HistoryViewController.cpp",
+			"HistoryController.cpp",
 			"FileTransferListController.cpp",
 			"StatusTracker.cpp",
 			"PresenceNotifier.cpp",
diff --git a/Swift/Controllers/Storages/FileStorages.cpp b/Swift/Controllers/Storages/FileStorages.cpp
index 6447099..cff87d3 100644
--- a/Swift/Controllers/Storages/FileStorages.cpp
+++ b/Swift/Controllers/Storages/FileStorages.cpp
@@ -9,6 +9,7 @@
 #include "Swift/Controllers/Storages/AvatarFileStorage.h"
 #include "Swift/Controllers/Storages/CapsFileStorage.h"
 #include "Swift/Controllers/Storages/RosterFileStorage.h"
+#include <Swiften/History/SQLiteHistoryStorage.h>
 
 namespace Swift {
 
@@ -18,6 +19,9 @@ FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& ji
 	capsStorage = new CapsFileStorage(baseDir / "caps");
 	avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars");
 	rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml");
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	historyStorage = new SQLiteHistoryStorage((baseDir / "history.db").string());
+#endif
 }
 
 FileStorages::~FileStorages() {
@@ -25,6 +29,9 @@ FileStorages::~FileStorages() {
 	delete avatarStorage;
 	delete capsStorage;
 	delete vcardStorage;
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	delete historyStorage;
+#endif
 }
 
 VCardStorage* FileStorages::getVCardStorage() const {
@@ -43,4 +50,12 @@ RosterStorage* FileStorages::getRosterStorage() const {
 	return rosterStorage;
 }
 
+HistoryStorage* FileStorages::getHistoryStorage() const {
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	return historyStorage;
+#else
+	return NULL;
+#endif
+}
+
 }
diff --git a/Swift/Controllers/Storages/FileStorages.h b/Swift/Controllers/Storages/FileStorages.h
index 28df314..5e89db8 100644
--- a/Swift/Controllers/Storages/FileStorages.h
+++ b/Swift/Controllers/Storages/FileStorages.h
@@ -15,6 +15,7 @@ namespace Swift {
 	class AvatarFileStorage;
 	class CapsFileStorage;
 	class RosterFileStorage;
+	class HistoryStorage;
 	class JID;
 
 	/**
@@ -43,11 +44,13 @@ namespace Swift {
 			virtual AvatarStorage* getAvatarStorage() const;
 			virtual CapsStorage* getCapsStorage() const;
 			virtual RosterStorage* getRosterStorage() const;
+			virtual HistoryStorage* getHistoryStorage() const;
 
 		private:
 			VCardFileStorage* vcardStorage;
 			AvatarFileStorage* avatarStorage;
 			CapsFileStorage* capsStorage;
 			RosterFileStorage* rosterStorage;
+			HistoryStorage* historyStorage;
 	};
 }
diff --git a/Swift/Controllers/UIEvents/RequestHistoryUIEvent.h b/Swift/Controllers/UIEvents/RequestHistoryUIEvent.h
new file mode 100644
index 0000000..025e91f
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestHistoryUIEvent.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+	class RequestHistoryUIEvent : public UIEvent {
+	};
+}
diff --git a/Swift/Controllers/UIInterfaces/HistoryWindow.h b/Swift/Controllers/UIInterfaces/HistoryWindow.h
new file mode 100644
index 0000000..ffb0ad5
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/HistoryWindow.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/Roster/Roster.h>
+
+namespace Swift {
+	class HistoryWindow {
+		public:
+			virtual ~HistoryWindow() {};
+
+			virtual void activate() = 0;
+			virtual void setRosterModel(Roster*) = 0;
+			virtual void addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop) = 0;
+			virtual void resetConversationView() = 0;
+			virtual void resetConversationViewTopInsertPoint() = 0; // this is a temporary fix used in adding messages at the top
+			virtual void setDate(const boost::gregorian::date& date) = 0;
+
+			virtual std::string getSearchBoxText() = 0;
+			virtual boost::gregorian::date getLastVisibleDate() = 0;
+
+			boost::signal<void (RosterItem*)> onSelectedContactChanged;
+			boost::signal<void (const std::string&)> onReturnPressed;
+			boost::signal<void (const boost::gregorian::date&)> onScrollReachedTop;
+			boost::signal<void (const boost::gregorian::date&)> onScrollReachedBottom;
+			boost::signal<void ()> onPreviousButtonClicked;
+			boost::signal<void ()> onNextButtonClicked;
+			boost::signal<void (const boost::gregorian::date&)> onCalendarClicked;
+	};
+}
diff --git a/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h b/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h
new file mode 100644
index 0000000..e91bc37
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/HistoryWindow.h>
+
+namespace Swift {
+	class UIEventStream;
+	class HistoryWindowFactory {
+		public:
+			virtual ~HistoryWindowFactory() {};
+			virtual HistoryWindow* createHistoryWindow(UIEventStream* eventStream) = 0;
+	};
+}
diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h
index cf89dab..d6bea77 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -8,6 +8,7 @@
 
 #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/EventWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/MainWindowFactory.h>
@@ -24,6 +25,7 @@ namespace Swift {
 	class UIFactory : 
 			public ChatListWindowFactory, 
 			public ChatWindowFactory, 
+			public HistoryWindowFactory,
 			public EventWindowFactory, 
 			public LoginWindowFactory, 
 			public MainWindowFactory, 
diff --git a/Swift/QtUI/MessageSnippet.cpp b/Swift/QtUI/MessageSnippet.cpp
index 7505905..a10ee2c 100644
--- a/Swift/QtUI/MessageSnippet.cpp
+++ b/Swift/QtUI/MessageSnippet.cpp
@@ -37,6 +37,7 @@ MessageSnippet::MessageSnippet(const QString& message, const QString& sender, co
 	content_.replace("%time%", wrapResizable("<span class='swift_time'>" + timeToEscapedString(time) + "</span>"));
 	content_.replace("%userIconPath%", escape(iconURI));
 	content_ = "<div id='" + id + "'>" + content_ + "</div>";
+	content_ = "<span class='date" + time.date().toString(Qt::ISODate) + "'>" + content_ + "</span>";
 }
 
 MessageSnippet::~MessageSnippet() {
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 49e5974..eaec3b6 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -28,7 +28,7 @@
 
 namespace Swift {
 
-QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), fontSizeSteps_(0) {
+QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent, bool disableAutoScroll) : QWidget(parent), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll) {
 	theme_ = theme;
 
 	QVBoxLayout* mainLayout = new QVBoxLayout(this);
@@ -61,6 +61,7 @@ QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), f
 	//webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
 	webView_->setPage(webPage_);
 	connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard()));
+	connect(webPage_, SIGNAL(scrollRequested(int, int, const QRect&)), SLOT(handleScrollRequested(int, int, const QRect&)));
 
 	viewReady_ = false;
 	isAtBottom_ = true;
@@ -85,7 +86,7 @@ void QtChatView::handleKeyPressEvent(QKeyEvent* event) {
 	webView_->keyPressEvent(event);
 }
 
-void QtChatView::addMessage(boost::shared_ptr<ChatSnippet> snippet) {
+void QtChatView::addMessageBottom(boost::shared_ptr<ChatSnippet> snippet) {
 	if (viewReady_) {
 		addToDOM(snippet);
 	} else {
@@ -94,6 +95,45 @@ void QtChatView::addMessage(boost::shared_ptr<ChatSnippet> snippet) {
 	}
 }
 
+void QtChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) {
+	// save scrollbar maximum value
+	if (!topMessageAdded_) {
+		scrollBarMaximum_ = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
+	}
+	topMessageAdded_ = true;
+
+	QWebElement continuationElement = firstElement_.findFirst("#insert");
+
+	bool insert = snippet->getAppendToPrevious();
+	bool fallback = continuationElement.isNull();
+
+	boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
+	QWebElement newElement = snippetToDOM(newSnippet);
+
+	if (insert && !fallback) {
+		Q_ASSERT(!continuationElement.isNull());
+		continuationElement.replace(newElement);
+	} else {
+		continuationElement.removeFromDocument();
+		topInsertPoint_.prependOutside(newElement);
+	}
+
+	firstElement_ = newElement;
+
+	if (lastElement_.isNull()) {
+		lastElement_ = firstElement_;
+	}
+
+	if (fontSizeSteps_ != 0) {
+		double size = 1.0 + 0.2 * fontSizeSteps_;
+		QString sizeString(QString().setNum(size, 'g', 3) + "em");
+		const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable");
+		foreach (QWebElement span, spans) {
+			span.setStyleProperty("font-size", sizeString);
+		}
+	}
+}
+
 QWebElement QtChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) {
 	QWebElement newElement = newInsertPoint_.clone();
 	newElement.setInnerXml(snippet->getContent());
@@ -230,7 +270,7 @@ void QtChatView::displayReceiptInfo(const QString& id, bool showIt) {
 }
 
 void QtChatView::rememberScrolledToBottom() {
-	isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
+	isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1);
 }
 
 void QtChatView::scrollToBottom() {
@@ -240,7 +280,14 @@ void QtChatView::scrollToBottom() {
 }
 
 void QtChatView::handleFrameSizeChanged() {
-	if (isAtBottom_) {
+	if (topMessageAdded_) {
+		// adjust new scrollbar position
+		int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
+		webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_);
+		topMessageAdded_ = false;
+	}
+
+	if (isAtBottom_ && !disableAutoScroll_) {
 		scrollToBottom();
 	}
 }
@@ -282,6 +329,9 @@ void QtChatView::resizeFont(int fontSizeSteps) {
 
 void QtChatView::resetView() {
 	lastElement_ = QWebElement();
+	firstElement_ = lastElement_;
+	topMessageAdded_ = false;
+	scrollBarMaximum_ = 0;
 	QString pageHTML = theme_->getTemplate();
 	pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3");
 	pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getBase());
@@ -302,12 +352,16 @@ void QtChatView::resetView() {
 		syncLoop.exec();
 	}
 	document_ = webPage_->mainFrame()->documentElement();
+
+	resetTopInsertPoint();
 	QWebElement chatElement = document_.findFirst("#Chat");
 	newInsertPoint_ = chatElement.clone();
 	newInsertPoint_.setOuterXml("<div id='swift_insert'/>");
 	chatElement.appendInside(newInsertPoint_);
 	Q_ASSERT(!newInsertPoint_.isNull());
 
+	scrollToBottom();
+
 	connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection);
 }
 
@@ -384,4 +438,36 @@ void QtChatView::setMUCInvitationJoined(QString id) {
 	}
 }
 
+void QtChatView::handleScrollRequested(int, int dy, const QRect&) {
+	rememberScrolledToBottom();
+
+	int pos = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) - dy;
+	emit scrollRequested(pos);
+
+	if (pos == 0) {
+		emit scrollReachedTop();
+	}
+	else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) {
+		emit scrollReachedBottom();
+	}
+}
+
+int QtChatView::getSnippetPositionByDate(const QDate& date) {
+	QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate));
+
+	return message.geometry().top();
+}
+
+void QtChatView::resetTopInsertPoint() {
+	QWebElement continuationElement = firstElement_.findFirst("#insert");
+	continuationElement.removeFromDocument();
+	firstElement_ = QWebElement();
+
+	topInsertPoint_.removeFromDocument();
+	QWebElement chatElement = document_.findFirst("#Chat");
+	topInsertPoint_ = chatElement.clone();
+	topInsertPoint_.setOuterXml("<div id='swift_insert'/>");
+	chatElement.prependInside(topInsertPoint_);
+}
+
 }
diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h
index fdbdd5a..118f14b 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -20,6 +20,7 @@
 
 class QWebPage;
 class QUrl;
+class QDate;
 
 namespace Swift {
 	class QtWebView;
@@ -27,8 +28,9 @@ namespace Swift {
 	class QtChatView : public QWidget {
 			Q_OBJECT
 		public:
-			QtChatView(QtChatTheme* theme, QWidget* parent);
-			void addMessage(boost::shared_ptr<ChatSnippet> snippet);
+			QtChatView(QtChatTheme* theme, QWidget* parent, bool disableAutoScroll = false);
+			void addMessageTop(boost::shared_ptr<ChatSnippet> snippet);
+			void addMessageBottom(boost::shared_ptr<ChatSnippet> snippet);
 			void addLastSeenLine();
 			void replaceLastMessage(const QString& newMessage);
 			void replaceLastMessage(const QString& newMessage, const QString& note);
@@ -44,10 +46,15 @@ namespace Swift {
 			void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg);
 			void setMUCInvitationJoined(QString id);
 			void showEmoticons(bool show);
+			int getSnippetPositionByDate(const QDate& date);
+
 		signals:
 			void gotFocus();
 			void fontResized(int);
 			void logCleared();
+			void scrollRequested(int pos);
+			void scrollReachedTop();
+			void scrollReachedBottom();
 
 		public slots:
 			void copySelectionToClipboard();
@@ -55,6 +62,7 @@ namespace Swift {
 			void handleLinkClicked(const QUrl&);
 			void handleKeyPressEvent(QKeyEvent* event);
 			void resetView();
+			void resetTopInsertPoint();
 			void increaseFontSize(int numSteps = 1);
 			void decreaseFontSize();
 			void resizeFont(int fontSizeSteps);
@@ -63,6 +71,7 @@ namespace Swift {
 			void handleViewLoadFinished(bool);
 			void handleFrameSizeChanged();
 			void handleClearRequested();
+			void handleScrollRequested(int dx, int dy, const QRect& rectToScroll);
 
 		private:
 			void headerEncode();
@@ -72,14 +81,19 @@ namespace Swift {
 
 			bool viewReady_;
 			bool isAtBottom_;
+			bool topMessageAdded_;
+			int scrollBarMaximum_;
 			QtWebView* webView_;
 			QWebPage* webPage_;
 			int fontSizeSteps_;
 			QtChatTheme* theme_;
 			QWebElement newInsertPoint_;
+			QWebElement topInsertPoint_;
 			QWebElement lineSeparator_;
 			QWebElement lastElement_;
+			QWebElement firstElement_;
 			QWebElement document_;
+			bool disableAutoScroll_;
 	};
 }
 
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index f42469b..ddfe158 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -524,7 +524,7 @@ std::string QtChatWindow::addMessage(const QString &message, const std::string &
 	}
 	QString qAvatarPath =  scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded();
 	std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++);
-	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id))));
+	messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id))));
 
 	previousMessageWasSelf_ = senderIsSelf;
 	previousSenderName_ = P2QSTRING(senderName);
@@ -633,7 +633,7 @@ std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool se
 	}
 	QString qAvatarPath = "qrc:/icons/avatar.png";
 	std::string id = "ftmessage" + boost::lexical_cast<std::string>(idCounter_++);
-	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id))));
+	messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id))));
 
 	previousMessageWasSelf_ = senderIsSelf;
 	previousSenderName_ = P2QSTRING(senderName);
@@ -701,7 +701,7 @@ void QtChatWindow::addErrorMessage(const std::string& errorMessage) {
 
 	QString errorMessageHTML(Qt::escape(P2QSTRING(errorMessage)));
 	errorMessageHTML.replace("\n","<br/>");
-	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_)));
+	messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_)));
 
 	previousMessageWasSelf_ = false;
 	previousMessageKind_ = PreviousMessageWasSystem;
@@ -714,7 +714,7 @@ void QtChatWindow::addSystemMessage(const std::string& message) {
 
 	QString messageHTML(P2QSTRING(message));
 	messageHTML = linkimoticonify(messageHTML);
-	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_)));
+	messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_)));
 
 	previousMessageKind_ = PreviousMessageWasSystem;
 }
@@ -753,7 +753,7 @@ void QtChatWindow::addPresenceMessage(const std::string& message) {
 
 	QString messageHTML(P2QSTRING(message));
 	messageHTML = linkimoticonify(messageHTML);
-	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_)));
+	messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_)));
 
 	previousMessageKind_ = PreviousMessageWasPresence;
 }
@@ -950,7 +950,7 @@ void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& ji
 	}
 	QString qAvatarPath = "qrc:/icons/avatar.png";
 
-	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id)));
+	messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id)));
 	previousMessageWasSelf_ = false;
 	previousSenderName_ = P2QSTRING(senderName);
 	previousMessageKind_ = PreviousMessageWasMUCInvite;
diff --git a/Swift/QtUI/QtHistoryWindow.cpp b/Swift/QtUI/QtHistoryWindow.cpp
new file mode 100644
index 0000000..e54bd51
--- /dev/null
+++ b/Swift/QtUI/QtHistoryWindow.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <QtHistoryWindow.h>
+#include <QtTabbable.h>
+
+#include <QtSwiftUtil.h>
+#include <MessageSnippet.h>
+#include <Swiften/History/HistoryMessage.h>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <QTime>
+#include <QUrl>
+#include <QMenu>
+#include <QTextDocument>
+#include <QDateTime>
+#include <Swift/QtUI/QtScaledAvatarCache.h>
+#include <QLineEdit>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/date_time/gregorian/gregorian.hpp>
+
+namespace Swift {
+
+QtHistoryWindow::QtHistoryWindow(SettingsProvider* settings, UIEventStream* eventStream) :
+		previousTopMessageWasSelf_(false),
+		previousBottomMessageWasSelf_(false) {
+	ui_.setupUi(this);
+
+	theme_ = new QtChatTheme("");
+	idCounter_ = 0;
+
+	delete ui_.conversation_;
+	conversation_ = new QtChatView(theme_, this, true);
+	QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+	sizePolicy.setHorizontalStretch(80);
+	sizePolicy.setVerticalStretch(0);
+	conversation_->setSizePolicy(sizePolicy);
+
+	ui_.conversation_ = conversation_;
+	ui_.bottomLayout_->addWidget(conversation_);
+
+	delete ui_.conversationRoster_;
+	conversationRoster_ = new QtTreeWidget(eventStream, settings, this);
+	QSizePolicy sizePolicy2(QSizePolicy::Preferred, QSizePolicy::Expanding);
+	sizePolicy2.setVerticalStretch(80);
+	conversationRoster_->setSizePolicy(sizePolicy2);
+	ui_.conversationRoster_ = conversationRoster_;
+	ui_.bottomLeftLayout_->setDirection(QBoxLayout::BottomToTop);
+	ui_.bottomLeftLayout_->addWidget(conversationRoster_);
+
+	setWindowTitle(tr("History"));
+
+	conversationRoster_->onSomethingSelectedChanged.connect(boost::bind(&QtHistoryWindow::handleSomethingSelectedChanged, this, _1));
+	connect(conversation_, SIGNAL(scrollRequested(int)), this, SLOT(handleScrollRequested(int)));
+	connect(conversation_, SIGNAL(scrollReachedTop()), this, SLOT(handleScrollReachedTop()));
+	connect(conversation_, SIGNAL(scrollReachedBottom()), this, SLOT(handleScrollReachedBottom()));
+	connect(conversation_, SIGNAL(fontResized(int)), this, SLOT(handleFontResized(int)));
+	connect(ui_.searchBox_->lineEdit(), SIGNAL(returnPressed()), this, SLOT(handleReturnPressed()));
+	connect(ui_.calendarWidget_, SIGNAL(clicked(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&)));
+	connect(ui_.calendarWidget_, SIGNAL(activated(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&)));
+	connect(ui_.previousButton_, SIGNAL(clicked(bool)), this, SLOT(handlePreviousButtonClicked()));
+	connect(ui_.nextButton_, SIGNAL(clicked(bool)), this, SLOT(handleNextButtonClicked()));
+}
+
+QtHistoryWindow::~QtHistoryWindow() {
+	disconnect(conversation_, SIGNAL(scrollRequested(int)), this, SLOT(handleScrollRequested(int)));
+	disconnect(conversation_, SIGNAL(scrollReachedTop()), this, SLOT(handleScrollReachedTop()));
+	disconnect(conversation_, SIGNAL(scrollReachedBottom()), this, SLOT(handleScrollReachedBottom()));
+	disconnect(conversation_, SIGNAL(fontResized(int)), this, SLOT(handleFontResized(int)));
+	disconnect(ui_.searchBox_->lineEdit(), SIGNAL(returnPressed()), this, SLOT(handleReturnPressed()));
+	disconnect(ui_.calendarWidget_, SIGNAL(clicked(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&)));
+	disconnect(ui_.calendarWidget_, SIGNAL(activated(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&)));
+	disconnect(ui_.previousButton_, SIGNAL(clicked(bool)), this, SLOT(handlePreviousButtonClicked()));
+	disconnect(ui_.nextButton_, SIGNAL(clicked(bool)), this, SLOT(handleNextButtonClicked()));
+
+	delete theme_;
+	delete conversation_;
+	// TODO: delete ui_
+}
+
+void QtHistoryWindow::activate() {
+	emit wantsToActivate();
+}
+
+void QtHistoryWindow::showEvent(QShowEvent* event) {
+	emit windowOpening();
+	emit titleUpdated();
+	QWidget::showEvent(event);
+}
+
+void QtHistoryWindow::closeEvent(QCloseEvent* event) {
+	emit windowClosing();
+	event->accept();
+}
+
+void QtHistoryWindow::setRosterModel(Roster* model) {
+	conversationRoster_->setRosterModel(model);
+}
+
+void QtHistoryWindow::addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop) {
+	QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str());
+
+	QString messageHTML(P2QSTRING(message));
+	messageHTML = Qt::escape(messageHTML);
+	QString searchTerm = ui_.searchBox_->lineEdit()->text();
+	if (searchTerm.length()) {
+		messageHTML.replace(searchTerm, "<span style='background-color: yellow'>" + searchTerm + "</span>");
+	}
+
+	// note: time uses localtime
+	QDate date = QDate(time.date().year(), time.date().month(), time.date().day());
+	QTime dayTime = QTime(time.time_of_day().hours(), time.time_of_day().minutes(), time.time_of_day().seconds());
+	QDateTime qTime = QDateTime(date, dayTime);
+
+	std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++);
+
+	QString qAvatarPath =  scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded();
+
+	if (addAtTheTop) {
+		bool appendToPrevious = ((senderIsSelf && previousTopMessageWasSelf_) || (!senderIsSelf && !previousTopMessageWasSelf_&& previousTopSenderName_ == P2QSTRING(senderName)));
+		conversation_->addMessageTop(boost::shared_ptr<ChatSnippet>(new MessageSnippet(messageHTML, Qt::escape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id))));
+
+		previousTopMessageWasSelf_ = senderIsSelf;
+		previousTopSenderName_ = P2QSTRING(senderName);
+	}
+	else {
+		bool appendToPrevious = ((senderIsSelf && previousBottomMessageWasSelf_) || (!senderIsSelf && !previousBottomMessageWasSelf_&& previousBottomSenderName_ == P2QSTRING(senderName)));
+		conversation_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(messageHTML, Qt::escape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id))));
+		previousBottomMessageWasSelf_ = senderIsSelf;
+		previousBottomSenderName_ = P2QSTRING(senderName);
+	}
+
+	// keep track of the days viewable in the chatView
+	if (!dates_.count(date)) {
+		dates_.insert(date);
+	}
+}
+
+void QtHistoryWindow::handleSomethingSelectedChanged(RosterItem* item) {
+	onSelectedContactChanged(item);
+}
+
+void QtHistoryWindow::resetConversationView() {
+	previousTopMessageWasSelf_ = false;
+	previousBottomMessageWasSelf_ = false;
+	previousTopSenderName_.clear();
+	previousBottomSenderName_.clear();
+
+	dates_.clear();
+	conversation_->resetView();
+}
+
+void QtHistoryWindow::handleScrollRequested(int pos) {
+	// first message starts with offset 5
+	if (pos < 5) {
+		pos = 5;
+	}
+
+	QDate currentDate;
+	foreach (const QDate& date, dates_) {
+		int snippetPosition = conversation_->getSnippetPositionByDate(date);
+		if (snippetPosition <= pos) {
+			currentDate = date;
+		}
+	}
+
+	if (ui_.calendarWidget_->selectedDate() != currentDate) {
+		ui_.calendarWidget_->setSelectedDate(currentDate);
+	}
+}
+
+void QtHistoryWindow::handleScrollReachedTop() {
+	if (dates_.empty()) {
+		return;
+	}
+
+	int year, month, day;
+	QDate firstDate = *dates_.begin();
+	firstDate.getDate(&year, &month, &day);
+	onScrollReachedTop(boost::gregorian::date(year, month, day));
+}
+
+void QtHistoryWindow::handleScrollReachedBottom() {
+	if (dates_.empty()) {
+		return;
+	}
+
+	int year, month, day;
+	QDate lastDate = *dates_.rbegin();
+	lastDate.getDate(&year, &month, &day);
+	onScrollReachedBottom(boost::gregorian::date(year, month, day));
+}
+
+void QtHistoryWindow::handleReturnPressed() {
+	onReturnPressed(ui_.searchBox_->lineEdit()->text().toStdString());
+}
+
+void QtHistoryWindow::handleCalendarClicked(const QDate& date) {
+	int year, month, day;
+	QDate tempDate = date; // getDate discards const qualifier
+	tempDate.getDate(&year, &month, &day);
+	onCalendarClicked(boost::gregorian::date(year, month, day));
+}
+
+void QtHistoryWindow::setDate(const boost::gregorian::date& date) {
+	ui_.calendarWidget_->setSelectedDate(QDate::fromJulianDay(date.julian_day()));
+}
+
+void QtHistoryWindow::handleNextButtonClicked() {
+	onNextButtonClicked();
+}
+
+void QtHistoryWindow::handlePreviousButtonClicked() {
+	onPreviousButtonClicked();
+}
+
+void QtHistoryWindow::handleFontResized(int fontSizeSteps) {
+	conversation_->resizeFont(fontSizeSteps);
+
+	emit fontResized(fontSizeSteps);
+}
+
+void QtHistoryWindow::resetConversationViewTopInsertPoint() {
+	previousTopMessageWasSelf_ = false;
+	previousTopSenderName_ = QString();
+	conversation_->resetTopInsertPoint();
+}
+
+std::string QtHistoryWindow::getSearchBoxText() {
+	return ui_.searchBox_->lineEdit()->text().toStdString();
+}
+
+boost::gregorian::date QtHistoryWindow::getLastVisibleDate() {
+	if (!dates_.empty()) {
+		QDate lastDate = *dates_.rbegin();
+		int year, month, day;
+		lastDate.getDate(&year, &month, &day);
+
+		return boost::gregorian::date(year, month, day);
+	}
+	return boost::gregorian::date(boost::gregorian::not_a_date_time);
+}
+
+}
diff --git a/Swift/QtUI/QtHistoryWindow.h b/Swift/QtUI/QtHistoryWindow.h
new file mode 100644
index 0000000..49de098
--- /dev/null
+++ b/Swift/QtUI/QtHistoryWindow.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2012 Catalin Badea
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/HistoryWindow.h>
+#include <Swift/QtUI/ui_QtHistoryWindow.h>
+#include <QtChatView.h>
+#include <QtTabbable.h>
+#include <Swift/QtUI/Roster/QtTreeWidget.h>
+#include <set>
+#include <QDate>
+
+namespace Swift {
+	class QtHistoryWindow : public QtTabbable, public HistoryWindow {
+			Q_OBJECT
+
+		public:
+			QtHistoryWindow(SettingsProvider*, UIEventStream*);
+			~QtHistoryWindow();
+			void activate();
+			void setRosterModel(Roster*);
+			void addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop);
+			void resetConversationView();
+			void resetConversationViewTopInsertPoint();
+			void setDate(const boost::gregorian::date& date);
+
+			void closeEvent(QCloseEvent* event);
+			void showEvent(QShowEvent* event);
+
+			std::string getSearchBoxText();
+			boost::gregorian::date getLastVisibleDate();
+
+		signals:
+			void fontResized(int);
+
+		public slots:
+			void handleFontResized(int fontSizeSteps);
+
+		protected slots:
+			void handleScrollRequested(int pos);
+			void handleScrollReachedTop();
+			void handleScrollReachedBottom();
+			void handleReturnPressed();
+			void handleCalendarClicked(const QDate& date);
+			void handlePreviousButtonClicked();
+			void handleNextButtonClicked();
+
+		private:
+			void handleSomethingSelectedChanged(RosterItem* item);
+
+			Ui::QtHistoryWindow ui_;
+			QtChatTheme* theme_;
+			QtChatView* conversation_;
+			QtTreeWidget* conversationRoster_;
+			std::set<QDate> dates_;
+			int idCounter_;
+			bool previousTopMessageWasSelf_;
+			QString previousTopSenderName_;
+			bool previousBottomMessageWasSelf_;
+			QString previousBottomSenderName_;
+	};
+}
diff --git a/Swift/QtUI/QtHistoryWindow.ui b/Swift/QtUI/QtHistoryWindow.ui
new file mode 100644
index 0000000..77d592f
--- /dev/null
+++ b/Swift/QtUI/QtHistoryWindow.ui
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtHistoryWindow</class>
+ <widget class="QWidget" name="QtHistoryWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>608</width>
+    <height>522</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <layout class="QHBoxLayout" name="topLayout_">
+     <item>
+      <widget class="QLabel" name="label_">
+       <property name="text">
+        <string>Search:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="searchBox_">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="editable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="nextButton_">
+       <property name="text">
+        <string>Next</string>
+       </property>
+       <property name="flat">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="previousButton_">
+       <property name="text">
+        <string>Previous</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="0">
+    <widget class="QSplitter" name="bottomLayout_">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <widget class="QWidget" name="layoutWidget">
+      <layout class="QVBoxLayout" name="bottomLeftLayout_" stretch="0,0">
+       <item>
+        <widget class="QWidget" name="conversationRoster_" native="true">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+           <horstretch>5</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QCalendarWidget" name="calendarWidget_">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="conversation_" native="true">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+        <horstretch>85</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index d312546..ced375f 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -27,6 +27,7 @@
 #include <Swift/QtUI/QtLoginWindow.h>
 #include <Roster/QtRosterWidget.h>
 #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h>
@@ -122,6 +123,11 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr
 	QAction* joinMUCAction = new QAction(tr("Enter &Room…"), this);
 	connect(joinMUCAction, SIGNAL(triggered()), SLOT(handleJoinMUCAction()));
 	actionsMenu->addAction(joinMUCAction);
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	QAction* viewLogsAction = new QAction(tr("&View History…"), this);
+	connect(viewLogsAction, SIGNAL(triggered()), SLOT(handleViewLogsAction()));
+	actionsMenu->addAction(viewLogsAction);
+#endif
 	addUserAction_ = new QAction(tr("&Add Contact…"), this);
 	connect(addUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddUserActionTriggered(bool)));
 	actionsMenu->addAction(addUserAction_);
@@ -235,6 +241,10 @@ void QtMainWindow::handleJoinMUCAction() {
 	uiEventStream_->send(boost::make_shared<RequestJoinMUCUIEvent>());
 }
 
+void QtMainWindow::handleViewLogsAction() {
+	uiEventStream_->send(boost::make_shared<RequestHistoryUIEvent>());
+}
+
 void QtMainWindow::handleStatusChanged(StatusShow::Type showType, const QString &statusMessage) {
 	onChangeStatusRequest(showType, Q2PSTRING(statusMessage));
 }
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index 251c346..26d25e1 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -58,6 +58,7 @@ namespace Swift {
 			void handleShowOfflineToggled(bool);
 			void handleShowEmoticonsToggled(bool);
 			void handleJoinMUCAction();
+			void handleViewLogsAction();
 			void handleSignOutAction();
 			void handleEditProfileAction();
 			void handleAddUserActionTriggered(bool checked);
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 78de7aa..2197ec6 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -27,11 +27,13 @@
 #include "QtFileTransferListWidget.h"
 #include <Swift/Controllers/Settings/SettingsProviderHierachy.h>
 #include <Swift/QtUI/QtUISettingConstants.h>
+#include <QtHistoryWindow.h>
 
 namespace Swift {
 
 QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, bool startMinimized, bool emoticonsExist) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), startMinimized(startMinimized), emoticonsExist_(emoticonsExist) {
 	chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE);
+	historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE);
 }
 
 XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() {
@@ -44,6 +46,25 @@ XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() {
 	return widget;
 }
 
+HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) {
+	QtHistoryWindow* window = new QtHistoryWindow(settings, uiEventStream);
+	tabs->addTab(window);
+	if (!tabs->isVisible()) {
+		tabs->show();
+	}
+
+	connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int)));
+
+	window->handleFontResized(historyFontSize_);
+	window->show();
+	return window;
+}
+
+void QtUIFactory::handleHistoryWindowFontResized(int size) {
+	historyFontSize_ = size;
+	settings->storeSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE, size);
+}
+
 FileTransferListWidget* QtUIFactory::createFileTransferListWidget() {
 	QtFileTransferListWidget* widget = new QtFileTransferListWidget();
 	tabs->addTab(widget);
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index edb89ad..1b2431f 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -24,6 +24,7 @@ namespace Swift {
 	class QtChatWindowFactory;
 	class QtChatWindow;
 	class TimerFactory;
+	class historyWindow_;
 
 	class QtUIFactory : public QObject, public UIFactory {
 			Q_OBJECT
@@ -31,6 +32,7 @@ namespace Swift {
 			QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, bool startMinimized, bool emoticonsExist);
 
 			virtual XMLConsoleWidget* createXMLConsoleWidget();
+			virtual HistoryWindow* createHistoryWindow(UIEventStream*);
 			virtual MainWindow* createMainWindow(UIEventStream* eventStream);
 			virtual LoginWindow* createLoginWindow(UIEventStream* eventStream);
 			virtual EventWindow* createEventWindow();
@@ -47,6 +49,7 @@ namespace Swift {
 		private slots:
 			void handleLoginWindowGeometryChanged();
 			void handleChatWindowFontResized(int);
+			void handleHistoryWindowFontResized(int);
 
 		private:
 			SettingsProviderHierachy* settings;
@@ -61,6 +64,7 @@ namespace Swift {
 			std::vector<QPointer<QtChatWindow> > chatWindows;
 			bool startMinimized;
 			int chatFontSize;
+			int historyFontSize_;
 			bool emoticonsExist_;
 	};
 }
diff --git a/Swift/QtUI/QtUISettingConstants.cpp b/Swift/QtUI/QtUISettingConstants.cpp
index 81022ec..68001d7 100644
--- a/Swift/QtUI/QtUISettingConstants.cpp
+++ b/Swift/QtUI/QtUISettingConstants.cpp
@@ -13,5 +13,6 @@ const SettingsProvider::Setting<std::string> QtUISettingConstants::CLICKTHROUGH_
 const SettingsProvider::Setting<int> QtUISettingConstants::CURRENT_ROSTER_TAB("currentRosterTab", 0);
 const SettingsProvider::Setting<bool> QtUISettingConstants::SHOW_NICK_IN_ROSTER_HEADER("showNickInRosterHeader", true);
 const SettingsProvider::Setting<int> QtUISettingConstants::CHATWINDOW_FONT_SIZE("chatWindowFontSize", 0);
+const SettingsProvider::Setting<int> QtUISettingConstants::HISTORYWINDOW_FONT_SIZE("historyWindowFontSize", 0);
 const SettingsProvider::Setting<bool> QtUISettingConstants::SHOW_EMOTICONS("showEmoticons", true);
 }
diff --git a/Swift/QtUI/QtUISettingConstants.h b/Swift/QtUI/QtUISettingConstants.h
index 2740abb..8ac835f 100644
--- a/Swift/QtUI/QtUISettingConstants.h
+++ b/Swift/QtUI/QtUISettingConstants.h
@@ -16,6 +16,7 @@ namespace Swift {
 			static const SettingsProvider::Setting<int> CURRENT_ROSTER_TAB;
 			static const SettingsProvider::Setting<bool> SHOW_NICK_IN_ROSTER_HEADER;
 			static const SettingsProvider::Setting<int> CHATWINDOW_FONT_SIZE;
+			static const SettingsProvider::Setting<int> HISTORYWINDOW_FONT_SIZE;
 			static const SettingsProvider::Setting<bool> SHOW_EMOTICONS;
 	};
 }
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 064faab..27ff237 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -94,6 +94,7 @@ sources = [
     "QtTabWidget.cpp",
     "QtTextEdit.cpp",
     "QtXMLConsoleWidget.cpp",
+    "QtHistoryWindow.cpp",
     "QtFileTransferListWidget.cpp",
     "QtFileTransferListItemModel.cpp",
     "QtAdHocCommandWindow.cpp",
@@ -198,6 +199,7 @@ myenv.Uic4("UserSearch/QtUserSearchResultsPage.ui")
 myenv.Uic4("QtBookmarkDetailWindow.ui")
 myenv.Uic4("QtAffiliationEditor.ui")
 myenv.Uic4("QtJoinMUCWindow.ui")
+myenv.Uic4("QtHistoryWindow.ui")
 myenv.Qrc("DefaultTheme.qrc")
 myenv.Qrc("Swift.qrc")
 
diff --git a/Swift/resources/themes/Default/Template.html b/Swift/resources/themes/Default/Template.html
index e94701a..9d5c3a0 100755
--- a/Swift/resources/themes/Default/Template.html
+++ b/Swift/resources/themes/Default/Template.html
@@ -276,7 +276,7 @@
 			//return;
 			if( intervall_scroll ) clearInterval( intervall_scroll );
 			intervall_scroll = setInterval( function() {
-				var target_scroll = (document.body.scrollHeight-window.innerHeight);
+				var target_scroll = (document.body.scrollHeight-window.innerHeight) - 1;
 				var scrolldiff = target_scroll - document.body.scrollTop;
 				if ( document.body.scrollTop != target_scroll ) {
 					var saved_scroll = document.body.scrollTop;
diff --git a/Swift/resources/themes/Default/main.css b/Swift/resources/themes/Default/main.css
index d2d4b57..25bd5bc 100755
--- a/Swift/resources/themes/Default/main.css
+++ b/Swift/resources/themes/Default/main.css
@@ -291,3 +291,7 @@ body {
 .outgoingItem .timeStamp {
 	color:#9ecf35;
 }
+
+html {
+	height: 101%;
+}
diff --git a/Swiften/Base/foreach.h b/Swiften/Base/foreach.h
index 87f6147..3ad506d 100644
--- a/Swiften/Base/foreach.h
+++ b/Swiften/Base/foreach.h
@@ -10,3 +10,4 @@
 
 #undef foreach
 #define foreach BOOST_FOREACH
+#define reverse_foreach BOOST_REVERSE_FOREACH
diff --git a/Swiften/Client/MemoryStorages.cpp b/Swiften/Client/MemoryStorages.cpp
index fe171f7..703e9ff 100644
--- a/Swiften/Client/MemoryStorages.cpp
+++ b/Swiften/Client/MemoryStorages.cpp
@@ -9,6 +9,7 @@
 #include <Swiften/Avatars/AvatarMemoryStorage.h>
 #include <Swiften/Disco/CapsMemoryStorage.h>
 #include <Swiften/Roster/RosterMemoryStorage.h>
+#include <Swiften/History/SQLiteHistoryStorage.h>
 
 namespace Swift {
 
@@ -17,6 +18,9 @@ MemoryStorages::MemoryStorages() {
 	capsStorage = new CapsMemoryStorage();
 	avatarStorage = new AvatarMemoryStorage();
 	rosterStorage = new RosterMemoryStorage();
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	historyStorage = new SQLiteHistoryStorage(":memory:");
+#endif
 }
 
 MemoryStorages::~MemoryStorages() {
@@ -24,6 +28,9 @@ MemoryStorages::~MemoryStorages() {
 	delete avatarStorage;
 	delete capsStorage;
 	delete vcardStorage;
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	delete historyStorage;
+#endif
 }
 
 VCardStorage* MemoryStorages::getVCardStorage() const {
@@ -42,5 +49,13 @@ RosterStorage* MemoryStorages::getRosterStorage() const {
 	return rosterStorage;
 }
 
+HistoryStorage* MemoryStorages::getHistoryStorage() const {
+#ifdef SWIFT_EXPERIMENTAL_HISTORY
+	return historyStorage;
+#else
+	return NULL;
+#endif
+}
+
 
 }
diff --git a/Swiften/Client/MemoryStorages.h b/Swiften/Client/MemoryStorages.h
index ca01a7a..403a89a 100644
--- a/Swiften/Client/MemoryStorages.h
+++ b/Swiften/Client/MemoryStorages.h
@@ -24,11 +24,13 @@ namespace Swift {
 			virtual AvatarStorage* getAvatarStorage() const;
 			virtual CapsStorage* getCapsStorage() const;
 			virtual RosterStorage* getRosterStorage() const;
+			virtual HistoryStorage* getHistoryStorage() const;
 
 		private:
 			VCardMemoryStorage* vcardStorage;
 			AvatarStorage* avatarStorage;
 			CapsStorage* capsStorage;
 			RosterStorage* rosterStorage;
+			HistoryStorage* historyStorage;
 	};
 }
diff --git a/Swiften/Client/Storages.h b/Swiften/Client/Storages.h
index 89b770c..76650a6 100644
--- a/Swiften/Client/Storages.h
+++ b/Swiften/Client/Storages.h
@@ -13,6 +13,7 @@ namespace Swift {
 	class AvatarStorage;
 	class CapsStorage;
 	class RosterStorage;
+	class HistoryStorage;
 
 	/**
 	 * An interface to hold storage classes for different
@@ -26,5 +27,6 @@ namespace Swift {
 			virtual AvatarStorage* getAvatarStorage() const = 0;
 			virtual CapsStorage* getCapsStorage() const = 0;
 			virtual RosterStorage* getRosterStorage() const = 0;
+			virtual HistoryStorage* getHistoryStorage() const = 0;
 	};
 }
diff --git a/Swiften/History/HistoryManager.cpp b/Swiften/History/HistoryManager.cpp
deleted file mode 100644
index 7eb66ab..0000000
--- a/Swiften/History/HistoryManager.cpp
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#include <Swiften/History/HistoryManager.h>
-
-namespace Swift {
-
-HistoryManager::~HistoryManager() {
-}
-
-}
diff --git a/Swiften/History/HistoryManager.h b/Swiften/History/HistoryManager.h
deleted file mode 100644
index 7a4324b..0000000
--- a/Swiften/History/HistoryManager.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#pragma once
-
-#include <vector>
-#include <Swiften/JID/JID.h>
-#include <Swiften/History/HistoryMessage.h>
-
-namespace Swift {
-	class HistoryManager {
-		public:
-			virtual ~HistoryManager();
-
-			virtual void addMessage(const HistoryMessage& message) = 0;
-
-			virtual std::vector<HistoryMessage> getMessages() const = 0;
-	};
-}
diff --git a/Swiften/History/HistoryMessage.h b/Swiften/History/HistoryMessage.h
index 461f5de..b3d977f 100644
--- a/Swiften/History/HistoryMessage.h
+++ b/Swiften/History/HistoryMessage.h
@@ -12,33 +12,61 @@
 namespace Swift {
 	class HistoryMessage {
 		public:
-			HistoryMessage(const std::string& message, const JID& from, const JID& to, const boost::posix_time::ptime time) : message_(message), from_(from), to_(to), time_(time) {
+			enum Type {
+				Chat = 0,
+				Groupchat = 1,
+				PrivateMessage = 2
+			};
+
+			HistoryMessage(
+				const std::string& message,
+				const JID& fromJID,
+				const JID& toJID,
+				Type type,
+				const boost::posix_time::ptime& time,
+				int utcOffset = 0) :
+					message_(message),
+					fromJID_(fromJID),
+					toJID_(toJID),
+					type_(type),
+					time_(time),
+					utcOffset_(utcOffset) {
 			}
 
 			const std::string& getMessage() const {
 				return message_;
 			}
 
-			const JID& getFrom() const {
-				return from_;
+			const JID& getFromJID() const {
+				return fromJID_;
+			}
+
+			const JID& getToJID() const {
+				return toJID_;
 			}
 
-			const JID& getTo() const {
-				return to_;
+			Type getType() const {
+				return type_;
 			}
 
 			boost::posix_time::ptime getTime() const {
 				return time_;
 			}
 
+			int getOffset() const {
+				return utcOffset_;
+			}
+
 			bool operator==(const HistoryMessage& o) const {
-				return message_ == o.message_ && from_ == o.from_ && to_ == o.to_ && time_ == o.time_;
+				return message_ == o.message_ && fromJID_ == o.fromJID_ && toJID_ == o.toJID_ && type_ == o.type_ && time_ == o.time_;
 			}
 
 		private:
 			std::string message_;
-			JID from_;
-			JID to_;
+			JID fromJID_;
+			JID toJID_;
+			Type type_;
 			boost::posix_time::ptime time_;
+			int utcOffset_;
 	};
 }
diff --git a/Swiften/History/HistoryStorage.h b/Swiften/History/HistoryStorage.h
new file mode 100644
index 0000000..fcf28b5
--- /dev/null
+++ b/Swiften/History/HistoryStorage.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <set>
+#include <map>
+#include <vector>
+#include <Swiften/JID/JID.h>
+#include <Swiften/History/HistoryMessage.h>
+#include <boost/date_time/gregorian/gregorian_types.hpp>
+
+namespace Swift {
+	typedef std::map<JID, std::set<boost::gregorian::date> > ContactsMap;
+
+	class HistoryStorage {
+		/**
+		 * Messages are stored using localtime timestamps.
+		 */
+		public:
+			virtual ~HistoryStorage() {};
+
+			virtual void addMessage(const HistoryMessage& message) = 0;
+			virtual std::vector<HistoryMessage> getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0;
+			virtual std::vector<HistoryMessage> getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0;
+			virtual std::vector<HistoryMessage> getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0;
+			virtual ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const = 0;
+			virtual boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const = 0;
+	};
+}
diff --git a/Swiften/History/SConscript b/Swiften/History/SConscript
index 9c2a9d6..bc0d64c 100644
--- a/Swiften/History/SConscript
+++ b/Swiften/History/SConscript
@@ -1,11 +1,10 @@
 Import("swiften_env")
 
-#myenv = swiften_env.Clone()
-#if myenv["target"] == "native":
-#   myenv.MergeFlags(swiften_env.get("SQLITE_FLAGS", {}))
-#
-#objects = myenv.SwiftenObject([
-#			"HistoryManager.cpp",
-#			"SQLiteHistoryManager.cpp",
-#		])
-#swiften_env.Append(SWIFTEN_OBJECTS = [objects])
+myenv = swiften_env.Clone()
+if myenv["target"] == "native":
+   myenv.MergeFlags(swiften_env.get("SQLITE_FLAGS", {}))
+
+objects = myenv.SwiftenObject([
+			"SQLiteHistoryStorage.cpp",
+		])
+swiften_env.Append(SWIFTEN_OBJECTS = [objects])
diff --git a/Swiften/History/SQLiteHistoryManager.cpp b/Swiften/History/SQLiteHistoryManager.cpp
deleted file mode 100644
index 3b65f62..0000000
--- a/Swiften/History/SQLiteHistoryManager.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#include <iostream>
-#include <boost/lexical_cast.hpp>
-
-#include <sqlite3.h>
-#include <Swiften/History/SQLiteHistoryManager.h>
-
-namespace {
-
-inline Swift::std::string getEscapedString(const Swift::std::string& s) {
-	Swift::std::string result(s);
-	result.replaceAll('\'', Swift::std::string("\\'"));
-	return result;
-}
-
-}
-
-
-namespace Swift {
-
-SQLiteHistoryManager::SQLiteHistoryManager(const std::string& file) : db_(0) {
-	sqlite3_open(file.c_str(), &db_);
-	if (!db_) {
-		std::cerr << "Error opening database " << file << std::endl; // FIXME
-	}
-
-	char* errorMessage;
-	int result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS messages('from' INTEGER, 'to' INTEGER, 'message' STRING, 'time' INTEGER)", 0, 0, &errorMessage);
-	if (result != SQLITE_OK) {
-		std::cerr << "SQL Error: " << errorMessage << std::endl;
-		sqlite3_free(errorMessage);
-	}
-
-	result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS jids('id' INTEGER PRIMARY KEY ASC AUTOINCREMENT, 'jid' STRING UNIQUE NOT NULL)", 0, 0, &errorMessage);
-	if (result != SQLITE_OK) {
-		std::cerr << "SQL Error: " << errorMessage << std::endl;
-		sqlite3_free(errorMessage);
-	}
-}
-
-SQLiteHistoryManager::~SQLiteHistoryManager() {
-	sqlite3_close(db_);
-}
-
-void SQLiteHistoryManager::addMessage(const HistoryMessage& message) {
-	int secondsSinceEpoch = (message.getTime() - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds();
-	std::string statement = std::string("INSERT INTO messages('from', 'to', 'message', 'time') VALUES(") + boost::lexical_cast<std::string>(getIDForJID(message.getFrom())) + ", " + boost::lexical_cast<std::string>(getIDForJID(message.getTo())) + ", '" + getEscapedString(message.getMessage()) + "', " + boost::lexical_cast<std::string>(secondsSinceEpoch) + ")";
-	char* errorMessage;
-	int result = sqlite3_exec(db_, statement.c_str(), 0, 0, &errorMessage);
-	if (result != SQLITE_OK) {
-		std::cerr << "SQL Error: " << errorMessage << std::endl;
-		sqlite3_free(errorMessage);
-	}
-}
-
-std::vector<HistoryMessage> SQLiteHistoryManager::getMessages() const {
-	std::vector<HistoryMessage> result;
-	sqlite3_stmt* selectStatement;
-	std::string selectQuery("SELECT messages.'from', messages.'to', messages.'message', messages.'time' FROM messages");
-	int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL);
-	if (r != SQLITE_OK) {
-		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
-	}
-	r = sqlite3_step(selectStatement);
-	while (r == SQLITE_ROW) {
-		boost::optional<JID> from(getJIDFromID(sqlite3_column_int(selectStatement, 0)));
-		boost::optional<JID> to(getJIDFromID(sqlite3_column_int(selectStatement, 1)));
-		std::string message(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 2)));
-		int secondsSinceEpoch(sqlite3_column_int(selectStatement, 3));
-		boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch));
-
-		result.push_back(HistoryMessage(message, (from ? *from : JID()), (to ? *to : JID()), time));
-		r = sqlite3_step(selectStatement);
-	}
-	if (r != SQLITE_DONE) {
-		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
-	}
-	sqlite3_finalize(selectStatement);
-	return result;
-}
-
-int SQLiteHistoryManager::getIDForJID(const JID& jid) {
-	boost::optional<int> id = getIDFromJID(jid);
-	if (id) {
-		return *id;
-	}
-	else {
-		return addJID(jid);
-	}
-}
-
-int SQLiteHistoryManager::addJID(const JID& jid) {
-	std::string statement = std::string("INSERT INTO jids('jid') VALUES('") + getEscapedString(jid.toString()) + "')";
-	char* errorMessage;
-	int result = sqlite3_exec(db_, statement.c_str(), 0, 0, &errorMessage);
-	if (result != SQLITE_OK) {
-		std::cerr << "SQL Error: " << errorMessage << std::endl;
-		sqlite3_free(errorMessage);
-	}
-	return sqlite3_last_insert_rowid(db_);
-}
-
-boost::optional<JID> SQLiteHistoryManager::getJIDFromID(int id) const {
-	boost::optional<JID> result;
-	sqlite3_stmt* selectStatement;
-	std::string selectQuery("SELECT jid FROM jids WHERE id=" + boost::lexical_cast<std::string>(id));
-	int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL);
-	if (r != SQLITE_OK) {
-		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
-	}
-	r = sqlite3_step(selectStatement);
-	if (r == SQLITE_ROW) {
-		result = boost::optional<JID>(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 0)));
-	}
-	sqlite3_finalize(selectStatement);
-	return result;
-}
-
-boost::optional<int> SQLiteHistoryManager::getIDFromJID(const JID& jid) const {
-	boost::optional<int> result;
-	sqlite3_stmt* selectStatement;
-	std::string selectQuery("SELECT id FROM jids WHERE jid='" + jid.toString() + "'");
-	int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL);
-	if (r != SQLITE_OK) {
-		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
-	}
-	r = sqlite3_step(selectStatement);
-	if (r == SQLITE_ROW) {
-		result = boost::optional<int>(sqlite3_column_int(selectStatement, 0));
-	}
-	sqlite3_finalize(selectStatement);
-	return result;
-}
-
-}
diff --git a/Swiften/History/SQLiteHistoryManager.h b/Swiften/History/SQLiteHistoryManager.h
deleted file mode 100644
index ffd9492..0000000
--- a/Swiften/History/SQLiteHistoryManager.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#pragma once
-
-#include <boost/optional.hpp>
-
-#include <Swiften/History/HistoryManager.h>
-
-struct sqlite3;
-
-namespace Swift {
-	class SQLiteHistoryManager : public HistoryManager {
-		public:
-			SQLiteHistoryManager(const std::string& file);
-			~SQLiteHistoryManager();
-
-			virtual void addMessage(const HistoryMessage& message);
-			virtual std::vector<HistoryMessage> getMessages() const;
-
-			int getIDForJID(const JID&);
-			int addJID(const JID&);
-
-			boost::optional<JID> getJIDFromID(int id) const;
-			boost::optional<int> getIDFromJID(const JID& jid) const;
-
-		private:
-			sqlite3* db_;
-	};
-}
diff --git a/Swiften/History/SQLiteHistoryStorage.cpp b/Swiften/History/SQLiteHistoryStorage.cpp
new file mode 100644
index 0000000..ed0d6a3
--- /dev/null
+++ b/Swiften/History/SQLiteHistoryStorage.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <iostream>
+#include <boost/lexical_cast.hpp>
+
+#include <sqlite3.h>
+#include <3rdParty/SQLite/sqlite3async.h>
+#include <Swiften/History/SQLiteHistoryStorage.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+
+inline std::string getEscapedString(const std::string& s) {
+	std::string result(s);
+
+	size_t pos = result.find('\'');
+	while (pos != std::string::npos) {
+		result.insert(pos, "'");
+		pos = result.find('\'', pos + 2);
+	}
+	return result;
+}
+
+namespace Swift {
+
+SQLiteHistoryStorage::SQLiteHistoryStorage(const std::string& file) : db_(0) {
+	sqlite3_vfs vfs;
+
+	sqlite3async_initialize(NULL, true);
+	sqlite3_vfs_register(&vfs, false);
+
+	thread_ = new boost::thread(boost::bind(&SQLiteHistoryStorage::run, this));
+
+	sqlite3_open_v2(file.c_str(), &db_, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "sqlite3async");
+	if (!db_) {
+		std::cerr << "Error opening database " << file << std::endl;
+	}
+
+	char* errorMessage;
+	int result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS messages('message' STRING, 'fromBare' INTEGER, 'fromResource' STRING, 'toBare' INTEGER, 'toResource' STRING, 'type' INTEGER, 'time' INTEGER, 'offset' INTEGER)", 0, 0, &errorMessage);
+	if (result != SQLITE_OK) {
+		std::cerr << "SQL Error: " << errorMessage << std::endl;
+		sqlite3_free(errorMessage);
+	}
+
+	result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS jids('id' INTEGER PRIMARY KEY ASC AUTOINCREMENT, 'jid' STRING UNIQUE NOT NULL)", 0, 0, &errorMessage);
+	if (result != SQLITE_OK) {
+		std::cerr << "SQL Error: " << errorMessage << std::endl;
+		sqlite3_free(errorMessage);
+	}
+}
+
+SQLiteHistoryStorage::~SQLiteHistoryStorage() {
+	sqlite3async_shutdown();
+	sqlite3_close(db_);
+	delete thread_;
+}
+
+void SQLiteHistoryStorage::addMessage(const HistoryMessage& message) {
+	int secondsSinceEpoch = (message.getTime() - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds();
+
+	std::string statement = std::string("INSERT INTO messages('message', 'fromBare', 'fromResource', 'toBare', 'toResource', 'type', 'time', 'offset') VALUES(") +
+					"'" + getEscapedString(message.getMessage()) + "', " +
+					boost::lexical_cast<std::string>(getIDForJID(message.getFromJID().toBare())) + ", '" +
+					getEscapedString(message.getFromJID().getResource()) + "', " +
+					boost::lexical_cast<std::string>(getIDForJID(message.getToJID().toBare())) + ", '" +
+					getEscapedString(message.getToJID().getResource()) + "', " +
+					boost::lexical_cast<std::string>(message.getType()) + ", " +
+					boost::lexical_cast<std::string>(secondsSinceEpoch) + ", " +
+					boost::lexical_cast<std::string>(message.getOffset()) + ")";
+	char* errorMessage;
+	int result = sqlite3_exec(db_, statement.c_str(), 0, 0, &errorMessage);
+	if (result != SQLITE_OK) {
+		std::cerr << "SQL Error: " << errorMessage << std::endl;
+		sqlite3_free(errorMessage);
+	}
+}
+
+std::vector<HistoryMessage> SQLiteHistoryStorage::getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const {
+	sqlite3_stmt* selectStatement;
+
+	boost::optional<int> selfID = getIDFromJID(selfJID.toBare());
+	boost::optional<int> contactID = getIDFromJID(contactJID.toBare());
+
+	if (!selfID || !contactID) {
+		// JIDs missing from the database
+		return std::vector<HistoryMessage>();
+	}
+
+	std::string selectQuery = "SELECT * FROM messages WHERE (type=" + boost::lexical_cast<std::string>(type);
+	if (contactJID.isBare()) {
+		// match only bare jid
+		selectQuery += " AND ((fromBare=" + boost::lexical_cast<std::string>(*selfID) + " AND toBare=" +
+				boost::lexical_cast<std::string>(*contactID) + ") OR (fromBare=" +
+				boost::lexical_cast<std::string>(*contactID) + " AND toBare=" + boost::lexical_cast<std::string>(*selfID) + ")))";
+	}
+	else {
+		// match resource too
+		selectQuery += " AND ((fromBare=" + boost::lexical_cast<std::string>(*selfID) + " AND (toBare=" +
+				boost::lexical_cast<std::string>(*contactID) +" AND toResource='" +
+				getEscapedString(contactJID.getResource()) + "')) OR ((fromBare=" +
+				boost::lexical_cast<std::string>(*contactID) + " AND fromResource='" +
+				getEscapedString(contactJID.getResource()) + "') AND toBare=" +
+				boost::lexical_cast<std::string>(*selfID) + ")))";
+	}
+
+	if (!date.is_not_a_date()) {
+		int lowerBound = (boost::posix_time::ptime(date) - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds();
+		int upperBound = lowerBound + 86400;
+
+		selectQuery += " AND (time>=" + boost::lexical_cast<std::string>(lowerBound) +
+				" AND time<" + boost::lexical_cast<std::string>(upperBound) + ")";
+	}
+
+	int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL);
+	if (r != SQLITE_OK) {
+		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
+	}
+	r = sqlite3_step(selectStatement);
+
+	// Retrieve result
+	std::vector<HistoryMessage> result;
+	while (r == SQLITE_ROW) {
+		std::string message(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 0)));
+
+		// fromJID
+		boost::optional<JID> fromJID(getJIDFromID(sqlite3_column_int(selectStatement, 1)));
+		std::string fromResource(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 2)));
+		if (fromJID) {
+			fromJID = boost::optional<JID>(JID(fromJID->getNode(), fromJID->getDomain(), fromResource));
+		}
+
+		// toJID
+		boost::optional<JID> toJID(getJIDFromID(sqlite3_column_int(selectStatement, 3)));
+		std::string toResource(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 4)));
+		if (toJID) {
+			toJID = boost::optional<JID>(JID(toJID->getNode(), toJID->getDomain(), toResource));
+		}
+
+		// message type
+		HistoryMessage::Type type = static_cast<HistoryMessage::Type>(sqlite3_column_int(selectStatement, 5));
+
+		// timestamp
+		int secondsSinceEpoch(sqlite3_column_int(selectStatement, 6));
+		boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch));
+
+		// offset from utc
+		int offset = sqlite3_column_int(selectStatement, 7);
+
+		result.push_back(HistoryMessage(message, (fromJID ? *fromJID : JID()), (toJID ? *toJID : JID()), type, time, offset));
+		r = sqlite3_step(selectStatement);
+	}
+	if (r != SQLITE_DONE) {
+		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
+	}
+	sqlite3_finalize(selectStatement);
+
+	return result;
+}
+
+int SQLiteHistoryStorage::getIDForJID(const JID& jid) {
+	boost::optional<int> id = getIDFromJID(jid);
+	if (id) {
+		return *id;
+	}
+	else {
+		return addJID(jid);
+	}
+}
+
+int SQLiteHistoryStorage::addJID(const JID& jid) {
+	std::string statement = std::string("INSERT INTO jids('jid') VALUES('") + getEscapedString(jid.toString()) + "')";
+	char* errorMessage;
+	int result = sqlite3_exec(db_, statement.c_str(), 0, 0, &errorMessage);
+	if (result != SQLITE_OK) {
+		std::cerr << "SQL Error: " << errorMessage << std::endl;
+		sqlite3_free(errorMessage);
+	}
+	return sqlite3_last_insert_rowid(db_);
+}
+
+boost::optional<JID> SQLiteHistoryStorage::getJIDFromID(int id) const {
+	boost::optional<JID> result;
+	sqlite3_stmt* selectStatement;
+	std::string selectQuery("SELECT jid FROM jids WHERE id=" + boost::lexical_cast<std::string>(id));
+	int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL);
+	if (r != SQLITE_OK) {
+		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
+	}
+	r = sqlite3_step(selectStatement);
+	if (r == SQLITE_ROW) {
+		result = boost::optional<JID>(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 0)));
+	}
+	sqlite3_finalize(selectStatement);
+	return result;
+}
+
+boost::optional<int> SQLiteHistoryStorage::getIDFromJID(const JID& jid) const {
+	boost::optional<int> result;
+	sqlite3_stmt* selectStatement;
+	std::string selectQuery("SELECT id FROM jids WHERE jid='" + jid.toString() + "'");
+	int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL);
+	if (r != SQLITE_OK) {
+		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
+	}
+	r = sqlite3_step(selectStatement);
+	if (r == SQLITE_ROW) {
+		result = boost::optional<int>(sqlite3_column_int(selectStatement, 0));
+	}
+	sqlite3_finalize(selectStatement);
+	return result;
+}
+
+ContactsMap SQLiteHistoryStorage::getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const {
+	ContactsMap result;
+	sqlite3_stmt* selectStatement;
+
+	// get id
+	boost::optional<int> id = getIDFromJID(selfJID);
+	if (!id) {
+		return result;
+	}
+
+	// get contacts
+	std::string query = "SELECT DISTINCT messages.'fromBare', messages.'fromResource', messages.'toBare', messages.'toResource', messages.'time' "
+		"FROM messages WHERE (type="
+		+ boost::lexical_cast<std::string>(type) + " AND (toBare="
+		+ boost::lexical_cast<std::string>(*id) + " OR fromBare=" + boost::lexical_cast<std::string>(*id) + "))";
+
+	// match keyword
+	if (getEscapedString(keyword).length()) {
+		query += " AND message LIKE '%" + getEscapedString(keyword) + "%'";
+	}
+
+	int r = sqlite3_prepare(db_, query.c_str(), query.size(), &selectStatement, NULL);
+	if (r != SQLITE_OK) {
+		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
+	}
+
+	r = sqlite3_step(selectStatement);
+	while (r == SQLITE_ROW) {
+		int fromBareID = sqlite3_column_int(selectStatement, 0);
+		std::string fromResource(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 1)));
+		int toBareID = sqlite3_column_int(selectStatement, 2);
+		std::string toResource(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 3)));
+		std::string resource;
+
+		int secondsSinceEpoch(sqlite3_column_int(selectStatement, 4));
+		boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch));
+
+		boost::optional<JID> contactJID;
+
+		if (fromBareID == *id) {
+			contactJID = getJIDFromID(toBareID);
+			resource = toResource;
+		}
+		else {
+			contactJID = getJIDFromID(fromBareID);
+			resource = fromResource;
+		}
+
+		// check if it is a MUC contact (from a private conversation)
+		if (type == HistoryMessage::PrivateMessage) {
+			contactJID = boost::optional<JID>(JID(contactJID->getNode(), contactJID->getDomain(), resource));
+		}
+
+		if (contactJID) {
+			result[*contactJID].insert(time.date());
+		}
+
+		r = sqlite3_step(selectStatement);
+	}
+
+	if (r != SQLITE_DONE) {
+		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
+	}
+	sqlite3_finalize(selectStatement);
+
+	return result;
+}
+
+boost::gregorian::date SQLiteHistoryStorage::getNextDateWithLogs(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date, bool reverseOrder) const {
+	sqlite3_stmt* selectStatement;
+	boost::optional<int> selfID = getIDFromJID(selfJID.toBare());
+	boost::optional<int> contactID = getIDFromJID(contactJID.toBare());
+
+	if (!selfID || !contactID) {
+		// JIDs missing from the database
+		return boost::gregorian::date(boost::gregorian::not_a_date_time);
+	}
+
+	std::string selectQuery = "SELECT time FROM messages WHERE (type=" + boost::lexical_cast<std::string>(type);
+	if (contactJID.isBare()) {
+		// match only bare jid
+		selectQuery += " AND ((fromBare=" + boost::lexical_cast<std::string>(*selfID) + " AND toBare=" +
+				boost::lexical_cast<std::string>(*contactID) + ") OR (fromBare=" +
+				boost::lexical_cast<std::string>(*contactID) + " AND toBare=" + boost::lexical_cast<std::string>(*selfID) + ")))";
+	}
+	else {
+		// match resource too
+		selectQuery += " AND ((fromBare=" + boost::lexical_cast<std::string>(*selfID) + " AND (toBare=" +
+				boost::lexical_cast<std::string>(*contactID) +" AND toResource='" +
+				getEscapedString(contactJID.getResource()) + "')) OR ((fromBare=" +
+				boost::lexical_cast<std::string>(*contactID) + " AND fromResource='" +
+				getEscapedString(contactJID.getResource()) + "') AND toBare=" +
+				boost::lexical_cast<std::string>(*selfID) + ")))";
+	}
+
+	int timeStamp = (boost::posix_time::ptime(date) - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds() + (reverseOrder ? 0 : 86400);
+
+	selectQuery += " AND time" + (reverseOrder ? std::string("<") : std::string(">")) + boost::lexical_cast<std::string>(timeStamp);
+	selectQuery += " ORDER BY time " + (reverseOrder ? std::string("DESC") : std::string("ASC")) + " LIMIT 1";
+
+	int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL);
+	if (r != SQLITE_OK) {
+		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
+	}
+
+	r = sqlite3_step(selectStatement);
+	if (r == SQLITE_ROW) {
+		int secondsSinceEpoch(sqlite3_column_int(selectStatement, 0));
+		boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch));
+		std::cout << "next day is: " << time.date() << "\n";
+		return time.date();
+	}
+
+	return boost::gregorian::date(boost::gregorian::not_a_date_time);
+}
+
+std::vector<HistoryMessage> SQLiteHistoryStorage::getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const {
+	boost::gregorian::date nextDate = getNextDateWithLogs(selfJID, contactJID, type, date, false);
+
+	if (nextDate.is_not_a_date()) {
+		return std::vector<HistoryMessage>();
+	}
+
+	return getMessagesFromDate(selfJID, contactJID, type, nextDate);
+}
+
+std::vector<HistoryMessage> SQLiteHistoryStorage::getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const {
+	boost::gregorian::date previousDate = getNextDateWithLogs(selfJID, contactJID, type, date, true);
+
+	if (previousDate.is_not_a_date()) {
+		return std::vector<HistoryMessage>();
+	}
+
+	return getMessagesFromDate(selfJID, contactJID, type, previousDate);
+}
+
+boost::posix_time::ptime SQLiteHistoryStorage::getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const {
+	boost::optional<int> selfID = getIDFromJID(selfJID.toBare());
+	boost::optional<int> mucID = getIDFromJID(mucJID.toBare());
+
+	if (!selfID || !mucID) {
+		// JIDs missing from the database
+		return boost::posix_time::ptime(boost::posix_time::not_a_date_time);
+	}
+
+
+	sqlite3_stmt* selectStatement;
+	std::string selectQuery = "SELECT messages.'time', messages.'offset' from messages WHERE type=1 AND (toBare=" +
+				boost::lexical_cast<std::string>(*selfID) + " AND fromBare=" +
+				boost::lexical_cast<std::string>(*mucID) + ") ORDER BY time DESC LIMIT 1";
+
+	int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL);
+	if (r != SQLITE_OK) {
+		std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl;
+	}
+
+	r = sqlite3_step(selectStatement);
+	if (r == SQLITE_ROW) {
+		int secondsSinceEpoch(sqlite3_column_int(selectStatement, 0));
+		boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch));
+		int offset = sqlite3_column_int(selectStatement, 1);
+
+		return time - boost::posix_time::hours(offset);
+	}
+
+	return boost::posix_time::ptime(boost::posix_time::not_a_date_time);
+}
+
+void SQLiteHistoryStorage::run() {
+	sqlite3async_run();
+}
+
+}
diff --git a/Swiften/History/SQLiteHistoryStorage.h b/Swiften/History/SQLiteHistoryStorage.h
new file mode 100644
index 0000000..782334a
--- /dev/null
+++ b/Swiften/History/SQLiteHistoryStorage.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+
+#include <Swiften/History/HistoryStorage.h>
+#include <boost/thread.hpp>
+
+struct sqlite3;
+
+namespace Swift {
+	class SQLiteHistoryStorage : public HistoryStorage {
+		public:
+			SQLiteHistoryStorage(const std::string& file);
+			~SQLiteHistoryStorage();
+
+			void addMessage(const HistoryMessage& message);
+			ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const;
+			std::vector<HistoryMessage> getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const;
+			std::vector<HistoryMessage> getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const;
+			std::vector<HistoryMessage> getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const;
+			boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const;
+
+		private:
+			void run();
+			boost::gregorian::date getNextDateWithLogs(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date, bool reverseOrder) const;
+			int getIDForJID(const JID&);
+			int addJID(const JID&);
+
+			boost::optional<JID> getJIDFromID(int id) const;
+			boost::optional<int> getIDFromJID(const JID& jid) const;
+
+			sqlite3* db_;
+			boost::thread* thread_;
+	};
+}
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 9996728..7e8eb9e 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -6,7 +6,7 @@ Import("env")
 # Flags
 ################################################################################
 
-swiften_dep_modules = ["BOOST", "GCONF", "ICU", "LIBIDN", "ZLIB", "OPENSSL", "LIBXML", "EXPAT", "AVAHI", "LIBMINIUPNPC", "LIBNATPMP"]
+swiften_dep_modules = ["BOOST", "GCONF", "ICU", "LIBIDN", "ZLIB", "OPENSSL", "LIBXML", "EXPAT", "AVAHI", "LIBMINIUPNPC", "LIBNATPMP", "SQLITE"]
 
 if env["SCONS_STAGE"] == "flags" :
 	env["SWIFTEN_DLL"] = ARGUMENTS.get("swiften_dll")
-- 
cgit v0.10.2-6-g49f6