/*
 * (c) BayCom GmbH, http://www.baycom.de, info@baycom.de
 *
 * See the COPYING file for copyright information and
 * how to reach the author.
 *
 */

#undef DEBUG
#include "headers.h"

extern pthread_mutex_t lock;
extern recv_info_t receivers;

extern int mld_start;
static pthread_t mld_send_reports_thread;
static char iface[IFNAMSIZ];

static int find_mcg_in_mld_mcas (struct in6_addr *mld_mca, int len, struct in6_addr *mcg)
{
	int i;

	for (i = 0; i < len; i++) {
		if (!memcmp (mld_mca + i, mcg, sizeof (struct in6_addr))) {
			return 1;
		}
	}
	return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

typedef struct {
	struct in6_addr *mld_mca_add;
	struct in6_addr *mld_mca_drop;
} mld_reporter_context_t;

static void clean_mld_send_reports_thread(void *p)
{
	mld_reporter_context_t *c=(mld_reporter_context_t*)p;
	if(c->mld_mca_add) {
		free(c->mld_mca_add);
	}
	if(c->mld_mca_drop) {
		free(c->mld_mca_drop);
	}
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
static void *mld_send_reports (void *arg)
{
	recv_info_t *receivers = (recv_info_t *) arg;

	int grec_num_drop;
	int grec_num_add;
	pid_info_t *p;
	pid_info_t *ptmp;
	recv_info_t *r;
	int maxpids=128;
	mld_reporter_context_t c;
	memset(&c, 0, sizeof(mld_reporter_context_t));

	c.mld_mca_add=(struct in6_addr *)malloc(maxpids*sizeof(struct in6_addr));
	if (!c.mld_mca_add)
		err ("mld_send_reports: out of memory\n");
	c.mld_mca_drop=(struct in6_addr *)malloc(maxpids*sizeof(struct in6_addr));
	if (!c.mld_mca_drop)
		err ("mld_send_reports: out of memory\n");
	
	pthread_cleanup_push (clean_mld_send_reports_thread, &c);
	
	struct intnode *intn = int_find_name (iface);
	
	if( !c.mld_mca_add || !c.mld_mca_drop) {
		err ("Cannot get memory for add/drop list\n");
	}
	mld_start=1;	
	while (mld_start) {
		grec_num_drop=0;
		pthread_mutex_lock (&lock);
		
		int pids=count_all_pids(receivers);
		if(pids>maxpids) {
			maxpids=pids;
			c.mld_mca_add=(struct in6_addr *)realloc(c.mld_mca_add, pids*sizeof(struct in6_addr));
			if (!c.mld_mca_add)
				err ("mld_send_reports: out of memory\n");
			c.mld_mca_drop=(struct in6_addr *)realloc(c.mld_mca_drop, pids*sizeof(struct in6_addr));
			if (!c.mld_mca_drop)
				err ("mld_send_reports: out of memory\n");
		}

		//Send listener reports for all recently dropped MCGs
		DVBMC_LIST_FOR_EACH_ENTRY (r, &receivers->list, recv_info_t, list) {
			DVBMC_LIST_FOR_EACH_ENTRY_SAFE (p, ptmp, &r->slots.list, pid_info_t, list) {
				// prevent a somewhere running mcg on any device to be dropped and prevent to drop same mcg multiple times
				if (!p->run) {
					if ( p->dropped && !find_any_slot_by_mcg (receivers, &p->mcg) && !find_mcg_in_mld_mcas (c.mld_mca_drop, grec_num_drop, &p->mcg)) {
						memcpy (c.mld_mca_drop + grec_num_drop++, &p->mcg.s6_addr, sizeof (struct in6_addr));
						p->dropped--;
#ifdef DEBUG	
						char host[INET6_ADDRSTRLEN];
						inet_ntop (AF_INET6, p->mcg.s6_addr, (char *) host, INET6_ADDRSTRLEN);
						dbg ("DROP_GROUP %d %s\n", grec_num_drop, host);
#endif
					} else {
						dvbmc_list_remove(&p->list);
						free(p);
					}
				}
			}
		}
		if(grec_num_drop > maxpids) {
			err ("Wrong number of pids: %d>%d\n", grec_num_drop, maxpids);
		}
		grec_num_add=0;
		//Send listener reports for all current MCG in use
		DVBMC_LIST_FOR_EACH_ENTRY (r, &receivers->list, recv_info_t, list) {
			DVBMC_LIST_FOR_EACH_ENTRY (p, &r->slots.list, pid_info_t, list) {
				if (p->run && !find_mcg_in_mld_mcas (c.mld_mca_add, grec_num_add, &p->mcg)) {
					memcpy (c.mld_mca_add + grec_num_add++, p->mcg.s6_addr, sizeof (struct in6_addr));
#ifdef DEBUG
					char host[INET6_ADDRSTRLEN];
					inet_ntop (AF_INET6, &p->mcg.s6_addr, (char *) host, INET6_ADDRSTRLEN);
					dbg ("ADD_GROUP %d %s\n", grec_num_add, host);
#endif
				}
			}
		}

		if(grec_num_add > maxpids) {
			err ("Wrong number of pids: %d>%d\n", grec_num_add, maxpids);
		}

		pthread_mutex_unlock (&lock);

		if (intn && intn->mtu) {
			if (grec_num_drop) {
				send_mldv2_report (intn, grec_num_drop, c.mld_mca_drop, 0, NULL, MLD2_MODE_IS_INCLUDE);
			}
			if (grec_num_add) {
				send_mldv2_report (intn, grec_num_add, c.mld_mca_add, 0, NULL, MLD2_MODE_IS_EXCLUDE);
			}
		}
		usleep (REP_TIME);
		pthread_testcancel();
	}
	pthread_cleanup_pop (1);
	return NULL;
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

int mld_client_init (char *intf)
{
	if(intf) {
		strcpy(iface, intf);
	} else {
		iface[0]=0;
	}
	
	if (!strlen (iface)) {
		struct intnode *intn = int_find_first ();
		if (intn) {
			strcpy (iface, intn->name);
		} else {
			warn ("%s: Cannot find any usable network interface\n", __FUNCTION__);
			return -1;
		}
	}

#if ! (defined WIN32 || defined APPLE)
	g_conf->rawsocket = socket (PF_PACKET, SOCK_DGRAM, htons (ETH_P_ALL));
#endif
#ifdef WIN32
	g_conf->rawsocket = socket (PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
#endif
#ifdef APPLE
	g_conf->rawsocket = socket (PF_INET6, SOCK_RAW, IPPROTO_HOPOPTS);
#endif
	if (g_conf->rawsocket < 0) {
		warn ("%s: Cannot get a raw packet socket (on Linux apply: setcap cap_net_raw=pe <VDR binary>)\n", __FUNCTION__);
		return -1;
	}
#ifdef WIN32
	#define	IPV6_HDRINCL	2 
	DWORD n=1;
	if (setsockopt (g_conf->rawsocket, IPPROTO_IPV6, IPV6_HDRINCL, (char *)&n, sizeof (n)) < 0) {
		err ("setsockopt IPV6_HDRINCL");
	}
	int idx;
	if ((idx = if_nametoindex (iface))>0) {
		int ret=setsockopt (g_conf->rawsocket, IPPROTO_IPV6, IPV6_MULTICAST_IF, (_SOTYPE)&idx, sizeof (idx));
		if(ret<0) {
			warn("setsockopt for IPV6_MULTICAST_IF failed with %d error %s (%d)\n",ret,strerror (errno), errno);
		}
	}
#endif
	pthread_create (&mld_send_reports_thread, NULL, mld_send_reports, &receivers);
	return 0;
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

void mld_client_exit (void)
{
	if(g_conf) {
		mld_start=0;
		if(pthread_exist(mld_send_reports_thread)) {
			if(pthread_exist(mld_send_reports_thread) && !pthread_cancel (mld_send_reports_thread)) {
				pthread_join (mld_send_reports_thread, NULL);
			}
		}
#if 0
		struct intnode *intn;
		unsigned int i;
		for (i = 0; i < g_conf->maxinterfaces; i++) {
			intn = &g_conf->ints[i];
			if (intn->mtu == 0)
				continue;
			int_destroy (intn);
		}
#endif
		closesocket(g_conf->rawsocket);
	}
}
