/*
	This file is part of Warzone 2100.
	Copyright (C) 2020  Warzone 2100 Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 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 Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "chat.h"
#include "ai.h"
#include "lib/netplay/netplay.h"
#include "qtscript.h"
#include "hci/quickchat.h"

InGameChatMessage::InGameChatMessage(uint32_t messageSender, char const *messageText)
{
	sender = messageSender;
	text = messageText;
}

bool InGameChatMessage::isGlobal() const
{
	return !toAllies && toPlayers.empty();
}

bool InGameChatMessage::shouldReceive(uint32_t playerIndex) const
{
	if ((playerIndex >= game.maxPlayers) && ((playerIndex >= NetPlay.players.size()) || (!NetPlay.players[playerIndex].allocated)))
	{
		return false;
	}
	return isGlobal() || toPlayers.find(playerIndex) != toPlayers.end() || (toAllies && sender < MAX_PLAYERS && playerIndex < MAX_PLAYERS && aiCheckAlliances(sender, playerIndex));
}

std::vector<uint32_t> InGameChatMessage::getReceivers() const
{
	std::vector<uint32_t> receivers;

	for (auto playerIndex = 0; playerIndex < MAX_CONNECTED_PLAYERS; playerIndex++)
	{
		if (shouldReceive(playerIndex) && openchannels[playerIndex])
		{
			receivers.push_back(playerIndex);
		}
	}

	return receivers;
}

std::string InGameChatMessage::formatReceivers() const
{
	if (isGlobal()) {
		return _("Global");
	}

	if (toAllies && toPlayers.empty()) {
		return _("Allies");
	}

	auto directs = toPlayers.begin();
	std::stringstream ss;
	if (toAllies) {
		ss << _("Allies");
	} else {
		ss << _("private to ");
		ss << getPlayerName(*directs++);
	}

	while (directs != toPlayers.end())
	{
		auto nextName = getPlayerName(*directs++);
		if (!nextName)
		{
			continue;
		}
		ss << (directs == toPlayers.end() ? _(" and ") : ", ");
		ss << nextName;
	}

	return ss.str();
}

void InGameChatMessage::sendToHumanPlayers()
{
	char formatted[MAX_CONSOLE_STRING_LENGTH];
	ssprintf(formatted, "%s (%s): %s", getPlayerName(sender), formatReceivers().c_str(), text);

	auto message = NetworkTextMessage(sender, formatted);
	message.teamSpecific = toAllies && toPlayers.empty();

	if (sender == selectedPlayer || shouldReceive(selectedPlayer)) {
		printInGameTextMessage(message);
	}

	if (isGlobal()) {
		message.enqueue(NETbroadcastQueue());
		return;
	}

	for (auto receiver: getReceivers())
	{
		if (isHumanPlayer(receiver))
		{
			message.enqueue(NETnetQueue(receiver));
		}
	}
}

void InGameChatMessage::sendToAiPlayer(uint32_t receiver)
{
	if (!ingame.localOptionsReceived)
	{
		return;
	}

	uint32_t responsiblePlayer = whosResponsible(receiver);

	if (responsiblePlayer >= MAX_PLAYERS && responsiblePlayer != NetPlay.hostPlayer)
	{
		debug(LOG_ERROR, "sendToAiPlayer() - responsiblePlayer >= MAX_PLAYERS");
		return;
	}

	if (!isHumanPlayer(responsiblePlayer))
	{
		debug(LOG_ERROR, "sendToAiPlayer() - responsiblePlayer is not human.");
		return;
	}

	auto w = NETbeginEncode(NETnetQueue(responsiblePlayer), NET_AITEXTMSG);
	NETuint32_t(w, sender);
	NETuint32_t(w, receiver);
	NETstring(w, text, MAX_CONSOLE_STRING_LENGTH);
	NETend(w);
}

void InGameChatMessage::sendToAiPlayers()
{
	for (auto receiver: getReceivers())
	{
		if (!isHumanPlayer(receiver))
		{
			if (myResponsibility(receiver))
			{
				triggerEventChat(sender, receiver, text);
			}
			else
			{
				sendToAiPlayer(receiver);
			}
		}
	}
}

void InGameChatMessage::sendToSpectators()
{
	if (!ingame.localOptionsReceived)
	{
		return;
	}

	char formatted[MAX_CONSOLE_STRING_LENGTH];
	ssprintf(formatted, "%s (%s): %s", getPlayerName(sender), _("Spectators"), text);

	if ((sender == selectedPlayer || shouldReceive(selectedPlayer)) && NetPlay.players[selectedPlayer].isSpectator) {
		auto message = NetworkTextMessage(sender, formatted);
		printInGameTextMessage(message);
	}

	for (auto receiver: getReceivers())
	{
		if (isHumanPlayer(receiver) && NetPlay.players[receiver].isSpectator && receiver != selectedPlayer)
		{
			ASSERT(!myResponsibility(receiver), "Should not be my responsibility...");
			enqueueSpectatorMessage(NETnetQueue(receiver), formatted);
		}
	}
}

void InGameChatMessage::enqueueSpectatorMessage(NETQUEUE queue, char const* formattedMsg)
{
	auto w = NETbeginEncode(queue, NET_SPECTEXTMSG);
	NETuint32_t(w, sender);
	NETstring(w, formattedMsg, MAX_CONSOLE_STRING_LENGTH);
	NETend(w);
}

void InGameChatMessage::addReceiverByPosition(uint32_t playerPosition)
{
	int32_t playerIndex = findPlayerIndexByPosition(playerPosition);
	if (playerIndex >= 0)
	{
		toPlayers.insert(playerIndex);
	}
}

void InGameChatMessage::addReceiverByIndex(uint32_t playerIndex)
{
	toPlayers.insert(playerIndex);
}

void InGameChatMessage::send()
{
	if (NetPlay.bComms)
	{
		auto mutedUntil = playerSpamMutedUntil(sender);
		if (mutedUntil.has_value())
		{
			auto currentTime = std::chrono::steady_clock::now();
			addConsoleMessage(_("You have sent too many messages in the last few seconds. Please wait and try again."), DEFAULT_JUSTIFY, INFO_MESSAGE, false, static_cast<UDWORD>(std::chrono::duration_cast<std::chrono::milliseconds>(mutedUntil.value() - currentTime).count()));
			return;
		}
		recordPlayerMessageSent(sender);
	}

	if (NetPlay.players[selectedPlayer].isSpectator && !NetPlay.isHost)
	{
		sendToSpectators();
	}
	else
	{
		sendToHumanPlayers();
		if (NetPlay.isHost && NetPlay.players[selectedPlayer].isSpectator)
		{
			// spectator hosts do get to send messages visible to all players,
			// but not AI / scripts
			return;
		}
		sendToAiPlayers();
		triggerEventChat(sender, sender, text);
	}
}
