/**
 * @file src/bevent.c  Baresip event handling
 *
 * Copyright (C) 2017 Alfred E. Heggestad
 */

#include <re.h>
#include <baresip.h>
#include "core.h"


enum {
	EVENT_MAXSZ = 4096,
};


enum bevent_class {
	BEVENT_CLASS_UA,
	BEVENT_CLASS_CALL,
	BEVENT_CLASS_APP,
	BEVENT_CLASS_SIP,
	BEVENT_CLASS_UNDEFINED
};


struct bevent {
	enum bevent_ev ev;
	const char *txt;
	int err;
	bool stop;
	enum bevent_class ec;
	union {
		struct ua *ua;
		struct call *call;
		const struct sip_msg *msg;
		void *arg;
	} u;
};


struct ehe {
	struct le le;
	bevent_h *h;
	void *arg;
};


static struct list ehel;               /**< Event handlers (struct ehe)     */


static void bevent_emit_base(struct bevent *event);


static void ehe_destructor(void *arg)
{
	struct ehe *ehe = arg;

	list_unlink(&ehe->le);
}


static const char *bevent_class_name(enum bevent_class ec)
{
	switch (ec) {

	case BEVENT_CLASS_UA:
		return "ua";

	case BEVENT_CLASS_CALL:
		return "call";

	case BEVENT_CLASS_APP:
		return "application";

	case BEVENT_CLASS_SIP:
		return "sip";

	default:
		return "other";
	}
}


/**
 * Returns the call object of a call event, which was generated by function
 * bevent_call_emit(). If the event was generated by another emit function
 * then NULL is returned
 *
 * @param event Baresip event
 *
 * @return The call object, or NULL
 */
struct call *bevent_get_call(const struct bevent *event)
{
	if (!event)
		return NULL;

	if (event->ec == BEVENT_CLASS_CALL)
		return event->u.call;

	return NULL;
}


/**
 * Returns the User-Agent of a UA or call event, which was generated by
 * function bevent_ua_emit() or function bevent_call_emit(). If the event was
 * generated by another emit function then NULL is returned
 *
 * @param event Baresip event
 *
 * @return The User-Agent, or NULL
 */
struct ua *bevent_get_ua(const struct bevent *event)
{
	struct call *call;

	if (!event)
		return NULL;

	call = bevent_get_call(event);
	if (call)
		return call_get_ua(call);

	if (event->ec == BEVENT_CLASS_UA)
		return event->u.ua;

	return NULL;
}


/**
 * Returns the sip_msg of a SIP message event, which was generated by function
 * bevent_sip_msg_emit(). If the event was generated by another emit function
 * then NULL is returned
 *
 * @param event Baresip event
 *
 * @return The SIP message, or NULL
 */
const struct sip_msg *bevent_get_msg(const struct bevent *event)
{
	if (!event)
		return NULL;

	if (event->ec == BEVENT_CLASS_SIP)
		return event->u.msg;

	return NULL;
}


/**
 * Returns the argument of an application event, which was generated by
 * function bevent_app_emit(). If the event was generated by another emit
 * function then NULL is returned
 *
 * @param event Baresip event
 *
 * @return Pointer to the application argument
 */
void *bevent_get_apparg(const struct bevent *event)
{
	if (!event)
		return NULL;

	if (event->ec == BEVENT_CLASS_APP)
		return event->u.arg;

	return NULL;
}


/**
 * Returns the Baresip event value
 *
 * @param event Baresip event
 *
 * @return the event value
 */
enum bevent_ev bevent_get_value(const struct bevent *event)
{
	return event ? event->ev : BEVENT_MAX;
}


/**
 * Returns the event text
 *
 * @param event Baresip event
 *
 * @return the event text
 */
const char *bevent_get_text(const struct bevent *event)
{
	return event ? event->txt : "";
}


/**
 * Set error code of given event. This should be used in the event handler to
 * inform baresip that an error occurred. An event with set error is not passed
 * to further event handlers
 *
 * @param event  Baresip event
 * @param err    Error code
 */
void bevent_set_error(struct bevent *event, int err)
{
	if (!event)
		return;

	event->err = err;
}


/**
 * Stops event processing of given event. This should be used in an event
 * handler to stop passing the event to further event handlers
 *
 * @param event  Baresip event
 */
void bevent_stop(struct bevent *event)
{
	if (!event)
		return;

	event->stop = true;
}


static int add_rtcp_stats(struct odict *od_parent, const struct rtcp_stats *rs)
{
	struct odict *od = NULL, *tx = NULL, *rx = NULL;
	int err = 0;

	if (!od_parent || !rs)
		return EINVAL;

	err  = odict_alloc(&od, 8);
	err |= odict_alloc(&tx, 8);
	err |= odict_alloc(&rx, 8);
	if (err)
		goto out;

	err  = odict_entry_add(tx, "sent", ODICT_INT, (int64_t)rs->tx.sent);
	err |= odict_entry_add(tx, "lost", ODICT_INT, (int64_t)rs->tx.lost);
	err |= odict_entry_add(tx, "jit", ODICT_INT, (int64_t)rs->tx.jit);
	if (err)
		goto out;

	err  = odict_entry_add(rx, "sent", ODICT_INT, (int64_t)rs->rx.sent);
	err |= odict_entry_add(rx, "lost", ODICT_INT, (int64_t)rs->rx.lost);
	err |= odict_entry_add(rx, "jit", ODICT_INT, (int64_t)rs->rx.jit);
	if (err)
		goto out;

	err  = odict_entry_add(od, "tx", ODICT_OBJECT, tx);
	err |= odict_entry_add(od, "rx", ODICT_OBJECT, rx);
	err |= odict_entry_add(od, "rtt", ODICT_INT, (int64_t)rs->rtt);
	if (err)
		goto out;

	/* add object to the parent */
	err = odict_entry_add(od_parent, "rtcp_stats", ODICT_OBJECT, od);
	if (err)
		goto out;

 out:
	mem_deref(od);
	mem_deref(tx);
	mem_deref(rx);

	return err;
}


static int odict_encode_call(struct odict *od, struct call *call)
{
	const char *dir;
	enum sdp_dir ardir;
	enum sdp_dir vrdir;
	enum sdp_dir aldir;
	enum sdp_dir vldir;
	enum sdp_dir adir;
	enum sdp_dir vdir;
	struct sdp_media *amedia;
	struct sdp_media *vmedia;
	int err;

	dir = call_is_outgoing(call) ? "outgoing" : "incoming";

	err  = odict_entry_add(od, "direction", ODICT_STRING, dir);
	if (call_peeruri(call)) {
		err |= odict_entry_add(od, "peeruri",
				       ODICT_STRING, call_peeruri(call));
	}

	if (call_contacturi(call)) {
		err |= odict_entry_add(od, "contacturi",
				       ODICT_STRING, call_contacturi(call));
	}

	if (call_localuri(call)) {
		err |= odict_entry_add(od, "localuri",
				       ODICT_STRING, call_localuri(call));
	}

	if (call_peername(call)) {
		err |= odict_entry_add(od, "peerdisplayname",
				       ODICT_STRING, call_peername(call));
	}

	if (call_id(call))
		err |= odict_entry_add(od, "id", ODICT_STRING, call_id(call));

	amedia = stream_sdpmedia(audio_strm(call_audio(call)));
	ardir = sdp_media_rdir(amedia);
	aldir  = sdp_media_ldir(amedia);
	adir  = sdp_media_dir(amedia);
	if (!sa_isset(sdp_media_raddr(amedia), SA_ADDR))
		ardir = aldir = adir = SDP_INACTIVE;

	vmedia = stream_sdpmedia(video_strm(call_video(call)));
	vrdir = sdp_media_rdir(vmedia);
	vldir = sdp_media_ldir(vmedia);
	vdir  = sdp_media_dir(vmedia);
	if (!sa_isset(sdp_media_raddr(vmedia), SA_ADDR))
		vrdir = vldir = vdir = SDP_INACTIVE;

	err |= odict_entry_add(od, "remoteaudiodir", ODICT_STRING,
			       sdp_dir_name(ardir));
	err |= odict_entry_add(od, "remotevideodir", ODICT_STRING,
			       sdp_dir_name(vrdir));
	err |= odict_entry_add(od, "audiodir", ODICT_STRING,
			       sdp_dir_name(adir));
	err |= odict_entry_add(od, "videodir", ODICT_STRING,
			       sdp_dir_name(vdir));
	err |= odict_entry_add(od, "localaudiodir", ODICT_STRING,
			       sdp_dir_name(aldir));
	err |= odict_entry_add(od, "localvideodir", ODICT_STRING,
			       sdp_dir_name(vldir));
	if (call_diverteruri(call))
		err |= odict_entry_add(od, "diverteruri", ODICT_STRING,
				       call_diverteruri(call));

	if (pl_isset(call_user_data(call)))
		err |= odict_pl_add(od, "userdata", call_user_data(call));

	return err;
}


int odict_encode_bevent(struct odict *od, struct bevent *event)
{
	if (!event)
		return EINVAL;

	struct ua *ua     = bevent_get_ua(event);
	struct call *call = bevent_get_call(event);
	const char *prm = bevent_get_text(event);
	int err;

	if (!od)
		return EINVAL;

	err = odict_entry_add(od, "class",
			      ODICT_STRING, bevent_class_name(event->ec));

	if (event->ec == BEVENT_CLASS_SIP) {
		char *buf;
		const struct sip_hdr *hdr;
		const struct sip_msg *msg = bevent_get_msg(event);
		hdr = sip_msg_hdr(msg, SIP_HDR_CONTACT);
		if (hdr)
			err |= odict_pl_add(od, "contact", &hdr->val);

		if (pl_isset(&msg->from.dname))
			err |= odict_pl_add(od, "display", &msg->from.dname);

		err |= re_sdprintf(&buf, "%H", uri_encode, &msg->from.uri);
		if (err)
			return err;

		err |= odict_entry_add(od, "from", ODICT_STRING, buf);
		mem_deref(buf);
	}

	enum bevent_ev ev = event->ev;
	err |= odict_entry_add(od, "type", ODICT_STRING, bevent_str(ev));
	if (ua) {
		err |= odict_entry_add(od, "accountaor",
				       ODICT_STRING,
				       account_aor(ua_account(ua)));
		err |= odict_entry_add(od, "cuser",
				       ODICT_STRING, ua_cuser(ua));
	}

	if (call)
		err |= odict_encode_call(od, call);

	if (str_isset(prm))
		err |= odict_entry_add(od, "param", ODICT_STRING, prm);

	if (ev == BEVENT_CALL_RTCP && str_isset(prm)) {
		struct stream *strm = NULL;

		if (0 == str_casecmp(prm, "audio"))
			strm = audio_strm(call_audio(call));
		else if (0 == str_casecmp(prm, "video"))
			strm = video_strm(call_video(call));

		err |= add_rtcp_stats(od, stream_rtcp_stats(strm));
	}

	return err;
}


/**
 * Add audio buffer status
 *
 * @param od_parent  Dictionary to encode into
 * @param call       Call object
 *
 * @return 0 if success, otherwise errorcode
 */
int event_add_au_jb_stat(struct odict *od_parent, const struct call *call)
{
	int err = 0;
	err = odict_entry_add(od_parent, "audio_jb_ms",ODICT_INT,
			    (int64_t)audio_jb_current_value(call_audio(call)));
	return err;
}


/**
 * Register an Event handler
 *
 * @param eh  Event handler
 * @param arg Handler argument
 *
 * @return 0 if success, otherwise errorcode
 */
int  bevent_register(bevent_h *eh, void *arg)
{
	struct ehe *ehe;

	if (!eh)
		return EINVAL;

	bevent_unregister(eh);

	ehe = mem_zalloc(sizeof(*ehe), ehe_destructor);
	if (!ehe)
		return ENOMEM;

	ehe->h = eh;
	ehe->arg = arg;

	list_append(&ehel, &ehe->le, ehe);

	return 0;
}


/**
 * Unregister an Event handler
 *
 * @param eh  Event handler
 */
void bevent_unregister(bevent_h *eh)
{
	struct le *le;

	for (le = ehel.head; le; le = le->next) {

		struct ehe *ehe = le->data;

		if (ehe->h == eh) {
			mem_deref(ehe);
			break;
		}
	}
}


/**
 * Send a BEVENT_MODULE event with a general format for modules
 *
 * @param module Module name
 * @param event  Event name
 * @param ua     User-Agent object (optional)
 * @param call   Call object (optional)
 * @param fmt    Formatted arguments
 * @param ...    Variable arguments
 */
void module_event(const char *module, const char *event, struct ua *ua,
		struct call *call, const char *fmt, ...)
{
	char *buf;
	char *p;
	size_t len = EVENT_MAXSZ;
	va_list ap;

	if (!module || !event)
		return;

	buf = mem_zalloc(EVENT_MAXSZ, NULL);
	if (!buf)
		return;

	if (-1 == re_snprintf(buf, len, "%s,%s,", module, event))
		goto out;

	p = buf + str_len(buf);
	len -= str_len(buf);

	va_start(ap, fmt);
	(void)re_vsnprintf(p, len, fmt, ap);
	va_end(ap);

	struct bevent bevent = {.ev = BEVENT_MODULE, .txt = buf};
	if (call) {
		bevent.u.call = call;
		bevent.ec = BEVENT_CLASS_CALL;
	}
	else if (ua) {
		bevent.u.ua = ua;
		bevent.ec = BEVENT_CLASS_UA;
	}
	else
		bevent.ec = BEVENT_CLASS_UNDEFINED;

	bevent_emit_base(&bevent);

out:
	mem_deref(buf);
}


static bool bevent_is_valid(const struct bevent *event, struct ua *ua)
{
	if (!event)
		return false;

	if (event->ec != BEVENT_CLASS_CALL)
		return true;

	struct list *calls = ua_calls(ua);
	struct le *le;

	LIST_FOREACH(calls, le) {
		if (le->data == event->u.call)
			return true;
	}

	return false;
}


static void bevent_emit_base(struct bevent *event)
{
	struct ua *ua = bevent_get_ua(event);
	struct le *le;
	le = ehel.head;
	while (le) {
		struct ehe *ehe = le->data;
		le = le->next;

		ehe->h(event->ev, event, ehe->arg);
		if (ua && !bevent_is_valid(event, ua))
			return;

		if (event->stop)
			return;
	}
}


static int bevent_emit(struct bevent *event, const char *fmt, va_list ap)
{
	char *buf;
	int err;

	if (!fmt)
		err = re_sdprintf(&buf, "");
	else
		err = re_vsdprintf(&buf, fmt, ap);
	if (err)
		return err;

	event->txt = buf;
	event->err = 0;
	bevent_emit_base(event);
	if (event->err) {
		err = event->err;
		goto out;
	}

out:
	mem_deref(buf);
	return err;
}


/**
 * Emit an application event
 *
 * @param ev   User-agent event
 * @param arg  Application specific argument (optional)
 * @param fmt  Formatted arguments
 * @param ...  Variable arguments
 *
 * @return 0 if success, otherwise errorcode
 */
int bevent_app_emit(enum bevent_ev ev, void *arg, const char *fmt, ...)
{
	va_list ap;
	struct bevent event = {.ev = ev, .ec = BEVENT_CLASS_APP};
	int err;

	event.u.arg = arg;

	va_start(ap, fmt);
	err = bevent_emit(&event, fmt, ap);
	va_end(ap);

	return err;
}


/**
 * Emit a Baresip event value
 *
 * @param ev   Baresip event value
 * @param ua   User-Agent
 * @param fmt  Formatted arguments
 * @param ...  Variable arguments
 *
 * @return 0 if success, otherwise errorcode
 */
int bevent_ua_emit(enum bevent_ev ev, struct ua *ua, const char *fmt, ...)
{
	struct bevent event = {.ev = ev, .ec = BEVENT_CLASS_UA};
	va_list ap;
	int err;

	if (!ua)
		return EINVAL;

	event.u.ua = ua;

	va_start(ap, fmt);
	err = bevent_emit(&event, fmt, ap);
	va_end(ap);

	return err;
}


/**
 * Emit a Call event
 *
 * @param ev    Baresip event value
 * @param call  Call object
 * @param fmt   Formatted arguments
 * @param ...   Variable arguments
 *
 * @return 0 if success, otherwise errorcode
 */
int bevent_call_emit(enum bevent_ev ev, struct call *call,
		     const char *fmt, ...)
{
	struct bevent event = {.ev = ev, .ec = BEVENT_CLASS_CALL};
	va_list ap;
	int err;

	if (!call)
		return EINVAL;

	event.u.call = call;

	va_start(ap, fmt);
	err = bevent_emit(&event, fmt, ap);
	va_end(ap);

	return err;
}


/**
 * Emit a SIP message event
 *
 * @param ev    Baresip event value
 * @param msg   SIP message
 * @param fmt   Formatted arguments
 * @param ...   Variable arguments
 *
 * @return 0 if success, otherwise errorcode
 */
int bevent_sip_msg_emit(enum bevent_ev ev, const struct sip_msg *msg,
		       const char *fmt, ...)
{
	struct bevent event = {.ev = ev, .ec = BEVENT_CLASS_SIP};
	va_list ap;
	int err;

	if (!msg)
		return EINVAL;

	event.u.msg = msg;

	va_start(ap, fmt);
	err = bevent_emit(&event, fmt, ap);
	va_end(ap);

	return err;
}


/**
 * Get the name of the Baresip event value
 *
 * @param ev Baresip event value
 *
 * @return Name of the event
 */
const char *bevent_str(enum bevent_ev ev)
{
	switch (ev) {

	case BEVENT_REGISTERING:          return "REGISTERING";
	case BEVENT_REGISTER_OK:          return "REGISTER_OK";
	case BEVENT_REGISTER_FAIL:        return "REGISTER_FAIL";
	case BEVENT_FALLBACK_OK:          return "FALLBACK_OK";
	case BEVENT_FALLBACK_FAIL:        return "FALLBACK_FAIL";
	case BEVENT_UNREGISTERING:        return "UNREGISTERING";
	case BEVENT_MWI_NOTIFY:           return "MWI_NOTIFY";
	case BEVENT_CREATE:               return "CREATE";
	case BEVENT_SHUTDOWN:             return "SHUTDOWN";
	case BEVENT_EXIT:                 return "EXIT";
	case BEVENT_SIPSESS_CONN:         return "SIPSESS_CONN";
	case BEVENT_CALL_INCOMING:        return "CALL_INCOMING";
	case BEVENT_CALL_OUTGOING:        return "CALL_OUTGOING";
	case BEVENT_CALL_RINGING:         return "CALL_RINGING";
	case BEVENT_CALL_PROGRESS:        return "CALL_PROGRESS";
	case BEVENT_CALL_ANSWERED:        return "CALL_ANSWERED";
	case BEVENT_CALL_ESTABLISHED:     return "CALL_ESTABLISHED";
	case BEVENT_CALL_CLOSED:          return "CALL_CLOSED";
	case BEVENT_CALL_TRANSFER:        return "TRANSFER";
	case BEVENT_CALL_TRANSFER_FAILED: return "TRANSFER_FAILED";
	case BEVENT_CALL_REDIRECT:        return "CALL_REDIRECT";
	case BEVENT_CALL_DTMF_START:      return "CALL_DTMF_START";
	case BEVENT_CALL_DTMF_END:        return "CALL_DTMF_END";
	case BEVENT_CALL_RTPESTAB:        return "CALL_RTPESTAB";
	case BEVENT_CALL_RTCP:            return "CALL_RTCP";
	case BEVENT_CALL_MENC:            return "CALL_MENC";
	case BEVENT_VU_TX:                return "VU_TX_REPORT";
	case BEVENT_VU_RX:                return "VU_RX_REPORT";
	case BEVENT_AUDIO_ERROR:          return "AUDIO_ERROR";
	case BEVENT_CALL_LOCAL_SDP:       return "CALL_LOCAL_SDP";
	case BEVENT_CALL_REMOTE_SDP:      return "CALL_REMOTE_SDP";
	case BEVENT_CALL_HOLD:            return "CALL_HOLD";
	case BEVENT_CALL_RESUME:          return "CALL_RESUME";
	case BEVENT_REFER:                return "REFER";
	case BEVENT_MODULE:               return "MODULE";
	case BEVENT_END_OF_FILE:          return "END_OF_FILE";
	case BEVENT_CUSTOM:               return "CUSTOM";
	default: return "?";
	}
}
