#include "TwitterPlugin.h"
#include "Requests/StatusUpdateRequest.h"
#include "Requests/DirectMessageRequest.h"
#include "Requests/TimelineRequest.h"
#include "Requests/FetchFriends.h"
#include "Requests/HelpMessageRequest.h"
#include "Requests/PINExchangeProcess.h"
#include "Requests/OAuthFlow.h"
#include "Requests/CreateFriendRequest.h"
#include "Requests/DestroyFriendRequest.h"
#include "Requests/RetweetRequest.h"
#include "Requests/ProfileImageRequest.h"
#include "Swiften/StringCodecs/Hexify.h"

DEFINE_LOGGER(logger, "Twitter Backend");

TwitterPlugin *np = NULL;
Swift::SimpleEventLoop *loop_; // Event Loop

const std::string OLD_APP_KEY = "PCWAdQpyyR12ezp2fVwEhw";
const std::string OLD_APP_SECRET = "EveLmCXJIg2R7BTCpm6OWV8YyX49nI0pxnYXh7JMvDg";

#define abs(x) ((x)<0?-(x):(x))

//Compares two +ve intergers 'a' and 'b' represented as strings
static int cmp(std::string a, std::string b)
{
	int diff = abs((int)a.size() - (int)b.size());
	if(a.size() < b.size()) a = std::string(diff,'0') + a;
	else b = std::string(diff,'0') + b;

	if(a == b) return 0;
	if(a < b) return -1;
	return 1;
}


TwitterPlugin::TwitterPlugin(Config *config, Swift::SimpleEventLoop *loop, StorageBackend *storagebackend, const std::string &host, int port) : NetworkPlugin()
{
	this->config = config;
	this->storagebackend = storagebackend;
	this->m_firstPing = true;

	if (CONFIG_HAS_KEY(config, "twitter.consumer_key") == false) {
		consumerKey = "5mFePMiJi0KpeURONkelg";
	}
	else {
		consumerKey = CONFIG_STRING(config, "twitter.consumer_key");
	}
	if (CONFIG_HAS_KEY(config, "twitter.consumer_secret") == false) {
		consumerSecret = "YFZCDJwRhbkccXEnaYr1waCQejTJcOY8F7l5Wim3FA";
	}
	else {
		consumerSecret = CONFIG_STRING(config, "twitter.consumer_secret");
	}

	if (consumerSecret.empty() || consumerKey.empty()) {
		LOG4CXX_ERROR(logger, "Consumer key and Consumer secret can't be empty.");
		exit(1);
	}

	adminLegacyName = "twitter.com";
	adminChatRoom = "#twitter";
	adminNickName = "twitter";
	adminAlias = "twitter";

	OAUTH_KEY = "twitter_oauth_token";
	OAUTH_SECRET = "twitter_oauth_secret";
	MODE = "mode";

	m_factories = new Swift::BoostNetworkFactories(loop);
	m_conn = m_factories->getConnectionFactory()->createConnection();
	m_conn->onDataRead.connect(boost::bind(&TwitterPlugin::_handleDataRead, this, _1));
	m_conn->connect(Swift::HostAddressPort(*(Swift::HostAddress::fromString(host)), port));

	tp = new ThreadPool(loop_, 10);

	LOG4CXX_INFO(logger, "Fetch timeout is set to " << CONFIG_INT_DEFAULTED(config, "twitter.fetch_timeout", 90000));
	tweet_timer = m_factories->getTimerFactory()->createTimer(CONFIG_INT_DEFAULTED(config, "twitter.fetch_timeout", 90000));
	message_timer = m_factories->getTimerFactory()->createTimer(CONFIG_INT_DEFAULTED(config, "twitter.fetch_timeout", 90000));

	tweet_timer->onTick.connect(boost::bind(&TwitterPlugin::pollForTweets, this));
	//message_timer->onTick.connect(boost::bind(&TwitterPlugin::pollForDirectMessages, this));

	tweet_timer->start();
	message_timer->start();
    cryptoProvider = std::shared_ptr<Swift::CryptoProvider>(Swift::PlatformCryptoProvider::create());


	LOG4CXX_INFO(logger, "Starting the plugin.");
}

TwitterPlugin::~TwitterPlugin()
{
	delete storagebackend;
	std::set<std::string>::iterator it;
	for (it = onlineUsers.begin() ; it != onlineUsers.end() ; it++) delete userdb[*it].sessions;
	delete tp;
}

// Send data to NetworkPlugin server
void TwitterPlugin::sendData(const std::string &string)
{
	m_conn->write(Swift::createSafeByteArray(string));
}

// Receive date from the NetworkPlugin server and invoke the appropirate payload handler (implement in the NetworkPlugin class)
void TwitterPlugin::_handleDataRead(std::shared_ptr<Swift::SafeByteArray> data)
{
	if (m_firstPing) {
		m_firstPing = false;
		// Users can join the network without registering if we allow
		// one user to connect multiple IRC networks.
		NetworkPlugin::PluginConfig cfg;
		cfg.setNeedPassword(false);
		sendConfig(cfg);
	}

	std::string d(data->begin(), data->end());
	handleDataRead(d);
}

// User trying to login into his twitter account
void TwitterPlugin::handleLoginRequest(const std::string &user, const std::string &legacyName, const std::string &password, const std::map<std::string, std::string> &settings)
{
	if(userdb.count(user) && (userdb[user].connectionState == NEW ||
										userdb[user].connectionState == CONNECTED ||
										userdb[user].connectionState == WAITING_FOR_PIN)) {
		LOG4CXX_INFO(logger, std::string("A session corresponding to ") + user + std::string(" is already active"));
		return;
	}

	LOG4CXX_INFO(logger, std::string("Received login request for ") + user);
	initUserSession(user, legacyName, password);
	handleConnected(user);

	LOG4CXX_INFO(logger, "SPECTRUM 1 USER? - " << (userdb[user].spectrum1User? "true" : "false"));

	LOG4CXX_INFO(logger, user << ": Adding Buddy " << adminLegacyName << " " << adminAlias);
	handleBuddyChanged(user, adminLegacyName, adminAlias, std::vector<std::string>(), pbnetwork::STATUS_ONLINE);
	userdb[user].nickName = "";

	LOG4CXX_INFO(logger, "Querying database for usersettings of " << user);
	std::string key, secret;
	getUserOAuthKeyAndSecret(user, key, secret);

	if(key == "" || secret == "") {
		LOG4CXX_INFO(logger, "Intiating OAuth Flow for user " << user);
		setTwitterMode(user, 0);
		tp->runAsThread(new OAuthFlow(np, userdb[user].sessions, user, userdb[user].sessions->getTwitterUsername()));
	} else {
		LOG4CXX_INFO(logger, user << " is already registered. Using the stored oauth key and secret");
		LOG4CXX_INFO(logger, key << " " << secret);
		pinExchangeComplete(user, key, secret);
	}
}

// User logging out
void TwitterPlugin::handleLogoutRequest(const std::string &user, const std::string &legacyName)
{
	if (userdb.count(user)) {
		delete userdb[user].sessions;
		userdb[user].sessions = NULL;
		userdb[user].connectionState = DISCONNECTED;
	}

	if(onlineUsers.count(user)) {
		onlineUsers.erase(user);
	}
}

// User joining a Chatroom
void TwitterPlugin::handleJoinRoomRequest(const std::string &user, const std::string &room, const std::string &nickname, const std::string &password)
{
	if(room == adminChatRoom) {
		LOG4CXX_INFO(logger, "Received Join Twitter room request for " << user << " '" << nickname << "'");

		setTwitterMode(user, 2);
		handleParticipantChanged(user, nickname, room, 0, pbnetwork::STATUS_ONLINE);
		handleParticipantChanged(user, adminNickName, room, 0, pbnetwork::STATUS_ONLINE);
		userdb[user].nickName = nickname;
		handleMessage(user, adminChatRoom, "Connected to Twitter room! Populating your followers list", adminNickName);
		tp->runAsThread(new FetchFriends(userdb[user].sessions, user,
										 boost::bind(&TwitterPlugin::populateRoster, this, _1, _2, _3, _4)));
	} else {
		setTwitterMode(user, 0);
		LOG4CXX_ERROR(logger, "Couldn't connect to chatroom - " << room <<"! Try twitter-chatroom as the chatroom to access Twitter account");
		handleMessage(user, adminLegacyName, "Couldn't connect to chatroom! Try twitter-chatroom as the chatroom to access Twitter account");
	}
}

// User leaving a Chatroom
void TwitterPlugin::handleLeaveRoomRequest(const std::string &user, const std::string &room)
{
	if(room == adminChatRoom && onlineUsers.count(user)) {
		LOG4CXX_INFO(logger, "Leaving chatroom! Switching back to default mode 0");
		setTwitterMode(user, 0);
		handleBuddyChanged(user, adminLegacyName, adminAlias, std::vector<std::string>(), pbnetwork::STATUS_ONLINE);
	}
}

// Messages to be sent to Twitter
void TwitterPlugin::handleMessageSendRequest(const std::string &user, const std::string &legacyName, const std::string &message, const std::string &xhtml, const std::string &/*id*/)
{

	LOG4CXX_INFO(logger, "Received " << user << " --> " << legacyName << " - " << message);

	if(legacyName == adminLegacyName || legacyName == adminChatRoom)  {
		std::string cmd = "", data = "";

		/** Parsing the message - Assuming message format to be <cmd>[ ]*<data>**/
		unsigned i;
		for (i=0 ; i<message.size() && message[i] != ' '; i++) cmd += message[i];
		while (i<message.size() && message[i] == ' ') i++;
		data = message.substr(i);
		/***********************************************************************/

		if(cmd == "#pin")
			tp->runAsThread(new PINExchangeProcess(np, userdb[user].sessions, user, data));
		else if(cmd == "#help")
			tp->runAsThread(new HelpMessageRequest(user, CONFIG_STRING(config, "service.jid"), boost::bind(&TwitterPlugin::helpMessageResponse, this, _1, _2)));
		else if(cmd[0] == '@') {
			std::string username = cmd.substr(1);
			tp->runAsThread(new DirectMessageRequest(userdb[user].sessions, user, username, data,
												     boost::bind(&TwitterPlugin::directMessageResponse, this, _1, _2, _3, _4)));
		}
		else if(cmd == "#status")
			tp->runAsThread(new StatusUpdateRequest(userdb[user].sessions, user, data,
														boost::bind(&TwitterPlugin::statusUpdateResponse, this, _1, _2)));
		else if(cmd == "#timeline")
			tp->runAsThread(new TimelineRequest(userdb[user].sessions, user, data, "",
														boost::bind(&TwitterPlugin::displayTweets, this, _1, _2, _3, _4)));
		else if(cmd == "#friends")
			tp->runAsThread(new FetchFriends(userdb[user].sessions, user,
													   boost::bind(&TwitterPlugin::displayFriendlist, this, _1, _2, _3, _4)));
		else if(cmd == "#follow")
			tp->runAsThread(new CreateFriendRequest(userdb[user].sessions, user, data.substr(0,data.find('@')),
													   boost::bind(&TwitterPlugin::createFriendResponse, this, _1, _2, _3, _4)));
		else if(cmd == "#unfollow")
			tp->runAsThread(new DestroyFriendRequest(userdb[user].sessions, user, data.substr(0,data.find('@')),
													   boost::bind(&TwitterPlugin::deleteFriendResponse, this, _1, _2, _3)));
		else if(cmd == "#retweet")
			tp->runAsThread(new RetweetRequest(userdb[user].sessions, user, data,
													   boost::bind(&TwitterPlugin::RetweetResponse, this, _1, _2)));
		else if(cmd == "#mode") {
			int m = 0;
			m = atoi(data.c_str());
			mode prevm = userdb[user].twitterMode;

			if((mode)m == userdb[user].twitterMode) return; //If same as current mode return
			if(m < 0 || m > 1) { // Invalid modes
				handleMessage(user, adminLegacyName, std::string("Error! Unknown mode ") + data + ". Allowed values 0 or 1." );
				return;
			}

			setTwitterMode(user, m);
			if((userdb[user].twitterMode == SINGLECONTACT || userdb[user].twitterMode == CHATROOM) && prevm == MULTIPLECONTACT) clearRoster(user);
			else if(userdb[user].twitterMode == MULTIPLECONTACT)
				tp->runAsThread(new FetchFriends(userdb[user].sessions, user, boost::bind(&TwitterPlugin::populateRoster, this, _1, _2, _3, _4)));

			handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
								std::string("Changed mode to ") + data, userdb[user].twitterMode == CHATROOM ? adminNickName : "");

			LOG4CXX_INFO(logger, user << ": Changed mode to " << data  << " <" << (userdb[user].twitterMode == CHATROOM ? adminNickName : "") << ">" );
		}

		else if(userdb[user].twitterMode == CHATROOM) {
			std::string buddy = message.substr(0, message.find(":"));
			if(userdb[user].buddies.count(buddy) == 0) {
				tp->runAsThread(new StatusUpdateRequest(userdb[user].sessions, user, message,
														boost::bind(&TwitterPlugin::statusUpdateResponse, this, _1, _2)));
			} else {
				data = message.substr(message.find(":")+1); //Can parse better??:P
				tp->runAsThread(new DirectMessageRequest(userdb[user].sessions, user, buddy, data,
												 		 boost::bind(&TwitterPlugin::directMessageResponse, this, _1, _2, _3, _4)));
			}
		}
		else handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
				                 "Unknown command! Type #help for a list of available commands.", userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	}

	else {
		std::string buddy = legacyName;
		if(userdb[user].twitterMode == CHATROOM) buddy = legacyName.substr(legacyName.find("/") + 1);
		if(legacyName != "twitter") {
			tp->runAsThread(new DirectMessageRequest(userdb[user].sessions, user, buddy, message,
												 boost::bind(&TwitterPlugin::directMessageResponse, this, _1, _2, _3, _4)));
		}
	}
}

void TwitterPlugin::handleBuddyUpdatedRequest(const std::string &user, const std::string &buddyName, const std::string &alias, const std::vector<std::string> &groups)
{
	if(userdb[user].connectionState != CONNECTED) {
		LOG4CXX_ERROR(logger, user << " is not connected to twitter!");
		return;
	}

	LOG4CXX_INFO(logger, user << " - Adding Twitter contact " << buddyName);
	tp->runAsThread(new CreateFriendRequest(userdb[user].sessions, user, buddyName,
											boost::bind(&TwitterPlugin::createFriendResponse, this, _1, _2, _3, _4)));
}

void TwitterPlugin::handleBuddyRemovedRequest(const std::string &user, const std::string &buddyName, const std::vector<std::string> &groups)
{
	if(userdb[user].connectionState != CONNECTED) {
		LOG4CXX_ERROR(logger, user << " is not connected to twitter!");
		return;
	}

	if (getTwitterMode(user) == MULTIPLECONTACT) {
		LOG4CXX_ERROR(logger, user << " not removing Twitter contact " << buddyName << ", because the mode is not MULTIPLECONTACT");
		return;
	}

	LOG4CXX_INFO(logger, user << " - Removing Twitter contact " << buddyName);
	tp->runAsThread(new DestroyFriendRequest(userdb[user].sessions, user, buddyName,
											 boost::bind(&TwitterPlugin::deleteFriendResponse, this, _1, _2, _3)));
}

void TwitterPlugin::handleVCardRequest(const std::string &user, const std::string &legacyName, unsigned int id)
{
	if(userdb[user].connectionState != CONNECTED) {
		LOG4CXX_ERROR(logger, user << " is not connected to twitter!");
		return;
	}

	LOG4CXX_INFO(logger, user << " - VCardRequest for " << legacyName << ", " << userdb[user].buddiesInfo[legacyName].getProfileImgURL());

	if(getTwitterMode(user) != SINGLECONTACT && userdb[user].buddies.count(legacyName)
		&& userdb[user].buddiesInfo[legacyName].getProfileImgURL().length()) {
		if(userdb[user].buddiesImgs.count(legacyName) == 0) {
			tp->runAsThread(new ProfileImageRequest(config, user, legacyName, userdb[user].buddiesInfo[legacyName].getProfileImgURL(), id,
													boost::bind(&TwitterPlugin::profileImageResponse, this, _1, _2, _3, _4, _5)));
		}
		handleVCard(user, id, legacyName, legacyName, "", userdb[user].buddiesImgs[legacyName]);
	}
}

void TwitterPlugin::pollForTweets()
{
	boost::mutex::scoped_lock lock(userlock);
	std::set<std::string>::iterator it = onlineUsers.begin();
	while (it != onlineUsers.end()) {
		std::string user = *it;
		tp->runAsThread(new TimelineRequest(userdb[user].sessions, user, "", getMostRecentTweetIDUnsafe(user),
											boost::bind(&TwitterPlugin::displayTweets, this, _1, _2, _3, _4)));
		it++;
	}
	tweet_timer->start();
}

void TwitterPlugin::pollForDirectMessages()
{
	boost::mutex::scoped_lock lock(userlock);
	std::set<std::string>::iterator it = onlineUsers.begin();
	while (it != onlineUsers.end()) {
		std::string user = *it;
		tp->runAsThread(new DirectMessageRequest(userdb[user].sessions, user, "", getMostRecentDMIDUnsafe(user),
											boost::bind(&TwitterPlugin::directMessageResponse, this, _1, _2, _3, _4)));
		it++;
	}
	message_timer->start();
}


bool TwitterPlugin::getUserOAuthKeyAndSecret(const std::string user, std::string &key, std::string &secret)
{
	boost::mutex::scoped_lock lock(dblock);

	UserInfo info;
	if(storagebackend->getUser(user, info) == false) {
		LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
		return false;
	}

	key="", secret=""; int type = TYPE_STRING;;
	storagebackend->getUserSetting((long)info.id, OAUTH_KEY, type, key);
	storagebackend->getUserSetting((long)info.id, OAUTH_SECRET, type, secret);
	return true;
}

bool TwitterPlugin::checkSpectrum1User(const std::string user)
{
	boost::mutex::scoped_lock lock(dblock);

	UserInfo info;
	if(storagebackend->getUser(user, info) == false) {
		LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
		return false;
	}

	std::string first_synchronization_done = "";
	int type = TYPE_STRING;
	storagebackend->getUserSetting((long)info.id, "first_synchronization_done", type, first_synchronization_done);

	LOG4CXX_INFO(logger, "first_synchronization_done: " << first_synchronization_done);

	if(first_synchronization_done.length()) return true;
	return false;
}

int TwitterPlugin::getTwitterMode(const std::string user)
{
	boost::mutex::scoped_lock lock(dblock);

	UserInfo info;
	if(storagebackend->getUser(user, info) == false) {
		LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
		return -1;
	}

	int type = TYPE_STRING; int m;
	std::string s_m;
	storagebackend->getUserSetting((long)info.id, MODE, type, s_m);
	if(s_m == "") {
		s_m  = "0";
		storagebackend->updateUserSetting((long)info.id, MODE, s_m);
	}
	m = atoi(s_m.c_str());
	return m;
}

bool TwitterPlugin::setTwitterMode(const std::string user, int m)
{
	boost::mutex::scoped_lock lock(dblock);

	UserInfo info;
	if(storagebackend->getUser(user, info) == false) {
		LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
		return false;
	}

	if(m < 0 || m > 2) {
		LOG4CXX_ERROR(logger, "Unknown mode " << m <<". Using default mode 0");
		m = 0;
	}

	userdb[user].twitterMode = (mode)m;

	//int type;
	std::string s_m = std::string(1,m+'0');
	LOG4CXX_INFO(logger, "Storing mode " << m <<" for user " << user);
	storagebackend->updateUserSetting((long)info.id, MODE, s_m);
	return true;
}

bool TwitterPlugin::storeUserOAuthKeyAndSecret(const std::string user, const std::string OAuthKey, const std::string OAuthSecret)
{

	boost::mutex::scoped_lock lock(dblock);

	UserInfo info;
	if(storagebackend->getUser(user, info) == false) {
		LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
		return false;
	}

	storagebackend->updateUserSetting((long)info.id, OAUTH_KEY, OAuthKey);
	storagebackend->updateUserSetting((long)info.id, OAUTH_SECRET, OAuthSecret);
	return true;
}

void TwitterPlugin::initUserSession(const std::string user, const std::string legacyName, const std::string password)
{
	boost::mutex::scoped_lock lock(userlock);

	std::string username = legacyName;
	std::string passwd = password;
	LOG4CXX_INFO(logger, username + "  " + passwd);

	userdb[user].sessions = new twitCurl();
	if(CONFIG_HAS_KEY(config,"proxy.server")) {
		std::string ip = CONFIG_STRING(config,"proxy.server");

		std::ostringstream out;
		out << CONFIG_INT(config,"proxy.port");
		std::string port = out.str();

		std::string puser = CONFIG_STRING(config,"proxy.user");
		std::string ppasswd = CONFIG_STRING(config,"proxy.password");

		LOG4CXX_INFO(logger, ip << " " << port << " " << puser << " " << ppasswd);

		if(ip != "localhost" && port != "0") {
			userdb[user].sessions->setProxyServerIp(ip);
			userdb[user].sessions->setProxyServerPort(port);
			userdb[user].sessions->setProxyUserName(puser);
			userdb[user].sessions->setProxyPassword(ppasswd);
		}
	}

	//Check if the user is spectrum1 user
	userdb[user].spectrum1User = checkSpectrum1User(user);

	userdb[user].connectionState = NEW;
	userdb[user].legacyName = username;
	userdb[user].sessions->setTwitterUsername(username);
	userdb[user].sessions->setTwitterPassword(passwd);

	if(!userdb[user].spectrum1User) {
		userdb[user].sessions->getOAuth().setConsumerKey(consumerKey);
		userdb[user].sessions->getOAuth().setConsumerSecret(consumerSecret);
	} else {
		userdb[user].sessions->getOAuth().setConsumerKey(OLD_APP_KEY);
		userdb[user].sessions->getOAuth().setConsumerSecret(OLD_APP_SECRET);
	}
}

void TwitterPlugin::OAuthFlowComplete(const std::string user, twitCurl *obj)
{
	boost::mutex::scoped_lock lock(userlock);

	delete userdb[user].sessions;
	userdb[user].sessions = obj->clone();
	userdb[user].connectionState = WAITING_FOR_PIN;
}

void TwitterPlugin::pinExchangeComplete(const std::string user, const std::string OAuthAccessTokenKey, const std::string OAuthAccessTokenSecret)
{
	boost::mutex::scoped_lock lock(userlock);

	userdb[user].sessions->getOAuth().setOAuthTokenKey( OAuthAccessTokenKey );
	userdb[user].sessions->getOAuth().setOAuthTokenSecret( OAuthAccessTokenSecret );
	userdb[user].connectionState = CONNECTED;
	userdb[user].twitterMode = (mode)getTwitterMode(user);

	if(userdb[user].twitterMode == MULTIPLECONTACT) {
		tp->runAsThread(new FetchFriends(userdb[user].sessions, user, boost::bind(&TwitterPlugin::populateRoster, this, _1, _2, _3, _4)));
	}

	onlineUsers.insert(user);
	userdb[user].mostRecentTweetID = "";
	userdb[user].mostRecentDirectMessageID = "";
}

void TwitterPlugin::updateLastTweetID(const std::string user, const std::string ID)
{
	boost::mutex::scoped_lock lock(userlock);
	userdb[user].mostRecentTweetID = ID;

	UserInfo info;
	if(storagebackend->getUser(user, info) == false) {
		LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
		return;
	}

	storagebackend->updateUserSetting((long)info.id, "twitter_last_tweet", ID);
}

std::string TwitterPlugin::getMostRecentTweetIDUnsafe(const std::string user)
{
	std::string ID = "";
	if(onlineUsers.count(user)) {
		ID = userdb[user].mostRecentTweetID;
		if (ID.empty()) {
			int type = TYPE_STRING;
			UserInfo info;
			if(storagebackend->getUser(user, info) == false) {
				LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
			}
			else {
				storagebackend->getUserSetting(info.id, "twitter_last_tweet", type, ID);
			}
		}
	}
	return ID;
}

std::string TwitterPlugin::getMostRecentTweetID(const std::string user)
{
	boost::mutex::scoped_lock lock(userlock);
	return getMostRecentTweetIDUnsafe(user);
}

void TwitterPlugin::updateLastDMID(const std::string user, const std::string ID)
{
	boost::mutex::scoped_lock lock(userlock);
	userdb[user].mostRecentDirectMessageID = ID;

	UserInfo info;
	if(storagebackend->getUser(user, info) == false) {
		LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
		return;
	}

	storagebackend->updateUserSetting((long)info.id, "twitter_last_dm", ID);
}

std::string TwitterPlugin::getMostRecentDMIDUnsafe(const std::string user) {
	std::string ID = "";
	if(onlineUsers.count(user)) {
		ID = userdb[user].mostRecentDirectMessageID;
		if (ID.empty()) {
			int type = TYPE_STRING;
			UserInfo info;
			if(storagebackend->getUser(user, info) == false) {
				LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!");
			}
			else {
				storagebackend->getUserSetting(info.id, "twitter_last_dm", type, ID);
			}
		}
	}
	return ID;
}

std::string TwitterPlugin::getMostRecentDMID(const std::string user)
{
	boost::mutex::scoped_lock lock(userlock);
	return getMostRecentDMIDUnsafe(user);
}

/************************************** Twitter response functions **********************************/
void TwitterPlugin::statusUpdateResponse(std::string &user, Error &errMsg)
{
	if(errMsg.getMessage().length()) {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	} else {
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							"Status Update successful", userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	}
}

void TwitterPlugin::helpMessageResponse(std::string &user, std::string &msg)
{
	handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
						msg, userdb[user].twitterMode == CHATROOM ? adminNickName : "");
}

void TwitterPlugin::clearRoster(const std::string user)
{
	if(userdb[user].buddies.size() == 0) return;
	std::set<std::string>::iterator it = userdb[user].buddies.begin();
	while (it != userdb[user].buddies.end()) {
		handleBuddyRemoved(user, *it);
		it++;
	}
	userdb[user].buddies.clear();
}

void TwitterPlugin::populateRoster(std::string &user, std::vector<User> &friends, std::vector<std::string> &friendAvatars, Error &errMsg)
{
	if(errMsg.getMessage().length() == 0)
	{
		for (unsigned i=0 ; i<friends.size() ; i++) {
			userdb[user].buddies.insert(friends[i].getScreenName());
			userdb[user].buddiesInfo[friends[i].getScreenName()] = friends[i];
			userdb[user].buddiesImgs[friends[i].getScreenName()] = friendAvatars[i];

			if(userdb[user].twitterMode == MULTIPLECONTACT) {
				std::string lastTweet = friends[i].getLastStatus().getTweet();
				//LOG4CXX_INFO(logger, user << " - " << SHA(friendAvatars[i]))
				handleBuddyChanged(user, friends[i].getScreenName(), friends[i].getUserName(), std::vector<std::string>(),
					pbnetwork::STATUS_ONLINE, lastTweet, Swift::Hexify::hexify(cryptoProvider->getSHA1Hash(Swift::createByteArray(friendAvatars[i]))));
			}
			else if(userdb[user].twitterMode == CHATROOM)
				handleParticipantChanged(user, friends[i].getScreenName(), adminChatRoom, 0, pbnetwork::STATUS_ONLINE);

			/*handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							   	friends[i].getScreenName() + " - " + friends[i].getLastStatus().getTweet(),
								userdb[user].twitterMode == CHATROOM ? adminNickName : "");*/
		}
	} else {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							   std::string("Error populating roster - ") + errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	}

	if(userdb[user].twitterMode == CHATROOM) handleParticipantChanged(user, userdb[user].nickName, adminChatRoom, 0, pbnetwork::STATUS_ONLINE);
}

void TwitterPlugin::displayFriendlist(std::string &user, std::vector<User> &friends, std::vector<std::string> &friendAvatars, Error &errMsg)
{
	if(errMsg.getMessage().length() == 0)
	{
		std::string userlist = "\n***************USER LIST****************\n";
		for (unsigned i=0 ; i < friends.size() ; i++) {
			userlist += " - " + friends[i].getUserName() + " (" + friends[i].getScreenName() + ")\n";
		}
		userlist += "***************************************\n";
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							userlist, userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	} else {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							   errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	}

}

void TwitterPlugin::displayTweets(std::string &user, std::string &userRequested, std::vector<Status> &tweets , Error &errMsg)
{
	if(errMsg.getMessage().length() == 0) {
		std::map<std::string, int> lastTweet;
		std::map<std::string, int>::iterator it;

		for (int i = tweets.size() - 1 ; i >= 0 ; i--) {
			if(userdb[user].twitterMode != CHATROOM) {
				std::string m = " - " + tweets[i].getUserData().getScreenName() + ": " + tweets[i].getTweet() + " (MsgId: " + (tweets[i].getRetweetID().empty() ? tweets[i].getID() : tweets[i].getRetweetID()) + ")\n";
				handleMessage(user, adminLegacyName, m, "", "", tweets[i].getCreationTime(), true);

				std::string scrname = tweets[i].getUserData().getScreenName();
				if(lastTweet.count(scrname) == 0 || cmp(tweets[lastTweet[scrname]].getID(), tweets[i].getID()) <= 0) lastTweet[scrname] = i;

			} else {
				handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
									tweets[i].getTweet() + " (MsgId: " + (tweets[i].getRetweetID().empty() ? tweets[i].getID() : tweets[i].getRetweetID()) + ")", tweets[i].getUserData().getScreenName(), "", tweets[i].getCreationTime(), true);
			}
		}

		if(userdb[user].twitterMode == MULTIPLECONTACT) {
			//Set as status user's last tweet
			for (it=lastTweet.begin() ; it!=lastTweet.end() ; it++) {
				int t =  it->second;
				if (userdb[user].buddies.count(tweets[t].getUserData().getScreenName()) != 0) {
					handleBuddyChanged(user, tweets[t].getUserData().getScreenName(), tweets[t].getUserData().getUserName(),
									std::vector<std::string>(), pbnetwork::STATUS_ONLINE, tweets[t].getTweet());
				}
			}
		}

		if((userRequested == "" || userRequested == user) && tweets.size()) {
			std::string tweetID = getMostRecentTweetID(user);
			if(tweetID != tweets[0].getID()) updateLastTweetID(user, tweets[0].getID());
		}

	} else {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							   errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	}
}

void TwitterPlugin::directMessageResponse(std::string &user, std::string &username, std::vector<DirectMessage> &messages, Error &errMsg)
{
	if(errMsg.getCode() == "93") //Permission Denied
		return;

	if(errMsg.getMessage().length()) {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}

		if(username != "")
			handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
						  std::string("Error while sending direct message! - ") + errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
		else
			handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
						  std::string("Error while fetching direct messages! - ") + errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
		return;
	}

	if(username != "") {
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
						   "Message delivered!", userdb[user].twitterMode == CHATROOM ? adminNickName : "");
		return;
	}

	if(!messages.size()) return;

	if(userdb[user].twitterMode == SINGLECONTACT) {

		std::string msglist = "";
		std::string msgID = getMostRecentDMID(user);
		std::string maxID = msgID;

		for (unsigned i=0 ; i < messages.size() ; i++) {
			if(cmp(msgID, messages[i].getID()) == -1) {
				msglist += " - " + messages[i].getSenderData().getScreenName() + ": " + messages[i].getMessage() + "\n";
				if(cmp(maxID, messages[i].getID()) == -1) maxID = messages[i].getID();
			}
		}

		if(msglist.length()) handleMessage(user, adminLegacyName, msglist, "");
		updateLastDMID(user, maxID);

	} else {

		std::string msgID = getMostRecentDMID(user);
		std::string maxID = msgID;

		for (unsigned i=0 ; i < messages.size() ; i++) {
			if(cmp(msgID, messages[i].getID()) == -1) {
				if(userdb[user].twitterMode == MULTIPLECONTACT)
					handleMessage(user, messages[i].getSenderData().getScreenName(), messages[i].getMessage(), "");
				else
					handleMessage(user, adminChatRoom, messages[i].getMessage() + " - <Direct Message>", messages[i].getSenderData().getScreenName());
				if(cmp(maxID, messages[i].getID()) == -1) maxID = messages[i].getID();
			}
		}

		if(maxID == getMostRecentDMID(user)) LOG4CXX_INFO(logger, "No new direct messages for " << user);
		updateLastDMID(user, maxID);
	}
}

void TwitterPlugin::createFriendResponse(std::string &user, User &frnd, std::string &img, Error &errMsg)
{
	if(errMsg.getMessage().length()) {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
		return;
	}

	handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
						std::string("You are now following ") + frnd.getScreenName(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");

	userdb[user].buddies.insert(frnd.getScreenName());
	userdb[user].buddiesInfo[frnd.getScreenName()] = frnd;
	userdb[user].buddiesImgs[frnd.getScreenName()] = img;

	LOG4CXX_INFO(logger, user << " - " << frnd.getScreenName() << ", " << frnd.getProfileImgURL());
	if(userdb[user].twitterMode == MULTIPLECONTACT) {
		handleBuddyChanged(user, frnd.getScreenName(), frnd.getUserName(), std::vector<std::string>(), pbnetwork::STATUS_ONLINE, "", Swift::byteArrayToString(cryptoProvider->getSHA1Hash(Swift::createByteArray(img))));
	} else if(userdb[user].twitterMode == CHATROOM) {
		handleParticipantChanged(user, frnd.getScreenName(), adminChatRoom, 0, pbnetwork::STATUS_ONLINE);
	}
}

void TwitterPlugin::deleteFriendResponse(std::string &user, User &frnd, Error &errMsg)
{
	if(errMsg.getMessage().length()) {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
		return;
	}

	LOG4CXX_INFO(logger, user << " - " << frnd.getScreenName() << ", " << frnd.getProfileImgURL());
	userdb[user].buddies.erase(frnd.getScreenName());

	handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
						std::string("You are not following ") + frnd.getScreenName() + " anymore", userdb[user].twitterMode == CHATROOM ? adminNickName : "");

	if (userdb[user].twitterMode == CHATROOM) {
		handleParticipantChanged(user, frnd.getScreenName(), adminLegacyName, 0, pbnetwork::STATUS_NONE);
	}

	if(userdb[user].twitterMode == MULTIPLECONTACT) {
		handleBuddyRemoved(user, frnd.getScreenName());
	}
}


void TwitterPlugin::RetweetResponse(std::string &user, Error &errMsg)
{
	if(errMsg.getMessage().length()) {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	} else {
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							"Retweet successful", userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	}
}

void TwitterPlugin::profileImageResponse(std::string &user, std::string &buddy, std::string &img, unsigned int reqID, Error &errMsg)
{
	if(errMsg.getMessage().length()) {
		if (errMsg.isCurlError()) {
			handleDisconnected(user, 3, errMsg.getMessage());
			return;
		}
		handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName,
							errMsg.getMessage(), userdb[user].twitterMode == CHATROOM ? adminNickName : "");
	} else {
		LOG4CXX_INFO(logger, user << " - Sending VCard for " << buddy);
		handleVCard(user, reqID, buddy, buddy, "", img);
	}
}
