/*
** CBLM v3
** $Id: thread.c,v 1.58 2011/12/05 00:33:43 sella Exp $
** Copyright (c) 2004-2007 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
** 
** Cheap Bastard Latency Monitor v3
*/

static const char rcsid[] = "$Id: thread.c,v 1.58 2011/12/05 00:33:43 sella Exp $";

#include "thread.h"
#include "transmit.h"
#include "receive.h"
#include "data.h"

#include <strings.h>
#include <errno.h>
#include <stdio.h>

#define NEW_SLICE_ALGO 1
//#define RECONNECT_FOR_QUERY_FAILURE 1

/* Global Variables */
struct packet_list_t *packets = NULL;
struct packet_list_t **pl_end = &packets;
uint32_t p_count = 0;
int update_prefs = 0;

/* External  Global Variables */
extern struct args_t args;
extern struct prefs_t prefs;
extern struct probe_t *probes;
extern int running;
extern pthread_mutex_t probe_mutex, packet_list_mutex, db_mutex, hash_mutex;
extern struct data_list_t *data_list;
extern struct data_list_t **dl_end;
extern uint32_t d_count;


void transmit_thread() {
	struct probe_t *curp;
	struct tos_t *curt;
	uint32_t remaining, pending, slices, slices_past, should_be_done, set = 0;
	uint64_t start, end, t;
	double slice_packets;
	struct timeval now;
	struct timespec timer;
	int s = 0, no_probes = 0;

	if (args.debug == 0 && fileno(stdin) != -1) fclose(stdin);
	if (args.debug == 0 && fileno(stdout) != -1) fclose(stdout);
	if (args.debug == 0 && fileno(stderr) != -1) fclose(stderr);

	if ((s = setup_socket()) == -1) {
		running = 0;

		goto exit;
	}

	/* Slice sleep time. */
	timer.tv_sec = 0;
	timer.tv_nsec = 1000000 * 1000 / SLICES_PER_SEC;

	while (running) {
		/* Update probe list. */
		read_probes();

		/* If there are no probes, wait and try again. */
		if (probes == NULL) {
			no_probes = 1;
			syslog(LOG_INFO, "No remote probe(s) available. Retry in %lu seconds.", NOPROBES_WAIT);

			t = time(NULL) + NOPROBES_WAIT;
			while (running && t >= time(NULL)) {
				usleep(250000); /* 1/4 sec */
			};

			continue;
		}

		if (no_probes == 1) {
			no_probes = 0;
			syslog(LOG_INFO, "Remote probe(s) found. Transmit cycle beginning.");
		}

		pthread_mutex_lock(&probe_mutex);

		/* Initialize data for transmit cycle */
		gettimeofday(&now, NULL);
		start = tvtousec(now);
		end = start + CYCLE_USEC;
		slice_packets = (double) prefs.rate / CYCLE_SECONDS / SLICES_PER_SEC;
		remaining = prefs.rate;
		slices = 0;

		if (args.debug >= DEBUG_INTERNAL) {
			syslog(LOG_INFO, "Transmit cycle (%lu sec) sending %0.5f pkts/slice over %lu slices.", CYCLE_SECONDS, slice_packets, SLICES_PER_CYCLE);
		}

		if (args.debug >= DEBUG_ERROR && (probes == NULL || prefs.tos == NULL)) {
			syslog(DEBUG_ERROR, "No probes and/or ToS found. Nothing will be sent. probes == %08x, prefs.tos == %08x", probes, prefs.tos);
		}

		/* Transmit cycle loop. */
		while (remaining > 0 && running) {
			/* How many should we transmit this cycle? */
			slices_past = (CYCLE_USEC - (uint32_t) (end - tvtousec(now))) / (1000000 / SLICES_PER_SEC);
			should_be_done = (slices_past * slice_packets);
			pending = should_be_done - (prefs.rate - remaining);
			pending = (pending > remaining) ? remaining : pending; /* pending must be <= remaining */

			assert(pending <= remaining);

			if (pending > 0) {
				/* Loop for probes. */
				for (curp = probes; curp != NULL; curp = curp->next) {
					/* Loop though ToS. */
					for (curt = prefs.tos; curt != NULL; curt = curt->next) {
						transmit_packet(s, &(curp->ip), pending, curt, set);
					}
				}

				/* Subtract what we transmitted. */
				remaining -= pending;
			}

			/* Update now to see if the cycle is complete. */
			gettimeofday(&now, NULL);

			/* Sleep */
			nanosleep(&timer, NULL);

			slices++;
		}

		if (args.debug >= DEBUG_INTERNAL && running) {
			syslog(LOG_INFO, "Transmit cycle complete in %0.4f sec using %lu of %lu slices.", (tvtousec(now) - start) / 1000000.0, slices, SLICES_PER_CYCLE);
		}

		pthread_mutex_unlock(&probe_mutex);

		/* Increament to the next set. */
		set++;

		/* Update preferences. */
		if (running && prefs.last != last_prefs()) {
			syslog(LOG_INFO, "Preferences have been updated. Pausing transmit and migrating to new configuration.");

			snmptrap_database_reconfig();

			/* Prevent remote probes from writing results for us. */
			unregister_probe();

			/* Pull in new preferences */
			read_prefs(1);

			if (args.debug >= DEBUG_NOTICE) {
				display_prefs(0);
			}

			/* Notify other threads of new preferences. */
			update_prefs = 1;

			/* Wait for 1 cycle to allow remote probes to restart receive thread. */
			usleep(CYCLE_SECONDS * 1000000);
			
			/* Resetup transmit socket. Options may have changed. */
			close(s);
			if ((s = setup_socket()) == -1) {
				running = 0;

				goto exit;
			}

			syslog(LOG_INFO, "Preferences update completed. Resuming transmit.");
		}
	}

exit:
	if (s > 0) {
		close(s);
	}

	if (args.debug >= DEBUG_NOTICE) {
		syslog(LOG_INFO, "transmit_thread has shutdown");
	}

	pthread_exit(NULL);
}

void receive_thread() {
	int s, n, c, fd, optval;
	pid_t pid;
	fd_set fdset, fds;
	struct timeval timeout;

	if (args.debug == 0 && fileno(stdin) != -1) fclose(stdin);
	if (args.debug == 0 && fileno(stdout) != -1) fclose(stdout);
	if (args.debug == 0 && fileno(stderr) != -1) fclose(stderr);

	/* Bind our socket */
	if ((s = bind_port(&fdset)) == -1) {
		running = 0;

		goto exit;
	}

	n = s + 1;
	while (running) {
		/* Copy our fdset, select screws with it. */
		FD_ZERO(&fds);
		bcopy(&fdset, &fds, sizeof(fd_set));

		/* Use a 3/4 sec timeout. */
		timeout.tv_sec = 0;
		timeout.tv_usec = 750000;

		/* Wait for a connection. */
		c = select(n, &fds, 0, 0, &timeout);
		if (c == -1) {
			syslog(LOG_CRIT, "select() %s", strerror(errno));
			return;
		} else if (c > 0) {
			/* Read incoming packets */
			receive_packet(s);
		}

		if (update_prefs) {
			/* Clear update_prefs flag */
			update_prefs = 0;

			/* Re-setup receive socket. Options may have changed. */
			close(s);

			if ((s = bind_port(&fdset)) == -1) {
				running = 0;

				goto exit;
			}

			n = s + 1;

			/* Register probe allowing remote probes to send to us. */
			register_probe();
		}
	}

	/* Close ports */
	if (s > 0) {
		close(s);
	}

	if (args.debug >= DEBUG_NOTICE) {
		syslog(LOG_INFO, "receive_thread has shutdown");
	}

exit:
	pthread_exit(NULL);
}

void data_thread() {
	struct timespec timer;
	struct packet_t *packet;
	struct packet_list_t *pl_ptr;
	struct data_t *data;
	struct data_list_t *dl_ptr;
	uint64_t probe_tm = 0, reconnect_tm = 0;
	uint64_t p_max;

	if (args.debug == 0 && fileno(stdin) != -1) fclose(stdin);
	if (args.debug == 0 && fileno(stdout) != -1) fclose(stdout);
	if (args.debug == 0 && fileno(stderr) != -1) fclose(stderr);

	/* Initialize our hash */
	pthread_mutex_lock(&hash_mutex);
	hash_init();
	pthread_mutex_unlock(&hash_mutex);

	/* Slice sleep time. */
	timer.tv_sec = 0;
	timer.tv_nsec = 1000000 * 1000 / SLICES_PER_SEC;

top:
	while (running) {
		/* Verify connection to database and handle reconnection */
		pthread_mutex_lock(&db_mutex);
		if (db_check() != 0) { /* Not connected */
			if (db_isconnected() == 1) { /* Returns 1 until db_disconnect() is called. */
				syslog(LOG_WARNING, "Lost connection to database. Attemping to reconnect...");

				snmptrap_database_down();

				db_disconnect();
			}

			/* Reconnect if timer has expired. */
			if (time(NULL) > reconnect_tm) {
				reconnect_tm = time(NULL) + DB_RECON_WAIT;

				if (db_connect() != 0) {
					if (args.debug >= DEBUG_ERROR) {
						syslog(LOG_ERR, "Failed to reconnect to database. Retry in %lu sec.", DB_RECON_WAIT);
					}
				} else {
					syslog(LOG_WARNING, "Reconnected to database.");

					snmptrap_database_up();

					probe_tm = 0; /* Force probe to reregister immediately. */
				}
			}
		}
		pthread_mutex_unlock(&db_mutex);

		/* Refresh registration of probe */
		if (time(NULL) > probe_tm && !update_prefs) { /* Connected */
			probe_tm = time(NULL) + (prefs.maxage / 3);
			if (register_probe() != 0) {
#ifdef RECONNECT_FOR_QUERY_FAILURE
				syslog(LOG_WARNING, "Failed to register probe. Reconnecting to database...");

				snmptrap_database_down();

				db_disconnect();

				goto top;
#else
				if (args.debug >= DEBUG_ERROR) {
					syslog(LOG_ERR, "Failed to register probe.");
				}
#endif
			}
		}

		/* Process received packets */
		p_max = (p_count + 1) * 3; /* Max packets to be processed per loop */
		while (packets && p_max-- > 0) { /* Loop though packets until exhaustion or p_max */
			/* Retrieve packet from the packet_list. */
			pthread_mutex_lock(&packet_list_mutex);

			/* Grab reference to current packet. */
			pl_ptr = packets;
			packet = &(pl_ptr->packet);

			/* Move to the next packet. */
			packets = packets->next;

			if (packets == NULL) {
				pl_end = &packets;
			}

			/* Decrease packet count by 1 */
			p_count--;

			pthread_mutex_unlock(&packet_list_mutex);

			/* Process the packet. */
			process_packet(packet);

			/* Free the node. */
			free(pl_ptr);
			pl_ptr = NULL;
		}

		/* Write pending data */
		while (data_list) {
			/* Grab reference to current data. */
			dl_ptr = data_list;
			data = &(dl_ptr->data);

			/* Write the data. */
			if (write_data(data) != 0) {
#ifdef RECONNECT_FOR_QUERY_FAILURE
				syslog(LOG_WARNING, "Failed to register probe. Reconnecting to database...");

				snmptrap_database_down();

				db_disconnect();

				goto top;
#else
				break;
#endif
			}

			/* Decrease data count by 1 */
			d_count--;

			/* Move to the next data. */
			if ((data_list = data_list->next) == NULL) {
				dl_end = &data_list;
			}

			/* Free the node. */
			free(dl_ptr);
			dl_ptr = NULL;
		}

		/* Summarize if needed */
		summarize_data();

		/* Check snmptrap states */
#if 0
		while (snmptrap_list) {
			/* Grab reference to first node. */
			sl_ptr = snmptrap_list;
			data = &(sl_ptr->data);

			/* Write the data. */
			if (write_data(data) != 0) {
#ifdef RECONNECT_FOR_QUERY_FAILURE
				syslog(LOG_WARNING, "Failed to register probe. Reconnecting to database...");

				snmptrap_database_down();

				db_disconnect();

				goto top;
#else
				break;
#endif
			}

			/* Move to the next node. */
			if ((snmptrap_list = snmptrap_list->next) == NULL) {
				sl_end = &snmptrap_list;
			}

			/* Free the node. */
			free(sl_ptr);
			sl_ptr = NULL;
		}
#endif

		/* Sleep */
		if (p_count < 1) {
			nanosleep(&timer, NULL);
		}
	}

	/* Unregister probe */
	unregister_probe();

	/* Free our hash */
	pthread_mutex_lock(&hash_mutex);
	hash_free();
	pthread_mutex_unlock(&hash_mutex);

	if (args.debug >= DEBUG_NOTICE) {
		syslog(LOG_INFO, "data_thread has shutdown");
	}

	pthread_exit(NULL);
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim: noet ts=3 sw=3
*/
