/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of Liblinphone
 * (see https://gitlab.linphone.org/BC/public/liblinphone).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "belle-sip/utils.h"

#include "address/address.h"
#include "c-wrapper/c-wrapper.h"
#include "chat/chat-room/abstract-chat-room.h"
#include "conference/participant-device.h"
#include "conference/participant.h"
#include "conference/server-conference.h"
#include "content/content-manager.h"
#include "content/content-type.h"
#include "content/content.h"
#include "core/core.h"
#include "linphone/api/c-address.h"
#include "linphone/api/c-content.h"
#include "linphone/enums/chat-room-enums.h"
#include "linphone/utils/utils.h"
#include "logger/logger.h"
#include "server-conference-event-handler.h"
#include "server-conference-list-event-handler.h"
#include "xml/resource-lists.h"
#include "xml/rlmi.h"

// TODO: Remove me later.
#include "private.h"

// =============================================================================

using namespace std;

class ServerConference;

LINPHONE_BEGIN_NAMESPACE

// -----------------------------------------------------------------------------

ServerConferenceListEventHandler::ServerConferenceListEventHandler(const std::shared_ptr<Core> &core)
    : CoreAccessor(core) {
}

// -----------------------------------------------------------------------------

void ServerConferenceListEventHandler::notifyResponseCb(LinphoneEvent *lev) {
	auto ev = dynamic_pointer_cast<EventSubscribe>(Event::toCpp(const_cast<LinphoneEvent *>(lev))->getSharedFromThis());
	shared_ptr<EventCbs> cbs = ev->getCurrentCallbacks();
	ServerConferenceListEventHandler *listHandler = static_cast<ServerConferenceListEventHandler *>(cbs->getUserData());
	cbs->setUserData(nullptr);
	cbs->notifyResponseCb = nullptr;

	if (ev->getReason() != LinphoneReasonNone) return;

	for (const auto &p : listHandler->handlers) {
		try {
			std::shared_ptr<ServerConferenceEventHandler> handler(p.second);
			cbs->setUserData(handler.get());
			ServerConferenceEventHandler::notifyResponseCb(ev->toC());
		} catch (const bad_weak_ptr &) {
		}
	}
	cbs->setUserData(nullptr);
}

// -----------------------------------------------------------------------------

void ServerConferenceListEventHandler::subscribeReceived(const std::shared_ptr<EventSubscribe> &ev,
                                                         const LinphoneContent *body) {
	const string &xmlBody = string(linphone_content_get_utf8_text(body));
	if (xmlBody.empty()) {
		ev->deny(LinphoneReasonDeclined);
		return;
	}

	const auto &participantAddr = ev->getFrom();
	const auto &deviceAddr = ev->getRemoteContact();

	// Create Rlmi body
	Xsd::Rlmi::List::ResourceSequence resources;

	// Parse resource list
	list<Content> contents;
	istringstream data(xmlBody);
	unique_ptr<Xsd::ResourceLists::ResourceLists> rl;
	try {
		rl = Xsd::ResourceLists::parseResourceLists(data, Xsd::XmlSchema::Flags::dont_validate);
	} catch (const exception &) {
		lError() << "Error while parsing the body of " << *ev << " for conferences asked by: " << *participantAddr;
		return;
	}

	LinphoneSubscriptionState subscriptionState = ev->getState();
	auto core = getCore();
	for (const auto &l : rl->getList()) {
		for (const auto &entry : l.getEntry()) {
			std::shared_ptr<Address> addr = Address::create(entry.getUri());
			string notifyIdStr = addr->getUriParamValue("Last-Notify");
			addr->removeUriParam("Last-Notify");
			ConferenceId conferenceId(addr, addr, core->createConferenceIdParams());
			std::shared_ptr<ServerConferenceEventHandler> handler = findHandler(conferenceId);
			if (!handler) continue;

			shared_ptr<AbstractChatRoom> chatRoom = core->findChatRoom(conferenceId, false);
			if (!chatRoom) {
				lError() << "Received " << *ev << " for unknown chat room: " << conferenceId;
				continue;
			}

			shared_ptr<Participant> participant = chatRoom->findParticipant(participantAddr);
			if (!participant) {
				lError() << "Received " << *ev << " for unknown participant [" << *participantAddr << "] in "
				         << *chatRoom;
				continue;
			}

			shared_ptr<ParticipantDevice> device = participant->findDevice(deviceAddr);
			if (!device) {
				lError() << "Received " << *ev << " for unknown device [" << *deviceAddr << "] of " << *participant
				         << " in " << *chatRoom;
				continue;
			}

			auto deviceState = device->getState();
			if ((deviceState != ParticipantDevice::State::Present) &&
			    (deviceState != ParticipantDevice::State::Joining)) {
				lError() << "Cannot process " << *ev << " for " << *device << " of " << *participant << " in "
				         << *chatRoom << " because the device state is " << Utils::toString(deviceState);
				continue;
			}

			char token[17];
			belle_sip_random_token(token, sizeof(token));

			// Add entry into the Rlmi content of the notify body
			Xsd::Rlmi::Resource resource(addr->asStringUriOnly());
			Xsd::Rlmi::Resource::InstanceSequence instances;
			Xsd::Rlmi::Instance instance(token, Xsd::Rlmi::State::Value::active);
			instances.push_back(instance);
			resource.setInstance(instances);
			resources.push_back(resource);

			device->setConferenceSubscribeEvent((subscriptionState == LinphoneSubscriptionIncomingReceived) ? ev
			                                                                                                : nullptr);

			int notifyId = (notifyIdStr.empty() || device->getState() == ParticipantDevice::State::Joining)
			                   ? 0
			                   : Utils::stoi(notifyIdStr);
			auto content = handler->getNotifyForId(notifyId, device->getConferenceSubscribeEvent());
			if (content->isEmpty()) continue;

			/* FIXME:
			 * the content-id value shall be surrounded with angle-quotes.
			 * The client fix to handle this is made in 5.4.45.
			 * Do the server change not before 2027.
			 */
			content->addHeader("Content-Id", token);
			content->addHeader("Content-Length", Utils::toString(content->getSize()));
			contents.push_back(std::move(*content));
		}
	}

	list<Content *> contentsAsPtr;

	Content rlmiContent;
	if (resources.empty()) {
		lError() << "Unable to accept a subscription for any chatrooms that should be handled by " << *ev;
		ev->deny(LinphoneReasonDeclined);
		return;
	} else {
		// The subscription has been accepted for at least one chatroom
		ev->accept();
		Xsd::Rlmi::List rlmiList("", 0, TRUE);
		rlmiList.setResource(resources);
		Xsd::XmlSchema::NamespaceInfomap map;
		stringstream rlmiBody;
		Xsd::Rlmi::serializeList(rlmiBody, rlmiList, map);

		rlmiContent.setContentType(ContentType::Rlmi);
		rlmiContent.setBodyFromUtf8(rlmiBody.str());

		contentsAsPtr.push_back(&rlmiContent);
	}

	if (subscriptionState != LinphoneSubscriptionIncomingReceived &&
	    subscriptionState != LinphoneSubscriptionTerminated) {
		return;
	}

	for (Content &content : contents) {
		contentsAsPtr.push_back(&content);
	}
	shared_ptr<EventCbs> cbs = EventCbs::create();
	cbs->setUserData(this);
	cbs->notifyResponseCb = notifyResponseCb;
	ev->addCallbacks(cbs);
	std::shared_ptr<Content> multipart;
	if (!contentsAsPtr.empty()) {
		multipart = Content::create(ContentManager::contentListToMultipart(contentsAsPtr));
		if (linphone_core_content_encoding_supported(core->getCCore(), "deflate"))
			multipart->setContentEncoding("deflate");
	}
	ev->notify(multipart);
}

// -----------------------------------------------------------------------------

void ServerConferenceListEventHandler::addHandler(std::shared_ptr<ServerConferenceEventHandler> handler) {
	if (!handler) {
		lError() << "Trying to insert null handler in the server conference handler list";
		return;
	}

	auto conf = handler->getConference();
	if (!conf) {
		lError() << "Unable to add handler because it is not associated to any conference";
		return;
	}

	const auto &id = conf->getConferenceId();

	if (!id.isValid()) {
		// Do not add the evetn handler if the conference ID is not valid as it might be changed later on and therefore
		// the core ends up in a scenario where 2 entries point to the same handler
		lError() << *conf << " associated to handler " << handler << " has an invalid " << id;
		return;
	}

	auto [it, success] = handlers.insert(std::make_pair(id, handler));
	if (!success) {
		lError() << "Trying to insert an already present handler  with " << id << " associated to " << *conf
		         << " in the server conference handler list";
	}
}

void ServerConferenceListEventHandler::removeHandler(std::shared_ptr<ServerConferenceEventHandler> handler) {
	if (!handler) {
		return;
	}

	auto conf = handler->getConference();
	if (conf) {
		const ConferenceId &conferenceId = conf->getConferenceId();
		auto it = handlers.find(conferenceId);
		if (it != handlers.end()) {
			handlers.erase(it);
			lInfo() << "Server Conference Event Handler with " << conferenceId << " [" << handler
			        << "] has been removed.";
		} else {
			lError() << "Server Conference Event Handler with " << conferenceId << " has not been found.";
		}
	} else {
		lInfo() << "Unable to remove handler " << handler << " from ServerConferenceListEventHandler [" << this
		        << "] because conference was likely already terminated.";
	}
}

std::shared_ptr<ServerConferenceEventHandler>
ServerConferenceListEventHandler::findHandler(const ConferenceId &conferenceId) const {
	try {
		auto &weakHandler = handlers.at(conferenceId);
		std::shared_ptr<ServerConferenceEventHandler> handler(weakHandler);
		return handler;
	} catch (const bad_weak_ptr &) {
	} catch (const out_of_range &) {
		if (linphone_core_get_global_state(getCore()->getCCore()) == LinphoneGlobalStartup) {
			lDebug() << "Unable to find handler with " << conferenceId;
		} else {
			lInfo() << "Unable to find handler with " << conferenceId;
		}
	}
	return nullptr;
}

LINPHONE_END_NAMESPACE
