/*************************************************************************
 * gruftistats program
 * Copyright (c) 1998-9    Andy Kempling (aurikan@hotmail.com)
 * Copyright (c) 1998-2001 Colin Phipps <cphipps@doomworld.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *************************************************************************/

/* $Id: ircstats.c,v 1.28 2001/08/11 11:31:55 cph Exp $ */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <ctype.h>

#define DAYSEC0 6
#define DAYSEC1 12
#define DAYSEC2 18
#define DAYSEC3 24
#define USERMAX0 8		/* DO NOT MAKE THIS 0!!!!!!! */
#define CHANMAX0 1		/* DO NOT MAKE THIS 0!!!!!!! */
#define INFO_MAX 30
#define LINES_HISTORY 3

# include "ircstats.h"

static some_random_stuff random_topics, random_kicks, random_signoffs, random_urls;

#ifdef RECORDS
recordset oldrec[RECORD_NUM];
#endif

int channum = 0;
int chanmax = CHANMAX0;
int usernum = 0;
int usermax = USERMAX0;
int deluser = 0;

int verbose = 0; /* Verbosity level */

chan *channel;
chan *chan0;
user *userloc;

typedef char html_colour_t[7];

html_colour_t page_bg = { "E0E0F0" }, /* Background to the page */
  page_text = { "181C18" },   /* Normal text */
  page_text_contrast = { "000000" },   /* Normal text */
  day_usage_date_bg = {"90A0D8"}, 
  table_head_nick = {"A07088"}, 
  table_head_other = {"70A098"}, 
  table_index_bg = {"889480"}, 
  table_nick_bg = {"C090A8"}, 
  table_2_bg = {"90C0B8"}, 
  table_3_bg = {"90A0D8"};

char botname1[10] = {"grufti"};
char botname2[10] = {"unused1"};
char botname3[10] = {"unused2"};
char botkick1[10] = {"Banned"};
char botkick2[10] = {"moron"};
char picext  [10] = {".png"};
char randomwordslist[100], statword[20];
char credits[200];
char header_text[200];
char font1[20] = {"Times New Roman"};
char font2[20] = {"Verdana"};
char font3[20] = {"Arial Narrow"};
#define MISC_EXP_LEN 1000
char quit_msg_exp[MISC_EXP_LEN] = "Ping timeout";
char kick_str_exp[MISC_EXP_LEN] = "Public flood";

regex_t ignore_signoff_regex, ignore_kick_regex;

void InitChan (chan * channel, const char *name);
user *FindUser (char *name);
user *CreateUser (char *name);
void DeleteUser (char *name);
chan *FindChan (const char *date);
chan *CreateChan (const char* name);
int compare (const void *arg1, const void *arg2);
int comparechan (const void *arg1, const void *arg2);
void quoterep (rquote_t *q, const char *source);
long int datestr (char *str);
void addrandom(some_random_stuff* p, char* text, char* bywho);
void ParseLog(FILE* infp, int verbose, struct log_parse_s* plp, size_t* pbp);
#ifdef RECORDS
void GenRecords_One(char * recordin, char * recordnew, char * recordout);
void GenRecords_Seven(char * recordin, char * recordnew, chan * weekstat, char * recordout);
#endif /* RECORDS */

enum html_flags_e { html_bold = 1, html_underline = 2 };

/* addressed_to_bot - is a line directed at the/a bot */

void addletters(user *u, const char* text)
{
  register char *swp = statword;
  while (*text) {
    register char ch = *text++;
    if (isalnum(ch)) {
      u->letters++;
      if (isupper(*text)) u->loud++;
    }
    if (ispunct(ch)) {
      u->letters++; u->punct++;
      if (ch == '?') {
        u->questions++;
        u->flags |= UF_ASKING;
      }
      if (ch == '!') u->loud++;
    }
    /* cph - word stats 
     * we are looking for only this one word */
    if (ch == *swp) {
      if (!*++swp) {
	u->statword++;
	swp = statword;
        if (verbose>2) fprintf(stderr, "Statword hit (%s)\n", statword);
      }
    } else swp = statword;
  }
}

void clearrandom(some_random_stuff* p)
{
  int i;
  p->num = 0;
  p->delled = 0;
  for (i=0; i<NUM_RANDOM; i++)
  {
    p->random[i] = p->bywho[i] = NULL;
  }
}

void do_random_table(FILE* output, some_random_stuff* p, const char* whatisit)
{
  int i;
  fprintf (output, "<Table border=0 cellpadding=2 cellspacing=2>\n");
  fprintf (output, "<tr><td></td><td align=\"left\" bgcolor=\"#%s\">"
	   "<font color=\"#%s\" size=3 face=\"%s\">"
	   "<i><b>Who by</b></i></font></td>\n", table_head_nick, page_text_contrast, font1);
  fprintf (output, "<td align=\"left\" bgcolor=\"#%s\">"
	   "<font color=\"#%s\" size=3 face=\"%s\"><i><b>%s</b></i></font></td>\n",
	   table_head_other, page_text_contrast, font1, whatisit);
  fprintf (output, "</tr>\n");
  for (i=0; (i<5) && (i<p->num - p->delled); i++) {
    fprintf(output, "<tr><td align=\"right\" bgcolor=\"#%s\">"
	    "<font size=1 face=\"%s\">%i</font></td>"
	    "<td align=\"left\" bgcolor=\"#%s\">"
	    "<font size=2 face=\"%s\">%s</font></td>"
	    "<td align=\"left\" bgcolor=\"#%s\">"
	    "<font size=2 face=\"%s\"><i>",
	    table_index_bg, font2, i+1, table_nick_bg, font2, p->bywho[i], table_2_bg, font1);
    output_content(output, p->random[i]);
    fputs("</i></font></td></tr>\n", output);
  }
  fprintf (output, "</table>\n");
}

void do_factoid(FILE* output, int (*userstat)(const user*), const char* spec, int min, int topnum)
{
  int x, u_top = 0, u_next = 1, u_top_val = 0, u_next_val = 1;

  if (userstat(userloc) < userstat(userloc+1))
    {
    u_top = 1; u_next = 0;
    }
  u_top_val = userstat(userloc+u_top);
  u_next_val = userstat(userloc+u_next);
  if (topnum > usernum) topnum = usernum;

  for (x = 2; x < topnum; x++)
    {
    int v = userstat(userloc+x);
    if (v > u_next_val)
      {
      if (v > u_top_val)
        {
	u_next_val = u_top_val; u_next = u_top;
	u_top_val = v; u_top = x;
        }
      else
        {
	u_next_val = v; u_next = x;
        }
      }
    }
  if (u_top_val >= min) {
    fprintf(output, "<tr><td align=\"center\" bgcolor=\"#%s\"><font size=2 face=\"%s\">%s"
	    "</font></td><td align=\"left\" bgcolor=\"#%s\"><font size=3 face=\"%s\">", 
	    table_nick_bg, font2, (userloc+u_top)->name, table_2_bg, font1);
    fprintf(output, spec, u_top_val); 
    fprintf(output, "</font></td><td align=\"left\" bgcolor=\"#%s\">"
	    "<font size=1 face=\"%s\">%s (%d)</font></td></tr>\n", 
	    table_3_bg, font2, (userloc+u_next)->name, u_next_val);
  }
}

void do_factoid_inv(FILE* output, int (*userstat)(const user*), const char* spec, int max, int topnum)
{
  int x, u_top = 0, u_next = 1, u_top_val = 0, u_next_val = 1;

  if (-userstat(userloc) < -userstat(userloc+1))
    {
    u_top = 1; u_next = 0;
    }
  u_top_val = -userstat(userloc+u_top);
  u_next_val = -userstat(userloc+u_next);
  if (topnum > usernum) topnum = usernum;

  for (x = 2; x < topnum; x++)
    {
    int v = -userstat(userloc+x);
    if (v == 0) continue;
    if (v > u_next_val)
      {
      if (v > u_top_val)
        {
	u_next_val = u_top_val; u_next = u_top;
	u_top_val = v; u_top = x;
        }
      else
        {
	u_next_val = v; u_next = x;
        }
      }
    }
  if (-u_top_val < max) {
    fprintf(output, "<tr><td align=\"center\" bgcolor=\"#%s\"><font size=2 face=\"%s\">%s"
	    "</font></td><td align=\"left\" bgcolor=\"#%s\"><font size=3 face=\"%s\">", 
	    table_nick_bg, font2, (userloc+u_top)->name, table_2_bg, font1);
    fprintf(output, spec, -u_top_val); 
    fprintf(output, "</font></td><td align=\"left\" bgcolor=\"#%s\">"
	    "<font size=1 face=\"%s\">%s (%d)</font></td></tr>\n", 
            table_3_bg, font2, (userloc+u_next)->name, -u_next_val);
  }
}

/* Function to return the suffix for a given ordinal 
 * i.e. st is the number ends in 1, nd if it ends in 2, ...
 * Made simpler and removed static buffer
 */
const char * position_suffix(int pos)
{
  pos %= 10;
  switch(pos) {
   case 1:
     return "st";
   case 2:
     return "nd";
   case 3:
     return "rd";
   default:
     return "th";
  }
}

int user_kicks(const user *u) { return u->kicks; };
int user_kicked(const user *u) { return u->kicked; };
int user_nicks(const user *u) { return u->nicks; };
int user_joins(const user *u) { return u->joins; };
int user_linelength(const user *u) { return (u->lines ? u->letters/u->lines : 0); };
int user_questions(const user *u) { return (u->lines ? 100*u->questions/u->lines : 0); };
int user_loud(const user *u) { return (u->letters ? 100*u->loud/u->letters : 0); };
int user_punct(const user *u) { return (u->letters ? 100*u->punct/u->letters : 0); };
int user_word(const user *u) { return u->statword; };
int user_stats(const user *u) { return u->statcalls; };
int user_seens(const user *u) { return u->seens; };
int user_monos(const user *u) { return u->monologues; };
int user_urls(const user *u) { return u->urls; };
int user_fourseasons(const user *u)
{
if ((u->texts[0]+u->acts[0])!=0 && (u->texts[1]+u->acts[1])!=0 && (u->texts[2]+u->acts[2])!=0 && (u->texts[3]+u->acts[3])!=0)
 return (u->lines);
else
 return (101);
}
int user_topics(const user *u) { return u->topics; };

int callback_constant = 0;
int user_linesec(const user *u) { return u->texts[callback_constant] + u->acts[callback_constant]; }
int user_lines(const user *u) { return u->lines; };
int user_answers(const user *u) { return u->answered; };

void do_factoids(FILE* output)
{
  char *buf = malloc(100 + strlen(statword)); /* removed static limit */
  int r = rand();
  /* User number with most kicks/joins/etc */

  sprintf(buf, (r&1) ? "knew the right word, and said \"%s\" %%ld times" : 
	  "kept saying \"%s\", a whole %%ld times in fact", statword);

  fprintf (output, "<Table border=0 cellpadding=2 cellspacing=2>\n"
	   "<tr><td align=\"left\" bgcolor=\"#%s\">"
	   "<font color=\"#%s\" size=3 face=\"%s\">"
	   "<i><b>Who</b></i></font></td>\n<td align=\"left\" bgcolor=\"#%s\">"
	   "<font color=\"#%s\" size=3 face=\"%s\">"
	   "<i><b>Fact!</b></i></font></td><td align=\"left\" bgcolor=\"#%s\">"
	   "<font color=\"#%s\" size=3 face=\"%s\">"
	   "<i><b>Runner-up</b></i></font></td></tr>\n", 
	   table_head_nick, page_text_contrast, font1, table_head_other, page_text_contrast, font1,
	   table_head_other, page_text_contrast, font1);

  do_factoid(output, user_kicks, (r&0x4)?"was in a mood not to be messed with, and kicked %ld times" :
             "was the channel bouncer, booting %ld people out", 1, 30);
  do_factoid(output, user_kicked, (r&0x10)?"wasn't very popular, and got booted out %ld times"
             : "just didn't fit in, getting kicked %ld times", 1, 30);
  do_factoid(output, user_nicks, (r&0x40)?"couldn't find the right nick, despite changing it %ld times"
             : "is still looking for that perfect nick after %ld changes", 2, 30);
  do_factoid(output, user_joins, (r&0x100)?"couldn't decide whether to stay or go, and was in and out %ld times"
             : "abused the door, coming and going %ld times", 7, 30);
  do_factoid(output, user_linelength, "had the longest lines, averaging %ld in length", 7, 30);
  do_factoid(output, user_loud,  (r&0x400)?"was the loud mouth, with %ld%% shouting" : 
             "was giving us earache, yelling %ld%% of the time", 5, 30);
  do_factoid(output, user_questions, (r&0x1000)?"seemed uncertain - %ld%% of lines were questions"
             : "didn't know, so asked questions %ld%% of the time", 5, 30);
  do_factoid(output, user_answers, (r&0x40)?"was very helpful, answering %ld questions":
	     "was a know-it-all, answering %ld questions", 1, usernum);
  do_factoid(output, user_word, buf, 10, 30);

  r = rand(); /* heh, let's not exceed FFFF */

  do_factoid(output, user_seens, (r&0x1)?"couldn't find who he was looking for, and performed %ld seens"
             : "missed his friends, he did %ld seens", 3, 30);
  do_factoid_inv(output, user_linelength, "wrote only %ld letters per line, despite talking our ears off", 10, 30);
  do_factoid(output, user_urls, (r&0x4)?"was busy surfing the web, and found %ld cool URLs"
             : "dug up %ld interesting web sites to visit", 5, 30);
  do_factoid_inv(output, user_fourseasons, "wrote only %ld lines, yet still managed to be around<br>morning, afternoon, evening, and the graveyard shift", 100, usernum);
  do_factoid(output, user_monos, (r&0x10)?"talked to himself a lot, with %ld monologues":
	     "delivered %ld monologues, although nobody listened", 1, usernum);
  do_factoid(output, user_topics, "wasn't satisfied with the topic, so changed it %ld times", 4, 30);

  fprintf(output, "</table>\n");

  free(buf);
}

#ifdef RECORDS
void do_records(FILE* output, char * inputnewstr)
{
  if (inputnewstr)
   {
   FILE * inputnew;

   fprintf (output, "<Table border=0 cellpadding=2 cellspacing=2>\n"
	   "<tr><td align=\"left\" bgcolor=\"#%s\">"
	   "<font color=\"#%s\" size=3 face=\"%s\">"
	   "<i><b>Who</b></i></font></td>\n<td align=\"left\" bgcolor=\"#%s\">"
	   "<font color=\"#%s\" size=3 face=\"%s\">"
           "<i><b>Event</b></i></font></td><td align=\"left\" bgcolor=\"#%s\">"
	   "<font color=\"#%s\" size=3 face=\"%s\">"
           "<i><b>Old Record</b></i></font></td></tr>\n", 
	   table_head_nick, page_text_contrast, font1, table_head_other, page_text_contrast, font1,
	   table_head_other, page_text_contrast, font1);

   inputnew = fopen(inputnewstr, "rt");
   if (inputnew != NULL)
    {
    char recordname[32], newname[32], oldname[32], newdate[32], olddate[32];
    int newnum, oldnum, pos, numrecords = 0;

    while( fscanf(inputnew, "%[^\t] ", recordname) != EOF)
     {
     numrecords++;
     fscanf(inputnew, "%d", &pos);
     fscanf(inputnew, "%[^\t] %[^\t] %d ", newname, newdate, &newnum);
     fscanf(inputnew, "%[^\t] %[^\t] %d ", oldname, olddate, &oldnum);

     fprintf(output, "<tr><td align=\"center\" bgcolor=\"#%s\"><font size=2 face=\"%s\">%s"
                     "</font></td><td align=\"left\" bgcolor=\"#%s\"><font size=3 face=\"%s\">", 
                     table_nick_bg, font2, newname, table_2_bg, font1);
     fprintf(output, "took %d%s place for \"%s\" with %d",
                     pos+1, position_suffix(pos), recordname, newnum);
     fprintf(output, "</font></td><td align=\"left\" bgcolor=\"#%s\">"
                     "<font size=1 face=\"%s\">%s (%d)</font></td></tr>\n", 
                     table_3_bg, font2, oldname, oldnum);
     }
     fprintf(output, "<tr><td align=\"center\" bgcolor=\"#%s\"><font size=2 face=\"%s\">%d"
                     "</font></td><td align=\"left\" bgcolor=\"#%s\"><font size=3 face=\"%s\">", 
                     table_nick_bg, font2, numrecords, table_2_bg, font1);
     fprintf(output, "record%s set today", (numrecords==1)?" was":"s were");
     fprintf(output, "</font></td><td align=\"left\" bgcolor=\"#%s\">"
                     "<font size=1 face=\"%s\"></font></td></tr>\n", 
                     table_3_bg, font2);
    
    fclose(inputnew);
    }
    fprintf(output, "</font></table>\n");
   }
}
#endif

char channame[INFO_MAX+1];
char   myname[INFO_MAX+1];
char  myemail[INFO_MAX+1];
char max_quote_len_str[6] = {"1000"};
char min_quote_len_str[6] = {"2"};
int min_quote_len, max_quote_len;

void read_options(FILE* fp)
{
  char line[200];
  response_settings setting[] =
  {
    {"channame", channame, 30},  
    {"myname", myname, 30},  
    {"myemail", myemail, 30},
    {"statwords", randomwordslist, 100},
    {"page_text", page_text, 6}, 
    {"page_text_contrast", page_text_contrast, 6}, 
    {"page_bg", page_bg, 6}, 
    {"date_bg", day_usage_date_bg, 6}, 
    {"table_head_nick", table_head_nick, 6}, 
    {"table_head_other", table_head_other, 6}, 
    {"table_index_bg", table_index_bg, 6}, 
    {"table_nick_bg", table_nick_bg, 6}, 
    {"table_2_bg", table_2_bg, 6}, 
    {"table_3_bg", table_3_bg, 6}, 
    {"botname", botname1, 9},
    {"botname2", botname2, 9},
    {"botname3", botname3, 9},
    {"picext",picext,9},
    {"credits",credits,199},
    {"max_quote_len",max_quote_len_str,6},
    {"min_quote_len",min_quote_len_str,6},
    {"ignore_kicks",kick_str_exp,sizeof(kick_str_exp)},
    {"ignore_quits",quit_msg_exp,sizeof(quit_msg_exp)},
    {"header",header_text,199},
    {"font1",font1,19},
    {"font2",font2,19},
    {"font3",font3,19},
    {"", NULL, 0}
  };

  while (fgets(line, 200, fp) != NULL)
    {
    char *p = line + strlen(line) - 1;
    if ((p >= line) && (*p == '\n')) *p = 0;
    p = strchr(line, '=');
    if (p && line[0] != '#')
      {
      int i = 0;
      *p++ = 0;
      while (setting[i].where)
        {
	if (!strcasecmp(line, setting[i].tag))
          {
	  if (verbose>1) fprintf(stderr, "Setting %s to %s\n", setting[i].tag, p);
	  strncpy(setting[i].where, p, setting[i].max);
          }
	i++;
        }
      }
    }
}

int main (int argc, char **argv)
{
  FILE *inputstats;
  FILE *output;
  int filec, pfc = 0;
  char *filename;
  char *recordin = NULL;
  char *recordnew = NULL;
  char *recordout = NULL;
  const char *recordhtml = "-";
  const char *outfile = "-";
  time_t start = time (NULL);
  int optind = 1;
  int recordkeep = 0;
  char *banfile = NULL;
  struct log_parse_s* plp = NULL;
  size_t total_bytes_parsed = 0;

  clearrandom(&random_topics);
  clearrandom(&random_kicks);
  clearrandom(&random_signoffs);
  clearrandom(&random_urls);

  srand ((unsigned) time (NULL));

  chan0 = (chan *) malloc (CHANMAX0 * sizeof (chan));
  userloc = (user *) malloc (USERMAX0 * sizeof (user));

  while ((optind < argc) && (argv[optind][0] == '-') && argv[optind][1] != '\0')
    {
    char opt = argv[optind++][1];

    switch (opt)
      {
        case 'r':
          if (optind < argc) {
            FILE *optfile = fopen(argv[optind++], "rt");
            if (!optfile) perror("fopen");
            else {
              read_options(optfile);
              fclose(optfile);
            }
          }
          break;
        case 'o':
          if (optind < argc)
            outfile = argv[optind++];
          break;
        case 's':
          if (optind < argc)
            recordin = argv[optind++];
          break;
        case 'i':
          if (optind < argc)
            optind++;
          break;
        case 't':
          if (optind < argc)
            recordout = argv[optind++];
          break;
        case 'l':
          if (optind < argc)
            {
            recordhtml = argv[optind++];
            recordkeep = 1;
            }
          break;
        case 'n':
          if (optind < argc)
            recordnew = argv[optind++];
          break;
        case 'v':
          verbose++;
          break;
        case 'b':
	  if (optind < argc)
	    banfile = argv[optind++];
	  break;
        case 'p':
          if (!plp && optind < argc) {
            FILE *infp = fopen(argv[optind++],"r");
            if (!infp) perror("fopen");
            else {
              plp = ReadLogFormatSpec(infp, verbose);
              fclose(infp);
            }
          }
          break;
                                                                                  default:
          fprintf( stderr, "Unknown option %s", argv[optind]);
          break;
      }
    }

    if (!plp) {
      fprintf(stderr, "No log format file - unable to continue\n");
      exit(-1);
    }

    { /* Compile some misc regexps */
      int rc;
      rc = regcomp(&ignore_signoff_regex, quit_msg_exp, 0);
      if (rc) {
        fprintf(stderr, "Error %d compiling signoffs regexp %s\n", rc, quit_msg_exp);
        exit(-1);
      }
      rc = regcomp(&ignore_kick_regex, kick_str_exp, 0);
      if (rc) {
        fprintf(stderr, "Error %d compiling kicks regexp %s\n", rc, kick_str_exp);
        exit(-1);
      }
    }
    min_quote_len = atoi(min_quote_len_str);
    max_quote_len = atoi(max_quote_len_str);

    { /* Choose a random word to stat for */
    if (!randomwordslist[0]) strlcpy(statword, "\n\n", sizeof(statword)); /* impossible to match */
    else
      {
      int n = 1;
      char *p = randomwordslist;
      while (*p)
        {
        char *q = strchr(p, ',');
        if (q) *q++ = 0;
        if (!(rand() % n++)) strcpy(statword, p);
        if (q) p = q;
        else *p=0;
        }
      if (verbose) fprintf(stderr, "Collecting stats for word \"%s\"\n", statword);
      }
    }

  for (filec = optind; filec < argc; filec++)
    {
      {
      filename = *(argv + filec);
      if (strcmp(filename, "-"))
	inputstats = fopen (filename, "rt");
      else 
	inputstats = stdin; /* Support for input from stdin */
      if (inputstats == NULL)
	printf ("Error opening %s, skipping file\n", filename);
      else
        {
        pfc++;
        ParseLog(inputstats, verbose, plp, &total_bytes_parsed);
        }
      if (inputstats) fclose (inputstats);
#ifdef RECORDS
      if (recordkeep == 1 && pfc == 1)
        GenRecords_One(recordin, recordnew, recordout);
#endif
    }
  }
  if (banfile) {
    FILE* bfp = fopen(banfile, "rt");
    char nick[16];

    if (bfp) {
      while (fgets(nick, 16, bfp)) 
	if (strlen(nick)>2) {
	  nick[strlen(nick)-1] = 0; /* Strip trailing '\n' */
	  DeleteUser(nick);
	}
      fclose(bfp);
    } else perror(banfile);
  }
  if (pfc == 0) {
    fprintf (stderr, "No logs parsed. Nothing to output");
    return 0;
    } else {
    if (strcmp(outfile, "-")) output = fopen (outfile, "wt");
    else output = stdout;
    if (output == NULL) {
      fprintf (stderr, "Error opening output html file");
      return 0;
    }

    {
      int max_lines = 0, tot_lines = 0, x, y, z;
      chan *chantotal = (chan *) malloc (sizeof (chan));
      chan *edate = FindChan("Unknown");
      time_t now = time (NULL);

      InitChan (chantotal, "Unknown");
      for (x = 0; x < channum; x++)
	for (y = 0; y < 24; y++)
	  chantotal->lines[y] += (chan0 + x)->lines[y];

      qsort ((void *) chan0, channum, sizeof (chan), comparechan);
      qsort ((void *) userloc, usernum, sizeof (user), compare);

#ifdef RECORDS
      /* this is actually a 7-day tally */
      /* Assuming that this is run at 0:05, where there is no significant
      impact of the 8th day */
      if (recordkeep == 1 && pfc == 8)
        GenRecords_Seven(recordin, recordnew, chantotal, recordout);
#endif /* Records */

      for (x = 0; x < channum; x++) {
	if (strcmp ((chan0 + x)->date, "Unknown")) {
	  edate = chan0 + x;
	  break;
	}
      }

      fprintf (output, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">");
      fprintf (output, "<!-- %s %s $Id: ircstats.c,v 1.28 2001/08/11 11:31:55 cph Exp $ by Colin Phipps (cph@lxdoom.linuxgames.com) and Andy Kempling (aurikan@hotmail.com)-->\n",PACKAGE,VERSION);
      fprintf (output, "<html><head><title>#%s statistics created by %s and %s</title></head>\n", channame, myname, PACKAGE);
      fprintf (output, "<body bgcolor=\"#%s\" text=\"#%s\" link=\"#%s\" vlink=\"#%s\"><center>\n%s\n", page_bg, page_text, page_text, page_text, header_text);
      fprintf (output, "<font color=\"#%s\" size=5 face=\"%s\">"
	       "Channel stats for #%s - created by <a href=\"mailto:%s\">"
	       "<font color=\"#%s\" size=4 face=\"%s\">%s</font></a></font><br>\n", 
	       page_text_contrast, font2, channame, myemail, page_text_contrast, font2, myname);
      fprintf (output, "<hr width=\"50%%\">\n<font size=2 face=\"%s\">"
	       "Statistics generated from %s to %s</font><br>\n", 
	       font2, edate->date, (chan0 + channum - 1)->date);
      fprintf (output, "<font size=2 face=\"%s\">Stats generated on %s</font><br>\n", font2, ctime (&now));
      fprintf (output, "<font size=2 face=\"%s\">During this %li-day reporting period a "
	       "total of %i persons visited #%s</font><br>\n", 
	       font2, datestr ((chan0 + channum - 1)->date) - datestr (edate->date) + 1, 
	       usernum - deluser, channame);

      /* Print most recent topic only, if any */
      for (x = channum-1; x>0; --x)
        if (chan0[x].curtopic) {
          fprintf (output,"<font size=1 face=\"%s\">", font2);
	  fputs ("The current topic is <b>", output);
          output_content(output, chan0[x].curtopic);
          fputs ("</b></font><br>\n", output);
          break;
        }

      x = 0;
      if (channum != 1)
       while (!strcmp((chan0 + x)->date, "Unknown"))
        {
        x = (rand()%(channum));
        if (verbose>1) fprintf(stderr, "Random Channel Number: %i (%s)\n", x, (chan0 + x)->date);
        }
      fputs("<font size=1 face=\"", output);
      fputs(font2, output);
      fputs("\"><b><i>", output);
      if (chan0[x].quote.quote) output_content(output, chan0[x].quote.quote);
      fputs("</i></b></font><br>\n", output);
      fprintf (output, "<hr width=\"50%%\">\n");

      fprintf (output, "<hr width=\"90%%\">\n");
      fprintf (output, "<font color=\"#%s\" size=4 face=\"%s\"><i><u>Daily statistics</u></i>"
	       "</font><font color=\"#%s\" size=2 face=\"%s\">"
	       " <i>(Number of lines / 6 hours)</i></font><br>\n", 
	       page_text_contrast, font2, page_text_contrast, font2);
      fprintf (output, "<Table border=0><tr>\n");

      for (x = 0; x < channum; x++)
	for (y = 0; y < 4; y++) {
	  tot_lines = 0;
	  for (z = 0; z < 6; z++)
	    tot_lines += (chan0 + x)->lines[z + 6 * y];
	  if (tot_lines > max_lines)
	    max_lines = tot_lines;
	}
      for (x = 0; x < channum; x++)
	if ((chan0 + x)->tlines)
	  for (y = 0; y < 4; y++) {
	    tot_lines = 0;
	    for (z = 0; z < 6; z++)
	      tot_lines += (chan0 + x)->lines[z + 6 * y];
	    fprintf (output, "<td align=\"center\" valign=\"bottom\">"
		     "<font size=1 face=\"%s\">%i<br>"
		     "<img src=\"pipe%iv%s\" width=22 height=%i alt=\"Bar\"></font></td>\n", 
		     font3, tot_lines, y + 1, picext, (int) (128 * tot_lines / max_lines));
	  }
      fprintf (output, "</tr><tr>\n");
      for (x = 0; x < channum; x++)
	if ((chan0 + x)->tlines)
	  fprintf (output, "<td colspan=4 bgcolor=\"#%s\" align=\"center\" valign=\"bottom\">"
		   "<font size=1 face=\"%s\">%s</font></td>\n", 
		   day_usage_date_bg, font3, (chan0 + x)->date);
      fprintf (output, "</tr></table>\n");
      fprintf (output, "<br><hr width=\"90%%\">\n<Table border=0><tr>\n");
      fprintf (output, "<td width=150><img src=\"pipe1h%s\" height=15 width=30 align=top alt=\"Pink\"><font color=\"#%s\" size=2 face=\"%s\"> Hours %.2i-%.2i</font></td>\n", picext, page_text_contrast, font2, 0, DAYSEC0);
      fprintf (output, "<td width=150><img src=\"pipe2h%s\" height=15 width=30 align=top alt=\"Pink\"><font color=\"#%s\" size=2 face=\"%s\"> Hours %.2i-%.2i</font></td>\n", picext, page_text_contrast, font2, DAYSEC0, DAYSEC1);
      fprintf (output, "<td width=150><img src=\"pipe3h%s\" height=15 width=30 align=top alt=\"Pink\"><font color=\"#%s\" size=2 face=\"%s\"> Hours %.2i-%.2i</font></td>\n", picext, page_text_contrast, font2, DAYSEC1, DAYSEC2);
      fprintf (output, "<td width=150><img src=\"pipe4h%s\" height=15 width=30 align=top alt=\"Pink\"><font color=\"#%s\" size=2 face=\"%s\"> Hours %.2i-%.2i</font></td>\n", picext, page_text_contrast, font2, DAYSEC2, DAYSEC3);
      fprintf (output, "</tr></table>\n");
      fprintf (output, "<hr width=\"90%%\">\n");
      fprintf (output, "<font color=\"#%s\" size=4 face=\"%s\">"
	       "<i><u>Channel load by hours</u></i></font><br>\n", page_text_contrast, font2);
      fprintf (output, "<Table border=0><tr>\n");

      max_lines = 1;
      tot_lines = 1;
      for (x = 0; x < 24; x++) {
	tot_lines += chantotal->lines[x];
	if (max_lines < chantotal->lines[x])
	  max_lines = chantotal->lines[x];
      }
      for (x = 0; x < 24; x++)
	fprintf (output, "<td align=\"center\" valign=\"bottom\"><font size=1 face=\"%s\">%.1f%%<br><img src=\"pipe%iv%s\" width=20 height=%i alt=\"Bar\"></font></td>\n", font3, (float) (1000 * chantotal->lines[x] / tot_lines) / 10, (int) (x / 6 + 1), picext, (int) (192 * chantotal->lines[x] / max_lines));
      fprintf (output, "</tr><tr>\n");
      for (x = 0; x < 24; x++)
	fprintf (output, "<td bgcolor=\"#%.2x00%.2x\" align=\"center\"><font color=\"#%.2x%.2x%.2x\" size=1 face=\"%s\">%i</font></td>\n", (int) (192 * chantotal->lines[x] / max_lines + 63), (int) (128 - 128 * chantotal->lines[x] / max_lines), 255, 255, 255, font3, x);
      fprintf (output, "</tr></table>\n");
      fprintf (output, "<hr width=\"90%%\"><br>\n");

      fprintf (output, "<font color=\"#%s\" size=4 face=\"%s\">"
	       "<i><u>Activity Statistics</u></i></font><br><font size=2 face=\"%s\">"
	       " <i>(Nicks sorted by number of lines written)</i></font><br><br>\n", 
	       page_text_contrast, font2, font2);
      fprintf (output, "<Table border=0 cellspacing=10><tr valign=\"top\"><td>\n");
      fprintf (output, "<Table border=0 cellpadding=2 cellspacing=2>\n");
      fprintf (output, "<tr><td></td><td align=\"left\" bgcolor=\"#%s\">"
	       "<font color=\"#%s\" size=3 face=\"%s\"><i><b> Nick</b></i></font></td>\n", 
	       table_head_nick, page_text_contrast, font1);
      fprintf (output, "<td align=\"left\" bgcolor=\"#%s\">"
	       "<font color=\"#%s\" size=3 face=\"%s\">"
	       "<i><b> Number of Lines</b></i></font></td>\n", table_head_other, page_text_contrast, font1);
      fprintf (output, "<td align=\"left\" bgcolor=\"#%s\">"
	       "<font color=\"#%s\" size=3 face=\"%s\"><i><b> Quote</b></i></font></td>\n",
	       table_head_other, page_text_contrast, font1);
      fprintf (output, "</tr>\n");

      /* Count total lines */
      tot_lines = 0;
      for (x = 0; x < usernum; x++) {
	if (!(userloc + x)->alive) {
	  continue;
	}
	if ((userloc + x)->lines > tot_lines)
	  tot_lines = (userloc + x)->lines;
      }

      /* Print users table */
      for (deluser=0, x = 0; (x - deluser < 30) && (x < usernum); x++) {
	if (!(userloc + x)->alive || !(userloc + x)->lines) {
	  deluser++;
	  continue;
	}
	fprintf (output, "<tr><td align=\"right\" bgcolor=\"#%s\">"
		 "<font size=1 face=\"%s\">%i</font></td>", table_index_bg, font2, x + 1 - deluser);
	fprintf (output, "<td align=\"left\" bgcolor=\"#%s\"><font size=2 face=\"%s\">"
		 "%s</font></td>\n", table_nick_bg, font2, (userloc + x)->name);
        fprintf (output, "<td valign=\"middle\" bgcolor=\"#%s\">\n", table_2_bg);
	for (y = 0; y < 4; y++)
	  if ((userloc + x)->texts[y] + (userloc + x)->acts[y])
	    fprintf (output, "<img src=\"pipe%ih%s\" height=15 width=%i align=top alt=\"%li\">", y + 1, picext, (int) (100 * ((userloc + x)->texts[y] + (userloc + x)->acts[y]) / tot_lines), (userloc + x)->texts[y] + (userloc + x)->acts[y]);
        fprintf (output, "<font size=2 face=\"%s\"> %s%li%s</font></td>\n", 
		 font2, (x - deluser) ? "" : "<b>", (userloc + x)->lines, (x - deluser) ? "" : "</b>");
        fprintf (output, "<td width=\"75%%\" align=\"left\" bgcolor=\"#%s\">"
		 "<font size=2 face=\"%s\"><i>", table_3_bg, font1);
	if (userloc[x].quote.quote) output_content(output, userloc[x].quote.quote);
	fputs("</i></font></td>\n</tr>\n", output);
      }
      fputs("</table></td><td>\n", output);
      fprintf (output, "<Table border=0 cellpadding=0 cellspacing=0>\n");

      tot_lines = 0;
      for (y = 0; y < 24; y++)
	tot_lines += chantotal->lines[y];
      deluser = 0;
      for (x = 0; x < usernum; x++) {
	if (!(userloc + x)->alive) {
	  deluser++;
	  continue;
	}
	max_lines = (userloc + x)->lines;
	if (max_lines / .015625 > tot_lines)
          fprintf (output, "<tr><td align=\"center\" nowrap>"
		   "<font color=\"#%s\" size=1 face=\"%s\">%s</font></td>"
		   "<td><img src=\"pipe%iv%s\" height=%i width=22 alt=\"%s (%i)\">"
		   "</td></tr>\n", page_text_contrast, font3, (userloc + x)->name, (x - deluser) % 4 + 1, picext,  
		   (int) (1024 * max_lines / tot_lines), (userloc + x)->name, max_lines);
	else {
	  (userloc + x)->alive = 2;
	  deluser++;
	}
      }
      fprintf (output, "</table></td></tr></table>\n");

      /* Now the random topics table */
      fprintf (output, "<hr width=\"90%%\"><font color=\"#%s\" size=4 face=\"%s\">"
	       "<i><u>5 Random Topics</u></i></font>\n", page_text_contrast, font2);
      do_random_table(output, &random_topics, "Topic");

      /* Now the random kicks table */
      fprintf(output, "<hr width=\"90%%\"><font color=\"#%s\" size=4 face=\"%s\">"
	       "<i><u>5 Random Kicks</u></i></font>\n", page_text_contrast, font2);
      do_random_table(output, &random_kicks, "What happened");
      
      /* Now some random facts */
      fprintf (output, "<hr width=\"90%%\"><font color=\"#%s\" size=4 face=\"%s\">"
	       "<i><u>Big Numbers</u></i></font>\n", page_text_contrast, font2);
      if (0 < usernum) 
        do_factoids(output);

#ifdef RECORDS
      /* New records */
      fprintf (output, "<hr width=\"90%%\"><font color=\"#%s\" size=4 face=\"%s\">"
               "<i><u>Records Set Today</u></i></font>\n", page_text_contrast, font2);
      if (0 < usernum) 
       do_records(output, recordnew);
#endif /* RECORDS */

      /* Random URLs */
      fprintf(output, "<hr width=\"90%%\"><font color=\"#%s\" size=4 face=\"%s\">"
               "<i><u>5 Random URLs</u></i></font>\n", page_text_contrast, font2);
      do_random_table(output, &random_urls, "URL");

      /* Random signoffs */
      fprintf(output, "<hr width=\"90%%\"><font color=\"#%s\" size=4 face=\"%s\">"
	       "<i><u>5 Random Signoffs</u></i></font>\n", page_text_contrast, font2);
      do_random_table(output, &random_signoffs, "Quit message");

      /* Now for the terminal table; credits, date, misc */
      fprintf (output, "<hr width=\"100%%\"><Table width=\"95%%\" border=2><tr><td><Table width=\"100%%\" border=0 cellpadding=5 cellspacing=0>\n");
      fprintf (output, "<tr><td align=\"left\" valign=\"middle\"><font size=2 face=\"%s\">This page was created on %s,\n", font2, ctime (&now));
      fprintf (output, "with <a href=\"http://gruftistats.sourceforge.net/\">%s</a> %s by <a href=\"mailto:aurikan@hotmail.com\">Andy Kempling</a> and <A HREF=\"mailto:cph@lxdoom.linuxgames.com\">Colin Phipps</a>.</font><br>", PACKAGE, VERSION);
      fprintf (output, "<font size=1 face=\"%s\">Processed %dKiB in %ds.</font></td>\n", font2, total_bytes_parsed >> 10, (int)(now - start));
      fprintf (output, "<td><a href=\"http://validator.w3.org/check/referer\"><img border=0 src=\"http://validator.w3.org/images/vh40\" alt=\"Valid HTML 4.0!\" height=31 width=88></a></td>");
      fprintf (output, "<td align=\"right\" valign=\"middle\"><font size=2 face=\"%s\">%s</font></td></tr>\n", font2, credits);
      fprintf (output, "</table></td></tr></table>");

      fprintf (output, "</center></body></html>");
      if (verbose) fprintf (stderr, "Done (Parsed %i files)\n", pfc);
      free(chantotal);
    }
    fclose (output);
  }
  return 0;
}

void InitChan (chan * channel, const char *name)
{
  int x;

  for (x = 0; x < 24; x++)
    channel->lines[x] = 0;
  channel->curtopic = NULL;
  channel->quote.quote = NULL;
  channel->quote.choices = 0;
  channel->tlines = 0;

  strcpy (channel->date, name);
}

chan * FindChan (const char *date)
{
  int x;

  for (x = 0; x < channum; x++)
    {
    if (verbose>1) fprintf(stderr, "Channel Name: '%s' (%s)\n", (chan0+x)->date, date);
    if (!stricmp (date, (chan0 + x)->date))
      return chan0 + x;
    }

  return 0;
}


chan * CreateChan (const char *name)
{
  if (channum == chanmax) {
    chanmax *= 2;
    chan0 = realloc ((void *) chan0, chanmax * sizeof (chan));
  }

  InitChan (chan0 + channum, name);
  return chan0 + channum++;
}

int compare_user_names (const void *arg1, const void *arg2)
{
  return strcasecmp(((user *) arg2)->name, ((user *) arg1)->name);
}

user *
FindUser (char *name)
{
  user fakeuser;

  fakeuser.name = name;
  return bsearch(&fakeuser, userloc, usernum, 
		 sizeof *userloc, compare_user_names);
}

void DeleteUser (char *name)
{
  user fakeuser, *p;

  fakeuser.name = strdup(name);
  p = bsearch(&fakeuser, userloc, usernum, 
	      sizeof *userloc, compare_user_names);

  if (p) {
    free(p->name);
    free(p->curnick);
    free(p->quote.quote);
    free(p->lastline);

    if (p != &userloc[usernum-1])
      *p = userloc[usernum-1];
    qsort(userloc, --usernum, sizeof *userloc, compare_user_names);
  }
}

user *
CreateUser (char *name)
{
  int x;

  if (usernum == usermax) {
    usermax *= 2;
    userloc = realloc ((void *) userloc, usermax * sizeof (user));
  }

  userloc[usernum].lastline = userloc[usernum].quote.quote = NULL;
  userloc[usernum].quote.choices = 0;
  userloc[usernum].name = strdup(name);
  userloc[usernum].curnick = strdup(name);

  for (x = 0; x < 4; x++) {
    (userloc + usernum)->texts[x] = 0;
    (userloc + usernum)->acts[x] = 0;
  }
  (userloc + usernum)->lines = 0;
  (userloc + usernum)->letters = 0;
  (userloc + usernum)->loud     = 0;
  (userloc + usernum)->questions = 0;
  (userloc + usernum)->punct     = 0;
  (userloc + usernum)->statword  = 0;
  (userloc + usernum)->joins = 0;
  (userloc + usernum)->kicked = 0;
  (userloc + usernum)->kicks = 0;
  (userloc + usernum)->nicks = 0;
  (userloc + usernum)->statcalls = 0;
  (userloc + usernum)->seens = 0;
  (userloc + usernum)->urls = 0;
  (userloc + usernum)->topics = 0;
  (userloc + usernum)->sought = 0;
  (userloc + usernum)->monologues = 0;
  (userloc + usernum)->alive = 1;
  (userloc + usernum)->answered = 0;
  (userloc + usernum)->flags = UF_NONE;

  qsort(userloc, ++usernum, sizeof *userloc, compare_user_names);
  return FindUser(name);
}

int
compare (const void *arg1, const void *arg2)
{
  return ((user *) arg2)->lines - ((user *) arg1)->lines;
}


void quoterep (rquote_t *q, const char *source)
{
  { /* cph 2001/07/15 - allow length of quoted text to be limited */
    size_t l = strlen(source);
    if (l < min_quote_len || l > max_quote_len) return;
  }
  {
    int prob = ++(q->choices);

    if (prob > 1 && rand() >= RAND_MAX / prob) return;

    if (q->quote) free(q->quote);
    q->quote = strdup(source);
  }
}

int
comparechan (const void *arg1, const void *arg2)
{
  long int day1 = 0, day2 = 0;

  if (!strcmp (((chan *) arg1)->date, "Unknown"))
    return -1;
  if (!strcmp (((chan *) arg2)->date, "Unknown"))
    return 1;
  if (!strcmp (((chan *) arg2)->date, ((chan *) arg1)->date))
    return 0;

  day1 = datestr (((chan *) arg1)->date);
  day2 = datestr (((chan *) arg2)->date);

  return day1 - day2;
}

long int
datestr (char *str)
{
  long int x = 0;
  int y;

  y = atoi (str + 11);

  if (strstr (str, "Dec"))
    x = 334 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Nov"))
    x = 304 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Oct"))
    x = 273 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Sep"))
    x = 243 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Aug"))
    x = 212 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Jul"))
    x = 181 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Jun"))
    x = 151 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "May"))
    x = 120 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Apr"))
    x = 90 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Mar"))
    x = 59 + ((y % 4) ? 0 : 1) - ((y % 100) ? 0 : 1) + ((y % 400) ? 0 : 1);
  else if (strstr (str, "Feb"))
    x = 31;
  else if (strstr (str, "Jan"))
    x = 0;

  x += (y - 1970) * 365 + (int) ((y - 1968) / 4);
  x += atoi (str + 8);

  return x;
}

void addrandom(some_random_stuff* p, char* text, char* bywho)
{
  
  int n = (++(p->num))-(p->delled);
  int i;

  for (i=0; i<NUM_RANDOM; i++)
   if (p->random[i] && !stricmp(text, p->random[i]))
    {
    p->delled++;
    return;
    }

  if (n<=NUM_RANDOM)
    i = n-1;
  else for (i=0; i<NUM_RANDOM; i++)
    if (!(rand() % n--) && i)
      break;

  if (i == NUM_RANDOM) return;

  if (p->random[i]) free(p->random[i]);
  if (p->bywho[i]) free(p->bywho[i]);
  p->random[i] = strdup(text);
  p->bywho[i] = strdup(bywho);
  return;

}

static char* history_line[LINES_HISTORY];

/* line_with_history mallocs a new buffer and returns the given 
 * line prepended with the LINES_HISTORY lines before it 
 * Intended for kicks and such where some pre-context is needed
 */
char *line_with_history(char *line)
{
  size_t len = 0;
  int i;
  char *retline;

  for (i=0; i<LINES_HISTORY; i++)
    if (history_line[i]) len += strlen(history_line[i]) + 1;

  len += strlen(line);
  retline = malloc(len * sizeof *retline + 1);
  retline[0] = 0;
  for (i=0; i<LINES_HISTORY; i++)
    if (history_line[i]) {
      strcat(retline, history_line[i]);
      strcat(retline, "\n");
    }

  strcat(retline, line);
  return retline;
}

/* New log parser. Completely rewritten to use regexps, remove line 
 * length limit, and hopefully be cleaner */
void ParseLog(FILE* infp, int verbose, struct log_parse_s* plp, size_t* pbp)
{
  size_t linelen = 512;
  char *line = malloc(sizeof(char)*linelen);
  int daysec = 0, hour = 0;
  int i;
  user* lastuser = NULL;
  int monologue = 0;
  
  if (!(channel = FindChan ("Unknown")))
    channel = CreateChan ("Unknown");   
  for (i=0; i<LINES_HISTORY; i++) history_line[i] = NULL; 

  for (;;) {
    enum line_type_e linetype;
    user *puser     = NULL; 
    char *nick      = NULL;
    char *str       = NULL;
    char *othernick = NULL;
    size_t ll;

    if (!fgets(line,linelen,infp)) {
      if (feof(infp)) break;
      perror("fgets"); exit(-1);
    }
    while (((ll = strlen(line)) == linelen-1) && line[linelen-1] != '\n') {
      /* Fiddly logic here.. double the buffer, read into the latter
       * half (with +/-1 fudging to allow for the old terminating \0)
       */
      char *p;
      line = realloc(line, linelen*2);
      p = line + linelen-1;
      fgets(p,linelen+1,infp);
      linelen *= 2;
    }
    if ((str = strchr(line,'\n'))) *str = 0;
    str = NULL;
    *pbp += ll;
    
    stripansi(line);

    {
      /* Max number of regexp subcomponents we may need to match
       * This is possibly > LP_NUM, since there may be parts which 
       * we don't use */
#define RM_NUM (2*LP_NUM)
      regmatch_t rmatch[RM_NUM];
      for (i=1; i<LT_NUM; i++)
        if (!regexec(&plp[i].compiled_regex,line,RM_NUM,rmatch,0)) break;
      
      if (i == LT_NUM) {
        if (verbose)
          fprintf(stderr, "Failed to parse \"%s\"\n", line);
        continue;
      }

      linetype = i;
      /* If the line was time stamped, set the day segment appropriately */
      if (plp[i].returned_bit[LP_TIME]) {
        daysec = (hour = atoi(line+rmatch[plp[i].returned_bit[LP_TIME]].rm_so))/6;
        if (daysec < 0 || daysec > 3) {
          fprintf(stderr, "Bad hour \"%s\"\n", line+rmatch[plp[i].returned_bit[LP_TIME]].rm_so);
          exit(-1);
        }
      }
      
      /* If date stamped, start a new channel struct for this day */
      if (plp[i].returned_bit[LP_DATE]) {
        /* Copy the name to a zero terminated string */
        char *newchan = strdup_from_rmatch(line, &rmatch[plp[i].returned_bit[LP_DATE]]);
        if (!(channel = FindChan (newchan)))
          channel = CreateChan(newchan);
        free(newchan);
      }
      /* Now copy the other normal info stuff to nice zero terminated 
       * strings to be friendly to the code below
       */
      if (plp[i].returned_bit[LP_NICK])
        nick = strdup_from_rmatch(line, &rmatch[plp[i].returned_bit[LP_NICK]]);
      if (plp[i].returned_bit[LP_OTHERNICK])
        othernick = strdup_from_rmatch(line, &rmatch[plp[i].returned_bit[LP_OTHERNICK]]);
      if (plp[i].returned_bit[LP_STRING])
        str = strdup_from_rmatch(line, &rmatch[plp[i].returned_bit[LP_STRING]]);
      if (verbose>1) fprintf(stderr, "Parsed line \"%s\" as %d, %s,%s,%s\n", line, linetype, nick, othernick, str);
    }

    /* Get the user record */
    if (nick)
      if (!(puser = FindUser (nick)))
        puser = CreateUser (nick);
    if (puser == lastuser) {
      if (monologue++>=8) {
        puser->monologues++;
        monologue = 0;
      }
    } else { 
      lastuser = puser;
      monologue = 0;
    }

    /* Smooth sailing from here; we have the line type and the bits of data, 
     * so lets update the stats with it
     */
    switch (linetype) {
    case LT_TEXT:
      { /* See if this line is addressed to someone */
        char *p = str+1;
        while (*p && (*p != ' ') && (*p != 2) && (*p != ':') && (*p != ','))
          p++;
        if (*p) {
          char *un = calloc(p-str+1,1);
	  user *ou;
          memcpy(un, str, p-str);
          if ((ou = FindUser(un))) {
            if (ou->flags & UF_ASKING)
              puser->answered++;
          }
          free(un);
        }
      }
    case LT_ACT:
      channel->tlines++; channel->lines[hour]++;
      quoterep (&channel->quote, line);
      if (linetype == LT_ACT)
        puser->acts[daysec]++;
      else
        puser->texts[daysec]++;
      puser->lines++;
      { /* Scan for URLs */
        char* us;
        if ((us = strstr(str,"http://")) || (us = strstr(str,"www.")) || (us = strstr(str,"ftp://"))) {
	  /* cph - remove most of the logic from here. Just let 
	   * text2html.c do the hard work */
	  char       *graburl = strchr(us, ' ');
          size_t      len;

          puser->urls++;

          if (graburl) len = graburl - us;
          else len = strlen(us);

          graburl = malloc(len+1);
	  graburl[len]=0; memcpy(graburl, us, len);
	  addrandom(&random_urls, graburl, nick);
	  free(graburl);
        }
      }
      { /* Scan for various interesting strings */
        char *lwr = strdup(str);
        strlwr(lwr);
        
        /* Still a lot of hard coded strings and logic here */
        if (strstr(lwr, botname1) || strstr(lwr, botname2) 
            || strstr(lwr, botname3)) {
          if (strstr(lwr + strlen(nick) + 3, "stat"))
            puser->statcalls++;
          if (strstr(lwr, "seen"))
            puser->seens++;
        } else if (strstr(lwr, "!seen"))
          puser->seens++;
        free(lwr);
      }
      puser->flags &= ~UF_ASKING;
      addletters(puser, str);
      {
        /* We don't quote the [time] <nick> part of normal lines, 
         * but we do want the * nick on actions. Messy part is, we 
         * would rather not have the timestamp on actions, but 
         * without knowing the line format we can't avoid it. FIXME.
         */
        char *str_to_quote = (linetype == LT_ACT) ? line : str;
        quoterep(&puser->quote, str_to_quote);
      }
      break;
    case LT_SIGNOFF:
      /* If the quit message does not match our list of boring quits.. */
      if (regexec(&ignore_signoff_regex, str, 0, NULL, REG_ICASE))
        addrandom(&random_signoffs, str, nick);
    case LT_PART:
      break;
    case LT_KICK:
      quoterep (&channel->quote, line);
      puser->kicks++;
      {
        user* kuser;
        /* Get the user record */
        if (!(kuser = FindUser (othernick)))
          kuser = CreateUser (othernick);
        kuser->kicked++;
      }
      /* If the kick message does nto match our list of boring kicks.. */
      if (regexec(&ignore_kick_regex, str, 0, NULL, REG_ICASE)) {
        char* kickline = line_with_history(line);
        addrandom(&random_kicks, kickline, nick);
        free(kickline);
      }
      break;
    case LT_TOPIC:
      puser->topics++;
      addrandom(&random_topics, str, nick);
      free(channel->curtopic); channel->curtopic = strdup(str);
      break;
    case LT_JOIN:
      puser->joins++;
      /* Maybe trying to be too smart, but let this drop 
       * thru like a nick change (user might have been under their 
       * alternate nick previously, so we have to reset curnick). 
       * WARNING: have to strdup it, because it's freed lower down.
       * Maybe this is too messy for the slight work saved. */
      othernick = strdup(nick);
    case LT_NICK:
      if (linetype == LT_NICK)
        puser->nicks++;
      quoterep (&channel->quote, line);
      /* Set user's current nick */
      free(puser->curnick); puser->curnick = strdup(othernick);
      break;
    case LT_MODE:
      quoterep (&channel->quote, line);
      break;
    default:
      /* LT_DATE_STAMP and LT_CHANNEL can be ignored */
      continue; /* Avoid these being included in history lines */
    }
    if (history_line[0]) free(history_line[0]);
    for (i=0; i<LINES_HISTORY-1; i++)
      history_line[i] = history_line[i+1];
    history_line[LINES_HISTORY-1] = strdup(line);
    if (nick) free(nick);
    if (othernick) free(othernick);
    if (str) free(str);
  }
  free(line);
}

int (*criterion)(const user*);

int
compare_adv (const void *arg1, const void *arg2)
{
  return criterion((user *) arg2) - criterion((user *) arg1);
}


void qcsort(void * init, int elem, int size, int (*critfunc)(const user *))
{
criterion = critfunc;
qsort(init, elem, size, compare_adv);
}

#ifdef RECORDS

void ReplaceRecord(FILE * outputnew, user * locstart, int (*critfunc)(const user *), recordset * records, char * date)
{
int x, y, z;
int overnum; char overname[32], overdate[32];

for (x=0; x< RECORD_MAX; x++)
  {
  for (y=x; y<RECORD_MAX; y++)
    {
    if (critfunc(locstart + x) >= records->record[y].num)
      {
      overnum = records->record[y].num;
      strcpy(overname, records->record[y].name);
      strcpy(overdate, records->record[y].date);
      for (z=RECORD_MAX-2; z>=y; z--)
        records->record[z+1] = records->record[z];
      records->record[y].num = critfunc(locstart + x);
      strcpy(records->record[y].date, date);
      strcpy(records->record[y].name, (locstart + x)->name);

      if (outputnew != NULL)
       fprintf(outputnew, "%s\t%d %s\t%s\t%d %s\t%s\t%d\n",
                          records->name, y, (locstart + x)->name, date, critfunc(locstart + x),
                          overname, overdate, overnum);

      break;
      }
    }
  }
}

void ClearRecords(recordset * records, const char * recordname)
{
int y;

strcpy(records->name, recordname);
for(y=0; y<RECORD_MAX; y++)
  {
  strcpy(records->record[y].name, "None");
  strcpy(records->record[y].date, "None");
  records->record[y].num = 0;
  }

}

void GenRecords_One(char * recordin, char * recordnew, char * recordout)
{
FILE * outnew;
FILE * inold;
char date[16];
int x, y;

strcpy(date, (chan0 + chanmax - 1)->date);

for (x=0; x<4; x++)
 {
 char buf[32];
 sprintf(buf, "Hours %i-%i one-day high", x * 6, x*6+6-1);
 ClearRecords(&oldrec[x], buf);
 }
ClearRecords(&oldrec[4], "One day high");

if (recordin != NULL)
  {
  inold = fopen(recordin, "rt");
  if (inold != NULL)
    {
    for( x=0; x<RECORD_NUM; x++)
      {
      fscanf(inold, "%[^\n] ", oldrec[x].name);
      for (y=0; y<RECORD_MAX; y++)
        {
        oldrec[x].record[y].num = 0;
        fscanf(inold, "%[^\t] ", oldrec[x].record[y].name);
        fscanf(inold, "%[^\t] ", oldrec[x].record[y].date);
        fscanf(inold, "%i ", &(oldrec[x].record[y].num));
        if (verbose>1) fprintf(stderr, "Record %s: %s, %s (%i)\n", oldrec[x].name, oldrec[x].record[y].name, oldrec[x].record[y].date, oldrec[x].record[y].num);
        }
      }
    fclose(inold);
    }
  }

outnew = fopen(recordnew, "wt");

for (x=0; x<4; x++)
  {
  callback_constant = x;
  qcsort((void *) userloc, usernum, sizeof(user), user_linesec);
  ReplaceRecord(outnew, userloc, user_linesec, &oldrec[x], date);
  }
  {
  qcsort((void *) userloc, usernum, sizeof(user), user_lines);
  ReplaceRecord(outnew, userloc, user_lines, &oldrec[4], date);
  }
fclose(outnew);


if (recordout != NULL)
  {
  inold = fopen(recordout, "wt");
  if (inold != NULL)
    {
    for( x=0; x<RECORD_NUM; x++)
      {
      fprintf(inold, "%s\n", oldrec[x].name);
      for (y=0; y<RECORD_MAX; y++)
        {
        fprintf(inold, "%s\t", oldrec[x].record[y].name);
        fprintf(inold, "%s\t", oldrec[x].record[y].date);
        fprintf(inold, "%i\n", oldrec[x].record[y].num);
        }
      }
    fclose(inold);
    }
  }
}

void GenRecords_Seven(char * recordin, char * recordnew, chan * weekstat, char * record)
{
char * date = NULL;
strcpy(weekstat->name, "The channel");

date = (chan0 + chanmax - 1)->date;

}
#endif /* RECORDS */


syntax highlighted by Code2HTML, v. 0.9.1