#ifndef __ICE_H__
#define __ICE_H__

#include <arpa/inet.h>
#include <glib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdbool.h>

#include "str.h"
#include "obj.h"
#include "helpers.h"
#include "media_socket.h"
#include "socket.h"
#include "timerthread.h"
#include "types.h"
#include "call.h"

#define MAX_COMPONENTS			2
#define TIMER_RUN_INTERVAL		20 /* ms */
#define STUN_RETRANSMIT_INTERVAL	100 /* ms, with exponential backoff */
#define STUN_MAX_RETRANSMITS		7
#define MAX_ICE_CANDIDATES		100
#define ICE_FOUNDATION_LENGTH		16

#define ICE_AGENT_COMPLETED		0x0002
#define ICE_AGENT_CONTROLLING		0x0004
#define ICE_AGENT_NOMINATING		0x0008
#define ICE_AGENT_USABLE		0x0010
#define ICE_AGENT_LITE_SELF		0x0020

#define ICE_PAIR_FROZEN			0x0001
#define ICE_PAIR_IN_PROGRESS		0x0002
#define ICE_PAIR_FAILED			0x0004
#define ICE_PAIR_SUCCEEDED		0x0008
#define ICE_PAIR_NOMINATED		0x0010
#define ICE_PAIR_LEARNED		0x0020
#define ICE_PAIR_VALID			0x0040
#define ICE_PAIR_TO_USE			0x0080
#define ICE_PAIR_TRIGGERED		0x0100
#define ICE_PAIR_AUTHENTICATED		0x0200

#define PAIR_ISSET(p, f)	bf_isset(&(p)->pair_flags, ICE_PAIR_ ## f)
#define PAIR_SET(p, f)		bf_set(&(p)->pair_flags, ICE_PAIR_ ## f)
#define PAIR_SET2(p, f, g)	bf_set(&(p)->pair_flags, ICE_PAIR_ ## f | ICE_PAIR_ ## g)
#define PAIR_CLEAR(p, f)	bf_clear(&(p)->pair_flags, ICE_PAIR_ ## f)
#define PAIR_CLEAR2(p, f, g)	bf_clear(&(p)->pair_flags, ICE_PAIR_ ## f | ICE_PAIR_ ## g)

#define AGENT_ISSET(p, f)	bf_isset(&(p)->agent_flags, ICE_AGENT_ ## f)
#define AGENT_ISSET2(p, f, g)	bf_isset(&(p)->agent_flags, ICE_AGENT_ ## f | ICE_AGENT_ ## g)
#define AGENT_SET(p, f)		bf_set(&(p)->agent_flags, ICE_AGENT_ ## f)
#define AGENT_SET2(p, f, g)	bf_set(&(p)->agent_flags, ICE_AGENT_ ## f | ICE_AGENT_ ## g)
#define AGENT_CLEAR(p, f)	bf_clear(&(p)->agent_flags, ICE_AGENT_ ## f)
#define AGENT_CLEAR3(p, f, g, h) \
	bf_clear(&(p)->agent_flags, ICE_AGENT_ ## f | ICE_AGENT_ ## g | ICE_AGENT_ ## h)

struct logical_intf;
struct local_intf;
struct packet_stream;
struct call_media;
struct stream_params;
struct stun_attrs;
struct call_monologue;

enum ice_candidate_type {
	ICT_UNKNOWN = 0,
	ICT_HOST,
	ICT_SRFLX,
	ICT_PRFLX,
	ICT_RELAY,
	__ICT_LAST,
};

struct ice_candidate {
	str			foundation;
	unsigned long		component_id;
	socktype_t		*transport;
	unsigned long		priority;
	endpoint_t		endpoint;
	enum ice_candidate_type type;
	endpoint_t		related;
	str			ufrag;
};

struct ice_candidate_pair {
	struct ice_candidate	*remote_candidate;
	const struct local_intf	*local_intf;
	stream_fd	*sfd;
	atomic64		pair_flags;
	uint32_t		stun_transaction[3]; /* belongs to transaction_hash, thus agent->lock */
	unsigned int		retransmit_ms;
	int64_t			retransmit;
	unsigned int		retransmits;
	struct ice_agent	*agent;
	uint64_t		pair_priority;
	unsigned int		was_controlling:1,
				was_nominated:1;
};

TYPED_GHASHTABLE_PROTO(candidate_ht, struct ice_candidate, struct ice_candidate)
TYPED_GHASHTABLE_PROTO(candidate_pair_ht, struct ice_candidate_pair, struct ice_candidate_pair)
TYPED_GHASHTABLE_PROTO(foundation_ht, struct ice_candidate, struct ice_candidate)
TYPED_GHASHTABLE_PROTO(priority_ht, void, struct ice_candidate)
TYPED_GHASHTABLE_PROTO(transaction_ht, uint32_t, struct ice_candidate_pair)

/* these are protected by the call's master_lock */
struct ice_agent {
	struct timerthread_obj	tt_obj;
	call_t		*call; /* main reference */
	struct call_media	*media;
	const struct logical_intf	*logical_intf;
	sockfamily_t		*desired_family;
	atomic64		last_activity;

	mutex_t			lock; /* for elements below. and call must be locked in R */
				/* lock order: in_lock first, then agent->lock */
	candidate_q		remote_candidates;
	candidate_pair_q	candidate_pairs; /* for storage */
	candidate_pair_q	triggered;
	candidate_ht		candidate_hash;
	priority_ht		cand_prio_hash;
	candidate_pair_ht	pair_hash;
	transaction_ht		transaction_hash;
	foundation_ht		foundation_hash;
	GTree			*all_pairs;
	candidate_pair_q	all_pairs_list; /* sorted through gtree */
	GTree			*nominated_pairs; /* nominated by peer */
	GTree			*succeeded_pairs; /* checked by us */
	GTree			*valid_pairs; /* succeeded and nominated */
	unsigned int		active_components;
	int64_t			start_nominating;

	str			ufrag[2]; /* 0 = remote, 1 = local */
	str			pwd[2]; /* ditto */
	atomic64		agent_flags;
};




extern const unsigned int ice_type_preferences[];
extern const char * const ice_type_strings[];




void ice_init(void);
void ice_free(void);

enum ice_candidate_type ice_candidate_type(const str *s);
bool ice_has_related(enum ice_candidate_type);
void ice_foundation(str *);
bool ice_peer_address_known(struct ice_agent *, const endpoint_t *, struct packet_stream *,
		const struct local_intf *ifa);

void ice_agent_init(struct ice_agent **agp, struct call_media *media);
void ice_update(struct ice_agent *, struct stream_params *, bool allow_restart);
void ice_shutdown(struct ice_agent **);
void ice_restart(struct ice_agent *);

void ice_candidates_free(candidate_q *);
void ice_remote_candidates(candidate_q *, struct ice_agent *);

void ice_thread_launch(void);

int ice_request(stream_fd *, const endpoint_t *, struct stun_attrs *);
int ice_response(stream_fd *, const endpoint_t *src,
		struct stun_attrs *attrs, void *transaction);

void dequeue_sdp_fragments(struct call_monologue *);
bool trickle_ice_update(ng_buffer *ngbuf, call_t *call, sdp_ng_flags *flags,
		sdp_streams_q *streams);
void ice_fragments_cleanup(fragments_ht ht, bool all);

#include "call.h"


/* returns 0 if ICE still has work to do, 1 otherwise */
INLINE bool ice_has_finished(struct call_media *media) {
	if (!media)
		return true;
	if (!MEDIA_ISSET(media, ICE))
		return true;
	if (!media->ice_agent)
		return true;
	if (AGENT_ISSET(media->ice_agent, COMPLETED))
		return true;
	return false;
}
/* returns 1 if media has connectivity */
INLINE bool ice_is_usable(struct call_media *media) {
	if (!media)
		return true;
	if (!MEDIA_ISSET(media, ICE))
		return true;
	if (!media->ice_agent)
		return true;
	if (AGENT_ISSET(media->ice_agent, USABLE))
		return true;
	return false;
}
INLINE bool ice_is_restart(struct ice_agent *ag, struct stream_params *sp) {
	if (!ag || !sp)
		return false;
	struct call_media *media = ag->media;
	if (ag->ufrag[0].s && sp->ice_ufrag.s && str_cmp_str(&ag->ufrag[0], &sp->ice_ufrag))
		return true;
	else if (ag->pwd[0].s && sp->ice_pwd.s && str_cmp_str(&ag->pwd[0], &sp->ice_pwd))
		return true;
	else if (ag->logical_intf != media->logical_intf)
		return true;
	return false;

}
INLINE unsigned int ice_type_preference(enum ice_candidate_type type) {
	if (type >= __ICT_LAST)
		return 0;
	return ice_type_preferences[type];
}
/* local_pref starts with 0 */
INLINE uint32_t ice_priority_pref(unsigned int type_pref, unsigned int local_pref, unsigned int component) {
	return type_pref << 24 | (65535 - local_pref) << 8 | (256 - component);
}
INLINE uint32_t ice_priority(enum ice_candidate_type type, unsigned int local_pref, unsigned int component) {
	return ice_priority_pref(ice_type_preference(type), local_pref, component);
}
INLINE unsigned int ice_type_pref_from_prio(uint32_t prio) {
	return (prio & 0xff000000) >> 24;
}
INLINE unsigned int ice_local_pref_from_prio(uint32_t prio) {
	return 65535 - ((prio & 0xffff00) >> 8);
}
INLINE const char *ice_candidate_type_str(enum ice_candidate_type type) {
	if (type >= __ICT_LAST)
		return 0;
	return ice_type_strings[type];
}
INLINE int ice_ufrag_cmp(struct ice_agent *ag, const str *s) {
	if (!ag->ufrag[0].len) // fragment unknown
		return 0;
	return str_cmp_str0(&ag->ufrag[0], s);
}



#endif
