/*
u_yymmdd.log files

  date/time/username/ipname/newsgroupname/itemnumber/sizeinbytes

duse -by user|group|ip -sort bytes|reads|group|user  u_98*.log

*/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#ifdef WIN32
#include <io.h>
#endif

enum {S_NONE,S_BYTES,S_READS,S_NOTHER,S_BY};
enum {T_NONE,T_GROUP,T_USER,T_IP};
static int sort_type;
static int c_by_type;
#define BFSZ 1000
#ifndef FALSE
#define FALSE 0
#define TRUE (!FALSE)
#endif
static char c_by[BFSZ];
static char c_sort[BFSZ];

typedef struct LIST {char *name; int reads; double bytes;  struct LIST *next;} List;
typedef struct RECORD {char *by; double bytes; int reads; List *others; int nother; struct RECORD *next; } Record;

int token_split(char *s, char *p[], char *sep, int tmax);
int str_hash(char *s, int max);
int process_wild(char *files[], int nfiles);
int process_files(char *files[], int nfiles);
int do_report(void);
void hash_add(char *by, int bytes, char *other);
int add_other(List **top, char *other, int bytes);
int add_file(char *fname);
int add_record(char *p[], int np);
void add_wild(char *wild);
static int dodetail;

int main(int argc, char *argv[]) 
{
	int i;
	char *xfiles[10000];
	int nxfiles=0;
	if (argc<2) {
		printf("Usage: duse -by	user|group|ip -sort bytes|reads|group|user -detail  u_9805*.log\n");
		return 0;
	}
	for (i=1; i<argc; i++) {
		if (strcmp(argv[i],"-by")==0) strcpy(c_by,argv[++i]);
		else if (strcmp(argv[i],"-sort")==0) strcpy(c_sort,argv[++i]);
		else if (strcmp(argv[i],"-detail")==0) dodetail = TRUE;
		else if (strncmp(argv[i],"-",1)==0) {
			printf("Unrecognized qualifier %s\n",argv[i]);
			return 0;
		} else xfiles[nxfiles++] = argv[i];
	}

	if (strncmp(c_by,"user",4)==0) c_by_type = T_USER;
	if (strncmp(c_by,"group",5)==0) c_by_type = T_GROUP;
	if (strncmp(c_by,"ip",2)==0) c_by_type = T_IP;
	if (strncmp(c_sort,"byte",4)==0) sort_type = S_BYTES;
	if (strncmp(c_sort,"read",4)==0) sort_type = S_READS;
	if (strncmp(c_sort,"group",4)==0) {
		if (c_by_type==T_GROUP) sort_type = S_BY;
		else sort_type = S_NOTHER;
	}
	if (strncmp(c_sort,"USER",4)==0) {
		if (c_by_type==T_GROUP) sort_type = S_NOTHER;
		else sort_type = S_BY;
	}
	if (strncmp(c_sort,"IP",2)==0) {
		if (c_by_type==T_GROUP) sort_type = S_NOTHER;
		else sort_type = S_BY;
	}
	if (c_by_type==T_NONE) {
		printf("You must specify -by user or group or ip\n");
		return 0;
	}
	if (sort_type==S_NONE) {
		printf("You must specify -sort byte or reads or group\n");
		return 0;
	}

#ifdef WIN32
	process_wild(xfiles,nxfiles);
#else
	process_files(xfiles,nxfiles);
#endif
	do_report();
	return 0;
}
#ifdef WIN32
int process_wild(char *files[], int nfiles)
{
	int i;
	for (i=0; i<nfiles; i++) add_wild(files[i]);
	return TRUE;  
}
void add_wild(char *wild)
{
	char tmp[BFSZ];
        struct _finddata_t info;
        long handle,status;

        handle = _findfirst(wild,&info);
        status = handle;
        while (status!=-1) {
                strcpy(tmp,info.name);
		add_file(tmp);
                status = _findnext(handle,&info);
        }
        if (handle!=-1) _findclose(handle);
}
#endif
int process_files(char *files[], int nfiles)
{
	int i;
	for (i=0; i<nfiles; i++) add_file(files[i]);
	return TRUE;  
}
#define MAX_HASH 5000
static Record *rec_hash[MAX_HASH];

static Record **sorted;
int rec_cmp(const void *aa, const void *bb)
{
	Record **aaa=(Record **) aa;	
	Record **bbb=(Record **) bb;
	Record *a = *aaa;
	Record *b = *bbb;
	
	switch (sort_type) {
		case S_BYTES:
			return (int) (a->bytes - b->bytes);
		case S_READS:
			return (int) (a->reads - b->reads);
		case S_NOTHER:
			return (int) (a->nother - b->nother);
		case S_BY:
			return strcmp(a->by,b->by);
	}
	return 0;
}

int do_report(void)
{
	/* Count the entries, and set the others (set nother) */
	Record *r;
	List *o;
	int i;
	int nrec=0;
	int no;
	int n;

	for (i=0; i<MAX_HASH; i++) {
	  for (r=rec_hash[i];r!=NULL;r=r->next) {
		nrec++;
		for (no=0,o=r->others; o!=NULL; o=o->next) {
			no++;
		}
		r->nother = no;
	  }
	}
	sorted = malloc(nrec*sizeof(Record));
	n=0;
	for (i=0; i<MAX_HASH; i++) {
	  for (r=rec_hash[i];r!=NULL;r=r->next) {
		sorted[n++] = r;
	  }
	}
	qsort(sorted,nrec,sizeof(Record *),rec_cmp);
	if (c_by_type==T_GROUP) {
		printf("   Users    Items       Bytes Group\n");
	} else if (c_by_type==T_USER) {
		printf("  Groups    Items       Bytes User\n");
	} else if (c_by_type==T_IP) {
		printf("  Groups    Items       Bytes IP\n");
	} 
	for (i=0; i<nrec; i++) {
		r = sorted[i];
		printf("%8d %8d %10.0fk %s\n",
		r->nother,r->reads,(double) (r->bytes/1024.0),r->by);
		if (dodetail) {
			for (o=r->others; o!=NULL; o=o->next) {
				printf("         %8d %10.0fk      %s\n",
				o->reads,o->bytes/1024.0,o->name);
			}
		}
	}
/*	
  User Fred, 25 groups, 2004 items, 5.2M bytes, 
  IP 1.2.3.4, 25 groups, 2004 items, 5.2M bytes, 
  Group x.y.z, 30 user, 2030 items, 50 mb
*/
	return 0;
}
void hash_add(char *by, int bytes, char *other)
{
	int h = str_hash(by,MAX_HASH);
	Record *r;

	for (r=rec_hash[h];r!=NULL;r=r->next) {
		if (strcmp(by,r->by)==0) {
			goto add_one;
		}
	}
	r = malloc(sizeof(Record));
	r->by = strdup(by);
	r->bytes = r->reads = 0;
	r->others = NULL;
	r->next = rec_hash[h];
	rec_hash[h] = r;
add_one:	
	r->bytes += bytes;
	add_other(&r->others,other,bytes);
	r->reads++;
}
int add_other(List **top, char *other, int bytes)
{
	List *t;
	for (t=*top; t!=NULL; t=t->next) {
		if (strcmp(t->name,other)==0) {
			t->bytes+=bytes;
			t->reads++;
			return TRUE;
		}
	}
	t = malloc(sizeof(List));
	t->name = strdup(other);
	t->bytes = bytes;
	t->reads = 1;
	t->next = *top;
	*top = t;
	return TRUE;
}
int add_file(char *fname)
{
	FILE *f;
	char bf[BFSZ];
	char *p[100];
	int np;

	printf("Processing %s\n",fname);
	f = fopen(fname,"r");
	if (f==NULL) {perror(fname); return FALSE;}
	for (;!feof(f);) {
		if (fgets(bf,BFSZ-10,f)==NULL) break;
		np = token_split(bf,p,"/",100);
		if (np!=7) continue;
		add_record(p,np);
	}
	fclose(f);
}
int add_record(char *p[], int np)
{

/* date/time/username/ipname/newsgroupname/itemnumber/sizeinbytes*/
	char by[BFSZ];
	char other[BFSZ];
	switch (c_by_type) {
	  case T_GROUP:
		  strcpy(by,p[4]);
		  strcpy(other,p[3]);
		  break;
	  case T_USER:
		  strcpy(by,p[2]);
		  strcpy(other,p[4]);
		  break;
	  case T_IP:
		  strcpy(by,p[3]);
		  strcpy(other,p[4]);
		  break;
	}	
	hash_add(by,atoi(p[6]),other);
	return TRUE;
}
int token_split(char *s, char *p[], char *sep, int tmax)
{
        int n=0;

        for (;*s!=0;) {
                p[n++] = s;
                s = strchr(s,*sep); 
                if (s==NULL) break;
                *s = 0;
                s++;            
                if ((n+5)>=tmax) {
                        printf("Too many tokens in this header line\n");
                        break;
                }

        }
        p[n] = NULL;
        return n;
}

/*
Example output:

  -by user -sort reads
  User Fred, 25 groups, 2004 items, 5.2M bytes, 

  -by IP -sort bytes
  IP 1.2.3.4, 25 groups, 2004 items, 5.2M bytes, 

  -by group - sort bytes
  Group x.y.z, 30 user, 2030 items, 50 mb
-----------------------------------------------

For each line read

	Find group or user or ip, and then add to that record.
	If new one add it.
	Record contains all the info.
*/

int str_hash(char *s, int max)
{
        int total=0;
	int r;
        int i;
        for (i=0;*s!=0;s++,i++) {
                total += ((*s) * 8)*i;    
        }
	r = total % max;
	if (r<0) return -r;
	return r;
}

