/*
* Copyright (C) 2011-2024 AirDC++ Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "stdinc.h"
#include <airdcpp/private_chat/PrivateChatManager.h>

#include <airdcpp/hub/activity/ActivityManager.h>
#include <airdcpp/hub/ClientManager.h>
#include <airdcpp/connection/ConnectionManager.h>
#include <airdcpp/user/ignore/IgnoreManager.h>

#include <airdcpp/message/Message.h>
#include <airdcpp/private_chat/PrivateChat.h>
#include <airdcpp/util/Util.h>

#define CONFIG_DIR AppUtil::PATH_USER_CONFIG
#define CONFIG_NAME "IgnoredUsers.xml"

namespace dcpp 
{

PrivateChatManager::PrivateChatManager() noexcept {
	ClientManager::getInstance()->addListener(this);
	ConnectionManager::getInstance()->addListener(this);
}

PrivateChatManager::~PrivateChatManager() noexcept {
	ConnectionManager::getInstance()->removeListener(this);
	ClientManager::getInstance()->removeListener(this);

	{
		WLock l(cs);
		chats.clear();
	}

	ConnectionManager::getInstance()->disconnect();
}

pair<PrivateChatPtr, bool> PrivateChatManager::addChat(const HintedUser& aUser, bool aReceivedMessage) noexcept {
	PrivateChatPtr chat;

	// In case we need to update the hub address (the user is online in a different hub)
	auto ou = ClientManager::getInstance()->findOnlineUser(aUser, true);

	{
		WLock l(cs);
		auto [chatPair, added] = chats.try_emplace(
			aUser.user, 
			std::make_shared<PrivateChat>(ou ? ou->getHintedUser() : aUser, getPMConn(aUser.user))
		);

		chat = chatPair->second;
		if (!added) {
			return { chat, false };
		}
	}

	fire(PrivateChatManagerListener::ChatCreated(), chat, aReceivedMessage);
	return { chat, true };
}

PrivateChatPtr PrivateChatManager::getChat(const UserPtr& aUser) const noexcept {
	RLock l(cs);
	auto i = chats.find(aUser);
	return i != chats.end() ? i->second : nullptr;
}

PrivateChatManager::ChatMap PrivateChatManager::getChats() const noexcept {
	RLock l(cs);
	return chats;
}

bool PrivateChatManager::removeChat(const UserPtr& aUser) {
	PrivateChatPtr chat;

	{
		WLock l(cs);
		auto i = chats.find(aUser);
		if (i == chats.end()) {
			return false;
		}

		chat = i->second;

		chat->close();
		auto uc = chat->getUc();
		if (uc) {
			//Closed the window, keep listening to the connection until QUIT is received with CPMI;
			ccpms[aUser] = uc;
			uc->addListener(this);
		}

		chats.erase(i);
	}

	fire(PrivateChatManagerListener::ChatRemoved(), chat);
	return true;
}

void PrivateChatManager::closeAll(bool aOfflineOnly) {
	UserList toRemove;

	{
		RLock l(cs);
		for (const auto& [user, _] : chats) {
			if (aOfflineOnly && user->isOnline())
				continue;

			toRemove.push_back(user);
		}
	}

	for (const auto& u : toRemove) {
		removeChat(u);
	}
}

//LOCK!!
UserConnection* PrivateChatManager::getPMConn(const UserPtr& user) {
	auto i = ccpms.find(user);
	if (i != ccpms.end()) {
		auto uc = i->second;
		uc->removeListener(this);
		ccpms.erase(i);
		return uc;
	}
	return nullptr;
}


void PrivateChatManager::DisconnectCCPM(const UserPtr& aUser) {
	{
		RLock l(cs);
		auto i = chats.find(aUser);
		if (i != chats.end()) {
			i->second->closeCC(true, true);
			return;
		}
	}

	WLock l(cs);
	auto uc = getPMConn(aUser);
	if (uc)
		uc->disconnect(true);

}

void PrivateChatManager::onPrivateMessage(const ChatMessagePtr& aMessage) {
	bool myPM = aMessage->getReplyTo()->getUser() == ClientManager::getInstance()->getMe();
	const UserPtr& user = myPM ? aMessage->getTo()->getUser() : aMessage->getReplyTo()->getUser();
	size_t wndCnt;
	{
		WLock l(cs);
		wndCnt = chats.size();
		auto i = chats.find(user);
		if (i != chats.end()) {
			i->second->handleMessage(aMessage); //We should have a listener in the frame
			return;
		}
	}

	if (wndCnt > 200) {
		DisconnectCCPM(user);
		return;
	}


	const auto client = aMessage->getFrom()->getClient();
	const auto& identity = aMessage->getReplyTo()->getIdentity();
	if ((identity.isBot() && !SETTING(POPUP_BOT_PMS)) || (identity.isHub() && !SETTING(POPUP_HUB_PMS))) {
		client->statusMessage(STRING(PRIVATE_MESSAGE_FROM) + " " + identity.getNick() + ": " + aMessage->getText(), LogMessage::SEV_INFO);
		return;
	}

	auto chat = addChat(HintedUser(user, client->getHubUrl()), true).first;
	chat->handleMessage(aMessage);

	if (ActivityManager::getInstance()->isAway() && !myPM && (!SETTING(NO_AWAYMSG_TO_BOTS) || !user->isSet(User::BOT))) {
		ParamMap params;
		aMessage->getFrom()->getIdentity().getParams(params, "user", false);

		string error;
		const auto message = ActivityManager::getInstance()->getAwayMessage(client->get(HubSettings::AwayMsg), params);
		chat->sendMessageHooked(OutgoingChatMessage(message, nullptr, Util::emptyString, false), error);
	}
}

void PrivateChatManager::on(ConnectionManagerListener::Connected, const ConnectionQueueItem* cqi, UserConnection* uc) noexcept{
	if (cqi->getConnType() == CONNECTION_TYPE_PM) {
		WLock l(cs);
		auto i = chats.find(cqi->getUser());
		if (i != chats.end()) {
			i->second->CCPMConnected(uc);
		} else {
			// until a message is received, no need to open a PM window.
			ccpms[cqi->getUser()] = uc;
			uc->addListener(this);
		}
	}
}

void PrivateChatManager::on(ConnectionManagerListener::Removed, const ConnectionQueueItem* cqi) noexcept{
	if (cqi->getConnType() == CONNECTION_TYPE_PM) {
		{
			WLock l(cs);
			auto i = chats.find(cqi->getUser());
			if (i != chats.end()) {
				i->second->CCPMDisconnected();
			}
			getPMConn(cqi->getUser());
		}
	}
}

void PrivateChatManager::on(ClientManagerListener::PrivateMessage, const ChatMessagePtr& aMessage) noexcept {
	onPrivateMessage(aMessage);
}

void PrivateChatManager::on(UserConnectionListener::PrivateMessage, UserConnection*, const ChatMessagePtr& aMessage) noexcept{
	onPrivateMessage(aMessage);
}

void PrivateChatManager::on(AdcCommand::PMI, UserConnection* uc, const AdcCommand& cmd) noexcept{
	if (cmd.hasFlag("QU", 0)) {
		RLock l(cs);
		auto i = ccpms.find(uc->getUser());
		if (i != ccpms.end())
			uc->disconnect(true);
	}
}

}