/*
	**
	** summary-sort.c 
	**
	** Sorts hosts and peers tables into linked list
	**
	** Copyright 1998 Damien Miller <dmiller@ilogic.com.au>
	**
	** This software is licensed under the terms of the GNU General 
	** Public License (GPL). Please see the file COPYING for details.
	** 
	** $Id: summary-sort.c,v 1.2 1998/12/02 01:11:18 dmiller Exp $
	**
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>

#include <glib.h>

#include "summary-structures.h"
#include "util.h"
#include "summary-sort.h"

/* Function used to compare peers */
typedef int (*peer_compare_func_t)(peer_t *peer1, peer_t *peer2);

/* Function used to compare hosts */
typedef int (*host_compare_func_t)(host_t *host1, host_t *host2);

/* Used in building of host list */
typedef struct
{
	host_compare_func_t	compare;
	int						reverse;
	host_list_t				root;
} host_list_build_data_t;

/* Prototypes */
host_compare_func_t	choose_host_comparison_function(int sort_mode);
peer_compare_func_t	choose_peer_comparison_function(int sort_mode);
void						insert_host(u_int8_t *dummy, host_t *host, host_list_build_data_t *d);
void						insert_peer(peer_t *peer, peer_list_t *root, peer_compare_func_t compare, int peer_reverse);

/* Comparision function prototypes */
int	host_compare_none(host_t *host1, host_t *host2);
int	host_compare_bytes(host_t *host1, host_t *host2);
int	host_compare_bytes_sent(host_t *host1, host_t *host2);
int	host_compare_bytes_received(host_t *host1, host_t *host2);
int	host_compare_packets(host_t *host1, host_t *host2);
int	host_compare_packets_sent(host_t *host1, host_t *host2);
int	host_compare_packets_received(host_t *host1, host_t *host2);
int	host_compare_connections(host_t *host1, host_t *host2);
int	host_compare_connections_sent(host_t *host1, host_t *host2);
int	host_compare_connections_received(host_t *host1, host_t *host2);
int	host_compare_peers(host_t *host1, host_t *host2);
int	host_compare_hostname(host_t *host1, host_t *host2);
int	host_compare_ip_addr(host_t *host1, host_t *host2);
int	host_compare_first_seen(host_t *host1, host_t *host2);
int	host_compare_last_seen(host_t *host1, host_t *host2);
int	host_compare_random(host_t *host1, host_t *host2);

int	peer_compare_none(peer_t *peer1, peer_t *peer2);
int	peer_compare_bytes(peer_t *peer1, peer_t *peer2);
int	peer_compare_packets(peer_t *peer1, peer_t *peer2);
int	peer_compare_connections(peer_t *peer1, peer_t *peer2);
int	peer_compare_first_seen(peer_t *peer1, peer_t *peer2);
int	peer_compare_last_seen(peer_t *peer1, peer_t *peer2);
int	peer_compare_random(peer_t *peer1, peer_t *peer2);

/* Parses textual host sort key into numeric identifier */
int parse_host_sort_mode(const char *sort_mode)
{
	/* Make sure we got some input */
	if ((sort_mode == NULL) || (sort_mode[0] == '\0'))
		return(-1);
	
	/* Check for single character options */
	if (sort_mode[1] == '\0')
	{
		switch (sort_mode[0])
		{
			case 'N':
				return(SORT_NONE);
			case 'b':
				return(SORT_BYTES);
			case 's':
				return(SORT_BYTES_SENT);
			case 'r':
				return(SORT_BYTES_RECEIVED);
			case 'p':
				return(SORT_PACKETS);
			case 'S':
				return(SORT_PACKETS_SENT);
			case 'R':
				return(SORT_PACKETS_RECEIVED);
			case 'C':
				return(SORT_CONNECTS);
			case 'c':
				return(SORT_CONNECTS_SENT);
			case 'l':
				return(SORT_CONNECTS_RECEIVED);
			case 'P':
				return(SORT_PEERS);
			case 'h':
				return(SORT_HOSTNAME);
			case 'I':
				return(SORT_IP_ADDR);
			case 'F':
				return(SORT_FIRST_SEEN);
			case 'L':
				return(SORT_LAST_SEEN);
			case 'X':
				return(SORT_RANDOM);
			
			default:
				return(-1);
		}
	}
	
	/* Otherwise, look for textual sort key */
	if (strcasecmp(sort_mode, "none") == 0)
		return(SORT_NONE);
	else if (strcasecmp(sort_mode, "bytes") == 0)
		return(SORT_BYTES);
	else if (strcasecmp(sort_mode, "bsent") == 0)
		return(SORT_BYTES_SENT);
	else if (strcasecmp(sort_mode, "breceived") == 0)
		return(SORT_BYTES_RECEIVED);
	else if (strcasecmp(sort_mode, "packets") == 0)
		return(SORT_PACKETS);
	else if (strcasecmp(sort_mode, "psent") == 0)
		return(SORT_PACKETS_SENT);
	else if (strcasecmp(sort_mode, "preceived") == 0)
		return(SORT_PACKETS_RECEIVED);
	else if (strcasecmp(sort_mode, "connects") == 0)
		return(SORT_CONNECTS);
	else if (strcasecmp(sort_mode, "csent") == 0)
		return(SORT_CONNECTS_SENT);
	else if (strcasecmp(sort_mode, "creceived") == 0)
		return(SORT_CONNECTS_RECEIVED);
	else if (strcasecmp(sort_mode, "peers") == 0)
		return(SORT_PEERS);
	else if (strcasecmp(sort_mode, "hostname") == 0)
		return(SORT_HOSTNAME);
	else if (strcasecmp(sort_mode, "ipaddr") == 0)
		return(SORT_IP_ADDR);
	else if (strcasecmp(sort_mode, "firstseen") == 0)
		return(SORT_FIRST_SEEN);
	else if (strcasecmp(sort_mode, "lastseen") == 0)
		return(SORT_LAST_SEEN);
	else if (strcasecmp(sort_mode, "lastseen") == 0)
		return(SORT_RANDOM);
	else
		return(-1);
}

/* Parses textual host sort key into numeric identifier */
int parse_peer_sort_mode(const char *sort_mode)
{
	/* Make sure we got some input */
	if ((sort_mode == NULL) || (sort_mode[0] == '\0'))
		return(-1);
	
	/* Check for single character options */
	if (sort_mode[1] == '\0')
	{
		switch (sort_mode[0])
		{
			case 'N':
				return(SORT_NONE);
			case 'b':
				return(SORT_BYTES);
			case 'p':
				return(SORT_PACKETS);
			case 'C':
				return(SORT_CONNECTS);
			case 'F':
				return(SORT_FIRST_SEEN);
			case 'L':
				return(SORT_LAST_SEEN);
			case 'X':
				return(SORT_RANDOM);
			
			default:
				return(-1);
		}
	}
	
	/* Otherwise, look for textual sort key */
	if (strcasecmp(sort_mode, "none") == 0)
		return(SORT_NONE);
	else if (strcasecmp(sort_mode, "bytes") == 0)
		return(SORT_BYTES);
	else if (strcasecmp(sort_mode, "packets") == 0)
		return(SORT_PACKETS);
	else if (strcasecmp(sort_mode, "connects") == 0)
		return(SORT_CONNECTS);
	else if (strcasecmp(sort_mode, "firstseen") == 0)
		return(SORT_FIRST_SEEN);
	else if (strcasecmp(sort_mode, "lastseen") == 0)
		return(SORT_LAST_SEEN);
	else if (strcasecmp(sort_mode, "random") == 0)
		return(SORT_RANDOM);
	else
		return(-1);
}

/* Sorts hosts table into linked list */
host_list_t	*sort_hosts(GHashTable *hosts, unsigned int max_hosts, int host_sort_mode, int host_reverse)
{
	host_list_build_data_t	d;
	host_list_t			*p;
	host_list_t			*last_p;
	int					c;
	
	/* Build sorted linked list of hosts */
	d.compare = choose_host_comparison_function(host_sort_mode);
	d.reverse = host_reverse;
	d.root.next = NULL;
	g_hash_table_foreach(hosts, (GHFunc)insert_host, (gpointer)&d);
	
	/* Trim linked list to required length */
	if (max_hosts != 0)
	{
		c = 0;
		p = d.root.next;
		last_p = &(d.root);

		/* Find first entry that we want to delete */
		while ((c < max_hosts) && (p != NULL))
		{
			last_p = p;
			p = p->next;
			c++;
		}

		/* Terminate list */
		last_p->next = NULL;

		/* Discard until end of list */
		while (p != NULL)
		{
			last_p = p;
			p = p->next;
			free(last_p);
		}
	}
					
	return(d.root.next);
}

/* Sort peers list into linked list */
peer_list_t	*sort_peers(u_int64_t *peer_keys, unsigned int n_peers, GHashTable *peers, unsigned int max_peers, int peer_sort_mode, int peer_reverse)
{
	peer_list_t				root;
	peer_list_t				*trim_p;
	peer_list_t				*last_p;
	peer_t					*peer;
	peer_t					*src_peer;
	peer_t					*dst_peer;
	int						c;
	peer_compare_func_t	compare;

	compare = choose_peer_comparison_function(peer_sort_mode);
	root.next = NULL;

	for(c = 0; c < n_peers; c++)
	{
		peer = g_hash_table_lookup(peers, (gpointer)&(peer_keys[c]));
		if (peer == NULL)
		{
			fprintf(stderr, "Missing peer record for key %Li\n\n", peer_keys[c]);
			raise(SIGSEGV);
		}
	
		src_peer = util_zmalloc(sizeof(*src_peer));
		src_peer->host1 = peer->host1;
		src_peer->host2 = peer->host2;
		src_peer->bytes_sent_1_to_2 = peer->bytes_sent_1_to_2;
		src_peer->packets_sent_1_to_2 = peer->packets_sent_1_to_2;
		src_peer->connections_sent_1_to_2 = peer->connections_sent_1_to_2;
		src_peer->first_seen = peer->first_seen;
		src_peer->last_seen = peer->last_seen;
		
		dst_peer = util_zmalloc(sizeof(*dst_peer));
		dst_peer->host1 = peer->host2;
		dst_peer->host2 = peer->host1;
		dst_peer->bytes_sent_1_to_2 = peer->bytes_sent_2_to_1;
		dst_peer->packets_sent_1_to_2 = peer->packets_sent_2_to_1;
		dst_peer->connections_sent_1_to_2 = peer->connections_sent_2_to_1;
		dst_peer->first_seen = peer->first_seen;
		dst_peer->last_seen = peer->last_seen;
		
		if (src_peer->bytes_sent_1_to_2)
			insert_peer(src_peer, &root, compare, peer_reverse);

		if (dst_peer->bytes_sent_1_to_2)
			insert_peer(dst_peer, &root, compare, peer_reverse);
	}

	/* Trim linked list to required length */
	if (max_peers != 0)
	{
		c = 0;
		trim_p = root.next;
		last_p = &(root);

		/* Find first entry that we want to delete */
		while ((c < max_peers) && (trim_p != NULL))
		{
			last_p = trim_p;
			trim_p = trim_p->next;
			c++;
		}

		/* Terminate list */
		last_p->next = NULL;

		/* Discard until end of list */
		while (trim_p != NULL)
		{
			last_p = trim_p;
			trim_p = trim_p->next;
			free(last_p);
		}
	}
					
	return(root.next);
}

void insert_peer(peer_t *peer, peer_list_t *root, peer_compare_func_t compare, int peer_reverse)
{
	peer_list_t	*pl;
	peer_list_t	*last_pl;
	
	/* Initialise list pointers */
	pl = root->next;
	last_pl = root;
	
	/* Find correct position in list */	
	if (peer_reverse)
	{
		while ((pl != NULL) && (compare(peer, pl->peer) > 0))
		{
			last_pl = pl;
			pl = pl->next;
		}
	} else
	{
		while ((pl != NULL) && (compare(peer, pl->peer) <= 0))
		{
			last_pl = pl;
			pl = pl->next;
		}
	}
	
	/* last_pl is the peer to insert after */
	last_pl->next = util_zmalloc(sizeof(*(last_pl->next)));
	last_pl->next->next = pl;
	last_pl->next->peer = peer;
}

void insert_host(u_int8_t *dummy, host_t *host, host_list_build_data_t *d)
{
	host_list_t	*hl;
	host_list_t	*last_hl;
	
	/* Initialise list pointers */
	hl = d->root.next;
	last_hl = &(d->root);
	
	/* Find correct position in list */	
	if (d->reverse)
	{
		while ((hl != NULL) && (d->compare(host, hl->host) > 0))
		{
			last_hl = hl;
			hl = hl->next;
		}
	} else
	{
		while ((hl != NULL) && (d->compare(host, hl->host) <= 0))
		{
			last_hl = hl;
			hl = hl->next;
		}
	}
	
	/* last_hl is the host to insert after */
	last_hl->next = util_zmalloc(sizeof(*(last_hl->next)));
	last_hl->next->next = hl;
	last_hl->next->host = host;
}

host_list_t	*resort_host_list(host_list_t *hl, int host_sort_mode, int host_reverse)
{
	host_list_build_data_t	d;
	host_list_t					*p;
	host_list_t					*last_p;
	
	/* We recycle the hash-table iterator function for resorting */
	/* So we have to set up the build data structure */
	d.compare = choose_host_comparison_function(host_sort_mode);
	d.reverse = host_reverse;
	d.root.next = NULL;
	
	/* For each host in the list */
	p = hl;
	while (p != NULL)
	{
		/* Place it in the new list */
		insert_host(NULL, p->host, &d);

		/* Move on to the next */
		last_p = p;
		p = p->next;
		
		/* Zap the old */
		free(last_p);
	}
					
	return(d.root.next);
}

host_compare_func_t choose_host_comparison_function(int sort_mode)
{
	switch(sort_mode)
	{
		case SORT_NONE:
			return(host_compare_none);
		case SORT_BYTES:
			return(host_compare_bytes);
		case SORT_BYTES_SENT:
			return(host_compare_bytes_sent);
		case SORT_BYTES_RECEIVED:
			return(host_compare_bytes_received);
		case SORT_PACKETS:
			return(host_compare_packets);
		case SORT_PACKETS_SENT:
			return(host_compare_packets_sent);
		case SORT_PACKETS_RECEIVED:
			return(host_compare_packets_received);
		case SORT_CONNECTS:
			return(host_compare_connections);
		case SORT_CONNECTS_SENT:
			return(host_compare_connections_sent);
		case SORT_CONNECTS_RECEIVED:
			return(host_compare_connections_received);
		case SORT_PEERS:
			return(host_compare_peers);
		case SORT_HOSTNAME:
			return(host_compare_hostname);
		case SORT_IP_ADDR:
			return(host_compare_ip_addr);
		case SORT_FIRST_SEEN:
			return(host_compare_first_seen);
		case SORT_LAST_SEEN:
			return(host_compare_last_seen);
		case SORT_RANDOM:
			return(host_compare_random);
		default:
			abort();
	}
}

int host_compare_none(host_t *host1, host_t *host2)
{
	/* Always insert at head of list */
	return(1);
}

int host_compare_bytes(host_t *host1, host_t *host2)
{
	u_int64_t	host1_bytes;
	u_int64_t	host2_bytes;
	
	host1_bytes = host1->bytes_sent + host1->bytes_received;
	host2_bytes = host2->bytes_sent + host2->bytes_received;

	if (host1_bytes == host2_bytes)
		return(0);
	else if (host1_bytes > host2_bytes)
		return(1);
	else
		return(-1);
}

int host_compare_bytes_sent(host_t *host1, host_t *host2)
{
	if (host1->bytes_sent == host2->bytes_sent)
		return(0);
	else if (host1->bytes_sent > host2->bytes_sent)
		return(1);
	else
		return(-1);
}

int host_compare_bytes_received(host_t *host1, host_t *host2)
{
	if (host1->bytes_received == host2->bytes_received)
		return(0);
	else if (host1->bytes_received > host2->bytes_received)
		return(1);
	else
		return(-1);
}

int host_compare_packets(host_t *host1, host_t *host2)
{
	u_int64_t	host1_packets;
	u_int64_t	host2_packets;
	
	host1_packets = host1->packets_sent + host1->packets_received;
	host2_packets = host2->packets_sent + host2->packets_received;

	if (host1_packets == host2_packets)
		return(0);
	else if (host1_packets > host2_packets)
		return(1);
	else
		return(-1);
}

int host_compare_packets_sent(host_t *host1, host_t *host2)
{
	if (host1->packets_sent == host2->packets_sent)
		return(0);
	else if (host1->packets_sent > host2->packets_sent)
		return(1);
	else
		return(-1);
}

int host_compare_packets_received(host_t *host1, host_t *host2)
{
	if (host1->packets_received == host2->packets_received)
		return(0);
	else if (host1->packets_received > host2->packets_received)
		return(1);
	else
		return(-1);
}

int host_compare_connections(host_t *host1, host_t *host2)
{
	u_int64_t	host1_connections;
	u_int64_t	host2_connections;
	
	host1_connections = host1->connections_sent + host1->connections_received;
	host2_connections = host2->connections_sent + host2->connections_received;

	if (host1_connections == host2_connections)
		return(0);
	else if (host1_connections > host2_connections)
		return(1);
	else
		return(-1);
}

int host_compare_connections_sent(host_t *host1, host_t *host2)
{
	if (host1->connections_sent == host2->connections_sent)
		return(0);
	else if (host1->connections_sent > host2->connections_sent)
		return(1);
	else
		return(-1);
}

int host_compare_connections_received(host_t *host1, host_t *host2)
{
	if (host1->connections_received == host2->connections_received)
		return(0);
	else if (host1->connections_received > host2->connections_received)
		return(1);
	else
		return(-1);
}

int host_compare_peers(host_t *host1, host_t *host2)
{
	if (host1->n_peers == host2->n_peers)
		return(0);
	else if (host1->n_peers > host2->n_peers)
		return(1);
	else
		return(-1);
}

int host_compare_hostname(host_t *host1, host_t *host2)
{
	/* Use IP addresses if no hostnames found */
	if ((host1->hostname == NULL) && (host2->hostname == NULL))
		return(host_compare_ip_addr(host1, host2));
	
	/* Hosts without names come last */
	if (host1->hostname == NULL)
		return(-1);
	if (host2->hostname == NULL)
		return(1);

	/* If both hosts have names, compare them */		
	return(-strcasecmp(host1->hostname, host1->hostname));
}

int host_compare_ip_addr(host_t *host1, host_t *host2)
{
	if (host1->ip_addr == host2->ip_addr)
		return(0);
	else if (host1->ip_addr > host2->ip_addr)
		return(1);
	else
		return(-1);
}

int host_compare_first_seen(host_t *host1, host_t *host2)
{
	if (host1->first_seen == host2->first_seen)
		return(0);
	else if (host1->first_seen > host2->first_seen)
		return(1);
	else
		return(-1);
}

int host_compare_last_seen(host_t *host1, host_t *host2)
{
	if (host1->last_seen == host2->last_seen)
		return(0);
	else if (host1->last_seen > host2->last_seen)
		return(1);
	else
		return(-1);
}

int host_compare_random(host_t *host1, host_t *host2)
{
	if (random() < (RAND_MAX / 2))
		return(1);
	else
		return(-1);
}

peer_compare_func_t choose_peer_comparison_function(int sort_mode)
{
	switch(sort_mode)
	{
		case SORT_NONE:
			return(peer_compare_none);
		case SORT_BYTES:
			return(peer_compare_bytes);
		case SORT_PACKETS:
			return(peer_compare_packets);
		case SORT_CONNECTS:
			return(peer_compare_connections);
		case SORT_FIRST_SEEN:
			return(peer_compare_first_seen);
		case SORT_LAST_SEEN:
			return(peer_compare_last_seen);
		case SORT_RANDOM:
			return(peer_compare_random);
		default:
			abort();
	}
}

int peer_compare_first_seen(peer_t *peer1, peer_t *peer2)
{
	if (peer1->first_seen == peer2->first_seen)
		return(0);
	else if (peer1->first_seen > peer2->first_seen)
		return(1);
	else
		return(-1);
}

int peer_compare_last_seen(peer_t *peer1, peer_t *peer2)
{
	if (peer1->last_seen == peer2->last_seen)
		return(0);
	else if (peer1->last_seen > peer2->last_seen)
		return(1);
	else
		return(-1);
}

int peer_compare_connections(peer_t *peer1, peer_t *peer2)
{
	if (peer1->connections_sent_1_to_2 == peer2->connections_sent_1_to_2)
		return(0);
	else if (peer1->connections_sent_1_to_2 > peer2->connections_sent_1_to_2)
		return(1);
	else
		return(-1);
}

int peer_compare_packets(peer_t *peer1, peer_t *peer2)
{
	if (peer1->packets_sent_1_to_2 == peer2->packets_sent_1_to_2)
		return(0);
	else if (peer1->packets_sent_1_to_2 > peer2->packets_sent_1_to_2)
		return(1);
	else
		return(-1);
}

int peer_compare_bytes(peer_t *peer1, peer_t *peer2)
{
	if (peer1->bytes_sent_1_to_2 == peer2->bytes_sent_1_to_2)
		return(0);
	else if (peer1->bytes_sent_1_to_2 > peer2->bytes_sent_1_to_2)
		return(1);
	else
		return(-1);
}

int peer_compare_none(peer_t *peer1, peer_t *peer2)
{
	/* Always insert at head of list */
	return(1);
}

int peer_compare_random(peer_t *peer1, peer_t *peer2)
{
	if (random() < (RAND_MAX / 2))
		return(1);
	else
		return(-1);
}

