/* nntp-top.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include "libemfw.h"

#define ISDIGIT(c) ((c) >= '0' && (c) <= '9')

struct newsgroup
{
  struct newsgroup *next;	/* Hash bucket */
  char *name;
  long count;
  long bytes;
};

#define HASH_SIZE 997

static struct newsgroup *hash_table[HASH_SIZE];
static long newsgroup_count;
static int max_newsgroup_length;
static int by_articles;
static int by_bytes;

static void usage (void)
{
  puts ("Usage: nntp-top [-a] [-b] -n <number>");
  exit (1);
}

static void init (void)
{
  int i;

  for (i = 0; i < HASH_SIZE; ++i)
    hash_table[i] = NULL;
}

static void add (const char *name, int name_len, long count, long bytes)
{
  struct newsgroup *p;
  unsigned h;

  h = hash (name, name_len, HASH_SIZE);
  for (p = hash_table[h]; p != NULL; p = p->next)
    if (strncmp (p->name, name, name_len) == 0
	&& p->name[name_len] == 0)
      break;
  if (p == NULL)
    {
      p = xmalloc (sizeof (struct newsgroup));
      p->next = hash_table[h];
      hash_table[h] = p;
      p->name = xstrndup (name, name_len);
      p->count = 0; p->bytes = 0;
      ++newsgroup_count;
      if (name_len > max_newsgroup_length)
	max_newsgroup_length = name_len;
    }
  p->count += count; p->bytes += bytes;
}

static void load (void)
{
  static char buf[65536];
  size_t len;
  char *s, *t, *end;
  int c;
  long count, bytes;
  const char *pattern = " nntp-gw[";

  while (fgets (buf, sizeof (buf), stdin) != NULL)
    {
      len = strlen (buf);
      if (len == 0 || buf[len-1] != '\n')
	{
	  do
	    {
	      c = getchar ();
	    } while (c != EOF && c != '\n');
	  if (c == EOF)
	    break;
	  continue;
	}
      buf[len-1] = 0;

      s = strstr (buf, pattern);
      if (s == NULL)
	continue;
      s += strlen (pattern);
      if (!ISDIGIT (*s))
	continue;
      while (ISDIGIT (*s))
	++s;
      if (strncmp (s, "]: ", 3) != 0)
	continue;
      s += 3;
      if (strncmp (s, "group ", 6) == 0)
	{
	  s += 6;
	  t = s;
	  while (*t != 0
		 && !(*t == ':' && strncmp (t, ": articles=", 11) == 0))
	    ++t;
	  if (*t == 0)
	    continue;
	  len = t - s;
	  t += 11;
	  count = strtol (t, &end, 10);
	  if (count < 0 || end == t || *end != ' ')
	    continue;
	  t = end + 1;
	  if (strncmp (t, "bytes=", 6) != 0)
	    continue;
	  t += 6;
	  bytes = strtol (t, &end, 10);
	  if (bytes < 0 || end == t || *end != 0)
	    continue;
	  add (s, len, count, bytes);
	}
    }
}

/* Heapsort. */

static void sift (const struct newsgroup **tab, int left, int right,
		  int (*cmp)(const struct newsgroup *,
			     const struct newsgroup *))
{
  int i, j;
  const struct newsgroup *p;

  i = left; j = 2 * i + 1; p = tab[i];
  while (j <= right)
    {
      if (j < right && cmp (tab[j], tab[j+1]) < 0)
	++j;
      if (cmp (p, tab[j]) >= 0)
	break;
      tab[i] = tab[j]; i = j; j = 2 * i + 1;
    }
  tab[i] = p;
}

static void sort_and_report (int (*cmp)(const struct newsgroup *,
					const struct newsgroup *), long n)
{
  unsigned h;
  int left, right, i;
  const struct newsgroup *p;
  const struct newsgroup **tab;

  tab = xmalloc (newsgroup_count * sizeof (struct newsgroup *));
  i = 0;
  for (h = 0; h < HASH_SIZE; ++h)
    for (p = hash_table[h]; p != NULL; p = p->next)
      tab[i++] = p;
  if (i != newsgroup_count)
    abort ();
  left = i / 2; right = i - 1;
  while (left > 0)
    {
      --left;
      sift (tab, left, right, cmp);
    }
  while (right >= 0 && n > 0)
    {
      p = tab[0];
      printf ("%-*s %10ld %10ld\n",
	      max_newsgroup_length, p->name, p->count, p->bytes);
      p = tab[left]; tab[left] = tab[right]; tab[right] = p;
      --right; --n;
      if (right > 0 && n > 0)
	sift (tab, left, right, cmp);
    }

  free (tab);
}

static int cmp_count (const struct newsgroup *h1, const struct newsgroup *h2)
{
  if (h1->count < h2->count)
    return -1;
  else if (h1->count > h2->count)
    return 1;
  else if (h1->bytes < h2->bytes)
    return -1;
  else if (h1->bytes > h2->bytes)
    return 1;
  else
    return 0;
}

static int cmp_bytes (const struct newsgroup *h1, const struct newsgroup *h2)
{
  if (h1->bytes < h2->bytes)
    return -1;
  else if (h1->bytes > h2->bytes)
    return 1;
  else if (h1->count < h2->count)
    return -1;
  else if (h1->count > h2->count)
    return 1;
  else
    return 0;
}

static void report (long n)
{
  if (by_bytes)
    {
      printf ("Top %ld by number of bytes:\n\n", n);
      sort_and_report (cmp_bytes, n);
    }
  if (by_articles)
    {
      if (by_bytes)
	putchar ('\n');
      printf ("Top %ld by number of requests:\n\n", n);
      sort_and_report (cmp_count, n);
    }
}

int main (int argc, char *argv[])
{
  long n = 0;
  char *end;
  int c;

  by_bytes = by_articles = 0;
  while ((c = getopt (argc, argv, "abn:")) != -1)
    switch (c)
      {
      case 'a':
	by_articles = 1;
	break;
      case 'b':
	by_bytes = 1;
	break;
      case 'n':
	errno = 0;
	n = strtol (optarg, &end, 10);
	if (n < 1 || errno != 0 || *end != 0)
	  usage ();
	break;
      default:
	usage ();
      }
  if (optind != argc || n < 1)
    usage ();
  if (!by_bytes && !by_articles)
    by_bytes = by_articles = 1;

  init ();
  load ();
  report (n);

  return 0;
}
