/**
 * @file cand.c  ICE Candidates
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <string.h>
#include <re_types.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_tmr.h>
#include <re_sa.h>
#include <re_sys.h>
#include <re_stun.h>
#include <re_turn.h>
#include <re_ice.h>
#include "ice.h"


#define DEBUG_MODULE "icecand"
#define DEBUG_LEVEL 5
#include <re_dbg.h>


static void cand_destructor(void *arg)
{
	struct ice_cand *cand = arg;

	list_unlink(&cand->le);
	mem_deref(cand->foundation);
	mem_deref(cand->ifname);

	if (cand != cand->base)
		mem_deref(cand->base);
}


/** Foundation is a hash of IP address and candidate type */
static int compute_foundation(struct ice_cand *cand)
{
	uint32_t v;

	v  = sa_hash(&cand->addr, SA_ADDR);
	v ^= cand->type;

	return re_sdprintf(&cand->foundation, "%08x", v);
}


static int cand_alloc(struct ice_cand **candp, struct icem *icem,
		      enum ice_cand_type type, unsigned compid,
		      uint32_t prio, const char *ifname,
		      enum ice_transp transp, const struct sa *addr)
{
	struct ice_cand *cand;
	int err;

	if (!icem)
		return EINVAL;

	cand = mem_zalloc(sizeof(*cand), cand_destructor);
	if (!cand)
		return ENOMEM;

	list_append(&icem->lcandl, &cand->le, cand);

	cand->type   = type;
	cand->compid = compid;
	cand->prio   = prio;
	cand->transp = transp;

	sa_cpy(&cand->addr, addr);

	err = compute_foundation(cand);

	if (ifname)
		err |= str_dup(&cand->ifname, ifname);

	if (err)
		mem_deref(cand);
	else if (candp)
		*candp = cand;

	return err;
}


int icem_lcand_add_base(struct icem *icem, unsigned compid, uint16_t lprio,
			const char *ifname, enum ice_transp transp,
			const struct sa *addr)
{
	struct icem_comp *comp;
	struct ice_cand *cand;
	int err;

	comp = icem_comp_find(icem, compid);
	if (!comp)
		return ENOENT;

	err = cand_alloc(&cand, icem, ICE_CAND_TYPE_HOST, compid,
			 ice_cand_calc_prio(ICE_CAND_TYPE_HOST, lprio, compid),
			 ifname, transp, addr);
	if (err)
		return err;

	/* the base is itself */
	cand->base = cand;

	sa_set_port(&cand->addr, comp->lport);

	return 0;
}


int icem_lcand_add(struct icem *icem, struct ice_cand *base,
		   enum ice_cand_type type,
		   const struct sa *addr)
{
	struct ice_cand *cand;
	int err;

	if (!base)
		return EINVAL;

	err = cand_alloc(&cand, icem, type, base->compid,
			 ice_cand_calc_prio(type, 0, base->compid),
			 base->ifname, base->transp, addr);
	if (err)
		return err;

	cand->base = mem_ref(base);
	sa_cpy(&cand->rel, &base->addr);

	return 0;
}


int icem_rcand_add(struct icem *icem, enum ice_cand_type type, unsigned compid,
		   uint32_t prio, const struct sa *addr,
		   const struct sa *rel_addr, const struct pl *foundation)
{
	struct ice_cand *rcand;
	int err;

	if (!icem || !foundation)
		return EINVAL;

	rcand = mem_zalloc(sizeof(*rcand), cand_destructor);
	if (!rcand)
		return ENOMEM;

	list_append(&icem->rcandl, &rcand->le, rcand);

	rcand->type   = type;
	rcand->compid = compid;
	rcand->prio   = prio;

	sa_cpy(&rcand->addr, addr);
	sa_cpy(&rcand->rel, rel_addr);

	err = pl_strdup(&rcand->foundation, foundation);

	if (err)
		mem_deref(rcand);

	return err;
}


int icem_rcand_add_prflx(struct ice_cand **rcp, struct icem *icem,
			 unsigned compid, uint32_t prio,
			 const struct sa *addr)
{
	struct ice_cand *rcand;
	int err;

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

	rcand = mem_zalloc(sizeof(*rcand), cand_destructor);
	if (!rcand)
		return ENOMEM;

	list_append(&icem->rcandl, &rcand->le, rcand);

	rcand->type   = ICE_CAND_TYPE_PRFLX;
	rcand->compid = compid;
	rcand->prio   = prio;
	rcand->addr   = *addr;

	err = re_sdprintf(&rcand->foundation, "%08x", rand_u32());
	if (err)
		goto out;

	icecomp_printf(icem_comp_find(icem, compid),
		       "added PeerReflexive remote candidate"
		       " with priority %u (%J)\n", prio, addr);

 out:
	if (err)
		mem_deref(rcand);
	else if (rcp)
		*rcp = rcand;

	return err;
}


struct ice_cand *icem_cand_find(const struct list *lst, unsigned compid,
				const struct sa *addr)
{
	struct le *le;

	for (le = list_head(lst); le; le = le->next) {

		struct ice_cand *cand = le->data;

		if (compid && cand->compid != compid)
			continue;

		if (addr && !sa_cmp(&cand->addr, addr, SA_ALL))
			continue;

		return cand;
	}

	return NULL;
}


/**
 * Find the highest priority LCAND on the check-list of type HOST/RELAY
 *
 * @param icem    ICE Media object
 * @param compid  Component ID
 *
 * @return Local candidate if found, otherwise NULL
 */
struct ice_cand *icem_lcand_find_checklist(const struct icem *icem,
					   unsigned compid)
{
	struct le *le;

	for (le = icem->checkl.head; le; le = le->next) {
		struct ice_candpair *cp = le->data;

		if (cp->lcand->compid != compid)
			continue;

		switch (cp->lcand->type) {

		case ICE_CAND_TYPE_HOST:
		case ICE_CAND_TYPE_RELAY:
			return cp->lcand;

		default:
			break;
		}
	}

	return NULL;
}


struct ice_cand *icem_lcand_base(struct ice_cand *lcand)
{
	return lcand ? lcand->base : NULL;
}


const struct sa *icem_lcand_addr(const struct ice_cand *cand)
{
	return cand ? &cand->addr : NULL;
}


int icem_cands_debug(struct re_printf *pf, const struct list *lst)
{
	struct le *le;
	int err;

	err = re_hprintf(pf, " (%u)\n", list_count(lst));

	for (le = list_head(lst); le && !err; le = le->next) {

		const struct ice_cand *cand = le->data;

		err |= re_hprintf(pf, "  {%u} fnd=%-2s prio=%08x %24H",
				  cand->compid, cand->foundation, cand->prio,
				  icem_cand_print, cand);

		if (sa_isset(&cand->rel, SA_ADDR))
			err |= re_hprintf(pf, " (rel-addr=%J)", &cand->rel);

		err |= re_hprintf(pf, "\n");
	}

	return err;
}


int icem_cand_print(struct re_printf *pf, const struct ice_cand *cand)
{
	int err = 0;

	if (!cand)
		return 0;

	if (cand->ifname)
		err |= re_hprintf(pf, "%s:", cand->ifname);

	err |= re_hprintf(pf, "%s:%J",
			  ice_cand_type2name(cand->type), &cand->addr);

	return err;
}

enum ice_cand_type icem_cand_type(const struct ice_cand *cand)
{
	return cand ? cand->type : (enum ice_cand_type)-1;
}
