/** WebRTC signaling messages.
 *
 * @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
 * @author Philipp Jungkamp <Philipp.Jungkamp@opal-rt.com>
 * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
 * @copyright 2023, OPAL-RT Germany GmbH
 * @license Apache 2.0
 *********************************************************************************/

#include <fmt/format.h>
#include <villas/utils.hpp>
#include <villas/exceptions.hpp>
#include <villas/nodes/webrtc/signaling_message.hpp>
#include <algorithm>

using namespace villas;
using namespace villas::node;
using namespace villas::node::webrtc;

json_t * Peer::toJSON() const
{
	return json_pack("{ s:i, s:s*, s:s*, s:s* }",
		"id", id,
		"name", name.empty() ? nullptr : name.c_str(),
		"remote", remote.empty() ? nullptr : remote.c_str(),
		"user_agent", userAgent.empty() ? nullptr : userAgent.c_str()
		// TODO: created, connected
	);
}

Peer::Peer(json_t *json)
{
	const char *nme = nullptr, *rem = nullptr, *ua = nullptr, *tscreat, *tsconn;

	int ret = json_unpack(json, "{ s:i, s?:s, s?:s, s?:s, s?:s, s?: s }",
		"id", &id,
		"name", &nme,
		"remote", &rem,
		"user_agent", &ua,
		"created", &tscreat,
		"connected", &tsconn
	);
	if (ret)
		throw RuntimeError("Failed to decode signaling message");

	if (nme)
		name = nme;

	if (rem)
		remote = rem;

	if (ua)
		userAgent = ua;

	// TODO: created, connected
}

RelayMessage::RelayMessage(json_t *json)
{

	if (!json_is_array(json))
		throw RuntimeError("Failed to decode signaling message");

	int ret;
	char *url;
	char *user;
	char *pass;
	char *realm;
	char *expires;
	json_t *server_json;
	size_t i;
	json_array_foreach(json, i, server_json) {
		ret = json_unpack(server_json, "{ s:s, s:s, s:s, s:s, s:s }",
			"url", &url,
			"user", &user,
			"pass", &pass,
			"realm", &realm,
			"expires", &expires
		);
		if (ret)
			throw RuntimeError("Failed to decode signaling message");

		auto &server = servers.emplace_back(url);
		server.username = user;
		server.password = pass;

		// TODO: warn about unsupported realm
		// TODO: log info about expires time
	}
}

json_t * ControlMessage::toJSON() const
{
	json_t *json_peers = json_array();

	for (auto &p : peers) {
		json_t *json_peer = p.toJSON();

		json_array_append_new(json_peers, json_peer);
	}

	return json_pack("{ s:i, s:o }",
		"peer_id", peerID,
		"peers", json_peers
	);
}

ControlMessage::ControlMessage(json_t *j)
{
	int ret;

	json_t *json_peers;

	ret = json_unpack(j, "{ s:i, s:o }",
		"peer_id", &peerID,
		"peers", &json_peers
	);
	if (ret)
		throw RuntimeError("Failed to decode signaling message");

	if (!json_is_array(json_peers))
		throw RuntimeError("Failed to decode signaling message");

	json_t *json_peer;
	size_t i;
	// cppcheck-suppress unknownMacro
	json_array_foreach(json_peers, i, json_peer)
		peers.emplace_back(json_peer);
}

json_t * SignalingMessage::toJSON() const
{
	return std::visit(villas::utils::overloaded {
		[](ControlMessage const &c){
			return json_pack("{ s:o }", "control", c.toJSON());
		},
		[](rtc::Description const &d){
			return json_pack("{ s:{ s:s, s:s } }", "description",
				"spd", d.generateSdp().c_str(),
				"type", d.typeString().c_str()
			);
		},
		[](rtc::Candidate const &c){
			return json_pack("{ s:{ s:s, s:s } }", "candidate",
				"spd", c.candidate().c_str(),
				"mid", c.mid().c_str()
			);
		},
		[](auto &other){
			return (json_t *) { nullptr };
		}
	}, message);
}

std::string SignalingMessage::toString() const
{
	return std::visit(villas::utils::overloaded {
		[](RelayMessage const &r){
			return fmt::format("type=relay");
		},
		[](ControlMessage const &c){
			return fmt::format("type=control, control={}", json_dumps(c.toJSON(), 0));
		},
		[](rtc::Description const &d){
			return fmt::format("type=description, type={}, spd=\n{}", d.typeString(), d.generateSdp());
		},
		[](rtc::Candidate const &c){
			return fmt::format("type=candidate, mid={}, spd=\n{}", c.candidate(), c.mid());
		},
		[](auto other){
			return fmt::format("invalid signaling message");
		}
	}, message);
}

SignalingMessage SignalingMessage::fromJSON(json_t *json)
{
	auto self = SignalingMessage { std::monostate() };

	// Relay message
	json_t *rlys = nullptr;
	// Control message
	json_t *ctrl = nullptr;
	// Candidate message
	const char *cand = nullptr;
	const char *mid = nullptr;
	// Description message
	const char *desc = nullptr;
	const char *typ = nullptr;

	int ret = json_unpack(json, "{ s?o, s?o, s?{ s:s, s:s }, s?{ s:s, s:s } }",
		"servers", &rlys,
		"control", &ctrl,
		"candidate",
			"spd", &cand,
			"mid", &mid,
		"description",
			"spd", &desc,
			"type", &typ
	);

	// Exactly 1 field may be specified
	const void *fields[] = { ctrl, cand, desc };
	if (ret || std::count(std::begin(fields), std::end(fields), nullptr) < std::make_signed_t<size_t>(std::size(fields)) - 1)
		throw RuntimeError("Failed to decode signaling message");

	if (rlys) {
		self.message.emplace<RelayMessage>(rlys);
	}
	else if (ctrl) {
		self.message.emplace<ControlMessage>(ctrl);
	}
	else if (cand) {
		self.message.emplace<rtc::Candidate>(cand, mid);
	}
	else if (desc) {
		self.message.emplace<rtc::Description>(desc, typ);
	}

	return self;
}