/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of oRTP
 * (see https://gitlab.linphone.org/BC/public/ortp).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "scheduler.h"
#include <ortp/ortp.h>
#include <ortp/sessionset.h>

/**
 * Allocates and initialize a new empty session set.
 *
 * @return the empty session set.
 **/
SessionSet *session_set_new() {
	SessionSet *set = (SessionSet *)ortp_malloc(sizeof(SessionSet));
	session_set_init(set);
	return set;
}

/**
 * Destroys a session set.
 *
 **/

void session_set_destroy(SessionSet *set) {
	ortp_free(set);
}

int count_power_items_simple(uint32_t v) {
	int c = 0, j;
	for (j = 0; j < 32; j++) {
		if (((v) >> j) & 1) {
			++c;
		}
	}
	return c;
}

int count_power_items_fast(uint32_t v) {
	int c = 0;
	while (v) {
		c += (v & 1);
		v >>= 1;
	}
	return c;
}

int session_set_and(SessionSet *sched_set, int maxs, SessionSet *user_set, SessionSet *result_set) {
	uint32_t *mask1, *mask2, *mask3;
	int i = 0;
	int ret = 0;
	mask1 = (uint32_t *)(void *)&sched_set->rtpset;
	mask2 = (uint32_t *)(void *)&user_set->rtpset;
	mask3 = (uint32_t *)(void *)&result_set->rtpset;
	while (i < maxs + 1) {
		*mask3 = (*mask1) & (*mask2); /* computes the AND between the two masks*/
		/* and unset the sessions that have been found from the sched_set */
		*mask1 = (*mask1) & (~(*mask3));
		ret += count_power_items_fast(*mask3);
		i += 32;
		mask1++;
		mask2++;
		mask3++;
	}
	// printf("session_set_and: ret=%i\n",ret);
	return ret;
}

/**
 *	This function performs similarly as libc select() function, but performs on RtpSession
 *	instead of file descriptors.
 *	session_set_select() suspends the calling process until some events arrive on one of the
 *	three sets passed in argument. Two of the sets can be NULL.
 *	The first set \a recvs is interpreted as a set of RtpSession waiting for receive events:
 *	a new buffer (perhaps empty) is availlable on one or more sessions of the set, or the last
 *	receive operation with rtp_session_recv_with_ts() would have finished if it were in
 *	blocking mode.
 *	The second set is interpreted as a set of RtpSession waiting for send events, i.e. the last
 *	rtp_session_send_with_ts() call on a session would have finished if it were in blocking mode.
 *
 *	When some events arrived on some of sets, then the function returns and sets are changed
 *	to indicate the sessions where events happened.
 *	Sessions can be added to sets using session_set_set(), a session has to be tested to be
 *	part of a set using session_set_is_set().
 *
 * @param recvs a set of rtp sessions to be watched for read events
 * @param sends a set of rtp sessions to be watched for write events
 * @param errors a set of rtp sessions to be watched for errors
 * @return: the number of sessions on which the selected events happened.
 **/
int session_set_select(SessionSet *recvs, SessionSet *sends, SessionSet *errors) {
	int ret = 0, bits;
	SessionSet temp;
	RtpScheduler *sched = ortp_get_scheduler();

	/*lock the scheduler to not read the masks while they are being modified by the scheduler*/
	rtp_scheduler_lock(sched);

	while (1) {
		/* computes the SessionSet intersection (in the other words mask intersection) between
		the mask given by the user and scheduler masks */
		if (recvs != NULL) {
			session_set_init(&temp);
			bits = session_set_and(&sched->r_sessions, sched->all_max, recvs, &temp);
			ret += bits;
			/* copy the result set in the given user set (might be empty) */
			if (ret > 0) session_set_copy(recvs, &temp);
		}
		if (sends != NULL) {
			session_set_init(&temp);
			bits = session_set_and(&sched->w_sessions, sched->all_max, sends, &temp);
			ret += bits;
			if (ret > 0) {
				/* copy the result set in the given user set (might be empty)*/
				session_set_copy(sends, &temp);
			}
		}
		if (errors != NULL) {
			session_set_init(&temp);
			bits = session_set_and(&sched->e_sessions, sched->all_max, errors, &temp);
			ret += bits;
			if (ret > 0) {
				/* copy the result set in the given user set */
				session_set_copy(errors, &temp);
			}
		}
		if (ret > 0) {
			/* there are set file descriptors, return immediately */
			// printf("There are %i sessions set, returning.\n",ret);
			rtp_scheduler_unlock(sched);
			return ret;
		}
		// printf("There are %i sessions set.\n",ret);
		/* else we wait until the next loop of the scheduler*/
		ortp_cond_wait(&sched->unblock_select_cond, &sched->lock);
	}

	return -1;
}

int session_set_timedselect(SessionSet *recvs, SessionSet *sends, SessionSet *errors, struct timeval *timeout) {
	int ret = 0, bits;
	int remainingTime; // duration in ms
	SessionSet temp;
	RtpScheduler *sched;
	if (timeout == NULL) return session_set_select(recvs, sends, errors);
	sched = ortp_get_scheduler();
	remainingTime = timeout->tv_usec / 1000 + timeout->tv_sec * 1000;

	/*lock the scheduler to not read the masks while they are being modified by the scheduler*/
	rtp_scheduler_lock(sched);

	do {
		/* computes the SessionSet intersection (in the other words mask intersection) between
		the mask given by the user and scheduler masks */
		if (recvs != NULL) {
			session_set_init(&temp);
			bits = session_set_and(&sched->r_sessions, sched->all_max, recvs, &temp);
			ret += bits;
			/* copy the result set in the given user set (might be empty) */
			if (ret > 0) session_set_copy(recvs, &temp);
		}
		if (sends != NULL) {
			session_set_init(&temp);
			bits = session_set_and(&sched->w_sessions, sched->all_max, sends, &temp);
			ret += bits;
			if (ret > 0) {
				/* copy the result set in the given user set (might be empty)*/
				session_set_copy(sends, &temp);
			}
		}
		if (errors != NULL) {
			session_set_init(&temp);
			bits = session_set_and(&sched->e_sessions, sched->all_max, errors, &temp);
			ret += bits;
			if (ret > 0) {
				/* copy the result set in the given user set */
				session_set_copy(errors, &temp);
			}
		}
		if (ret > 0) {
			/* there are set file descriptors, return immediately */
			// printf("There are %i sessions set, returning.\n",ret);
			rtp_scheduler_unlock(sched);
			return ret;
		}
		// printf("There are %i sessions set.\n",ret);
		/* else we wait until the next loop of the scheduler*/
		ortp_cond_wait(&sched->unblock_select_cond, &sched->lock);
		remainingTime -= sched->timer_inc;
	} while (remainingTime > 0);
	rtp_scheduler_unlock(sched);

	return -1;
}
