/* StatServ routines

   OperStats - Copyright (c) 2000-2003 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

#include "../inc/operstats.h"

ServStat *statlist[HASHSIZE];
TLD *tldlist = NULL;
Zone *zonelist = NULL;
Boolean_T tldlookup = FALSE, zonelookup = FALSE;

static void do_help (User *u);
static void do_stats (User *u);
static void do_map (User *u);
static void do_servers (User *u);
static void do_tld (User *u);
static void do_tldmap (User *u);
static void do_tldstats (User *u);
static void do_zone (User *u);
static void do_reset (User *u);
static void do_flush (User *u);
static void do_sendpings (User *u);

static uint8 showleafs (User *u, char *server, char *prevpre, char *arrowchar);
static uint8 numleafs (char *server);

/* Main StatServ routine. */
void statserv (User *u, char *buf)  
{
    char *cmd, *s;
    char orig[BUFSIZE];

    /* Make a copy of the original message. */ 
    strscpy (orig, buf, sizeof (orig));   

    cmd = strtok (buf, " ");

    if (!cmd)
	return;
    
    if (!stricmp (cmd, "\1PING"))
    {
	if (!(s = strtok (NULL, "")))
	    s = "0";
	strip (s);
	ctcpreply (s_StatServ, u->nick, "PING %s", s);
	return;
    }
    else if (!stricmp (cmd, "\1VERSION\1"))
    {
	ctcpreply (s_StatServ, u->nick, "VERSION OperStats v%s", version);
	return;
    }

    /* A CTCP we don't recognize.. Ignore it. */
    else if (*cmd == '\1')
	return;

    else
    {
	Hash *command, hash_table[] =
	{
	    {"HELP",		H_NONE,		do_help},
	    {"STATS",		H_NONE,		do_stats},
	    {"TLD",		H_NONE,		do_tld},
	    {"TLDMAP",		H_NONE,		do_tldmap},
	    {"TLDSTATS",	H_NONE,		do_tldstats},
	    {"ZONE",		H_NONE,		do_zone},
	    {"MAP",		H_NONE,		do_map},
	    {"SERVERS",		H_NONE,		do_servers},
	    {"SEARCH",		H_IRCOP,	do_search},
	    {"AUTH",		H_IRCOP,	do_auth},
	    {"DEAUTH",		H_SRA,		do_deauth},
	    {"SHUTDOWN",	H_SRA,		do_shutdown},
	    {"RESTART",		H_SRA,		do_restart},
#ifdef RAWINJECT
	    {"RAW",		H_SRA,		do_raw},
	    {"INJECT",		H_SRA,		do_inject},
#endif
	    {"DEBUG",		H_SRA,		do_debug},
	    {"UPDATE",		H_SRA,		do_update},
	    {"REHASH",		H_SRA,		do_rehash},
	    {"CYCLELOGS",	H_SRA,		do_cyclelogs},
	    {"SETTINGS",	H_SRA,		do_settings},
	    {"SENDPINGS",	H_SRA,		do_sendpings},
	    {"FLUSH",		H_SRA,		do_flush},
	    {"RESET",		H_SRA,		do_reset},
	    {NULL}
	};
            
	if ((command = get_hash (s_StatServ, u, strupper (cmd), hash_table)))
	{
	    /* These are only available if OperServ is OFF. */
	    if ((command->process == do_auth ||
		 command->process == do_deauth ||
#ifdef RAWINJECT
		 command->process == do_raw ||
		 command->process == do_inject ||
#endif
		 command->process == do_debug ||
		 command->process == do_shutdown ||
		 command->process == do_restart ||
		 command->process == do_settings ||
		 command->process == do_update ||
		 command->process == do_search ||
		 command->process == do_cyclelogs ||
		 command->process == do_rehash) &&
		 operserv_on == TRUE)
	    {
		notice (s_StatServ, u->nick, RPL_UNKNOWN_COMMAND,
		    strupper (cmd), haveserv_on == TRUE ? "" : "MSG ",
		    s_StatServ, haveserv_on == TRUE ? "" : securitysetting == 1
		    ? "@" : "", haveserv_on == TRUE ? "" : securitysetting == 1
		    ? me.name : "");

		return;
	    }

	    (*command->process) (u);

	    /* If OperServ is off, we want to snoop several commands. */
	    if ((command->process == do_deauth ||
#ifdef RAWINJECT
		 command->process == do_raw ||
		 command->process == do_inject ||
#endif
		 command->process == do_debug ||
		 command->process == do_update ||
		 command->process == do_rehash ||
		 command->process == do_search ||
		 command->process == do_cyclelogs ||
		 command->process == do_settings) &&
		 operserv_on == FALSE)
	    {
		log ("StatServ: <%s> %s", u->nick, orig);
		snoop (s_StatServ, "<%s> %s", u->nick, orig);
	    }

	    if (command->process == do_auth)
	    {
		log ("StatServ: <%s> %s ...", u->nick, cmd);
		snoop (s_StatServ, "<%s> %s ...", u->nick, cmd);
	    }
	}
    }
}

/* Return a help message. */
static void do_help (User *u) 
{
    char *cmd = strtok (NULL, " ");
            
    if (!cmd)
	statserv_help_index (u);
    else
    {
	Hash *command, hash_table[] =
	{
	    {"STATS",		H_NONE,		statserv_help_stats},
	    {"TLD",		H_NONE,		statserv_help_tld},
	    {"TLDMAP",		H_NONE,		statserv_help_tldmap},
	    {"TLDSTATS",	H_NONE,		statserv_help_tldstats},
	    {"ZONE",		H_NONE,		statserv_help_zone},
	    {"MAP",		H_IRCOP,	statserv_help_map},
	    {"SERVERS",		H_IRCOP,	statserv_help_servers},
	    {"SEARCH",		H_IRCOP,	shared_help_search},
	    {"AUTH",		H_IRCOP,	shared_help_auth},
	    {"DEAUTH",		H_SRA,		shared_help_deauth},
	    {"SHUTDOWN",	H_SRA,		shared_help_shutdown},
	    {"RESTART",		H_SRA,		shared_help_restart},
#ifdef RAWINJECT
	    {"RAW",		H_SRA,		shared_help_raw},
	    {"INJECT",		H_SRA,		shared_help_inject},
#endif
	    {"DEBUG",		H_SRA,		shared_help_debug},
	    {"UPDATE",		H_SRA,		shared_help_update},
	    {"REHASH",		H_SRA,		shared_help_rehash},
	    {"CYCLELOGS",	H_SRA,		shared_help_cyclelogs},
	    {"SETTINGS",	H_SRA,		shared_help_settings},
	    {"SENDPINGS",	H_SRA,		statserv_help_sendpings},
	    {"FLUSH",		H_SRA,		statserv_help_flush},
	    {"RESET",		H_SRA,		statserv_help_reset},
	    {NULL}
	};

	if ((command = get_help_hash (s_StatServ, u, strupper (cmd), hash_table)))
	{
	    /* Hide disabled or non applicable commands. */
	    if (((command->process == statserv_help_tld) && tldlookup == FALSE) ||
		((command->process == statserv_help_tldmap) && tldlookup == FALSE) ||
		((command->process == statserv_help_tldstats) && tldlookup == FALSE) ||
		((command->process == statserv_help_zone) && zonelookup == FALSE) ||
		((command->process == shared_help_search) && operserv_on == TRUE) ||
		((command->process == shared_help_auth) && operserv_on == TRUE) ||
		((command->process == shared_help_deauth) && operserv_on == TRUE) ||
		((command->process == shared_help_shutdown) && operserv_on == TRUE) ||
		((command->process == shared_help_restart) && operserv_on == TRUE) ||
#ifdef RAWINJECT
		((command->process == shared_help_raw) && operserv_on == TRUE) ||
		((command->process == shared_help_inject) && operserv_on == TRUE) ||
#endif
		((command->process == shared_help_debug) && operserv_on == TRUE) ||
		((command->process == shared_help_update) && operserv_on == TRUE) ||
		((command->process == shared_help_rehash) && operserv_on == TRUE) ||
		((command->process == shared_help_cyclelogs) && operserv_on == TRUE) ||
		((command->process == shared_help_settings) && operserv_on == TRUE))
	    {
		notice (s_StatServ, u->nick, RPL_NO_HELP, strupper (cmd));
		return;
	    }

	    (*command->process) (u);
	}
    }
}

/* Show some basic stats. */
static void do_stats (User *u)
{
    char *param = strtok (NULL, " ");
    uint64 curspeedsample = speedsample;
    time_t now = time (NULL);

    if (param && !strchr (param, '+'))
    {
	ServStat *serverstat;

	serverstat = findservstat (param);

	if (!serverstat)
	{
	    notice (s_StatServ, u->nick, SS_SERVER_NOT_FOUND, param);
	    return;
	}

	notice (s_StatServ, u->nick, SS_SERVSTATS_HEADER, serverstat->name);
	notice (s_StatServ, u->nick, " ");
	notice (s_StatServ, u->nick, SS_SERVSTATS_SINCE, get_time
	    (serverstat->statssince, 4), time_ago (serverstat->statssince));

	if (serverstat->splits)
	    notice (s_StatServ, u->nick, SS_SERVSTATS_LASTSPLIT, get_time
		(serverstat->lastsplit, 4), time_ago (serverstat->lastsplit));

	notice (s_StatServ, u->nick, SS_SERVSTATS_SPLITS, serverstat->splits,
	    serverstat->is_split ? " (Split)" : "");

	if (serverstat->lastquit)
	    notice (s_StatServ, u->nick, SS_STATS, "Last Quit Message", serverstat->lastquit);

	if (serverstat->clients)
	    notice (s_StatServ, u->nick, SS_STATS, "Current Clients", itoa (serverstat->clients));

	if (serverstat->maxclients)
	    notice (s_StatServ, u->nick, SS_STATS, "Max Clients", itoa (serverstat->maxclients));

	if (serverstat->maxtime)
	    notice (s_StatServ, u->nick, SS_STATS, "Max Clients Time", get_time (serverstat->maxtime, 1));

	if (serverstat->lag.tv_sec == 999)
	    notice (s_StatServ, u->nick, SS_SERVSTATS_LAG, "999");
	else
	    notice (s_StatServ, u->nick, SS_SERVSTATS_LAG, tv2ms (&(serverstat->lag)));

	if (timerisset (&(serverstat->peaklag)))
	    notice (s_StatServ, u->nick, SS_SERVSTATS_PEAKLAG, tv2ms (&(serverstat->peaklag)));

	if (serverstat->is_split)
	{
	    if (serverstat->penalize)
		notice (s_StatServ, u->nick, SS_STATS, "Total Downtime", time_ago
		    (now - (serverstat->splittime + (now - serverstat->lastsplit))));
	    else
		notice (s_StatServ, u->nick, SS_STATS, "Total Downtime", time_ago
		    (now - serverstat->splittime));

	    notice (s_StatServ, u->nick, SS_STATS, "Split For", time_ago
		(serverstat->lastsplit));
	}
	else
	    notice (s_StatServ, u->nick, SS_STATS, "Total Downtime", time_ago
		(now - serverstat->splittime));

	if (serverstat->lastdowntime)
	    notice (s_StatServ, u->nick, SS_STATS, "Last Downtime", duration
		(serverstat->lastdowntime, 2));

	/* Update their uptime */
	serverstat->uptime += (now - serverstat->uptimetime);
	serverstat->uptimetime = now;

        if (serverstat->uptime > serverstat->maxuptime)
	    serverstat->maxuptime = serverstat->uptime;

	notice (s_StatServ, u->nick, SS_STATS, "Current Uptime", duration
	    (serverstat->uptime, 2));

	notice (s_StatServ, u->nick, SS_STATS, "Highest Uptime", duration
	    (serverstat->maxuptime, 2));

	notice (s_StatServ, u->nick, " ");
	notice (s_StatServ, u->nick, SS_STATS_FOOTER);
    }
    else
    {
	time_t uptime;
	uint16 ops = countops ();

	uptime = (now - me.since);

	/* Make sure they're not giving us bogus tokens */
	if (param && (!strchr (param, 'C') && !strchr (param, 'c') &&
		      !strchr (param, 'M') && !strchr (param, 'm') &&
		      !strchr (param, 'N') && !strchr (param, 'n') &&
		      !strchr (param, 'U') && !strchr (param, 'u') &&
		      !strchr (param, 'D') && !strchr (param, 'd')))
	{
	    notice (s_StatServ, u->nick, SS_BAD_TOKEN, param);
	    return;
	}

	notice (s_StatServ, u->nick, SS_STATS_HEADER, network_name);
	notice (s_StatServ, u->nick, " ");

	if (!param || (param && strchr (param, 'C')) ||
	    (param && strchr (param, 'c')))
	    notice (s_StatServ, u->nick, SS_STATS_CURUSER, userstat,
		awaystat ? itoa (awaystat) : "", awaystat ? " Away" :
		"", awaystat ? ", " : "", ops, ops == 1 ? "" : "s");

	if (!param || (param && strchr (param, 'M')) ||
	    (param && strchr (param, 'm')))
	    notice (s_StatServ, u->nick, SS_STATS, "Max Users", itoa (maxusercnt));

	if (!param || (param && strchr (param, 'C')) ||
	    (param && strchr (param, 'c')))
	{
	    if (is_oper (u))
		notice (s_StatServ, u->nick, SS_STATS_CURCHANOPER,
		    countchans (0), countchans (1));
	    else
		notice (s_StatServ, u->nick, SS_STATS, "Current Channels", itoa (countchans (0)));
	}

	if (!param || (param && strchr (param, 'M')) ||
	    (param && strchr (param, 'm')))
	    notice (s_StatServ, u->nick, SS_STATS, "Max Channels", itoa (maxchancnt));

	if (!param || (param && strchr (param, 'C')) ||
	    (param && strchr (param, 'c')))
	    notice (s_StatServ, u->nick, SS_STATS, "Current Servers", itoa (servstat));

	if (!param || (param && strchr (param, 'M')) ||
	    (param && strchr (param, 'm')))
	{
	    notice (s_StatServ, u->nick, SS_STATS, "Max Servers", itoa (maxservcnt));
	    notice (s_StatServ, u->nick, SS_STATS, "Max Hits", itoa (maxhitcnt));
	    notice (s_StatServ, u->nick, SS_STATS, "Max IRCOps", itoa (maxopers));
	}

	if (!param || (param && strchr (param, 'N')) ||
	    (param && strchr (param, 'n')))
	{
	    notice (s_StatServ, u->nick, SS_STATS_TRANSFER, (float) SIZEREAL (transfer),
		SIZENAME (transfer), (float) SIZEREAL (transfer / uptime),
		SIZENAME (transfer / uptime));

	    notice (s_StatServ, u->nick, SS_STATS_CURSPEED, (float) SIZEREAL (curspeedsample),
		SIZENAME (curspeedsample), (float) SIZEREAL (speedstat), SIZENAME (speedstat));

	    notice (s_StatServ, u->nick, SS_STATS, "Peak Speed Date", get_time (peaktime, 1));
	}

	if (!param || (param && strchr (param, 'U')) ||
	    (param && strchr (param, 'u')))
	{
	    notice (s_StatServ, u->nick, SS_STATS_UPTIME, "Current", duration (uptime, 2));

	    /* If the current uptime is higher than the maxuptime, update! */
	    if (uptime > maxuptime)
		maxuptime = uptime;

	    notice (s_StatServ, u->nick, SS_STATS_UPTIME, "Highest", duration (maxuptime, 2));
	}

	if (!param || (param && strchr (param, 'C')) ||
	    (param && strchr (param, 'c')))
	    notice (s_StatServ, u->nick, SS_STATS, "Current Time", get_time (now, 1));

	if (!param || (param && strchr (param, 'M')) ||
	    (param && strchr (param, 'm')))
	{
	    notice (s_StatServ, u->nick, SS_STATS, "Max User Time", get_time (maxusertime, 1));
	    notice (s_StatServ, u->nick, SS_STATS, "Max Channel Time", get_time (maxchantime, 1));
	    notice (s_StatServ, u->nick, SS_STATS, "Max Server Time", get_time (maxservtime, 1));
	}

	if (!param || (param && strchr (param, 'D')) ||
	    (param && strchr (param, 'd')))
	{
	    /* We only want this blank line here if theres something else
	       above.
	     */
	    if (!param || (param && (strchr (param, 'C') || strchr (param, 'c')
			    ||  strchr (param, 'M') || strchr (param, 'm')
			    ||  strchr (param, 'N') || strchr (param, 'n')
			    ||  strchr (param, 'U') || strchr (param, 'u'))))
		notice (s_StatServ, u->nick, " ");

	    notice (s_StatServ, u->nick, SS_STATS_DAILY, get_time (now, 0));
	    notice (s_StatServ, u->nick, " ");
	    notice (s_StatServ, u->nick, SS_STATS, "Hits", itoa (dailyhitcnt));
	    notice (s_StatServ, u->nick, SS_STATS_DAILY_X, "Max Users", dailyuserstat,
		get_time (dailyusertime, 2));
	    notice (s_StatServ, u->nick, SS_STATS_DAILY_X, "Max Channels", dailychanstat,
		get_time (dailychantime, 2));
	    notice (s_StatServ, u->nick, SS_STATS_DAILY_X, "Max Servers", dailyservstat,
		get_time (dailyservtime, 2));
	}

	notice (s_StatServ, u->nick, " ");
	notice (s_StatServ, u->nick, SS_STATS_FOOTER);
    }
}

/* Show a listing of servers and their version replies. */
static void do_servers (User *u)
{
    uint16 i;
    ServStat *server;

    notice (s_StatServ, u->nick, SS_SERVERS_HEADER);
    notice (s_StatServ, u->nick, " ");

    if (ignoreulines_on == FALSE)
    {
	uint8 tmp = servcount ();

	notice (s_StatServ, u->nick, "%s (operstats-%s. %s%s%s%s%s) (%d Max Users) (0 Splits)",
	    me.name, version, (operserv_on == TRUE) ? "O" : "", (statserv_on == TRUE) ? "S" : "",
	    (globalnoticer_on == TRUE) ? "g" : "", (spamserv_on == TRUE) ? "s" : "",
	    debuglevel ? "d" : "", tmp + 1);
    }

    for (i = 0; i < HASHSIZE; i++)
    {
	for (server = statlist[i]; server; server = server->next)
	{
	    if (finduline (server->name) && ignoreulines_on == TRUE)
		continue;

	    notice (s_StatServ, u->nick, "%s (%s %s) (%d Max Users) (%d Split%s)%s%s%s",
		server->name, server->version ? server->version : "Unknown",
		server->flags ? server->flags : "XX", server->maxclients,
		server->splits, server->splits == 1 ? "" : "s", server->is_split ? " (" : "",
		server->is_split ? "Split" : "", server->is_split ? ")" : "");
	}
    }

    notice (s_StatServ, u->nick, " ");
    notice (s_StatServ, u->nick, SS_SERVERS_FOOTER);
}

/* Display the country/group associated with a given TLD. */
static void do_tld (User *u)
{
    TLD *tld;
    char *param = strtok (NULL, "");

    if (tldlookup == FALSE)
    {
	notice (s_StatServ, u->nick, RPL_UNKNOWN_COMMAND, "TLD",
	    haveserv_on == TRUE ? "" : "MSG ", s_StatServ, haveserv_on == TRUE ?
	    "" : securitysetting == 1 ? "@" : "", haveserv_on == TRUE ? "" :
	    securitysetting == 1 ? me.name : "");

	return;
    }

    if (!param)
    {
	notice (s_StatServ, u->nick, RPL_SYNTAX, "TLD TopLevelDomain|Country");
	errmoreinfo (s_StatServ, u->nick, "TLD");
	return;
    }

    tld = findtld (param);

    if (!tld)
    {
	notice (s_StatServ, u->nick, SS_TLD_NOT_FOUND, param);
	return;
    }
    else
	notice (s_StatServ, u->nick, SS_TLD_IS, tld->name, tld->desc);
}

/* Show either the user count/percentage for the given TLD, or
   show all active TLDs.
 */
static void do_tldmap (User *u)
{
    TLD *tld;
    char *param = strtok (NULL, " ");

    if (tldlookup == FALSE)
    {
	notice (s_StatServ, u->nick, RPL_UNKNOWN_COMMAND, "TLDMAP",
	    haveserv_on == TRUE ? "" : "MSG ", s_StatServ, haveserv_on == TRUE ? "" :
	    securitysetting == 1 ? "@" : "", haveserv_on == TRUE ? "" :
	    securitysetting == 1 ? me.name : "");

	return;
    }

    if (!param)
    {
	notice (s_StatServ, u->nick, SS_TLDMAP_START);
	notice (s_StatServ, u->nick, " ");

	for (tld = tldlist; tld; tld = tld->next)
	    if (tld->cnt)
		notice (s_StatServ, u->nick, SS_TLDMAP, tld->desc,
		    " (", tld->name, ")", tld->cnt, tld->cnt == 1 ? "" : "s",
		    (float) (tld->cnt * 100) / usercnt);

	if (tldnores)
	    notice (s_StatServ, u->nick, SS_TLDMAP, "Non Resolving", "", "",
		"", tldnores, tldnores == 1 ? "" : "s",
		(float) (tldnores * 100) / usercnt);

	notice (s_StatServ, u->nick, " ");
	notice (s_StatServ, u->nick, SS_TLDMAP_END);
    }
    else
    {
	tld = findtld (param);

	if (!tld)
	{
	    notice (s_StatServ, u->nick, SS_TLD_NOT_FOUND, param);
	    return;
	}
	else
	    notice (s_StatServ, u->nick, SS_TLDMAP, tld->desc, " (", tld->name,
		")", tld->cnt, tld->cnt == 1 ? "" : "s",
		(float) (tld->cnt * 100) / usercnt);
    }
}

/* Show the per-tld daily hit counts. */
static void do_tldstats (User *u)
{
    TLD *tld;
    uint32 i, j = 0;
    char *param = strtok (NULL, " ");

    if (tldlookup == FALSE)
    {
	notice (s_StatServ, u->nick, RPL_UNKNOWN_COMMAND, "TLDSTATS",
	    haveserv_on == TRUE ? "" : "MSG ", s_StatServ, haveserv_on == TRUE ? "" :
	    securitysetting == 1 ? "@" : "", haveserv_on == TRUE ? "" :
	    securitysetting == 1 ? me.name : "");

	return;
    }

    if (!param)
    {
	notice (s_StatServ, u->nick, SS_TLDSTATS_START,
	    get_time (time (NULL), 0));

	for (tld = tldlist; tld; tld = tld->next)
	    if (tld->hits)
	    {
		i = (tld->hits * 100) / dailyhitcnt;
		j++;
		notice (s_StatServ, u->nick, SS_TLDSTATS, tld->desc, tld->name,
		    i, tld->hits, tld->hits == 1 ? "" : "s");
	    }

	if (tldnoreshits)
	{
	    i = (tldnoreshits * 100) / dailyhitcnt;
	    j++;
	    notice (s_StatServ, u->nick, SS_TLDSTATS, "Non Resolving",
		"Unknown", i, tldnoreshits, tldnoreshits == 1 ? "" : "s");
	}

	notice (s_StatServ, u->nick, SS_TLDSTATS_END, dailyhitcnt, j);
    }
    else
    {
	tld = findtld (param);

	if (!tld)
	{
	    notice (s_StatServ, u->nick, SS_TLD_NOT_FOUND, param);
	    return;
	}
	else
	{
	    i = (tld->hits * 100) / dailyhitcnt;
	    notice (s_StatServ, u->nick, SS_TLDSTATS, tld->desc, tld->name,
		i, tld->hits, tld->hits == 1 ? "" : "s");
	}
    }
}

/* Display the current time and/or GMT offset of the given timezone. */
static void do_zone (User *u)
{
    Zone *zone;
    char *param = strtok (NULL, " ");
    uint8 found = 0;

    if (zonelookup == FALSE)
    {
	notice (s_StatServ, u->nick, RPL_UNKNOWN_COMMAND, "ZONE",
	    haveserv_on == TRUE ? "" : "MSG ", s_StatServ, haveserv_on == TRUE ? "" :
	    securitysetting == 1 ? "@" : "", haveserv_on == TRUE ? "" :
	    securitysetting == 1 ? me.name : "");

	return;
    }

    if (!param)
    {
	notice (s_StatServ, u->nick, RPL_SYNTAX, "ZONE TimeZone");
	errmoreinfo (s_StatServ, u->nick, "ZONE");
	return;
    }

    /* Search the TimeZone list, returning matches as they're found.
       Note that this doesn't call an external function: Since several
       TimeZones may share the same abbreviation, we'll search the list
       and return all matches.
     */
    for (zone = zonelist; zone; zone = zone->next)
    {
        if (!stricmp (param, zone->name))
	{
	    notice (s_StatServ, u->nick, SS_ZONE_IS, zone_time (zone->offset),
		zone->desc, zone->name, zone->noffset);
	    found = 1;
	}
    }

    /* If we didn't find anything, let them know. */
    if (!found)
    {
	notice (s_StatServ, u->nick, SS_ZONE_NOT_FOUND, strupper (param));
	return;
    }
}

/* Reset a given server's stats */
static void do_reset (User *u)
{
    ServStat *server;
    char *param = strtok (NULL, " ");

    if (!param)
    {
	notice (s_StatServ, u->nick, RPL_SYNTAX, "RESET Server|ALL");
	errmoreinfo (s_StatServ, u->nick, "RESET");
	return;
    }

    /* Reset a specific server */
    if (stricmp (param, "ALL"))
    {
	server = findservstat (param);

	if (!server)
	{
	    notice (s_StatServ, u->nick, SS_SERVER_NOT_FOUND, param);
	    return;
	}
	else
	{
	    time_t now = time (NULL);

	    server->statssince = now;
	    server->lastsplit = now;
	    server->maxtime = now;
	    server->maxclients = server->clients;
	    server->lastdowntime = 0;
	    server->peaklag.tv_sec = 0;
	    server->peaklag.tv_usec = 0;
	    server->splittime = 0;
	    server->uptime = 0;
	    server->maxuptime = 0;

	    if (server->is_split)
		server->splits = 1;
	    else
		server->splits = 0;

	    if (server->lastquit)
		free (server->lastquit);

	    server->lastquit = "N/A";

	    /* Query it for uptime */
	    send_cmd (me.name, "%s u %s", me.token ? "2" : "STATS", server->name);

	    notice (s_StatServ, u->nick, SS_SERVER_RESET, server->name);
	}
    }

    /* Reset ALL servers */
    else
    {
	uint16 i;

	for (i = 0; i < HASHSIZE; i++)
	{
	    for (server = statlist[i]; server; server = server->next)
	    {
		time_t now = time (NULL);

		server->statssince = now;
		server->lastsplit = now;
		server->maxtime = now;
		server->maxclients = server->clients;
		server->lastdowntime = 0;
		server->peaklag.tv_sec = 0;
		server->peaklag.tv_usec = 0;
		server->splittime = 0;

		if (server->is_split)
		    server->splits = 1;
		else
		    server->splits = 0;

		if (server->lastquit)
		    free (server->lastquit);

		server->lastquit = "N/A";
	    }
	}

	notice (s_StatServ, u->nick, SS_SERVER_RESET, "all servers");
    }
}

/* FLUSH: Remove a server from the stats list (ie delinked) */
static void do_flush (User *u)
{
    ServStat *server;
    char *param = strtok (NULL, " ");

    if (!param)
    {
	notice (s_StatServ, u->nick, RPL_SYNTAX, "FLUSH Server");
	errmoreinfo (s_StatServ, u->nick, "FLUSH");
	return;
    }

    server = findservstat (param);

    if (!server)
    {
	notice (s_StatServ, u->nick, SS_SERVER_NOT_FOUND, param);
	return;
    }
    else
    {
	/* Only flush servers that aren't online. */
	if (findserv (param))
	{
	    notice (s_StatServ, u->nick, SS_CANT_FLUSH);
	    return;
	}

	notice (s_StatServ, u->nick, SS_SERVER_FLUSHED, server->name);

	if (server->version && strlen (server->version))
	    free (server->version);

	if (server->flags && strlen (server->flags))
	    free (server->flags);

	if (server->lastquit && strlen (server->lastquit))
	    free (server->lastquit);

	if (server->prev)
	    server->prev->next = server->next;
	else
	    statlist[SSHASH(server->name)] = server->next;
	if (server->next)
	    server->next->prev = server->prev;

	if (server->name)
	    free (server->name);

	free (server);
    }
}

/* Show a map of the network. */
static void do_map (User *u)
{
    Server *s;
    uint8 mycount, myops;

    /* Get our numbers */
    mycount = servcount ();
    myops = opcount ();

    notice (s_StatServ, u->nick, SS_MAP_HEADER, network_name);
    notice (s_StatServ, u->nick, " ");

    /* First, show ourselves. The info given here is mostly fake, just
       everything looks unified.
     */
    notice (s_StatServ, u->nick, "%s (0ms) (%d user%s (%d op%s), %3.2f%%)",
	me.name, mycount, mycount == 1 ? "" : "s", myops,
	myops == 1 ? "" : "s", ((float) (mycount * 100) / userstat));

    /* Find out which server we are connected to */
    for (s = firstserv (); s; s = nextserv ())
	if (s->hops == 1)
	    showleafs (u, s->name, "", "`");

    notice (s_StatServ, u->nick, " ");
    notice (s_StatServ, u->nick, SS_MAP_FOOTER);
}

/* Associated MAP function */
uint8 showleafs (User *u, char *server, char *prevpre, char *arrowchar)
{
    Server *s;
    ServStat *serverstat;
    uint8 shown = 0, num = 0;
    uint16 i, j = 0, k = 0;
    char buf[BUFSIZE];
    char pre[BUFSIZE];
    char *nextpre;
    static uint8 depth = 0;
 
    nextpre = malloc (BUFSIZE);

    snprintf (pre, sizeof (pre) + 1, prevpre);

    *buf = 0;

    for (i = 0; i < HASHSIZE; i++)
    {
	if (shown)
	    break;

	for (s = servlist[i]; s; s = s->next)
	{
	    uint8 islagged = 0, noreply = 0;

	    /* Hide U:Lined servers. */
	    if (finduline (s->name) && ignoreulines_on == TRUE)
		if (!is_oper (u))
		    continue;

	    /* Also hide servers with ulined uplinks (aka, jupes) */
	    if (finduline (s->uplink) && ignoreulines_on == TRUE)
		if (!is_oper (u))
		    continue;

	    if (match_wild_nocase (server, s->name))
	    {
		serverstat = findservstat (s->name);

		if (serverstat->lag.tv_sec == 999)
		    noreply = islagged = 1;

		if (tv2ms (&(serverstat->lag)) >= (lagthreshold * 1000))
		    islagged = 1;

		if (s->hops != 1)
		    strcat (buf, "  ");

		strcat (buf, pre);
		strcat (buf, arrowchar);
		strcat (buf, "-");

		if (islagged)
		    strcat (buf, "\2");

		strcat (buf, s->name);

		notice (s_StatServ, u->nick, "%s%s (%dms) (%d user%s, (%d op%s), %3.2f%%)",
		    buf, islagged ? "\2" : "", noreply ? 999 : tv2ms (&(serverstat->lag)),
		    serverstat->clients, serverstat->clients == 1 ? "" : "s", serverstat->opers,
		    serverstat->opers == 1 ? "" :  "s", ((float) (serverstat->clients * 100) / userstat));

		shown = 1;
	    }
	}
    }

    j = numleafs (server);
    k = depth;

    for (i = 0; i < HASHSIZE; i++)
    {
	for (s = servlist[i]; s; s = s->next)
	{
	    depth = k;

	    if (!stricmp (s->uplink, server))
	    {
		snprintf (nextpre, strlen (pre) + 1, pre);

		if (depth++ > 0)
		{
		    if (arrowchar[0] == '`')
		    {
			if (s->hops == 1)
			    strcat (nextpre, "    ");
			else
			    strcat (nextpre, "  ");
		    }
		    else
		    {
			if (s->hops == 1)
			    strcat (nextpre, "  | ");
			else
			    strcat (nextpre, "| ");
		    }
		}

		if (j > ++num)
		{
		    if (s->hops == 1)
			showleafs (u, s->name, nextpre, "  |");
		    else
			showleafs (u, s->name, nextpre, "|");
		}
		else
		{
		    if (s->hops == 1)
			showleafs (u, s->name, nextpre, "  `");
		    else
			showleafs (u, s->name, nextpre, "`");
		}
	    }
	}
    }

    free (nextpre);
    depth = 0;
    return 1;
}

/* Associated MAP function */
uint8 numleafs (char *server)
{
    Server *s;
    uint8 num = 0;

    for (s = firstserv (); s; s = nextserv ())
	if (!stricmp (s->uplink, server))
	    num++;

    return (num);
}

/* Load the StatServ DB from disk */
void load_ss_dbase ()
{
    FILE *f = fopen (STATSERV_DB, "r");
    char *item, *s, dBuf[2048], backup[2048];
    uint8 dbver = 0, dolog = 0, dbend = 0, ssin = 0;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

#ifdef HAVE_GETTIMEOFDAY
    log_sameline (1, "SS:DB: Loading... ");
#else
    log ("SS:DB: Loading...");
#endif

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (0, "No %s found!", STATSERV_DB);
#else
	log ("SS:DB: No %s found!", STATSERV_DB);
#endif

	dolog = 1;

	/* Check for a db.save */
	strcpy (backup, STATSERV_DB);
	strcat (backup, ".save");

	if (rename (backup, STATSERV_DB) > 0)
	{
#ifdef HAVE_GETTIMEOFDAY
	    log_sameline (0, "Recovering %s.save", STATSERV_DB);
#else
	    log ("SS:DB: Recovering %s.save", STATSERV_DB);
#endif
	    f = fopen (STATSERV_DB, "r");
	}
	else
	    return;
    }

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    while (fgets (dBuf, 2047, f))
    {
	item = strtok (dBuf, " ");

	/* Ignore certain things.. */
	if (*item == '#' || *item == '\n' || *item == '\t' || *item == ' ' ||
	    *item == '\0' || *item == '\r' || !*item)
	    continue;

	if (!strcmp (item, "SV"))
	    if ((s = strtok (NULL, "\n"))) { dbver = atoi (s); }

	/* Statistics */
	if (!strcmp (item, "ST"))
	{
	    maxusercnt	= atoi (strtok (NULL, " "));
	    maxusertime	= atol (strtok (NULL, " "));
	    maxchancnt	= atoi (strtok (NULL, " "));
	    maxchantime	= atol (strtok (NULL, " "));
	    maxservcnt	= atoi (strtok (NULL, " "));
	    maxservtime	= atol (strtok (NULL, " "));
	    maxhitcnt	= atoi (strtok (NULL, " "));

	    if (dbver > 3)
		maxuptime = atoi (strtok (NULL, " "));

	    if (dbver > 4)
	    {
		speedstat = strtoull (strtok (NULL, " "), NULL, 0);
		peaktime = atoi (strtok (NULL, "\n"));
	    }
	    else
	    {
		speedstat = strtoull (strtok (NULL, "\n"), NULL, 0);

		/* Peaktime wasn't stored in v4 dbs, so set it to now */
		peaktime = time (NULL);
	    }

	    continue;
	}

	/* Daily Stats */
	if (!strcmp (item, "SD"))
	{
	    dailyuserstat = atoi (strtok (NULL, " "));
	    dailyusertime = atol (strtok (NULL, " "));
	    dailychanstat = atoi (strtok (NULL, " "));
	    dailychantime = atol (strtok (NULL, " "));
	    dailyservstat = atoi (strtok (NULL, " "));
	    dailyservtime = atol (strtok (NULL, "\n"));

	    continue;
	}

	/* Server Stats */
	if (!strcmp (item, "SS"))
	{
	    ServStat *serverstat, **list;
	    char *name, *since, *lastsplit, *issplit, *splittime, *splits;
	    char *lastdowntime, *isuline, *peaklagsec, *peaklagusec;
	    char *maxtime, *maxclients, *lastquit, *suptime, *smaxuptime;

	    name = strtok (NULL, " ");
	    since = strtok (NULL, " ");
	    lastsplit = strtok (NULL, " ");
	    issplit = strtok (NULL, " ");
	    splittime = strtok (NULL, " ");
	    splits = strtok (NULL, " ");
	    lastdowntime = strtok (NULL, " ");
	    isuline = strtok (NULL, " ");
	    peaklagsec = strtok (NULL, " ");
	    peaklagusec = strtok (NULL, " ");
	    maxtime = strtok (NULL, " ");
	    maxclients = strtok (NULL, " ");

	    if (dbver > 4)
	    {
		suptime = strtok (NULL, " ");
		smaxuptime = strtok (NULL, " ");
	    }
	    else
		suptime = smaxuptime = "0";

	    lastquit = strtok (NULL, "\n");

	    if (!(name && since && lastsplit && issplit && splittime &&
		splits && lastdowntime && isuline && peaklagsec &&
		peaklagusec && maxtime && maxclients && lastquit &&
		suptime && smaxuptime))
		continue;

	    serverstat = scalloc (sizeof (ServStat), 1);

	    serverstat->name = sstrdup (name);
	    serverstat->statssince = atol (since);
	    serverstat->lastsplit = atol (lastsplit);
	    serverstat->is_split = atoi (issplit);
	    serverstat->splittime = atol (splittime);
	    serverstat->splits = atoi (splits);
	    serverstat->lastdowntime = atoi (lastdowntime);
	    serverstat->is_uline = atoi (isuline);
	    serverstat->peaklag.tv_sec = atoi (peaklagsec);
	    serverstat->peaklag.tv_usec = atoi (peaklagusec);
	    serverstat->maxtime = atol (maxtime);
	    serverstat->maxclients = atoi (maxclients);
	    serverstat->lastquit = sstrdup (lastquit);
	    serverstat->uptime = atoi (suptime);
	    serverstat->maxuptime = atoi (smaxuptime);

	    list = &statlist[SSHASH(serverstat->name)];
	    serverstat->next = *list;
	    if (*list)
		(*list)->prev = serverstat;
	    *list = serverstat;

	    ssin++;

	    continue;
	}

	/* End of DB */
	if (!strcmp (item, "DE"))
	{
	    uint8 newline = 0;

	    if ((s = strtok (NULL, "\n")))
	    {
		if (atoi (s) != ssin)
		{
		    log_sameline (0, "");
		    log ("Warning: Expected %s ServerStats, got %d.", s, ssin);
		    newline = 1;
		}
	    }

	    if (newline)
		log_sameline (1, "SS:DB: Loading... ");

	    dbend = 1;
	}
    }

    fclose (f);

    if (!dbend && dbver > 4 && !force)
    {
	/* We need a newline first. */
	log_sameline (0, "");
	log ("Incomplete statserv.db detected. Only found %d ServerStats.", ssin);
	fatal (1, "Please restore from backup or run OperStats with -force\n");
    }

#ifdef HAVE_GETTIMEOFDAY
    if (!dolog)
    {
	gettimeofday (&now, NULL);
	timersub (&now, &start, &tmp);
	log_sameline (0, "finished in %d ms. (%d ServerStat%s)",  tv2ms (&tmp), ssin, ssin == 1 ? "" : "s");
    }
#endif
}

/* Write the stats db to disk. */
void save_ss_dbase ()
{
    FILE *f;
    ServStat *server;
    char backup[2048];
    uint8 dolog = 0, sout = 0;
    uint16 i;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp; 
#endif

    if (logupdates_on == TRUE)
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (1, "SS:DB: Saving... ");
#else
	log ("SS:DB: Saving...");
#endif

    f = fopen (STATSERV_DB, "w");
    
    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (0, "Can't write to %s!", STATSERV_DB);
#else
	log ("SS:DB: Can't write to %s!", STATSERV_DB);
#endif
	dolog = 1;

        strcpy (backup, STATSERV_DB);
        strcat (backup, ".save");

	if (rename (backup, STATSERV_DB) < 0)
	    fatal (0, "SS:DB: Can't restore backup of %s!", STATSERV_DB);

	return;
    }

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    /* Write the version to the DB */
    fprintf (f, "SV %d\n", ss.version);

    /* Update maxuptime before we save it. Rather than check every second if it's
       a new record, we only check when someone request ss stats, and here. This
       will cause us to lose up to synctime of uptime in the invent of an improper
       shutdown, but this is acceptable to me.
     */
    if ((time (NULL) - me.since) > maxuptime)
	maxuptime = (time (NULL) - me.since);

    /* Various Statistics */
    fprintf (f, "ST %d %lu %d %lu %d %lu %d %lu %llu %lu\n", maxusercnt, maxusertime,
	maxchancnt, maxchantime, maxservcnt, maxservtime, maxhitcnt, maxuptime,
	speedstat, peaktime);

    /* Stats for the current day */
    fprintf (f, "SD %d %lu %d %lu %d %lu\n", dailyuserstat, dailyusertime,
	dailychanstat, dailychantime, dailyservstat, dailyservtime);

    /* Per-Server stats */
    for (i = 0; i < HASHSIZE; i++)
    {
	for (server = statlist[i]; server; server = server->next)
	{
	    fprintf (f, "SS %s %lu %lu %d %lu %d %d %d %ld %ld %lu %d %d %d %s\n", server->name,
		server->statssince, server->lastsplit, server->is_split,
		server->splittime, server->splits, server->lastdowntime,
		server->is_uline, server->peaklag.tv_sec, server->peaklag.tv_usec,
		server->maxtime, server->maxclients, server->uptime, server->maxuptime,
		server->lastquit ? server->lastquit : "N/A");

	    sout++;
	}
    }

    /* Write some stats. This helps us determine if a DB has been truncated as well. */
    fprintf (f, "DE %d\n", sout);

    fclose (f);

#ifdef HAVE_GETTIMEOFDAY
    if (logupdates_on == TRUE && !dolog)
    {
	gettimeofday (&now, NULL);
	timersub (&now, &start, &tmp);
	log_sameline (0, "finished in %d ms (%d serverstats)", tv2ms (&tmp), sout);
    }
#endif
}

/* Add a new TLD into the tld list. */
static TLD * new_tld (const char *name, const char *desc)
{
    TLD *tld;

    tld = scalloc (sizeof (TLD), 1);

    tld->name = sstrdup (name);
    tld->desc = sstrdup (desc);

    tld->next = tldlist;

    if (tld->next)
	tld->next->prev = tld;

    tldlist = tld;

    return tld;
}

/* Load the TLD listings */
void load_tlds ()
{
    FILE *f = fopen (TLDFILE, "r");
    char *s, dBuf[BUFSIZE], *name, *desc;

    log (RPL_LOADING, TLDFILE);

    if (!f)
    {
	tldlookup = FALSE;
	dumptlds_on = FALSE;
	return;
    }

    while (fgets (dBuf, 2047, f))
    {
	s = strtok (dBuf, " ");
	strip (s);

	if (s)
	    name = s;
	else
	    name = "";

	s = strtok (NULL, "");
	strip (s);

	if (s)
	    desc = s;
	else
	    desc = "";

	if (!desc)
	    log ("Found TLD %s but no description!", name);
	else
	    new_tld (name, desc);
    }
    fclose (f);
    tldlookup = TRUE;
}

/* Add a new TimeZone into the timezones list. */
static Zone *new_zone (const char *name,  const char *noffset,
		       const char *offset, const char *desc)
{
    Zone *zone;

    zone = scalloc (sizeof (Zone), 1);

    zone->name = sstrdup (name);
    zone->noffset = sstrdup (noffset);
    zone->offset = atoi (offset);
    zone->desc = sstrdup (desc);

    zone->next = zonelist;

    if (zone->next)
	zone->next->prev = zone;

    zonelist = zone;

    return zone;
}

/* Load the TimeZones into memory. */
void load_zones ()
{
    FILE *f = fopen (ZONEFILE, "r");
    char *s, dBuf[BUFSIZE], *name, *noffset, *offset, *desc;

    log (RPL_LOADING, ZONEFILE);

    if (!f)
    {
	zonelookup = FALSE;
	return;
    }

    while (fgets (dBuf, 2047, f))
    {
	s = strtok (dBuf, " ");
	strip (s);

	if (s)
	    name = s;
	else
	    name = "";

	s = strtok (NULL, " ");
	strip (s);

	if (s)
	    noffset = s;
	else
	    noffset = "";

	s = strtok (NULL, " ");
	strip (s);

	if (s)
	    offset = s;
	else
	    offset = "";

	s = strtok (NULL, "");
	strip (s);

	if (s)
	    desc = s;
	else
	    desc = "";

	if (!desc)
	    log ("Invalid TimeZone %s: Missing data!", name);
	else
	    new_zone (name, noffset, offset, desc);
    }

    fclose (f);
    zonelookup = TRUE;
}

/* Ping all servers. */
void do_sendpings (User *u)
{
    Server *s;

    for (s = firstserv (); s; s = nextserv ())
    {
	send_cmd (me.name, "%s %s %s", me.token ? "8" : "PING", me.name, s->name);
	gettimeofday (&(s->lastping), NULL);
    }

    notice (s_StatServ, u->nick, SS_PINGED_ALL);
}

/* Reset the daily statistics to current values. */
void reset_daily_stats ()
{
    TLD *tld;

    dailyuserstat = userstat;
    dailyusertime = time (NULL);
    dailychanstat = chanstat;
    dailychantime = time (NULL);
    dailyservstat = servstat;
    dailyservtime = time (NULL);
    dailyhitcnt = 0;
    tldnoreshits = 0;

    for (tld = tldlist; tld; tld = tld->next)
	if (tld->hits)
	    tld->hits = 0;
}
