/**
 * @file receiver.c
 *
 * Copyright (C) 2021 Commend.com - c.huber@commend.com
 */

#include <re.h>
#include <rem.h>
#include <baresip.h>

#include "multicast.h"

#define DEBUG_MODULE "mcreceiver"
#define DEBUG_LEVEL 6
#include <re_dbg.h>


struct list mcreceivl = LIST_INIT;
static mtx_t mcreceivl_lock;


enum {
	TIMEOUT = 1000,
};

enum state {
	LISTENING,
	RECEIVING,
	RUNNING,
	IGNORED,
};

/**
 * Multicast receiver struct
 *
 * Contains data to collect and controll all listeners
 */
struct mcreceiver {
	struct le le;
	struct sa addr;
	uint8_t prio;

	struct rtp_sock *rtp;          /**< RTP Socket                       */
	uint32_t ssrc;
	bool ssrc_set;                 /**< Incoming SSRC is set             */
	struct jbuf *jbuf;

	const struct aucodec *ac;

	struct tmr timeout;
	struct tmr tmr_decode;

	enum state state;
	bool muted;
	bool enable;
};


static void resume_uag_state(void);


static char* state_str(enum state s) {
	switch (s) {
		case LISTENING:
			return "listening";
		case RECEIVING:
			return "receiving";
		case RUNNING:
			return "running";
		case IGNORED:
			return "ignored";
		default:
			return "???";
	}
}


static void mcreceiver_destructor(void *arg)
{
	struct mcreceiver *mcreceiver = arg;

	tmr_cancel(&mcreceiver->timeout);
	tmr_cancel(&mcreceiver->tmr_decode);

	if (mcreceiver->state == RUNNING)
		mcplayer_stop();

	mcreceiver->ssrc = 0;

	mcreceiver->rtp  = mem_deref(mcreceiver->rtp);
	mcreceiver->jbuf = mem_deref(mcreceiver->jbuf);
}


/**
 * Multicast address comparison
 *
 * @param le  List element (mcreceiver)
 * @param arg Argument     (address)
 *
 * @return true  if mcreceiver->addr == address
 * @return false if mcreceiver->addr != address
 */
static bool mcreceiver_addr_cmp(struct le *le, void *arg)
{
	struct mcreceiver *mcreceiver = le->data;
	struct sa *addr = arg;

	return sa_cmp(&mcreceiver->addr, addr, SA_ALL);
}


/**
 * Multicast receiver priority comparison
 *
 * @param le  List element (mcreceiver)
 * @param arg Argument     (priority)
 *
 * @return true if mcreceiver->prio == prio
 * @return false if mcreceiver->prio != prio
 */
static bool mcreceiver_prio_cmp(struct le *le, void *arg)
{
	struct mcreceiver *mcreceiver = le->data;
	uint32_t *prio = (uint32_t *)arg;

	return mcreceiver->prio == *prio;
}


/**
 * Get running multicast receiver
 *
 * @param le  Multicast receiver list element
 * @param arg Unused
 *
 * @return true if multicast receiver is running
 * @return false if multicast receiver is not running
 */
static bool mcreceiver_running(struct le *le, void *arg)
{
	struct mcreceiver *mcreceiver = le->data;
	(void) arg;

	return mcreceiver->state == RUNNING;
}


/**
 * Convert std rtp codec payload type to audio codec
 *
 * @param hdr RTP header object
 *
 * @return struct aucodec*
 */
static const struct aucodec *pt2codec(const struct rtp_header *hdr)
{
	const struct aucodec *codec = NULL;

	switch (hdr->pt) {
		case 0:
			codec = aucodec_find(baresip_aucodecl(), "PCMU", 0, 1);
			break;

		case 8:
			codec = aucodec_find(baresip_aucodecl(), "PCMA", 0, 1);
			break;

		case 9:
			codec = aucodec_find(baresip_aucodecl(), "G722", 0, 1);
			break;

		default:
			warning ("multicast receiver: RTP Payload "
				"Type %d not found.\n", hdr->pt);
			break;
	}

	return codec;
}


/**
 * Resume to the pre-multicast uag state if no other high priority
 * multicasts are running
 */
static void resume_uag_state(void)
{
	uint8_t h = 255;
	struct le *le= NULL;
	struct mcreceiver *mcreceiver = NULL;

	for (le = list_head(&mcreceivl); le; le = le->next) {
		mcreceiver = le->data;

		if (mcreceiver->state == RUNNING && mcreceiver->prio < h)
			h = mcreceiver->prio;
	}

	if (h > multicast_callprio()) {
		multicast_set_dnd(false);
		uag_set_nodial(false);
		uag_hold_resume(NULL);
	}
}


/**
 * Stops, flush, start player
 *
 * @param mcreceiver Multicast receiver object
 *
 * @return int 0 if success, errorcode otherwise
 */
static int player_stop_start(struct mcreceiver *mcreceiver)
{
	mcplayer_fadeout();
	return mcplayer_start(mcreceiver->ac);
}


static void mcreceiver_stop(struct mcreceiver *mcreceiver)
{
	mcreceiver->state = RECEIVING;

	module_event("multicast",
		     "receiver stopped playing", NULL, NULL,
		     "addr=%J prio=%d enabled=%d state=%s",
		     &mcreceiver->addr, mcreceiver->prio,
		     mcreceiver->enable,
		     state_str(mcreceiver->state));

	jbuf_flush(mcreceiver->jbuf);
}


/**
 * Multicast Priority handling
 *
 * @param mcreceiver Multicast receiver object
 * @param ssrc       SSRC of received RTP packet
 *
 * @return int 0 if success, errorcode otherwise
 */
static int prio_handling(struct mcreceiver *mcreceiver, uint32_t ssrc)
{
	int err = 0;
	struct le *le;
	struct mcreceiver *hprio = NULL;

	if (!mcreceiver)
		return EINVAL;

	err = mtx_trylock(&mcreceivl_lock) != thrd_success;
	if (err)
		return ENOMEM;

	if (mcreceiver->state == LISTENING) {
		mcreceiver->state = RECEIVING;

		info ("multicast receiver: start addr=%J prio=%d enabled=%d "
			"state=%s\n", &mcreceiver->addr, mcreceiver->prio,
			mcreceiver->enable, state_str(mcreceiver->state));

		module_event("multicast", "receiver start", NULL, NULL,
			"addr=%J prio=%d enabled=%d state=%s",
			&mcreceiver->addr, mcreceiver->prio,
			mcreceiver->enable, state_str(mcreceiver->state));

	}

	if (!mcreceiver->enable) {
		mcreceiver->state = RECEIVING;
		err = ECANCELED;
		goto out;
	}

	if (mcreceiver->state == IGNORED) {
		err = ECANCELED;
		goto out;
	}

	if (mcreceiver->prio >= multicast_callprio() && uag_call_count()) {
		if (mcreceiver->state == RUNNING) {
			mcreceiver_stop(mcreceiver);
			mcplayer_stop();
		}
		goto out;
	}
	else if (mcreceiver->prio < multicast_callprio()) {
		struct le *leua;
		struct ua *ua;

		multicast_set_dnd(true);
		uag_set_nodial(true);

		for (leua = list_head(uag_list()); leua; leua = leua->next) {
			struct le *lecall;
			ua = leua->data;
			lecall = list_head(ua_calls(ua));
			while (lecall) {
				struct call *call = lecall->data;
				lecall = lecall->next;

				if (call_state(call) !=
					CALL_STATE_ESTABLISHED) {
					ua_hangup(ua, call, 0, NULL);
					continue;
				}

				if (!call_is_onhold(call))
					call_hold(call, true);
			}
		}
	}

	le = list_apply(&mcreceivl, true, mcreceiver_running, NULL);
	if (!le) {
		err = player_stop_start(mcreceiver);
		if (err)
			goto out;

		mcreceiver->state = RUNNING;
		mcreceiver->ssrc = ssrc;

		info ("multicast receiver: start addr=%J prio=%d enabled=%d "
			"state=%s\n", &mcreceiver->addr, mcreceiver->prio,
			mcreceiver->enable, state_str(mcreceiver->state));

		module_event("multicast", "receiver start", NULL, NULL,
			"addr=%J prio=%d enabled=%d state=%s",
			&mcreceiver->addr, mcreceiver->prio,
			mcreceiver->enable, state_str(mcreceiver->state));

		goto out;
	}

	hprio = le->data;
	if (hprio->prio < mcreceiver->prio) {
		goto out;
	}

	if (hprio->prio == mcreceiver->prio && mcreceiver->ssrc != ssrc) {
		if (hprio->state == IGNORED)
			hprio->state = RUNNING;

		mcplayer_fadein(true);
		mcreceiver->ssrc = ssrc;

		info ("multicast receiver: restart addr=%J prio=%d enabled=%d "
			"state=%s\n", &mcreceiver->addr, mcreceiver->prio,
			mcreceiver->enable, state_str(mcreceiver->state));

		module_event("multicast", "receiver restart", NULL, NULL,
			"addr=%J prio=%d enabled=%d state=%s",
			&mcreceiver->addr, mcreceiver->prio,
			mcreceiver->enable, state_str(mcreceiver->state));

		goto out;
	}
	else if (hprio->prio == mcreceiver->prio) {
		goto out;
	}

	err = player_stop_start(mcreceiver);
	if (err)
		goto out;

	hprio->state = RECEIVING;
	jbuf_flush(hprio->jbuf);
	mcreceiver->state = RUNNING;
	mcreceiver->ssrc = ssrc;


	info ("multicast receiver: start addr=%J prio=%d enabled=%d "
		"state=%s\n", &mcreceiver->addr, mcreceiver->prio,
		mcreceiver->enable, state_str(mcreceiver->state));

	module_event("multicast", "receiver start", NULL, NULL,
		"addr=%J prio=%d enabled=%d state=%s",
		&mcreceiver->addr, mcreceiver->prio, mcreceiver->enable,
		state_str(mcreceiver->state));

  out:
	mtx_unlock(&mcreceivl_lock);
	return err;
}


/**
 * RTP timeout handler
 *
 * @param arg Multicast receiver object
 */
static void timeout_handler(void *arg)
{
	struct mcreceiver *mcreceiver = arg;
	info ("multicast receiver: EOS addr=%J prio=%d enabled=%d state=%s\n",
		&mcreceiver->addr, mcreceiver->prio, mcreceiver->enable,
		state_str(mcreceiver->state));

	module_event("multicast", "receiver EOS", NULL, NULL,
		"addr=%J prio=%d enabled=%d state=%s",
		&mcreceiver->addr, mcreceiver->prio, mcreceiver->enable,
		state_str(mcreceiver->state));

	mtx_lock(&mcreceivl_lock);
	if (mcreceiver->state == RUNNING) {
		mcplayer_stop();
		jbuf_flush(mcreceiver->jbuf);
	}

	mcreceiver->state = LISTENING;
	mcreceiver->muted = false;
	mcreceiver->ssrc = 0;
	mcreceiver->ac   = NULL;
	resume_uag_state();

	mtx_unlock(&mcreceivl_lock);
	return;
}


static void decode_frames(struct mcreceiver *rx);
static void decode_tmr(void *arg)
{
	struct mcreceiver *rx = arg;

	decode_frames(rx);
}


/**
 * Decode RTP packet
 *
 * @return 0 if success, otherwise errorcode
 */
static void decode_frames(struct mcreceiver *rx)
{
	void *mb = NULL;
	struct rtp_header hdr;
	int err = 0;

	uint32_t n = jbuf_packets(rx->jbuf);

	do {
		err = jbuf_get(rx->jbuf, &hdr, &mb);
		if (err && err != EAGAIN) {
			break;
		}

		err = mcplayer_decode(&hdr, mb, err == EAGAIN);
		mb = mem_deref(mb);
		if (err)
			break;
	} while (n--);

	int32_t delay;
	delay = jbuf_next_play(rx->jbuf);
	if (delay < 0)
		delay = 10; /* Fallback time */

	tmr_start(&rx->tmr_decode, delay, decode_tmr, rx);
}


/**
 * Process incoming RTP packages
 *
 * @param src Source address
 * @param hdr RTP headers
 * @param mb  RTP payload
 * @param arg Multicast receiver object
 */
static void rtp_process(const struct sa *src, const struct rtp_header *hdr,
	struct mbuf *mb, void *arg)
{
	int err = 0;
	struct mcreceiver *mcreceiver = arg;

	(void) src;
	(void) mb;

	if (!mcreceiver->ac)
		mcreceiver->ac = pt2codec(hdr);

	if (!mcreceiver->ac)
		return;

	if (!mbuf_get_left(mb))
		goto out;

	err = prio_handling(mcreceiver, hdr->ssrc);
	if (err)
		goto out;

	if (mcreceiver->state == RUNNING) {
		if (mcreceiver->muted && mcplayer_fadeout_done()) {
			mcplayer_stop();
			jbuf_flush(mcreceiver->jbuf);
			goto out;
		}

		jbuf_put(mcreceiver->jbuf, hdr, mb);
	}

  out:
	tmr_start(&mcreceiver->timeout, TIMEOUT, timeout_handler, mcreceiver);
}


static int pt_to_crate(int pt)
{
	switch (pt) {

	case 0:  return 8000;   /* PCMU */
	case 8:  return 8000;   /* PCMA */
	case 9:  return 8000;   /* G722 due to an error in RFC */

	default: return 0;
	}
}


static void rtp_receive(const struct sa *src, const struct rtp_header *hdr,
			struct mbuf *mb, void *arg)
{
	struct mcreceiver *rx = arg;
	uint32_t ssrc0;

	if (rtp_pt_is_rtcp(hdr->pt)) {
		debug("multicast receiver: drop incoming RTCP packet on RTP "
			"port (pt=%u)\n", hdr->pt);
		return;
	}

	ssrc0 = rx->ssrc;
	if (!rx->ssrc_set) {
		rx->ssrc = hdr->ssrc;
		rx->ssrc_set = true;
		tmr_start(&rx->tmr_decode, 0, decode_tmr, rx);
		jbuf_set_srate(rx->jbuf, pt_to_crate(hdr->pt));
	}
	else if (hdr->ssrc != ssrc0) {
		debug("multicast receiver: SSRC changed 0x%x -> 0x%x"
		     " (%zu bytes from %J)\n",
		     ssrc0, hdr->ssrc,
		     mbuf_get_left(mb), src);

		rx->ssrc = hdr->ssrc;
		jbuf_flush(rx->jbuf);
	}

	struct rtp_header hdrdup = *hdr;
	hdrdup.ts_arrive = tmr_jiffies() * pt_to_crate(hdr->pt) / 1000;
	rtp_process(src, &hdrdup, mb, arg);
}


/**
 * Enable / Disable all mcreceiver with prio > (argument)prio
 *
 * @param prio Priority
 */
void mcreceiver_enprio(uint32_t prio)
{
	struct le *le;
	struct mcreceiver *mcreceiver;

	if (!prio)
		return;

	mtx_lock(&mcreceivl_lock);
	LIST_FOREACH(&mcreceivl, le) {
		mcreceiver = le->data;

		if (mcreceiver->prio <= prio) {
			mcreceiver->enable = true;
		}
		else {
			mcreceiver->enable = false;

			if (mcreceiver->state == RUNNING) {
				mcreceiver_stop(mcreceiver);
				mcplayer_stop();
			}
		}
	}

	mtx_unlock(&mcreceivl_lock);
	resume_uag_state();
}


/**
 * Enable / Disable a certain priority range
 *
 * @param priol Lower priority boundary
 * @param prioh Higher priority boundary
 * @param en    Enable / Disable flag
 */
void mcreceiver_enrangeprio(uint32_t priol, uint32_t prioh, bool en)
{
	struct le *le;
	struct mcreceiver *mcreceiver;

	if (!priol || !prioh)
		return;

	mtx_lock(&mcreceivl_lock);
	LIST_FOREACH(&mcreceivl, le) {
		mcreceiver = le->data;

		if (mcreceiver->prio >=priol && mcreceiver->prio <= prioh) {
			mcreceiver->enable = en;

			if (mcreceiver->state == RUNNING) {
				mcreceiver_stop(mcreceiver);
				mcplayer_stop();
			}
		}
	}

	mtx_unlock(&mcreceivl_lock);
	resume_uag_state();
}


/**
 * Enable / Disable all multicast receiver
 *
 * @param enable
 */
void mcreceiver_enable(bool enable)
{
	struct le *le;
	struct mcreceiver *mcreceiver;

	mtx_lock(&mcreceivl_lock);
	LIST_FOREACH(&mcreceivl, le) {
		mcreceiver = le->data;
		mcreceiver->enable = enable;
		if (mcreceiver->state == RUNNING)
			mcreceiver_stop(mcreceiver);
	}

	mtx_unlock(&mcreceivl_lock);
	mcplayer_stop();
	resume_uag_state();
}


/**
 * Change the priority of a multicast receiver
 *
 * @param addr Listen address
 * @param prio Priority
 *
 * @return int 0 if success, errorcode otherwise
 */
int mcreceiver_chprio(struct sa *addr, uint32_t prio)
{
	struct le *le;
	struct mcreceiver *mcreceiver;

	if (!addr || !prio)
		return EINVAL;

	le = list_apply(&mcreceivl, true, mcreceiver_addr_cmp, addr);
	if (!le) {
		warning ("multicast receiver: receiver %J not found\n", addr);
		return EINVAL;
	}

	if (list_apply(&mcreceivl, true, mcreceiver_prio_cmp, &prio)) {
		warning ("multicast receiver: priority %d already in use\n",
			prio);
		return EADDRINUSE;
	}

	mcreceiver = le->data;
	mtx_lock(&mcreceivl_lock);
	mcreceiver->prio = prio;
	mtx_unlock(&mcreceivl_lock);
	resume_uag_state();
	return 0;
}


/**
 * Search and set the ignore flag of the given priority multicast receiver
 *
 * @param prio Priority
 *
 * @return int 0 if success, errorcode otherwise
 */
int mcreceiver_prioignore(uint32_t prio)
{
	struct le *le;
	struct mcreceiver *mcreceiver;
	int err = 0;

	if (!prio)
		return EINVAL;

	le = list_apply(&mcreceivl, true, mcreceiver_prio_cmp, &prio);
	if (!le) {
		warning ("multicast receiver: priority %d not found\n", prio);
		return EINVAL;
	}

	mcreceiver = le->data;
	if (mcreceiver->state == IGNORED)
		return 0;

	mtx_lock(&mcreceivl_lock);
	switch (mcreceiver->state) {
		case RUNNING:
			mcreceiver->state = IGNORED;
			mcplayer_stop();
			jbuf_flush(mcreceiver->jbuf);
			break;
		case RECEIVING:
			mcreceiver->state = IGNORED;
			break;
		default:
			err = EPERM;
			warning ("multicast receiver: priority %d not"
				" running or receiving(%m)\n", prio, err);
			break;
	}

	mtx_unlock(&mcreceivl_lock);
	resume_uag_state();
	return err;
}


/**
 * Toggle mute flag of the given priority multicast receiver
 *
 * @param prio Priority
 *
 * @return int 0 if success, errorcode otherwise
 */
int mcreceiver_mute(uint32_t prio)
{
	struct le *le;
	struct mcreceiver *mcreceiver;
	int err = 0;

	if (!prio)
		return EINVAL;

	le = list_apply(&mcreceivl, true, mcreceiver_prio_cmp, &prio);
	if (!le) {
		warning ("multicast receiver: priority %d not found\n", prio);
		return EINVAL;
	}

	mcreceiver = le->data;
	mtx_lock(&mcreceivl_lock);
	mcreceiver->muted = !mcreceiver->muted;
	if (mcreceiver->state == RUNNING) {
		if (mcreceiver->muted) {
			mcplayer_fadeout();
		}
		else {
			mcplayer_fadein(false);
			err = mcplayer_start(mcreceiver->ac);
			if (err == EINPROGRESS)
				err = 0;
		}
	}
	mtx_unlock(&mcreceivl_lock);
	return err;
}


/**
 * Un-register all multicast listener
 */
void mcreceiver_unregall(void)
{
	mtx_lock(&mcreceivl_lock);
	list_flush(&mcreceivl);
	mtx_unlock(&mcreceivl_lock);
	resume_uag_state();
	mtx_destroy(&mcreceivl_lock);
}


/**
 * Un-register a multicast listener
 *
 * @param addr Listen address
 */
void mcreceiver_unreg(struct sa *addr){
	struct mcreceiver *mcreceiver = NULL;
	struct le *le;

	le = list_apply(&mcreceivl, true, mcreceiver_addr_cmp, addr);
	if (!le) {
		warning ("multicast: multicast receiver %J not found\n", addr);
		return;
	}

	mcreceiver = le->data;
	mtx_lock(&mcreceivl_lock);
	list_unlink(&mcreceiver->le);
	mtx_unlock(&mcreceivl_lock);
	mem_deref(mcreceiver);
	resume_uag_state();

	if (list_isempty(&mcreceivl))
		mtx_destroy(&mcreceivl_lock);
}


/**
 * Allocate a new multicast receiver object
 *
 * @param addr Listen address
 * @param prio Listener priority
 *
 * @return int 0 if success, errorcode otherwise
 */
int mcreceiver_alloc(struct sa *addr, uint8_t prio)
{
	int err = 0;
	uint16_t port;
	struct mcreceiver *mcreceiver = NULL;
	struct config_avt *cfg = &conf_config()->avt;
	struct range jbuf_del;
	enum jbuf_type jbtype;
	struct pl pl;

	if (!addr || !prio)
		return EINVAL;

	if (list_apply(&mcreceivl, true, mcreceiver_addr_cmp, addr)) {
		warning ("multicast receiver: address %J already in use\n",
			addr);
		return EADDRINUSE;
	}

	if (list_apply(&mcreceivl, true, mcreceiver_prio_cmp, &prio)) {
		warning ("multicast receiver: priority %d already in use\n",
			prio);
		return EADDRINUSE;
	}

	mcreceiver = mem_zalloc(sizeof(*mcreceiver), mcreceiver_destructor);
	if (!mcreceiver)
		return ENOMEM;

	if (list_isempty(&mcreceivl)) {
		err = mtx_init(&mcreceivl_lock, mtx_plain) != thrd_success;
		if (err) {
			err = ENOMEM;
			goto out;
		}
	}

	sa_cpy(&mcreceiver->addr, addr);
	port = sa_port(&mcreceiver->addr);
	mcreceiver->prio = prio;

	mcreceiver->enable = true;
	mcreceiver->muted = false;
	mcreceiver->state = LISTENING;

	uint32_t jbuf_sz = cfg->audio.jbuf_sz;
	jbuf_del  = cfg->audio.jbuf_del;
	jbtype = cfg->audio.jbtype;
	if (0 == conf_get(conf_cur(), "multicast_jbuf_delay", &pl)) {
		warning("multicast receiver: multicast_jbuf_delay is "
			"deprecated, use multicast_jbuf_ms and "
			"multicast_jbuf_size\n");
	}

	(void)conf_get_range(conf_cur(), "multicast_jbuf_ms", &jbuf_del);
	(void)conf_get_u32(conf_cur(),   "multicast_jbuf_size", &jbuf_sz);
	if (0 == conf_get(conf_cur(), "multicast_jbuf_type", &pl))
		jbtype = conf_get_jbuf_type(&pl);

	err = jbuf_alloc(&mcreceiver->jbuf, jbuf_del.min, jbuf_del.max,
			 jbuf_sz);
	err |= jbuf_set_type(mcreceiver->jbuf, jbtype);
	if (err)
		goto out;

	err = rtp_listen_single(&mcreceiver->rtp,
				&mcreceiver->addr, port, rtp_receive,
				mcreceiver);
	if (err) {
		warning("multicast receiver: udp listen failed:"
			"af=%s port=%u (%m)\n", net_af2name(sa_af(addr)),
			port, err);
		goto out;
	}

	if (IN_MULTICAST(sa_in(&mcreceiver->addr))
	    || IN6_IS_ADDR_MULTICAST(&mcreceiver->addr.u.in6.sin6_addr)) {
		err = udp_multicast_join(rtp_sock(mcreceiver->rtp),
					 &mcreceiver->addr);
		if (err) {
			warning ("multicast recevier: join multicast group "
				"failed %J (%m)\n", &mcreceiver->addr, err);
			goto out;
		}
	}

	mtx_lock(&mcreceivl_lock);
	list_append(&mcreceivl, &mcreceiver->le, mcreceiver);
	mtx_unlock(&mcreceivl_lock);

  out:
	if (err)
		mem_deref(mcreceiver);

	return err;
}


/**
 * Print all available multicast receiver
 *
 * @param pf Printer
 */
void mcreceiver_print(struct re_printf *pf)
{
	struct le *le = NULL;
	struct mcreceiver *mcreceiver = NULL;

	re_hprintf(pf, "Multicast Receiver List:\n");
	LIST_FOREACH(&mcreceivl, le) {
		mcreceiver = le->data;
		re_hprintf(pf, "   addr=%J prio=%d enabled=%d muted=%d "
			"state=%s\n", &mcreceiver->addr, mcreceiver->prio,
			mcreceiver->enable, mcreceiver->muted,
			state_str(mcreceiver->state));
	}
}
