/*
 * qstat 2.5c
 * by Steve Jankowski
 * steve@qstat.org
 * http://www.qstat.org
 *
 * Thanks to Per Hammer for the OS/2 patches (per@mindbend.demon.co.uk)
 * Thanks to John Ross Hunt for the OpenVMS Alpha patches (bigboote@ais.net)
 * Thanks to Scott MacFiggen for the quicksort code (smf@webmethods.com)
 *
 * Inspired by QuakePing by Len Norton
 *
 * Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
 *
 * Licensed under the Artistic License, see LICENSE.txt for license terms
 */

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

#ifdef __hpux
#include <sys/types.h>
#include <netinet/in.h>
#endif
#ifdef _AIX
#include <sys/types.h>
#include <netinet/in.h>
#endif

#include "qstat.h"

#ifdef _WIN32
#define strcasecmp	stricmp
#define strncasecmp	strnicmp
#endif

#ifdef __hpux
#define STATIC static
#else
#define STATIC
#endif

/*
#ifdef __cplusplus
extern "C" {
#endif
#ifndef _AIX
extern unsigned int ntohl(unsigned int n);
#endif
#ifdef __cplusplus
}
#endif
*/

extern int hostname_lookup;
extern int get_player_info;
extern int get_server_rules;
extern int num_servers_total;
extern int num_servers_timed_out;
extern int num_servers_down;
extern int num_players_total;
extern int max_players_total;
extern int html_names;
extern int hex_player_names;
extern FILE *OF;		/* output file */

static char *server_template;
static char *rule_template;
static char *header_template;
static char *trailer_template;
static char *player_template;

static void display_server_var( struct qserver *server, int var);
static void display_player_var( struct player *player, int var, struct qserver *server);
static void display_rule_var( struct rule *rule, int var, struct qserver *server);
static void display_generic_var( int var);
static int parse_var( char *varname, int *varlen);
static int read_template( char *filename, char **template_text);
static void display_string( char *str);
static int is_true( struct qserver *server, struct player *player,
	struct rule *rule, char *expr);


#define VARIABLE_CHAR '$'

static char *variable_option;
static int if_skip;
static int if_level;
static int if_skip_save;
static int if_level_save;
int html_mode= 0;
int clear_newlines_mode= 0;
int rule_name_spaces= 0;

struct vardef {
    char *var;
    int varcode;
    int options;
};

#define NO_OPTIONS 0
#define OPTIONS_OK 1
#define EXPR	   2

struct vardef variable_defs[] = {
#define V_HOSTNAME	1
	{"HOSTNAME", V_HOSTNAME, NO_OPTIONS },
#define V_SERVERNAME	2
	{"SERVERNAME", V_SERVERNAME, NO_OPTIONS },
#define V_PING		3
	{"PING", V_PING, NO_OPTIONS },
#define V_PLAYERS	4
	{"PLAYERS", V_PLAYERS, NO_OPTIONS | EXPR },
#define V_MAXPLAYERS	5
	{"MAXPLAYERS", V_MAXPLAYERS, NO_OPTIONS },
#define V_MAP		6
	{"MAP", V_MAP, NO_OPTIONS },
#define V_GAME		7
	{"GAME", V_GAME, NO_OPTIONS | EXPR },
#define V_RETRIES	8
	{"RETRIES", V_RETRIES, NO_OPTIONS },
#define V_IPADDR	9
	{"IPADDR", V_IPADDR, NO_OPTIONS },
#define V_PORT		10
	{"PORT", V_PORT, NO_OPTIONS },
#define V_ARG		11
	{"ARG", V_ARG, NO_OPTIONS },
#define V_QSTATURL	12
	{"QSTATURL", V_QSTATURL, NO_OPTIONS },
#define V_QSTATVERSION	13
	{"QSTATVERSION", V_QSTATVERSION, NO_OPTIONS },
#define V_QSTATAUTHOR	14
	{"QSTATAUTHOR", V_QSTATAUTHOR, NO_OPTIONS },
#define V_QSTATAUTHOREMAIL	15
	{"QSTATAUTHOREMAIL", V_QSTATAUTHOREMAIL, NO_OPTIONS },
#define V_TYPE	16
	{"TYPE", V_TYPE, NO_OPTIONS },
#define V_RULE	17
	{"RULE", V_RULE, OPTIONS_OK | EXPR },
#define V_ALLRULES	18
	{"ALLRULES", V_ALLRULES, NO_OPTIONS },
#define V_PLAYERTEMPLATE	19
	{"PLAYERTEMPLATE", V_PLAYERTEMPLATE, NO_OPTIONS },
#define V_PLAYERNAME	20
	{"PLAYERNAME", V_PLAYERNAME, NO_OPTIONS },
#define V_FRAGS		21
	{"FRAGS", V_FRAGS, NO_OPTIONS },
#define V_PLAYERPING	22
	{"PLAYERPING", V_PLAYERPING, NO_OPTIONS },
#define V_CONNECTTIME	23
	{"CONNECTTIME", V_CONNECTTIME, NO_OPTIONS },
#define V_SKIN		24
	{"SKIN", V_SKIN, NO_OPTIONS },
#define V_SHIRTCOLOR	25
	{"SHIRTCOLOR", V_SHIRTCOLOR, NO_OPTIONS },
#define V_PANTSCOLOR	26
	{"PANTSCOLOR", V_PANTSCOLOR, NO_OPTIONS },
#define V_PLAYERIP	27
	{"PLAYERIP", V_PLAYERIP, NO_OPTIONS },
#define V_IF	28
	{"IF", V_IF, OPTIONS_OK },
#define V_ENDIF	29
	{"ENDIF", V_ENDIF, NO_OPTIONS },
#define V_HTML	35
	{"HTML", V_HTML, NO_OPTIONS },
#define V_FLAG	36
	{"FLAG", V_FLAG, OPTIONS_OK | EXPR },
#define V_UP	37
	{"UP", V_UP, NO_OPTIONS | EXPR },
#define V_DOWN	38
	{"DOWN", V_DOWN, NO_OPTIONS | EXPR },
#define V_IFNOT	39
	{"IFNOT", V_IFNOT, OPTIONS_OK },
#define V_TIMEOUT	40
	{"TIMEOUT", V_TIMEOUT, NO_OPTIONS | EXPR },
#define V_NOW	41
	{"NOW", V_NOW, NO_OPTIONS },
#define V_TOTALSERVERS	42
	{"TOTALSERVERS", V_TOTALSERVERS, NO_OPTIONS },
#define V_TOTALUP	43
	{"TOTALUP", V_TOTALUP, NO_OPTIONS },
#define V_TOTALNOTUP	44
	{"TOTALNOTUP", V_TOTALNOTUP, NO_OPTIONS },
#define V_TOTALPLAYERS	45
	{"TOTALPLAYERS", V_TOTALPLAYERS, NO_OPTIONS },
#define V_TOTALMAXPLAYERS	46
	{"TOTALMAXPLAYERS", V_TOTALMAXPLAYERS, NO_OPTIONS },
#define V_TEAMNUM	49
	{"TEAMNUM", V_TEAMNUM, NO_OPTIONS },
#define V_BACKSLASH	50
	{"\\", V_BACKSLASH, NO_OPTIONS },
#define V_HOSTNOTFOUND	51
	{"HOSTNOTFOUND", V_HOSTNOTFOUND, NO_OPTIONS | EXPR },
#define V_MESH	52
	{"MESH", V_MESH, NO_OPTIONS },
#define V_ISEMPTY	56
	{"ISEMPTY", V_ISEMPTY, NO_OPTIONS | EXPR },
#define V_ISFULL	57
	{"ISFULL", V_ISFULL, NO_OPTIONS | EXPR },
#define V_PACKETLOSS	58
	{"PACKETLOSS", V_PACKETLOSS, NO_OPTIONS },
#define V_ISTEAM	59
	{"ISTEAM", V_ISTEAM, NO_OPTIONS | EXPR },
#define V_TEAMNAME	60
	{"TEAMNAME", V_TEAMNAME, NO_OPTIONS },
#define V_DEFAULTTYPE	61
	{"DEFAULTTYPE", V_DEFAULTTYPE, NO_OPTIONS },
#define V_TYPESTRING	62
	{"TYPESTRING", V_TYPESTRING, NO_OPTIONS },
#define V_FACE		63
	{"FACE", V_FACE, NO_OPTIONS },
#define V_SOLDIEROFFORTUNE		64
	{"SOLDIEROFFORTUNE", V_SOLDIEROFFORTUNE, NO_OPTIONS },
#define V_COLORNUMBERS	65
	{"COLORNUMBERS", V_COLORNUMBERS, NO_OPTIONS },
#define V_COLORNAMES	66
	{"COLORNAMES", V_COLORNAMES, NO_OPTIONS },
#define V_COLORRGB	67
	{"COLORRGB", V_COLORRGB, NO_OPTIONS },
#define V_TIMESECONDS	68
	{"TIMESECONDS", V_TIMESECONDS, NO_OPTIONS },
#define V_TIMECLOCK	69
	{"TIMECLOCK", V_TIMECLOCK, NO_OPTIONS },
#define V_TIMESTOPWATCH	70
	{"TIMESTOPWATCH", V_TIMESTOPWATCH, NO_OPTIONS },
#define V_NOWINT	71
	{"NOWINT", V_NOWINT, NO_OPTIONS },
#define V_ISMASTER	72
	{"ISMASTER", V_ISMASTER, NO_OPTIONS | EXPR },
#define V_HTMLPLAYERNAME	73
	{"HTMLPLAYERNAME", V_HTMLPLAYERNAME, NO_OPTIONS },
#define V_ISBOT	74
	{"ISBOT", V_ISBOT, NO_OPTIONS | EXPR },
#define V_ISALIAS	75
	{"ISALIAS", V_ISALIAS, NO_OPTIONS | EXPR },
#define V_TRIBETAG	76
	{"TRIBETAG", V_TRIBETAG, NO_OPTIONS | EXPR },
#define V_CLEARNEWLINES	77
	{"CLEARNEWLINES", V_CLEARNEWLINES, NO_OPTIONS },
#define V_GAMETYPE	78
	{"GAMETYPE", V_GAMETYPE, NO_OPTIONS },
#define V_DEATHS		79
	{"DEATHS", V_DEATHS, NO_OPTIONS | EXPR },
#define V_TYPEPREFIX		80
	{"TYPEPREFIX", V_TYPEPREFIX, NO_OPTIONS },
#define V_RULENAMESPACES	81
	{"RULENAMESPACES", V_RULENAMESPACES, NO_OPTIONS },
#define V_RULETEMPLATE		82
	{"RULETEMPLATE", V_RULETEMPLATE, NO_OPTIONS },
#define V_RULENAME		83
	{"RULENAME", V_RULENAME, OPTIONS_OK | EXPR },
#define V_RULEVALUE		84
	{"RULEVALUE", V_RULEVALUE, OPTIONS_OK | EXPR },
#define V_PLAYERSTATID		85
	{"PLAYERSTATID", V_PLAYERSTATID, NO_OPTIONS },
#define V_PLAYERLEVEL		86
	{"PLAYERLEVEL", V_PLAYERLEVEL, NO_OPTIONS },
#define V_TOTALUTILIZATION	87
	{"TOTALUTILIZATION", V_TOTALUTILIZATION, NO_OPTIONS },
#define V_SCORE			88
	{"SCORE", V_SCORE, NO_OPTIONS },
};

int
read_qserver_template( char *filename)
{
    return read_template( filename, &server_template);
}

int
read_rule_template( char *filename)
{
    return read_template( filename, &rule_template);
}

int
read_header_template( char *filename)
{
    return read_template( filename, &header_template);
}

int
read_trailer_template( char *filename)
{
    return read_template( filename, &trailer_template);
}

int
read_player_template( char *filename)
{
    return read_template( filename, &player_template);
}

STATIC int
read_template( char *filename, char **template_text)
{
    FILE *file;
    int length, rc;

    file= fopen( filename, "r");
    if ( file == NULL)  {
	perror( filename);
	return -1;
    }

    fseek( file, 0, SEEK_END);
    length= ftell( file);
    fseek( file, 0, SEEK_SET);

    *template_text= (char*)malloc( length+1);
    rc= fread( *template_text, 1, length, file);
    if ( rc == 0 && length > 0)  {
	perror( filename);
	fclose( file);
	free( *template_text);
	*template_text= NULL;
	return -1;
    }
    (*template_text)[rc]= '\0';
    return 0;
}

int
have_server_template()
{
    return server_template != NULL;
}

int
have_header_template()
{
    return header_template != NULL;
}

int
have_trailer_template()
{
    return trailer_template != NULL;
}

void
template_display_server( struct qserver *server)
{
    char *t= server_template;
    int var, varlen;

    if_level= 0;
    if_skip= 0;
    for ( ; *t; t++)  {
	if ( *t != VARIABLE_CHAR)  {
	    if ( ! if_skip)
		putc( *t, OF);
	    continue;
	}
	var= parse_var( t, &varlen);
	if ( var == -1)  {
	    if ( ! if_skip)
		putc( VARIABLE_CHAR, OF);
	    continue;
	}
	if ( var == V_BACKSLASH)  {
	    t+= 2;
	    if ( *t == '\r')  {
		if ( *++t == '\n')
		    t++;
	    }
	    else if ( *t == '\n')
		t++;
	    t--;
	    continue;
	}
	if ( (var == V_IF || var == V_IFNOT) && variable_option != NULL)  {
	    int truth= (var==V_IF)?1:0;
	    if ( !if_skip && is_true( server, NULL, NULL, variable_option) == truth)
		if_level++;
	    else
		if_skip++;
	}
	else if ( var == V_ENDIF)  {
	    if ( if_skip)
		if_skip--;
	    else if ( if_level)
		if_level--;
	}
	if ( ! if_skip)
	    display_server_var( server, var);
	t+= varlen;
    }
}

void
template_display_players( struct qserver *server)
{
    struct player *player;
    for ( player= server->players; player != NULL; player= player->next)
	template_display_player( server, player);
}

void
template_display_player( struct qserver *server, struct player *player)
{
    char *t= player_template;
    int var, varlen;

    if ( player_template == NULL)
	return;

    if_level= 0;
    if_skip= 0;
    for ( ; *t; t++)  {
	if ( *t != VARIABLE_CHAR)  {
	    if ( ! if_skip)
		putc( *t, OF);
	    continue;
	}
	var= parse_var( t, &varlen);
	if ( var == -1)  {
	    if ( ! if_skip)
		putc( VARIABLE_CHAR, OF);
	    continue;
	}
	if ( var == V_BACKSLASH)  {
	    t+= 2;
	    if ( *t == '\r')  {
		if ( *++t == '\n')
		    t++;
	    }
	    else if ( *t == '\n')
		t++;
	    t--;
	    continue;
	}
	if ( (var == V_IF || var == V_IFNOT) && variable_option != NULL)  {
	    int truth= (var==V_IF)?1:0;
	    if ( !if_skip && is_true( server, player, NULL, variable_option) == truth)
		if_level++;
	    else
		if_skip++;
	}
	else if ( var == V_ENDIF)  {
	    if ( if_skip)
		if_skip--;
	    else if ( if_level)
		if_level--;
	}
	if ( ! if_skip)
	    display_player_var( player, var, server);
	t+= varlen;
    }
}

void
template_display_rules( struct qserver *server)
{
    struct rule *rule;
    for ( rule= server->rules; rule != NULL; rule= rule->next)
	template_display_rule( server, rule);
}

void
template_display_rule( struct qserver *server, struct rule *rule)
{
    char *t= rule_template;
    int var, varlen;

    if ( rule_template == NULL)
	return;

    if_level= 0;
    if_skip= 0;
    for ( ; *t; t++)  {
	if ( *t != VARIABLE_CHAR)  {
	    if ( ! if_skip)
		putc( *t, OF);
	    continue;
	}
	var= parse_var( t, &varlen);
	if ( var == -1)  {
	    if ( ! if_skip)
		putc( VARIABLE_CHAR, OF);
	    continue;
	}
	if ( var == V_BACKSLASH)  {
	    t+= 2;
	    if ( *t == '\r')  {
		if ( *++t == '\n')
		    t++;
	    }
	    else if ( *t == '\n')
		t++;
	    t--;
	    continue;
	}
	if ( (var == V_IF || var == V_IFNOT) && variable_option != NULL)  {
	    int truth= (var==V_IF)?1:0;
	    if ( !if_skip && is_true( server, NULL, rule, variable_option) == truth)
		if_level++;
	    else
		if_skip++;
	}
	else if ( var == V_ENDIF)  {
	    if ( if_skip)
		if_skip--;
	    else if ( if_level)
		if_level--;
	}
	if ( ! if_skip)
	    display_rule_var( rule, var, server);
	t+= varlen;
    }
}

void
template_display_header()
{
    char *t= header_template;
    int var, varlen;

    if_level= 0;
    if_skip= 0;
    for ( ; *t; t++)  {
	if ( *t != VARIABLE_CHAR)  {
	    putc( *t, OF);
	    continue;
	}
	var= parse_var( t, &varlen);
	if ( var == -1)  {
	    putc( VARIABLE_CHAR, OF);
	    continue;
	}
	if ( var == V_BACKSLASH)  {
	    t+= 2;
	    if ( *t == '\r')  {
		if ( *++t == '\n')
		    t++;
	    }
	    else if ( *t == '\n')
		t++;
	    t--;
	    continue;
	}
	display_generic_var( var);
	t+= varlen;
    }
}

void
template_display_trailer()
{
    char *t= trailer_template;
    int var, varlen;

    if_level= 0;
    if_skip= 0;
    for ( ; *t; t++)  {
	if ( *t != VARIABLE_CHAR)  {
	    putc( *t, OF);
	    continue;
	}
	var= parse_var( t, &varlen);
	if ( var == -1)  {
	    putc( VARIABLE_CHAR, OF);
	    continue;
	}
	if ( var == V_BACKSLASH)  {
	    t+= 2;
	    if ( *t == '\r')  {
		if ( *++t == '\n')
		    t++;
	    }
	    else if ( *t == '\n')
		t++;
	    t--;
	    continue;
	}
	display_generic_var( var);
	t+= varlen;
    }
}

STATIC void
display_server_var( struct qserver *server, int var)
{
    char *game;
    int full_data= 1;
    if ( server->server_name == DOWN || server->server_name == TIMEOUT ||
		server->server_name == SYSERROR || 
		server->error != NULL || server->type->master)
	full_data= 0;

    switch( var)  {
    case V_HOSTNAME:
	fputs( (hostname_lookup) ? server->host_name : server->arg, OF);
	break;
    case V_SERVERNAME:
	fputs( xform_name( server->server_name, server), OF);
	break;
    case V_PING:
	if ( server->server_name != TIMEOUT && server->server_name != DOWN &&
		server->server_name != HOSTNOTFOUND && server->server_name != SYSERROR)
	    fprintf( OF, "%d",
		server->n_requests ? server->ping_total/server->n_requests : 999);
	break;
    case V_PLAYERS:
	if ( full_data)
	    fprintf( OF, "%d", server->num_players);
	break;
    case V_MAXPLAYERS:
	if ( full_data)
	    fprintf( OF, "%d", server->max_players);
	break;
    case V_MAP:
	if ( full_data)
	    fputs( (server->map_name) ? server->map_name : "?", OF);
	break;
    case V_GAME:
	if ( full_data)  {
	    game= get_qw_game( server);
	    fputs( (game) ? game : "", OF);
	}
	break;
    case V_GAMETYPE:
	{
	struct rule *rule;
	for ( rule= server->rules; rule != NULL; rule= rule->next)
	    if ( strcasecmp( rule->name, "g_gametype") == 0)
		break;
	if ( rule != NULL)  {
	    switch ( atoi(rule->value))  {
		case 0: fputs( "Free For All", OF); break;
		case 1: fputs( "Tournament", OF); break;
		case 3: fputs( "Team Deathmatch", OF); break;
		case 4: fputs( "Capture the Flag", OF); break;
		case 5: fputs( "Fortress or OSP", OF); break;
		case 6: fputs( "Capture and Hold", OF); break;
		case 8: fputs( "Arena", OF); break;
		default: fputs( "?", OF); break;
	    }
	}
	}
	break;
    case V_RETRIES:
	if ( server->server_name != TIMEOUT && server->server_name != DOWN &&
		server->server_name != HOSTNOTFOUND)
	    fprintf( OF, "%d", server->n_retries);
	break;
    case V_IPADDR:
	{ unsigned int ipaddr= ntohl(server->ipaddr);
	fprintf( OF, "%u.%u.%u.%u", (ipaddr>>24)&0xff,
		(ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff);
	}
	break;
    case V_PORT:
	fprintf( OF, "%hu", server->port);
	break;
    case V_ARG:
	fputs( server->arg, OF);
	break;
    case V_TYPE:
	fputs( server->type->game_name, OF);
	break;
    case V_TYPESTRING:
	fputs( server->type->type_string, OF);
	break;
    case V_TYPEPREFIX:
	fputs( server->type->type_prefix, OF);
	break;
    case V_RULE:
	{
	struct rule *rule;
	if ( variable_option == NULL)
	    break;
	for ( rule= server->rules; rule != NULL; rule= rule->next)
	    if ( strcasecmp( rule->name, variable_option) == 0)
		display_string( rule->value);
	}
	break;
    case V_ALLRULES:
	{
	struct rule *rule;
	for ( rule= server->rules; rule != NULL; rule= rule->next)  {
	    if ( rule != server->rules)
		fputs( ", ", OF);
	    display_string( rule->name);
	    putc( '=', OF);
	    display_string( rule->value);
	}
	}
	break;
    case V_PLAYERTEMPLATE:
	if_level_save= if_level;
	if_skip_save= if_skip;
	template_display_players( server);
	if_level= if_level_save;
	if_skip= if_skip_save;
	break;
    case V_RULETEMPLATE:
	if_level_save= if_level;
	if_skip_save= if_skip;
	template_display_rules( server);
	if_level= if_level_save;
	if_skip= if_skip_save;
	break;
    default:
	display_generic_var( var);
    }
}

STATIC void
display_player_var( struct player *player, int var, struct qserver *server)
{
    switch( var)  {
    case V_PLAYERNAME:
	fputs( xform_name( player->name, server), OF);
	break;
    case V_HTMLPLAYERNAME:  {
	int save_html_names= html_names;
	int save_hex_player_names= hex_player_names;
	html_names= 1;
	hex_player_names= 0;
	fputs( xform_name( player->name, server), OF);
	html_names= save_html_names;
	hex_player_names= save_hex_player_names;
	}
	break;
    case V_TRIBETAG:
	fputs( xform_name( player->tribe_tag, server), OF);
	break;
    case V_FRAGS:
	fprintf( OF, "%d", player->frags);
	break;
    case V_SCORE:
	fprintf( OF, "%d", player->score);
	break;
    case V_DEATHS:
	fprintf( OF, "%d", player->deaths);
	break;
    case V_PLAYERPING:
	fprintf( OF, "%d", player->ping);
	break;
    case V_CONNECTTIME:
	fputs( play_time(player->connect_time,0), OF);
	break;
    case V_SKIN:
	display_string( player->skin);
	break;
    case V_MESH:
	display_string( player->mesh);
	break;
    case V_FACE:
	display_string( player->face);
	break;
    case V_SHIRTCOLOR:
	if ( color_names)
	    fputs( quake_color(player->shirt_color), OF);
	else
	    fprintf( OF, "%d", player->shirt_color);
	break;
    case V_PANTSCOLOR:
	if ( color_names)
	    fputs( quake_color(player->pants_color), OF);
	else
	    fprintf( OF, "%d", player->pants_color);
	break;
    case V_PLAYERIP:
	if ( player->address)
	    fputs( player->address, OF);
	break;
    case V_TEAMNUM:
	fprintf( OF, "%d", player->team);
	break;
    case V_PACKETLOSS:
	fprintf( OF, "%d", player->packet_loss);
	break;
    case V_TEAMNAME:
	if ( player->team_name)
	    fprintf( OF, "%s", player->team_name);
	break;
    case V_COLORNUMBERS:
	color_names= 0;
	break;
    case V_COLORNAMES:
	color_names= 1;
	break;
    case V_COLORRGB:
	color_names= 2;
	break;
    case V_TIMESECONDS:
	time_format= SECONDS;
	break;
    case V_TIMECLOCK:
	time_format= CLOCK_TIME;
	break;
    case V_TIMESTOPWATCH:
	time_format= STOPWATCH_TIME;
	break;
    case V_PLAYERSTATID:
    case V_PLAYERLEVEL:
	fprintf( OF, "%u", player->ship);
	break;
    default:
	display_server_var( server, var);
    }
}

STATIC void
display_rule_var( struct rule *rule, int var, struct qserver *server)
{
    switch( var)  {
    case V_RULENAME:
	fputs( rule->name, OF);
	break;
    case V_RULEVALUE:
	fputs( xform_name( rule->value, server), OF);
	break;
    default:
	display_server_var( server, var);
    }
}

STATIC void
display_generic_var( int var)
{
    switch( var)  {
    case V_QSTATURL:
	fputs( "http://www.qstat.org", OF);
	break;
    case V_QSTATVERSION:
	fputs( qstat_version, OF);
	break;
    case V_QSTATAUTHOR:
	fputs( "Steve Jankowski", OF);
	break;
    case V_QSTATAUTHOREMAIL:
	fputs( "steve@qstat.org", OF);
	break;
    case V_HTML:
	html_mode^= 1;
	if ( html_mode && html_names == -1)
	    html_names= 1;
	break;
    case V_CLEARNEWLINES:
	clear_newlines_mode^= 1;
	break;
    case V_NOW:  {
	time_t now= time(0);
	char *now_string= ctime(&now);
	now_string[strlen(now_string)-1]= '\0';
	fputs( now_string, OF);
	break;
	}
    case V_NOWINT:
	fprintf( OF, "%u", (unsigned int)time(0));
	break;
    case V_TOTALSERVERS:
	fprintf( OF, "%d", num_servers_total);
	break;
    case V_TOTALUP:
	fprintf( OF, "%d", num_servers_total - num_servers_timed_out -
		num_servers_down);
	break;
    case V_TOTALNOTUP:
	fprintf( OF, "%d", num_servers_timed_out + num_servers_down);
	break;
    case V_TOTALPLAYERS:
	fprintf( OF, "%d", num_players_total);
	break;
    case V_TOTALMAXPLAYERS:
	fprintf( OF, "%d", max_players_total);
	break;
    case V_TOTALUTILIZATION:
	fprintf( OF, "%.1f", (100.0 / max_players_total) * num_players_total);
	break;
    case V_DEFAULTTYPE:
	fprintf( OF, "%s", default_server_type->game_name);
	break;
    case V_RULENAMESPACES:
	rule_name_spaces^= 1;
	break;
    default: break;
    }
}

STATIC int
parse_var( char *varname, int *varlen)
{
    char *v= ++varname, *colon= NULL;
    int i, quote= 0;

    if ( variable_option != NULL)  {
	free( variable_option);
	variable_option= NULL;
    }

    if ( *v == '(')  {
	v++;
	varname++;
	quote++;
    }
    else if ( *v == '\\')  {
	*varlen= 1;
	return V_BACKSLASH;
    }

    for ( ; *v; v++)
	if ( (!quote && !isalpha( *v)) || (quote && (*v == ')' || *v == ':')))
	    break;
    if ( v-varname == 0)
	return -1;

    *varlen= v-varname;
    if ( *v == ':')
	colon= v;
    else if ( quote && *v == ')')
	v++;

    for ( i= 0; i < sizeof(variable_defs)/sizeof(struct vardef); i++)
	if ( strncasecmp( varname, variable_defs[i].var, *varlen) == 0 &&
		*varlen == strlen(variable_defs[i].var))  {
	    if ( colon != NULL && ((variable_defs[i].options & OPTIONS_OK) || quote))  {
		for ( v++; *v; v++)  {
		    if ( (!quote && !isalnum( *v) && *v != '*' && *v != '_' && *v != '.' && (*v == ' ' && !rule_name_spaces)) ||
			(quote==1 && *v == ')')) break;
		    if ( *v == '(')
			quote++;
		    else if ( *v == ')')
			quote--;
		}
		variable_option= (char*)malloc( v-colon+1);
		strncpy( variable_option, colon+1, v-colon-1);
		variable_option[v-colon-1]= '\0';
		if ( quote && *v == ')')
		    v++;
	    }
	    *varlen= v-varname+quote;
	    return variable_defs[i].varcode;
	}

    return -1;
}

STATIC int
is_true( struct qserver *server, struct player *player, struct rule *rule,
	char *expr)
{
    int i, len, arglen;
    char *arg= NULL, *lparen, *rparen;
    server_type *t;

    if ( (lparen= strchr( expr, '(')) != NULL)  {
	if ( (rparen= strchr( lparen, ')')) != NULL)  {
	    len= lparen - expr;
	    arg= lparen + 1;
	    arglen= rparen - lparen - 1;
	}
    }
    else
	len= strlen( expr);

    for ( i= 0; i < sizeof(variable_defs)/sizeof(struct vardef); i++)
	if ( strncasecmp( expr, variable_defs[i].var, len) == 0 &&
		len == strlen( variable_defs[i].var))  {
	    if ( !(variable_defs[i].options & EXPR))  {
		fprintf( stderr, "unsupported IF expression \"%s\"\n", expr);
		return 1;
	    }
	    switch ( variable_defs[i].varcode)  {
	    case V_GAME:  {
		char *g= get_qw_game( server);
		if ( g == NULL || *g == '\0')
		    return 0;
		else
		    return 1;
	    }
	    case V_PLAYERS: return server->num_players > 0;
	    case V_ISEMPTY: return server->num_players == 0;
	    case V_ISFULL: return server->max_players ? server->num_players >= server->max_players : 0;
	    case V_ISTEAM: return player ? player->number == TRIBES_TEAM : 0;
	    case V_ISBOT: return player ? player->type_flag == PLAYER_TYPE_BOT : 0;
	    case V_ISALIAS: return player ? player->type_flag == PLAYER_TYPE_ALIAS : 0;
	    case V_TRIBETAG: return player ? player->tribe_tag != NULL : 0;
	    case V_RULE:  {
		struct rule *rule;
		if ( arg == NULL)
		    return 0;
		for ( rule= server->rules; rule != NULL; rule= rule->next)
		    if ( strncasecmp( rule->name, arg, arglen) == 0 &&
				strlen( rule->name) == arglen)
			return 1;
		return 0;
	    }
	    case V_RULENAME:  {
		if ( rule && arg && strncmp( rule->name, arg, arglen) == 0 &&
			strlen( rule->name) == arglen)
		    return 1;
		else
		    return 0;
	    }
	    case V_RULEVALUE:  {
		if ( rule && arg && strncmp( rule->value, arg, arglen) == 0 &&
			strlen( rule->value) == arglen)
		    return 1;
		else
		    return 0;
	    }
	    case V_FLAG:  {
		if ( strncmp( "-H", arg, arglen) == 0)
		    return hostname_lookup;
		if ( strncmp( "-P", arg, arglen) == 0)
		    return get_player_info;
		if ( strncmp( "-R", arg, arglen) == 0)
		    return get_server_rules;
		return 0;
	    }
	    case V_UP:
		return server->server_name != DOWN &&
			server->server_name != TIMEOUT &&
			server->server_name != HOSTNOTFOUND;
	    case V_DOWN:
		return server->server_name == DOWN;
	    case V_TIMEOUT:
		return server->server_name == TIMEOUT;
	    case V_HOSTNOTFOUND:
		return server->server_name == HOSTNOTFOUND;
	    case V_ISMASTER:
		return (server->type->id & MASTER_SERVER) ? 1 : 0;
	    case V_DEATHS:
		return player->deaths;
	    default: return 0;
	    }
	}

    t= &types[0];
    for ( ; t->id; t++)
	if ( strncasecmp( expr, t->template_var, len) == 0)
	    return server->type->id == t->id;

    fprintf( stderr, "bad IF expression \"%s\"\n", expr);
    return 0;
}

STATIC void
display_string( char *str)
{
    if ( str == NULL)
	return;
    if ( ! html_mode && ! clear_newlines_mode)  {
	fputs( str, OF);
	return;
    }

    if ( html_mode && ! clear_newlines_mode)  {
    for ( ; *str; str++)
	switch ( *str) {
	case '<': fputs( "&lt;", OF); break;
	case '>': fputs( "&gt;", OF); break;
	case '&': fputs( "&amp;", OF); break;
	case '\n': fputs( "NEWLINE", OF);
	default: putc( *str, OF);
	}
	return;
    }

    if ( ! html_mode && clear_newlines_mode)  {
    for ( ; *str; str++)
	switch ( *str) {
	case '\n':
	case '\r': putc( ' ', OF); break;
	default: putc( *str, OF);
	}
	return;
    }

    for ( ; *str; str++)
	switch ( *str) {
	case '<': fputs( "&lt;", OF); break;
	case '>': fputs( "&gt;", OF); break;
	case '&': fputs( "&amp;", OF); break;
	case '\n':
	case '\r': putc( ' ', OF); break;
	default: putc( *str, OF);
	}
}

