/*

  Source: readlog.c
  Author: Linda Murphy
  Date: December, 1991

  This program uses pips.read.log file to count documents that have
  been read.

  Added MIT pre-compile name to create a report that
  only prints the document listing.

  Changed increment_document() to check for a parsing problem.

  Changed a test for the date length in get_nextinputrec();

*/


#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include "date.h"

#define TITLEWIDTH 47
extern double percentage();
extern char *malloc();

#define DOCS      1
#define SOURCES   2

#define BYSOURCE  1
#define BYCOUNT   2
#define BYNODEID  3
#define BYTITLE   4

struct readrec {
  long nodeid;
  char title[1000];
  char source[100];
  struct daterec date;
};

struct datarec {
  long nodeid;
  char *source;
  char *title;
  long count;
  struct datarec *next;
};

/* global variables: */
char *Source;
int totalread;
int totalthissource;


/* list of documents: */
struct datarec docs = { -1, "", "", 0,   (struct datarec *) NULL };
/* list of sources */
struct datarec sources = { 0, "", "", 0, (struct datarec *) NULL };
     
int doallsources;
int listnodeids = 1;
int listdocsources = 1;
int printdocchanges = 0;
int numuniquedocs = 0;
     
float cutoff_totaldocs = 0.003;
float cutoff_srcdocs = 0.0;
     
/* end of global variables */
     
     
     
sort_datarecs (int which, int how, int ascending)
/* sort by count or sort by nodeid, ascending or descending */
{
  struct datarec *new, *temp, *item, *new2, *b4;
  int found;
  struct datarec *rec;
  int j;

  switch (which)
    {       /* point to first one on the list */
    case DOCS:
      new = docs.next;
      break;
    case SOURCES:
      new = sources.next;
      break;
    }
  
  if (new == NULL)  /* empty list */
    return 0;

  temp = new->next;     /* temp will point to the list as we work through it*/
  new->next = NULL;     /* nothing on the new list except the first one */
  
  while (temp != (struct datarec *) NULL)
    {  /* take head off of temp list, put it on new list */

      item = temp;         /* `item' points to head of temp list */
      temp = temp->next;   /* move temp along */ 
      item->next = NULL;   /* this record has no next just yet */

      /* figure out where item goes on the new list */
      new2 = new; 
      b4 = NULL;
      found = 0;

      while (!found && new2 != NULL) {
	switch (how) {
	case BYCOUNT:
	  if (ascending && (new2->count >= item->count))
	    found = 1;
	  else if (!ascending && (new2->count <= item->count))
	    found = 1;
	  break;
	  
	case BYSOURCE:
	  if (ascending && (strcasecmp(new2->source, item->source) >= 0))
	    found = 1;
	  else if (!ascending && (strcasecmp(new2->source, item->source) <= 0))
	    found = 1;
	  break;
	  
	case BYNODEID:
	  if (ascending && (new2->nodeid >= item->nodeid))
	    found = 1;
	  else if (!ascending && (new2->nodeid <= item->nodeid))
	    found = 1;
	  break;
	  
	case BYTITLE:
	  if (ascending && (strcasecmp(new2->title, item->title) >= 0))
	    found = 1;
	  else if (!ascending && (strcasecmp(new2->title, item->title) <= 0))
	    found = 1;
	  break;
	  
	default:
	  fprintf(stderr, "Unknown sort key: %d\n", how);
	  exit (-1);
	}  /* end of switch */
	
	if (!found) {
	  b4 = new2;
	  new2 = new2->next;
	}
      }  /* end of while !found and !at end of list */

      if (b4 != NULL) {
	item->next = b4->next;
	b4->next = item;
      }
      else { /* b4 is null */
	item->next = new;
	new = item;
      }
    }  /* end of while temp list is not empty */
  
  /* at the end, the new list is sorted */
  switch (which)
    {
    case DOCS:
      docs.next = new;
      break;
    case SOURCES:
      sources.next = new;
      break;
    }
}


int maxsourcelen()
{
  struct datarec *s;
  int max;

  max = 0;
  for (s = sources.next; s != (struct datarec *) NULL; s = s->next)
    if (strlen(s->source) > max)
      max = strlen(s->source);
  return (max);
}


print_list (int which)
{
  struct datarec *item;
  int maxlen;
  int checktotal = 0;
  int mincnt, i;
  long total = 0;

  switch (which) {
  case DOCS:

    printf ("%-5s ", "Count");
    if (listdocsources && doallsources) {
      maxlen = maxsourcelen();
      printf("%-*s ", 18 /*maxlen*/ , "Source");
    }
    
    printf ("%-*s %-6s\n", TITLEWIDTH, "Title", "Nodeid");
    printf ("%-5s ", "-----");
    if (doallsources)
      printf ("%-18s ", "------");
    for (i=0; i < TITLEWIDTH; i++)
      printf ("-");
    printf (" ------\n");

    if (numuniquedocs < 26)  /* if 25 or fewer docs, just list all of them */
	mincnt = 0;
    else {
      if (doallsources)
	mincnt = (int) (cutoff_totaldocs * totalread);
      else
	mincnt = (int) (cutoff_srcdocs * totalthissource);
    }

    for (item = &docs; item != NULL; item = item->next) {
      if (item->count > mincnt) {
	total += item->count;

	printf ("%5d ", item->count);
	
	if (listdocsources && doallsources)
	  printf ("%-*s ", 18 /*maxlen*/, item->source);
	
	printf ("%-*s %6d\n", TITLEWIDTH, item->title, item->nodeid);
      }
    }
    if (mincnt > 0)
      printf ("(Documents with counts <= %d not listed)\n", mincnt);
    break;

  case SOURCES:
    printf ("    %%     Cnt  Source\n");
    for (item = &sources; item != NULL; item = item->next) {
      if (item->count > 0) {
	total += item->count;
	printf ("  %5.2f  %4d  %s\n",
		percentage(item->count, totalread), item->count, item->source);
	fflush(stdout);
      }
    }
    break;
  }
#ifndef MIT
  printf ("Total: %d\n", total);
#endif
}


init_list(int which)
{
  struct datarec *item;
  
  switch (which) {
  case DOCS:
    for (item = &docs; item != (struct datarec *) NULL; item = item->next) 
      item->count = 0;
    /*  sort_datarecs(DOCS, BYNODEID, 1); if we sort, makes incr_doc faster */
    break;

  case SOURCES:
    for (item = &sources; item != (struct datarec *) NULL; item = item->next) 
      item->count = 0;
    break;
  }
}


init_intervaldata()
{
  totalread = 0;
  totalthissource = 0;
  init_list (DOCS);
  init_list (SOURCES);
}

fixdoctitle (struct datarec *doc, char *newtitle)
{
  if (strcasecmp(doc->title, newtitle)) {  /* if they are different */
    if (printdocchanges) {
      fprintf(stderr,"Nodeid %d: title changed from %s to %s\n",
	      doc->nodeid, doc->title, newtitle);
    }
    free (doc->title);
    doc->title = malloc(TITLEWIDTH +1);
    bzero(doc->title,TITLEWIDTH+1);
    strncpy (doc->title, newtitle, TITLEWIDTH);
  }
}

fixdocsource (struct datarec *doc, char *newsource)
{
  if (strcasecmp(doc->source, newsource)) {  /* if the are different */
    if (printdocchanges) {
      fprintf(stderr,"Nodeid %d: source changed from %s to %s\n",
	      doc->nodeid, doc->source, newsource);
    }
    free (doc->source);
    doc->source = malloc (strlen(newsource)+1);
    strcpy (doc->source, newsource);
  }
}

increment_document (struct readrec input)
{
  struct datarec *doc, *b;
  int found;

  b = NULL;
  doc = &docs;
  found = 0;
  
  while ( doc != (struct datarec *) NULL && !found) {

    if (doc->nodeid == input.nodeid) {
      found = 1;
      doc->count += 1;

	/* check for a parsing problem */
      if (doc->title == NULL)
	fprintf(stderr,"Bad Title for Nodeid: %d",doc->nodeid);
      else
	fixdoctitle (doc, input.title);

	/* check for a parsing problem */
      if (doc->source == NULL)
	fprintf(stderr,"Bad Source for Nodeid: %d",doc->nodeid);
      else
	fixdocsource (doc, input.source);
    }
    else {
      b = doc;
      doc = doc->next;
    }
  }
/*
  if (!found) {
    b->next = (struct datarec *) malloc (sizeof (struct datarec));
    b = b->next;
    b->count = 1;
    b->nodeid = input.nodeid;
    b->title = malloc(strlen(input.title)+1);
    strncpy (b->title, input.title, TITLEWIDTH);
    b->source = malloc(strlen(input.source)+1);
    strcpy (b->source, input.source);
    b->next = NULL;
  }
*/
  if (!found) {
    doc = (struct datarec *) malloc (sizeof (struct datarec));
    bzero(doc, sizeof (struct datarec));
    doc->count = 1;
    doc->nodeid = input.nodeid;
    doc->next = docs.next;
    docs.next = doc;
/* old version wade 10/3/94
    doc->title = malloc(strlen(input.title)+1);
    bzero(doc->title, strlen(input.title)+1);
    strncpy (doc->title, input.title, TITLEWIDTH);
*/
    doc->title = malloc(TITLEWIDTH)+1;
    bzero(doc->title, TITLEWIDTH +1);
    strncpy (doc->title, input.title, TITLEWIDTH);

    doc->source = malloc(strlen(input.source)+1);
    bzero(doc->source, strlen(input.source)+1);
    strcpy (doc->source, input.source);
  }
}

increment_source (struct readrec input)
{
  struct datarec *src, *b;
  int found;

  if (*(input.source) == NULL)
    return;

  b = NULL;
  src = &sources;
  found = 0;

  while (src != NULL && !found && src->source != NULL) {
    if (strcasecmp(src->source, input.source) == 0) {
      found = 1;
      src->count += 1;
    }
    else {
      b = src;
      src = src->next;
    }
  }

  if (!found) {
    b->next = (struct datarec *) malloc (sizeof (struct datarec));
    b = b->next;
    b->count = 1;
    b->source = malloc(strlen(input.source)+1);
    strcpy (b->source, input.source);
    b->next = NULL;
  }
}

dorec (struct readrec input)
{
  totalread++;
  if ( ! doallsources && !strcasecmp(input.source, Source)) {
    totalthissource++;
    increment_document (input);  /* increment counter for this document */
  }
  if (doallsources) {
    increment_source(input);
    increment_document (input);  /* increment counter for this document */
  }
}



totaldocs()
{
  struct datarec *temp;
  int cnt = 0;
  
  for (temp=docs.next; temp != NULL; temp=temp->next)
    if (temp->count > 0)
      cnt++;
  return (cnt);
}


print_interval (int number,
		struct daterec begof_int, struct daterec endof_int,
		char increment)
{
  struct daterec *temp;
  char datestr[50];
  
#ifndef MIT
  printf ("\n");
  if (increment != 'O')
    printf ("%2d) ", number);

  temp = &begof_int;
  if (temp->hour == 0 && temp->minute == 0 && temp->sec == 0)
    sprintdate (*temp, datestr, 3);
  else
    sprintdate (*temp, datestr, 0);
  printf ("%s -> ", datestr);

  temp = &endof_int;
  if (temp->hour == 0 && temp->minute == 0 && temp->sec == 0)
    sprintdate (*temp, datestr, 3);
  else
    sprintdate (*temp, datestr, 0);
  printf ("%s\n", datestr); 
#endif

  printf ("Total number of requests for documents: %d.\n", totalread);
  if (totalread > 0) {
    numuniquedocs = totaldocs();
    if (doallsources)
      printf ("Number of individual docs read = %d.\n", totaldocs());
    else
      printf ("For %s: %d total read requests, %d individual docs read\n",
	      Source, totalthissource, numuniquedocs);
  }

#ifndef MIT
  if (doallsources && totalread > 0) {
    printf ("\nBroken down by sources:\n");
    sort_datarecs (SOURCES, BYCOUNT, 0);
    print_list (SOURCES);
  }
#endif
  if (totalread > 0) {
#ifndef MIT
    printf ("\nBroken down by documents:\n");
#endif
    sort_datarecs (DOCS, BYCOUNT, 0);
    print_list (DOCS);
  }
}

int get_nextinputrec (struct readrec *input, FILE *infile)
{
  char astr[4000];
  char *ptr, *ptr2;
  char datestr[30];

  do {
    if (fgets (astr, sizeof(astr), infile) == NULL)
      return EOF;
    
#ifdef DEBUG
    printf(astr);
#endif

    input->nodeid = atol(astr);
    for (ptr = astr; *ptr && *ptr != ':' ; ptr++);
    if (*ptr == ':')
      ptr++;
    else continue;

    for (ptr2 = ptr; *ptr2 && *ptr2 != ':'; ptr2++);
    if (*ptr2 == ':') {
      *ptr2 = '\0';
      ptr2++;
    } else continue;

    strncpy (input->title, ptr, TITLEWIDTH);
    ptr = ptr2;

    for (ptr2 = ptr; *ptr2 && *ptr2 != ':'; ptr2++);
    if (*ptr2 == ':') {
      *ptr2 = '\0';
      ptr2++;
    } else continue;

    if (strlen(ptr) > 99) {
      fprintf (stderr, "Bad source string %s\n", ptr);
      continue;
    }
    strcpy (input->source, ptr);
    ptr = ptr2;
    
    if (strlen (ptr) != 25) {
      fprintf (stderr, "Date not long enough %s\n", ptr);
      continue;
    }
    else {
      strcpy (datestr, ptr+4);
      if (parse_logdate(ptr+4, &(input->date), 0) )
	break;
      else
	fprintf(stderr, "Didn't parse date %s\n", datestr);
    }
  } while (1);
  
  return 1;
}

dousage(char *pgm)
{
  fprintf (stderr,
	   "Usage: %s [<source>] [<increment>] [<starttime>] [<endtime>]\n\
Source is the name of the source (e.g. DCCS).  `All' means compute stats\n\
   across all sources.  `All' is the default.\n\
Increment must be one of H,D,W,M,Y,O (default=O)\n\
   (Hour, Day, Week, Month, or Year, or\n\
   One interval from startdate to enddate)\n\
Date & time format -- `month day [hour:minute[:second]] [year]'\n\
   E.g. 'Aug 1 1991' (quotes are needed because of embedded spaces)\n\
   Default startdate is 28 days ago, default enddate is today\n", pgm);
  exit (-1);
}


main (int argc, char *argv[])
{
  struct daterec startdate, enddate;
  struct daterec begof_intrvl, endof_intrvl, temp;
  struct readrec input;
  char increment;
  FILE *datafile = stdin;
  int intervalnumber;
  int stat;
  char msg[100];

  if (argc > 1 && !strncmp (argv[1], "-h", 2))
    dousage(argv[0]); /* never returns */

  if (argc < 2)
    Source = "all";
  else
    Source = argv[1];

  doallsources = (strcasecmp(Source, "all") == 0);
  if (!doallsources)
    listnodeids = 1;
  
  if (argc < 3) 
    increment = 'O';    /* Default interval size */
  else 
    increment = toupper ( *(argv[2]) );
  if (!index("HDWMYO", increment)) {
    fprintf (stderr, "Increment must be one of HDWMYO\n");
    exit (-21);
  }
  
  if (argc < 4) { /* startdate default = 1 month before now */
    setdaterec (&startdate, time(0));
    startdate.hour = startdate.minute = startdate.sec = 0;
    startdate.day = 1;
  }
  else
    parse_logdate (argv[3], &startdate, 1);
  
  if (argc < 5)  /* enddate default =  now */
    setdaterec (&enddate, time(0));
  else
    parse_logdate (argv[4], &enddate, 1);

  if (!BEFORE(startdate, enddate)) {
    fprintf (stderr, "Startdate must be before enddate\n");
    exit (-1);
  }

  datecpy (&begof_intrvl, startdate); /* copy startdate into begof_intrvl */
  intervalnumber = 1;

  /* skip over data which is before the startdate */
  do {
    stat = get_nextinputrec(&input, datafile);
  } while ((stat != EOF) && BEFORE(input.date, startdate));


  /* okay, now input contains our first real data, so let's do something
     with it! */
  do {
    init_intervaldata();

    /* try to figure out what timepoint the end of interval is */
    if (increment == 'O')                /* just one interval */
      datecpy (&endof_intrvl, enddate);

    else { 
      datecpy (&temp, begof_intrvl);   /* copy begof_intrvl into temp */
      dateincre (&temp, increment);    /* increase temp */

      if (BEFORE(temp, enddate))
	datecpy (&endof_intrvl, temp);     /* temp is the end of interval */
      else
	datecpy (&endof_intrvl, enddate);  /* enddate is the end of interval */
    }

    /* gather data for current interval */
    while (!AFTER(input.date, endof_intrvl) &&
	   !BEFORE(input.date, begof_intrvl) && (stat != EOF) ) {
      dorec (input);
      stat = get_nextinputrec(&input, datafile);
    }
    
    print_interval (intervalnumber, begof_intrvl, endof_intrvl, increment);
    datecpy (&begof_intrvl, endof_intrvl); /* new begof_intrvl is endof intv */
    intervalnumber++; 
  }
  while (BEFORE(begof_intrvl, enddate));
}



