/*! \file history.c
 This module keeps track of and reports historical data including
 logon/logoff statistics, playing time, character creation/deletion,
 and user counts.
 \author Jon A. Lambert
 \version 1.96
 \date 06/27/2002
 \remarks
    This code copyright (C) 2000-2002 by Jon A. Lambert<BR>
    <antilochos@ix.netcom.com> -  All rights reserved.<BR>
    No part of this code may be used without my written consent.<BR>

This code released to the PUBLIC DOMAIN on 4/2/2006
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "merc.h"
#include "recycle.h"
#include "dates.h"
#include "history.h"

#define HIST_DIR "../log"    /* in with log data */

struct stats g_stats = { 0, 0, "\0", 99, 99, 0, 0, 0, 0, 0 };

struct history_record {
  char hist_date[10];
  int hist_weekday;
  int hist_hour;
  int hist_play_time;
  int hist_logins;
  int hist_logouts;
  int hist_deletions;
  int hist_creates;
  int hist_max_on;
  int hist_count_on;
};

struct history_record *history_table;

/* local functions */
static int load_history_table (int report_type, char *start_date,
  char *end_date);
static void print_report (CHAR_DATA * ch, int report_type, int history_size,
  char *start_date, char *end_date);

void do_history (CHAR_DATA * ch, char *argument)
{
  char arg1[MAX_INPUT_LENGTH];
  char arg2[MAX_INPUT_LENGTH];
  char arg3[MAX_INPUT_LENGTH];
  char arg4[MAX_INPUT_LENGTH];
  char start_date[MAX_INPUT_LENGTH];
  char end_date[MAX_INPUT_LENGTH];
  time_t t;
  struct tm *time_block;
  int report_type = 0;
  int retval;

  history_table = NULL;

  if (argument == NULL || argument[0] == '\0') {
    send_to_char
      ("Syntax : HISTORY daily|hourly|weekday totals|averages [start_date [end_date]]\r\n",
      ch);
    send_to_char ("         Date formats are MMDDYY.\r\n", ch);
    send_to_char
      ("         Default start_date is the beginning of the current month.\r\n",
      ch);
    send_to_char ("         Default end_date is the current date.\r\n", ch);
    send_to_char ("Example: history hourly averages 072300 102200\r\n", ch);
    return;
  }

  argument = one_argument (argument, arg1);
  argument = one_argument (argument, arg2);
  argument = one_argument (argument, arg3);
  argument = one_argument (argument, arg4);

  if (str_prefix (arg1, "daily")
    && str_prefix (arg1, "hourly")
    && str_prefix (arg1, "weekday")) {
    send_to_char ("Report type must be daily, hourly or weekday.\r\n", ch);
    return;
  }

  if (str_prefix (arg2, "totals")
    && str_prefix (arg2, "averages")) {
    send_to_char ("Report format must be totals or averages.\r\n", ch);
    return;
  }

  time (&t);
  time_block = localtime (&t);

  if (arg3 == NULL || arg3[0] == '\0') {
    time_block->tm_mday = 1;
    MakeDateString (start_date, time_block);
  } else if (is_number (arg3)
    && strlen (arg3) == 6 && ValidateDate (arg3)) {
    strcpy (start_date, arg3);
  } else {
    send_to_char ("Start date must be in MMDDYY format.\r\n", ch);
    return;
  }

  time_block = localtime (&t);

  if (arg4 == NULL || arg4[0] == '\0') {
    MakeDateString (end_date, time_block);
  } else if (is_number (arg4)
    && strlen (arg4) == 6 && ValidateDate (arg4)) {
    strcpy (end_date, arg4);
  } else {
    send_to_char ("End date must be in MMDDYY format.\r\n", ch);
    return;
  }

  if (DaysSince1800 (end_date) < DaysSince1800 (start_date)) {
    send_to_char ("End date must be greater than start date.\r\n", ch);
    return;
  }

  if (!str_prefix (arg1, "daily")) {
    report_type = 10;
    if (!str_prefix (arg2, "totals"))
      report_type = 11;
    if (DaysSince1800 (end_date) - DaysSince1800 (start_date) + 1 > 31)
      report_type += 2;
  } else if (!str_prefix (arg1, "hourly")) {
    report_type = 20;
    if (!str_prefix (arg2, "totals"))
      report_type = 21;
  } else if (!str_prefix (arg1, "weekday")) {
    report_type = 30;
    if (!str_prefix (arg2, "totals"))
      report_type = 31;
  }

  retval = load_history_table (report_type, start_date, end_date);
  if (retval != 0) {
    print_report (ch, report_type, retval, start_date, end_date);
    free (history_table);
  }
  return;
}


static int load_history_table (int report_type, char *start_date,
  char *end_date)
{
  int *counts;
  FILE *fp;
  struct history_record temp;
  signed int start_yr;
  signed int orig_start_yr;
  signed int start_mo;
  signed int orig_start_mo;
  signed int start_dy;
  signed int end_yr;
  signed int end_mo;
  signed int end_dy;
  int history_size;
  char name[MAX_STRING_LENGTH];
  int i;
  int indexval = 0;

  /* translate dates to numeric values */
  start_yr = Year (start_date);
  orig_start_yr = Year (start_date);
  start_mo = Month (start_date);
  orig_start_mo = Month (start_date);
  start_dy = Day (start_date);
  end_yr = Year (end_date);
  end_mo = Month (end_date);
  end_dy = Day (end_date);

  /* calculate sizes of history table  */
  if (report_type == 10 || report_type == 11) {
    history_size = DaysSince1800 (end_date) - DaysSince1800 (start_date) + 1;
  } else if (report_type == 12 || report_type == 13) {
    history_size = ((end_yr - start_yr) * 12) + (end_mo - start_mo) + 1;
  } else if (report_type == 20 || report_type == 21) {
    history_size = 24;
  } else if (report_type == 30 || report_type == 31) {
    history_size = 7;
  } else {
    return 0;
  }

  history_table =
    (struct history_record *) calloc (history_size,
    sizeof (struct history_record));
  if (history_table == NULL)
    return 0;
  counts = (int *) calloc (history_size, sizeof (counts));
  if (counts == NULL) {
    free (history_table);
    return 0;
  }

  for (;;) {
    if (start_yr > end_yr)
      break;
    if (start_yr == end_yr && start_mo > end_mo)
      break;

    sprintf (name, "%sY%dM%d", HIST_DIR, start_yr, start_mo);
    if ((fp = fopen (name, "r")) == NULL) {
      start_mo++;
      if (start_mo > 12) {
        start_yr++;
        start_mo = 1;
      }
      continue;
    }

    for (;;) {

      fscanf (fp, "%s %d %d %d %d %d %d %d %d %d\r\n",
        temp.hist_date,
        &temp.hist_weekday,
        &temp.hist_hour,
        &temp.hist_play_time,
        &temp.hist_logins,
        &temp.hist_logouts,
        &temp.hist_deletions,
        &temp.hist_creates, &temp.hist_max_on, &temp.hist_count_on);
      if (Month (temp.hist_date) == orig_start_mo
        && Day (temp.hist_date) < start_dy)
        continue;

      if (Month (temp.hist_date) == end_mo && Day (temp.hist_date) > end_dy) {
        start_mo++;
        if (start_mo > 12) {
          start_yr++;
          start_mo = 1;
        }
        fclose (fp);
        break;
      }

      /* calculate index into history table based on report type */
      if (report_type < 20) {
        if (report_type < 12) {
          indexval =
            DaysSince1800 (temp.hist_date) - DaysSince1800 (start_date);
        } else {
          indexval = ((Year (temp.hist_date) - orig_start_yr) * 12)
            + (Month (temp.hist_date) - orig_start_mo);
        }
      } else if (report_type < 30) {
        indexval = temp.hist_hour;
      } else if (report_type < 40) {
        indexval = temp.hist_weekday;
      }

      /* populate it */
      counts[indexval]++;
      strcpy (history_table[indexval].hist_date, temp.hist_date);
      history_table[indexval].hist_play_time += temp.hist_play_time;
      history_table[indexval].hist_logins += temp.hist_logins;
      history_table[indexval].hist_logouts += temp.hist_logouts;
      history_table[indexval].hist_deletions += temp.hist_deletions;
      history_table[indexval].hist_creates += temp.hist_creates;
      history_table[indexval].hist_count_on += temp.hist_count_on;
      history_table[indexval].hist_max_on =
        UMAX (history_table[indexval].hist_max_on, temp.hist_max_on);

      if (feof (fp)) {
        fclose (fp);
        start_mo++;
        if (start_mo > 12) {
          start_yr++;
          start_mo = 1;
        }
        break;
      }

    }
  }

  /* Calculate averages */
  if (report_type % 2 == 0) {
    for (i = 0; i < history_size; i++) {
      int tempcount;
      if (report_type == 12 || report_type == 10 || report_type == 30) {
        tempcount = counts[i] / 24;
      } else {
        tempcount = counts[i];
      }
      tempcount = UMAX (tempcount, 1);
      history_table[i].hist_play_time /= UMAX (history_table[i].hist_logouts,
        1);
      history_table[i].hist_logins /= tempcount;
      history_table[i].hist_logouts /= tempcount;
      history_table[i].hist_deletions /= tempcount;
      history_table[i].hist_creates /= tempcount;
      if (report_type == 12 || report_type == 10 || report_type == 30) {
        history_table[i].hist_count_on /= UMAX (counts[i], 1);
      } else {
        history_table[i].hist_count_on /= tempcount;
      }
    }
  }

  free (counts);
  return history_size;
}


static void print_report (CHAR_DATA * ch, int report_type, int history_size,
  char *start_date, char *end_date)
{
  BUFFER *fBuf;
  char buf[MAX_STRING_LENGTH];
  char y_axis[MAX_STRING_LENGTH];
  int i;
  char *const dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  char *const month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  };
  char *const hrs[] =
    { "12:00a", "01:00a", "02:00a", "03:00a", "04:00a", "05:00a",
    "06:00a", "07:00a", "08:00a", "09:00a", "10:00a", "11:00a",
    "12:00p", "01:00p", "02:00p", "03:00p", "04:00p", "05:00p",
    "06:00p", "07:00p", "08:00p", "09:00p", "10:00p", "11:00p"
  };

  fBuf = new_buf ();
  sprintf (buf, "                Report for %d/%d/%d to %d/%d/%d\r\n",
    Month (start_date),
    Day (start_date),
    Year (start_date), Month (end_date), Day (end_date), Year (end_date)
    );
  add_buf (fBuf, buf);
  if (report_type == 10) {
    sprintf (buf, "                Daily Averages (per Day)\r\n");
    add_buf (fBuf, buf);
  } else if (report_type == 11) {
    sprintf (buf, "                Daily Totals\r\n");
    add_buf (fBuf, buf);
  } else if (report_type == 12) {
    sprintf (buf, "                Monthly Averages (per Day)\r\n");
    add_buf (fBuf, buf);
  } else if (report_type == 13) {
    sprintf (buf, "                Monthly Totals\r\n");
    add_buf (fBuf, buf);
  } else if (report_type == 20) {
    sprintf (buf, "                Hourly Averages (per Hour)\r\n");
    add_buf (fBuf, buf);
  } else if (report_type == 21) {
    sprintf (buf, "                Hourly Totals\r\n");
    add_buf (fBuf, buf);
  } else if (report_type == 30) {
    sprintf (buf, "                Weekday Averages (per Day)\r\n");
    add_buf (fBuf, buf);
  } else if (report_type == 31) {
    sprintf (buf, "                Weekday Totals\r\n");
    add_buf (fBuf, buf);
  }

  sprintf (buf,
    "       | Playing  Number Number  Number   New     Count   Max \r\n");
  add_buf (fBuf, buf);
  sprintf (buf,
    "       |  Time      of     of      of     Char.   on the Players\r\n");
  add_buf (fBuf, buf);
  sprintf (buf,
    "       | (mins.)  Logins Logouts Deletes Creates   Hour  [NoAvg]\r\n");
  add_buf (fBuf, buf);
  sprintf (buf,
    "       |--------------------------------------------------------\r\n");
  add_buf (fBuf, buf);

  for (i = 0; i < history_size; i++) {

    if (report_type < 12) {
      if (history_table[i].hist_date == NULL || history_table[i].hist_date[0] == '\0')
        sprintf (y_axis, "NoData");
      else
        sprintf (y_axis, "%02d/%02d ", Month (history_table[i].hist_date),
          Day (history_table[i].hist_date));
    } else if (report_type < 20) {
      if (history_table[i].hist_date == NULL || history_table[i].hist_date[0] == '\0')
        sprintf (y_axis, "NoData");
      else
        sprintf (y_axis, "%3s   ",
          month[Month (history_table[i].hist_date) - 1]);
    } else if (report_type < 30) {
      sprintf (y_axis, "%6s", hrs[i]);
    } else {
      sprintf (y_axis, "%3s   ", dow[i]);
    }


    sprintf (buf, "%6s | %7d %7d %7d %7d %7d %7d %7d\r\n",
      y_axis,
      history_table[i].hist_play_time / 60,
      history_table[i].hist_logins,
      history_table[i].hist_logouts,
      history_table[i].hist_deletions,
      history_table[i].hist_creates,
      history_table[i].hist_count_on, history_table[i].hist_max_on);
    add_buf (fBuf, buf);
  }
  page_to_char (buf_string (fBuf), ch);
  free_buf (fBuf);
}

/*!
  This routine maintains accurate high player counts
  and logs player history on an hourly basis
 */
void update_stats ()
{
  FILE *fp;
  time_t t;
  struct tm *time_block;
  char name[MAX_STRING_LENGTH];
  int current_count = 0;
  DESCRIPTOR_DATA *d;

  for ( d = descriptor_list; d != NULL; d = d->next )
    if ( d->connected == CON_PLAYING )
      current_count++;

  g_stats.max_on = UMAX (g_stats.max_on, current_count);

  time (&t);
  time_block = localtime (&t);

  /* we must be starting up - set the fields up and leave */
  if (g_stats.current_hour == 99) {
    MakeDateString (g_stats.current_date, time_block);
    g_stats.current_weekday = time_block->tm_wday;
    g_stats.current_hour = time_block->tm_hour;
    log_string ("Copyover/Startup: Refresh time counters");
    return;
  }

  if (g_stats.current_hour != time_block->tm_hour) {
    /* Update history file */
    sprintf (name, "%sY%dM%d", HIST_DIR, Year (g_stats.current_date),
      Month (g_stats.current_date));
    if ((fp = fopen (name, "a")) == NULL) {
      bug("save_history: fopen", 0);
    } else {
      fprintf (fp, "%s %d %d %d %d %d %d %d %d %d\n\r",
        g_stats.current_date,
        g_stats.current_weekday,
        g_stats.current_hour,
        g_stats.current_play_time,
        g_stats.current_logins,
        g_stats.current_logouts,
        g_stats.current_deletions,
        g_stats.current_creates,
        g_stats.max_on,
        current_count);
    }
    fclose (fp);

    MakeDateString (g_stats.current_date, time_block);
    g_stats.current_weekday = time_block->tm_wday;
    g_stats.current_hour = time_block->tm_hour;
    g_stats.current_play_time = 0;
    g_stats.current_logins = 0;
    g_stats.current_logouts = 0;
    g_stats.current_deletions = 0;
    g_stats.current_creates = 0;
  }

  /* The date has changed - let us refresh count and current julian day */
  if (time_block->tm_yday != g_stats.current_juldate) {
    g_stats.current_juldate = time_block->tm_yday;
    g_stats.max_on = current_count;
  }

  return;
}