/*
  Alternate voting system, Quake III: Arena.
  Copyright 2002  by PhaethonH <phaethon@linux.ucla.edu>

  Permission granted to copy, modify, distribute, or otherwise use this code,
  provided this copyright notice remains intact.
*/

#include "q_shared.h"
#include "g_local.h"

/*
Drop-in replacement for vote system.

To use:
  in g_main.c, change CheckVote() to CheckVote2()
  in g_cmds.c, change Cmd_CallVote_f() to Cmd_callvote_f()
               change Cmd_Vote_f() to Cmd_vote_f()
  in g_svcmds.c, in ConsoleCommand() add towards the end:
               if (Vote2_hook_svcmd(cmd)) return qtrue;

(in g_svcmds.c):
 	if (Q_stricmp (cmd, "listip") == 0) {
 		trap_SendConsoleCommand( EXEC_NOW, "g_banIPs\n" );
 		return qtrue;
 	}
	if (Vote2_hook_svcmd(cmd)) return qtrue;

*/

/**************/
/* Interface. */
/**************/

/* Having distinct votebox instances allows separation of global vote from team vote, etc. */

typedef struct votebox_s votebox_t;

struct votebox_s {
  char title[MAX_CVAR_VALUE_STRING];  /* displayed to clients. */
  char motion[MAX_CVAR_VALUE_STRING];  /* pending command. */
  int weight[MAX_CLIENTS];  /* Weight of voter (0 = can't vote) */
  char ballot[MAX_CLIENTS]; /* the vote cast of each voter. */
  int numvoters;  /* how many voters registered. */
  float majority;   /* the minimal fraction of yes votes to pass.  Minimal number of no votes to fail implied (1.0 - majority). */
  int opentime;   /* When voting started. */
  int closetime;  /* Time when voting ends. */
  int enacttime;  /* Time when command executes. */
  qboolean recastable;  /* if voters can change their minds or not. */
  int aye;  /* votes for yes. */
  int nay;  /* votes for no. */

};

/*
Methods:
 * init (title, cmd) - set up voting
 * destroy () - tear down voting
 * open (majority, time) - Start election (collect votes)
 * close () - End election (do not accept further votes)
 * register (clientnum) - Add client for votability
 * unregister (clientnum) - Remove client for votability
 * cast (clientnum, vote) - Take in vote
 * update () - tally votes, run pending enactments
 * isbusy () - vote in progress (taking votes)
 * enact (time) - schedule command for execution (votes collected, has passed)
 * force () - force execution of command immediately
*/



int Vote2_hook_svcmd (const char *);

void Cmd_callvote_f (gentity_t *ent);
void Cmd_vote_f (gentity_t *ent);
int CheckVote2 ();





/*******************/
/* Implementation. */
/*******************/

#define VOTE_YES 1
#define VOTE_NO 2



/* XXX: this should go into an external file.  Later, though. */
static char * strdup(const char *orig)
{
  char *x;
  int l;

  l = strlen(orig) + 1;
  x = (char*)malloc(l);
  memcpy(x, orig, l);
  return x;
}


/*
Submit a vote (callvote), entitled `name', which executes command `cmd'.

Returns:
 0 -  petitioning failed (e.g. vote still in progress).
 non-zero - petitioning succeeded (vote in progress).
*/
int
votebox_init (votebox_t *self, char *name, char *cmd)
{
  /* Can't set up if previous vote still in progress. */
  if (votebox_isbusy(self))
      return 0;
  /* flush pending enactments. */
  if (self->enacttime)
      votebox_force(self);
  if (cmd)
      Q_strncpyz(self->motion, cmd, sizeof(self->motion));
  else
      *self->motion = 0;
  if (name)
      Q_strncpyz(self->title, name, sizeof(self->title));
  else
      strcpy(self->title, self->motion);
  return 1;
}


/*
Tear down election.

Returns:
  nothing of significance.
*/
int
votebox_destroy (votebox_t *self)
{
  int i;
  memset(self, 0, sizeof(*self));
  return 42;
}


/*
Check if vote in progress.

Returns:
  0 - not in progress
  non-zero - in progress
*/
int
votebox_isbusy (votebox_t *self)
{
//  if (self->title[0])
  if (self->opentime)
    {
      return 1;
    }
  return 0;
}


/*
Begin collecting votes for `votetime' seconds.
Vote passes after `passfrac' fraction of votes are Yes.
Vote fails when No votes exceed (1.0 - `passfrac').

Returns:
 0 - failure/problem
 non-zero - success
*/
int
votebox_open (votebox_t *self, float passfrac, int votetime)
{
  int i;

  if (!self->motion)
    {
      return 0;
    }
  self->majority = passfrac;
  self->numvoters = 0;
  for (i = 0; i < MAX_CLIENTS; i++)
    {
      self->ballot[i] = 0;
      if (self->weight[i] > 0)
          self->numvoters++;
    }
  self->opentime = level.time;
  self->closetime = self->opentime + (votetime * 1000);
//printf("Election started at %d with %d registered voters\n", self->opentime, self->numvoters);
  return 1;
}

/*
Close the election.
Stop accepting votes, preserve enactment data.

Returns:
  0 - failure
  non-zero - success
*/
int
votebox_close (votebox_t *self)
{
  int i;

//printf("Voting ends.\n");
  self->title[0] = 0;
  self->numvoters = 0;
  self->aye = 0;
  self->nay = 0;
  self->majority = 0;
  self->opentime = 0;
  self->closetime = 0;
  for (i = 0; i < MAX_CLIENTS; i++)
    {
      self->weight[i] = 0;
      self->ballot[i] = 0;
    }
}

/*
Register client `voter' for voting, with ballot weight `weight' (typically 1).

Returns:
  0 - failure in registrations process
  other - success
*/
int
votebox_register (votebox_t *self, int voter, int weight)
{
//  if (!self->weight[voter])
//      self->numvoters++;
  self->weight[voter] = weight;
  self->ballot[voter] = 0;
  return 1;
}

/*
Remove client from vote registration, revoking the accompanying ballot.

Returns:
  0 - failure in process
  other - success
*/
int
votebox_unregister (votebox_t *self, int voter)
{
//  if (self->weight[voter])
//      self->numvoters--;
  self->weight[voter] = 0;
  self->ballot[voter] = 0;
  return 1;
}

/*
Player casts a vote.

Returns:
  0 - vote denied (already cast, not allowed to vote)
  other - registered choice.
*/
int
votebox_cast (votebox_t *self, int voter, int choice)
{
  if (!votebox_isbusy(self))
      return 0;  /* No vote in progress. */
  if (!self->weight[voter])
      return 0;  /* Not allowed to vote. */
  if (!self->recastable)
    {
      if (self->ballot[voter])
          return 0;  /* Not allowed to re-vote. */
    }
  /* record. */
  self->ballot[voter] = choice;
//  /* tally. */
//  votebox_update(self);
  /* report. */
  return self->ballot[voter];
}

/*
Schedule the command to run in `graceperiod' seconds.

Returns:
 0 - failure
 non-zero - success
*/
int
votebox_enact (votebox_t *self, int graceperiod)
{
  /* XXX: proclaim passage? */
  self->enacttime = level.time + (graceperiod * 1000);
//  trap_SendConsoleCommand(EXEC_APPEND, self->motion);
//  self->motion[0] = 0;

  return 1;
}

/*
Force immediate execution regardless of grace period.

Returns:
 0 - failure
 else - success
*/
int
votebox_force (votebox_t *self)
{
  if (self->enacttime)
    {
      trap_SendConsoleCommand(EXEC_APPEND, va("%s\n", self->motion));
      self->enacttime = 0;
      self->motion[0] = 0;
    }
  return 1;
}















/*
Find out the state of voting.

Returns:
 VOTE_NO : no wins
 VOTE_YES : yes wins
 0 : still voting/no change in election result
*/
int
votebox_update (votebox_t *self)
{
  int i;

//  if (self->closetime > level.time)
//    {
//      /* Vote fails.  If it succeeded, we wouldn't be triggering this check. */
//      return -1;
//    }
  if (!votebox_isbusy(self))
    {
      /* not taking votes.  Do we need to enact? */
      if (!self->enacttime)
          return 0;
      if (self->enacttime < level.time)
          votebox_force(self);
      return 0;
    }
  /* if no one can vote, then vote cannot ever pass. */
  if (self->numvoters == 0)
      return VOTE_NO;
  self->aye = self->nay = 0;
  for (i = 0; i < MAX_CLIENTS; i++)
    {
      switch (self->ballot[i])
        {
          case VOTE_YES:
            self->aye += self->weight[i];
            break;
          case VOTE_NO:
            self->nay += self->weight[i];
            break;
          default:
            break;
        }
    }
  if (level.time - self->opentime >= VOTE_TIME)
      return VOTE_NO;
  if ((self->aye/(float)self->numvoters) > self->majority)
      return VOTE_YES;
  if ((self->nay/(float)self->numvoters) >= (1.0 - self->majority))
      return VOTE_NO;

  return 0;
}




/*
Set configstrings.

Returns:
 0 - failure
 else - success
*/
int
votebox_configstrings (votebox_t *self, int cs_time, int cs_string, int cs_aye, int cs_nay)
{
  if (cs_time > 1)
    {
      if (self->opentime)
          trap_SetConfigstring(cs_time, va("%d", self->opentime));
      else
          trap_SetConfigstring(cs_time, "");
    }
  if (cs_string > 1)
      trap_SetConfigstring(cs_string, self->title);
  if (cs_aye > 1)
      trap_SetConfigstring(cs_aye, va("%d", self->aye));
  if (cs_nay > 1)
      trap_SetConfigstring(cs_nay, va("%d", self->nay));
  return 1;
}








/* Callvoting. */

/*
Ruleset:
  |
  |-- "kick"
  |     \-- :playername
  |
  |-- "fraglimit"
  |     \-- :integer
  |            |- 20
  |            \- 200
  |
  |-- "gametype"
  |     |-- ! -- :integer
  |     |           \-- 2
  |     \-- :integer
  |           |- 0
  |           \- 5
  |
  \-- "g_friendlyfire"
        \-- :real
              |- -1.0
              \- 1.0
*/


#define MAX_RULESET 32

enum {
  RULETYPE_ANY,
  RULETYPE_INT,
  RULETYPE_REAL,
  RULETYPE_ENUM,
  RULETYPE_PLAYERNAME,
  RULETYPE_MAPNAME,
  RULETYPE_VOID,
};

union callvote_ruleitem_range_u {
  struct {
    int lower;
    int upper;
  } N;
  struct {
    float lower;
    float upper;
  } R;
  char *s;
};

struct callvote_ruleitem_s {
  qboolean allow;
  float pass;     /* percentage "yes" for passage. */
  int ruletype;
  union callvote_ruleitem_range_u range;
  struct callvote_ruleitem_s *next;
};

/* tail points to last valid member.  NULL means no list. */

struct callvote_rule_s {
  char *cmd;    /* base command name. */
  int num_constraints;  /* number of parameter constraints. */
  struct callvote_ruleitem_s *head, *tail;
  struct callvote_rule_s *next;
};

struct callvote_ruleset_s {
  char list[MAX_TOKEN_CHARS];
  int num_rules;
  struct callvote_rule_s *head, *tail;
};




void
callvote_ruleitem_init (struct callvote_ruleitem_s *self)
{
  memset(self, 0, sizeof(*self));
  self->allow = 1;
  self->next = NULL;
}

void
callvote_ruleitem_destroy (struct callvote_ruleitem_s *self)
{
  self->allow = 0;
  self->ruletype = 0;
  self->range.R.upper = 0;
  self->range.R.lower = 0;
  self->next = 0;
}

void
callvote_ruleitem_setany (struct callvote_ruleitem_s *self)
{
  self->ruletype = RULETYPE_ANY;
  self->range.s = 0;
}

void
callvote_ruleitem_setint (struct callvote_ruleitem_s *self, int lower, int upper)
{
  self->ruletype = RULETYPE_REAL;
  self->range.N.lower = lower;
  self->range.N.upper = upper;
}

void
callvote_ruleitem_setreal (struct callvote_ruleitem_s *self, float lower, float upper)
{
  self->ruletype = RULETYPE_REAL;
  self->range.R.lower = lower;
  self->range.R.upper = upper;
}

void
callvote_ruleitem_setenum (struct callvote_ruleitem_s *self, char *string)
{
  self->ruletype = RULETYPE_ENUM;
  self->range.s = string;
}

void
callvote_ruleitem_allow (struct callvote_ruleitem_s *self)
{
  self->allow = 1;
}

void
callvote_ruleitem_deny (struct callvote_ruleitem_s *self)
{
  self->allow = 0;
}




void
callvote_rule_init (struct callvote_rule_s *self, char *cmd)
{
  if (!self) return;
  self->head = self->tail = NULL;
  self->cmd = cmd;
  self->num_constraints = 0;
}

void
callvote_rule_destroy (struct callvote_rule_s *self)
{
  struct callvote_ruleitem_s *curr, *next;

  curr = self->head;
  while (curr)
    {
      next = curr->next;
      callvote_ruleitem_destroy(curr);
      free(curr);
      curr = next;
    }
  self->num_constraints = 0;
  free(self->cmd);
  self->cmd = 0;
  self->head = self->tail = NULL;
}

/*
Get pointer such that ->next points to nth element.
Returns:
  NULL - is head.
  otherwise - elements such that ->next points to the nth element.
              if ->next points to NULL, is tail.
*/
struct callvote_ruleitem_s *
callvote_rule_find (struct callvote_rule_s *self, int n)
{
  struct callvote_ruleitem_s *curr;
  int i;

  if (!self->head)
      return NULL;
  if (n == 0)
      return NULL;
  curr = self->head;
  /* So that '1' stops at head (head->next is index 1) */
  for (i = 1; i < n; i++)
    {
      if (curr->next == NULL)
          break;
      curr = curr->next;
    }
  return curr;
}

/* Returns pointer such that ->next == tail */
struct callvote_ruleitem_s *
callvote_rule_harm_tail (struct callvote_rule_s *self)
{
  struct callvote_ruleitem_s *curr;

  if (!self->head)
      return NULL;
  if (!self->tail)
      return NULL;
  curr = self->head;
  while (curr && (curr->next != self->tail))
    {
      curr = curr->next;
    }
  return curr;
}

int
callvote_rule_add (struct callvote_rule_s *self, struct callvote_ruleitem_s *ruleitem)
{
  struct callvote_ruleitem_s *curr;

  ruleitem->next = NULL;
  if (!self->head)
    {
      self->head = ruleitem;
      self->head->next = NULL;
      self->tail = self->head;
    }
  self->tail->next = ruleitem;
  self->tail = ruleitem;
  self->tail->next = NULL;
  self->num_constraints++;
  return self->num_constraints;
}

int
callvote_rule_insert (struct callvote_rule_s *self, int n, struct callvote_ruleitem_s *ruleitem)
{
  struct callvote_ruleitem_s *curr;

  if (!self->head)
    {
      return callvote_rule_add(self, ruleitem);
    }

  curr = callvote_rule_find(self, n);
  if (curr == NULL)
    {
      /* start of list. */
      ruleitem->next = self->head;
      self->head = ruleitem;
    }
  else
    {
      if (curr->next)
        {
          /* middle of list. */
          ruleitem->next = curr->next;
          curr->next = ruleitem->next;
        }
      else
        {
          /* end of list. */
          self->tail->next = ruleitem;
          ruleitem->next = NULL;
        }
    }
  self->num_constraints++;
  return self->num_constraints;
}

int
callvote_rule_replace (struct callvote_rule_s *self, int n, struct callvote_ruleitem_s *ruleitem)
{
  struct callvote_ruleitem_s *curr;

  if (!self->head)
    {
       return callvote_rule_add(self, ruleitem);
    }

  curr = callvote_rule_find(self, n);
  if (curr == NULL)
    {
      /* start of list. */
      ruleitem->next = self->head->next;
      free(self->head);
      self->head = ruleitem;
    }
  else
    {
      if (curr->next)
        {
          /* middle of list. */
          ruleitem->next = curr->next->next;
          free(curr->next);
          curr->next = ruleitem;
        }
      else
        {
          /* end of list. */
          /* nothing to replace.  append instead. */
          curr = callvote_rule_harm_tail(self);
          if (curr)
            {
              free(curr->next);
              curr->next = ruleitem;
              self->tail = curr->next;
              self->tail->next = NULL;
            }
        }
    }
  return self->num_constraints;
}

int
callvote_rule_remove (struct callvote_rule_s *self, int n)
{
  struct callvote_ruleitem_s *curr, *loss;

  curr = callvote_rule_find(self, n);
  if (curr == NULL)
    {
      /* start of list. */
      curr = self->head;
      self->head = self->head->next;
      free(curr);
    }
  else
    {
      if (curr->next)
        {
          /* middle of list. */
          loss = curr->next;
          curr->next = curr->next->next;
          free(loss);
        }
      else
        {
          /* end of list. */
          curr = callvote_rule_harm_tail(self);
          if (!curr)
              return self->num_constraints;
          free(curr->next);
          self->tail = curr;
          self->tail->next = NULL;
        }
    }
  self->num_constraints--;
  return self->num_constraints;
}





int
callvote_ruleset_init (struct callvote_ruleset_s *self)
{
  if (!self) return 0;
  self->num_rules = 0;
  self->head = self->tail = NULL;
  return 0;
}

void
callvote_ruleset_destroy (struct callvote_ruleset_s *self)
{
  struct callvote_rule_s *curr, *next;

  curr = self->head;
  while (curr)
    {
      next = curr->next;
      callvote_rule_destroy(curr);
      free(curr);
      curr = next;
    }
  self->num_rules = 0;
  self->head = self->tail = NULL;
}

/*
Add `cmdname' to list of ruleset.

Returns:
  NULL - a problem.
  pointer to rule struct - memory space of rule item (malloc'd if needed)
*/
struct callvote_rule_s *
callvote_ruleset_add (struct callvote_ruleset_s *self, char *cmdname)
{
  struct callvote_rule_s *curr;

  if (!self->head)
    {
      self->head = malloc(sizeof(*curr));
      callvote_rule_init(self->head, strdup(cmdname));
      self->tail = self->head;
      self->num_rules = 1;
      snprintf(self->list, sizeof(self->list), "%s", cmdname);
      return self->head;
    }
  /* see if this command already in rulesets list. */
  curr = self->head;
  while (curr)
    {
      if (Q_stricmp(curr->cmd, cmdname) == 0)
          break;
      curr = curr->next;
    }
  if (curr)
    {
      /* exists. */
      return curr;
    }
  /* does not exist.  append. */
  self->tail->next = malloc(sizeof(*curr));
  self->tail = self->tail->next;
  callvote_rule_init(self->tail, strdup(cmdname));
  snprintf(self->list, sizeof(self->list), va("%s, %s", self->list, cmdname));
  self->num_rules++;
  return self->tail;
}





/*
check if particular vote parameter is allowed.

Returns fraction votes for passage.
*/
struct callvote_ruleitem_s *
callvote_rule_match (struct callvote_rule_s *rule, char *parm)
{
  struct callvote_ruleitem_s *ruleitem;
  int N;
  float R;
  int match;

  for (ruleitem = rule->head; ruleitem; ruleitem = ruleitem->next)
    {
      switch (ruleitem->ruletype)
        {
          case RULETYPE_INT:
            if (!parm || !*parm) break;
            R = atof(parm);
            N = (int)R;
#define EPSILON 0.0001
            if (fabs(R - (float)N) > EPSILON)
                break;
            N = atoi(parm);
            if ((ruleitem->range.N.lower <= N) && (N <= ruleitem->range.N.upper))
                return ruleitem;
            break;
          case RULETYPE_REAL:
            if (!parm || !*parm) break;
            R = atoi(parm);
            if ((ruleitem->range.R.lower <= N) && (N <= ruleitem->range.R.upper))
                return ruleitem;
            break;
          case RULETYPE_ENUM:
            if (parm == ruleitem->range.s)
                return ruleitem;
            if (!parm || !*parm) break;
            if (Q_stricmp(ruleitem->range.s, parm) == 0)
                return ruleitem;
            break;
          case RULETYPE_VOID:
            if (parm && *parm) break;
            return ruleitem;
            break;
          case RULETYPE_ANY:
            if (!parm || !*parm) break;
            return ruleitem;
            break;
          default:
            return ruleitem;
            break;
        }
    }
  /* deny by default. */
//printf("Rule for [%s] in [%s] not found\n", parm, rule->cmd);
  return NULL;
}

#if 0
/*
check if particular vote is allowed.

Returns fraction votes for passage.
*/
float
callvote_ruleset_check (struct callvote_ruleset_s *ruleset, char *cmdname, char *parm)
{
  struct callvote_rule_s *curr;
  int i;

  for (curr = ruleset->head; curr; curr = curr->next)
    {
      if (Q_stricmp(curr->cmd, cmdname) == 0)
        {
          return callvote_rule_check(curr, parm);
        }
    }
  /* deny by default. */
//printf("Ruleset for [%s] not found\n");
  return -1;
}
#endif /* 0 */

struct callvote_rule_s *
callvote_ruleset_match (struct callvote_ruleset_s *ruleset, char *cmdname, char *parm)
{
  struct callvote_rule_s *curr;

  for (curr = ruleset->head; curr; curr = curr->next)
    {
      if (Q_stricmp(curr->cmd, cmdname) == 0)
        {
//          return callvote_rule_match(curr, parm);
          return curr;
        }
    }
  return NULL;
}










/***************************
* Q3 commands interfacing. *
****************************/


votebox_t globalvote;
struct callvote_ruleset_s CallvoteRuleset = { 0, };


int
Svcmd_votefilter_add ()
{
  struct callvote_rule_s *rule;
  struct callvote_ruleitem_s *ruleitem;
  char cmdname[256];
  char str[256];
  char buf[256];
  int argn, slen;
  char *p;

  ruleitem = malloc(sizeof(struct callvote_ruleitem_s));
  if (!ruleitem)
      return 0;
  callvote_ruleitem_init(ruleitem);
  argn = 1;
  trap_Argv(argn++, buf, sizeof(buf));
  ruleitem->pass = atof(buf);
  if (strchr(buf, '!'))
      ruleitem->allow = 0;
  if (strchr(buf, '%'))
      ruleitem->pass /= 100;
  trap_Argv(argn++, cmdname, sizeof(cmdname));
  trap_Argv(argn++, buf, sizeof(buf));
  /* determine parameter type. */
  slen = strspn(buf, "-+0123456789.:");
  if (0);
  else if (*buf == 0)
    {
      /* empty parameter. */
      ruleitem->ruletype = RULETYPE_ANY;
    }
  else if (Q_stricmp(buf, ".") == 0)
    {
      /* Disallow parameter. */
      ruleitem->ruletype = RULETYPE_VOID;
    }
  else if (slen == strlen(buf))
    {
      /* range (numbers). */
      if (strchr(buf, '.'))
          ruleitem->ruletype = RULETYPE_REAL;
      else
          ruleitem->ruletype = RULETYPE_INT;
      p = strchr(buf, ':');  /* indicates explicit range (upper-bound). */
      switch (ruleitem->ruletype)
        {
          case RULETYPE_REAL:
            ruleitem->range.R.lower = atof(buf);
            if (p)
                ruleitem->range.R.upper = atof(p+1);
            else
                ruleitem->range.R.upper = ruleitem->range.R.lower;
            break;
          case RULETYPE_INT:
            ruleitem->range.N.lower = atoi(buf);
            if (p)
                ruleitem->range.N.upper = atoi(p+1);
            else
                ruleitem->range.N.upper = ruleitem->range.N.lower;
            break;
        }
    }
  else
    {
      /* enumeration (exact string). */
      ruleitem->ruletype = RULETYPE_ENUM;
      ruleitem->range.s = strdup(buf);
    }

  rule = callvote_ruleset_add(&CallvoteRuleset, cmdname);
  if (rule)
    {
      callvote_rule_add(rule, ruleitem);
    }
  else
    {
      free(ruleitem);
    }
  return 0;
}

int
Svcmd_votefilter_clear ()
{
  callvote_ruleset_destroy(&CallvoteRuleset);
  callvote_ruleset_init(&CallvoteRuleset);
  return 0;
}

int
Svcmd_votefilter_show ()
{
  struct callvote_rule_s *rule;
  struct callvote_ruleitem_s *ruleitem;

  Com_Printf("begin callvote ruleset\n");
  Com_Printf("pass   command         parmtype  lower  upper\n");
  for (rule = CallvoteRuleset.head; rule; rule = rule->next)
    {
      for (ruleitem = rule->head; ruleitem; ruleitem = ruleitem->next)
        {
          if (ruleitem->allow)
              Com_Printf("%.3f  ", ruleitem->pass);
          else
              Com_Printf("%5s  ", "!");
          Com_Printf("%20s ", rule->cmd);
          switch (ruleitem->ruletype)
            {
              case RULETYPE_INT:
                Com_Printf("N  %6d %6d\n", ruleitem->range.N.lower, ruleitem->range.N.upper);
                break;
              case RULETYPE_REAL:
                Com_Printf("R  %#3.4f %#3.4f\n", ruleitem->range.R.lower, ruleitem->range.R.upper);
                break;
              case RULETYPE_ENUM:
                Com_Printf("S  %s\n", ruleitem->range.s);
                break;
              case RULETYPE_VOID:
                Com_Printf(".\n");
                break;
              default:
                Com_Printf("(any)\n");
                break;
            }
        }
    }
  Com_Printf("end callvote ruleset\n");
  return 0;
}




extern char *ConcatArgs(int);

int
Svcmd_votefilter_test ()
{
  char cmdname[256];
  char parm[256];
  char reason[MAX_TOKEN_CHARS];
//  float x;
  struct callvote_ruleitem_s *x;
  int argn;

  argn = 1;
  trap_Argv(argn++, cmdname, sizeof(cmdname));
  trap_Argv(argn++, parm, sizeof(parm));
  Q_strncpyz (reason, ConcatArgs(argn++), sizeof(reason));
/* send through filters. */
  x = callvote_rule_match(callvote_ruleset_match(&CallvoteRuleset, cmdname, parm), parm);
  if (x && (x->allow) && (x->pass >= 0))
    {
      Com_Printf("callvote [%s] [%s]  =>  ALLOW (%2.2f%%)\n", cmdname, parm, x->pass*100);
    }
  else
    {
      Com_Printf("callvote [%s] [%s]  =>  DENY\n", cmdname, parm);
    }
  return 0;
}


#define TellClient(foo) trap_SendServerCommand(clientnum, va("print \"%s\"", foo))

void Cmd_callvote_f (gentity_t *ent)
{
  struct callvote_rule_s *rule;
  struct callvote_ruleitem_s *ruleitem;
  int i;
  int clientnum;
//  float passfrac;
  char cmdname[256];
  char parm[256];
  char reason[MAX_TOKEN_CHARS];
  char votecmd[MAX_TOKEN_CHARS];
  char votetitle[MAX_TOKEN_CHARS];
  int argn;

  clientnum = ent - g_entities;
  if ((!g_allowVote.integer) || (!CallvoteRuleset.num_rules))
    {
      TellClient("Voting not allowed here.\n");
      return;
    }
#ifdef USE_SANE_NEXTMAP

    /* Save original nextmap. */
    if (!*sv_nextmap.string) {
        trap_Cvar_VariableStringBuffer("nextmap", sv_nextmap.string, sizeof(sv_nextmap.string));
    }
#endif /* USE_SANE_NEXTMAP */

  if (votebox_isbusy(&globalvote))
    {
      TellClient("Vote already in progress.\n");
      return;
    }

  if (ent->client->pers.voteCount >= MAX_VOTE_COUNT)
    {
      TellClient("You have called the maximum number of votes.\n");
      return;
    }
/* Allow spectator vote.  Why shouldn't they? */
#if 0
  if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
    {
      TellClient("Not allowed to call a vote as spectator.\n");
      return;
    }
#endif /* 0 */


  argn = 1;
  trap_Argv(argn++, cmdname, sizeof(cmdname));
  trap_Argv(argn++, parm, sizeof(parm));
  Q_strncpyz (reason, ConcatArgs(argn++), sizeof(reason));
  rule = callvote_ruleset_match(&CallvoteRuleset, cmdname, parm);
  ruleitem = callvote_rule_match(rule, parm);
  if (!rule)
    {
      /* Bad command.  Tell what's available. */
      TellClient(va("Allowed vote commands: %s\n", CallvoteRuleset.list));
      return;
    }
  if ((!ruleitem) || (!ruleitem->allow))
    {
      /* Disallowed parameter.  Tell what's allowed. */
      TellClient(va("Parameter '%s' to command '%s' not allowed\n", parm, cmdname));
      return;
    }

  /* Execute any pending enactment before starting election. */
  votebox_force(&globalvote);

  /* Special-case commands. */
  if (0);
  else if (Q_stricmp(cmdname, "map") == 0)
    {
      // special case for map changes, we want to reset the nextmap setting
      // this allows a player to change maps, but not upset the map rotation
      char s[MAX_STRING_CHARS];

      trap_Cvar_VariableStringBuffer("nextmap", s, sizeof(s));
      if (*s)
        {
/* XXX: something about this thing may be causing map-rotation breakage... */
          snprintf(votecmd, sizeof(votecmd), "%s %s; set nextmap \"%s\"", cmdname, parm, s);
        }
      else
        {
          snprintf(votecmd, sizeof(votecmd), "%s %s", cmdname, parm);
        }
      snprintf(votetitle, sizeof(votetitle), "%s", votecmd);
    }
  else if (Q_stricmp(cmdname, "nextmap") == 0)
    {
      char s[MAX_STRING_CHARS];

      trap_Cvar_VariableStringBuffer("nextmap", s, sizeof(s));
      if (!*s)
        {
          TellClient("nextmap not set.\n");
          return;
        }

      snprintf(votecmd, sizeof(votecmd), "vstr nextmap");
      snprintf(votetitle, sizeof(votetitle), "%s", votecmd);
    }
  else
    {
      snprintf(votecmd, sizeof(votecmd), "%s \"%s\"", cmdname, parm);
      snprintf(votetitle, sizeof(votetitle), "%s", votecmd);
    }


  /* obliterate vote box and build it up. */
  votebox_destroy(&globalvote);
  if (!votebox_init(&globalvote, votetitle, votecmd))
    {
      /* Why would it fail now?  Oh well. */
      return;
    }

  trap_SendServerCommand(-1, va("print \"%s^7 called a vote.\n\"", ent->client->pers.netname));

  // start the voting, the caller automatically votes yes
  /* Register valid voters. */
  for (i = 0; i < MAX_CLIENTS; i++)
    {
      /* Filter out based on who can't vote. */
      if (level.clients[i].pers.connected != CON_CONNECTED) continue;
      if (level.clients[i].sess.sessionTeam == TEAM_SPECTATOR) continue;
      votebox_register(&globalvote, i, 1);
    }
  votebox_open(&globalvote, ruleitem->pass, 30);
  votebox_cast(&globalvote, clientnum, VOTE_YES);

  votebox_configstrings(&globalvote, CS_VOTE_TIME, CS_VOTE_STRING, CS_VOTE_YES, CS_VOTE_NO);
#if 0
  for (i = 0; i < level.maxclients; i++)
    {
      level.clients[i].ps.eFlags &= ~EF_VOTED;
    }

  ent->client->ps.eFlags |= EF_VOTED;

  trap_SetConfigstring(CS_VOTE_TIME, va("%i", level.voteTime));
  trap_SetConfigstring(CS_VOTE_STRING, level.voteDisplayString);
  trap_SetConfigstring(CS_VOTE_YES, va("%i", level.voteYes));
  trap_SetConfigstring(CS_VOTE_NO, va("%i", level.voteNo));
#endif /* 0 */

  return;
}

int
CheckVote2 ()
{
  int v;
  float margin = 0;

  v = votebox_update(&globalvote);
  switch (v)
    {
      case VOTE_YES:
        if (globalvote.numvoters)
            margin = globalvote.aye / (float)globalvote.numvoters;
//        trap_SendServerCommand(-1, va("print \"Vote passed (Y=%d N=%d, V=%d, with %.2f, needed %.2f).\n\"", globalvote.aye, globalvote.nay, globalvote.numvoters, margin, globalvote.majority));
        trap_SendServerCommand(-1, va("print \"Vote passed (with %2.1f, needed %2.1f)\n\"", margin * 100, globalvote.majority * 100));
//        trap_SendServerCommand(-1, "print \"Vote passed.\n"");
        votebox_close(&globalvote);
        votebox_enact(&globalvote, 3);
        break;
      case VOTE_NO:
        if (globalvote.numvoters)
            margin = globalvote.aye / (float)globalvote.numvoters;
//        trap_SendServerCommand(-1, va("print \"Vote failed (Y=%d N=%d, V=%d, with %.2f, needed %.2f).\n\"", globalvote.aye, globalvote.nay, globalvote.numvoters, margin, globalvote.majority));
        trap_SendServerCommand(-1, va("print \"Vote failed (at %2.1f, needs more than %2.1f)\n\"", margin * 100, globalvote.majority * 100));
//        trap_SendServerCommand(-1, "print \"Vote failed.\n\");
        votebox_close(&globalvote);
        break;
      case 0:
      default:
        /* Still pending. */
        break;
    }
    
  votebox_configstrings(&globalvote, CS_VOTE_TIME, CS_VOTE_STRING, CS_VOTE_YES, CS_VOTE_NO);

  return 1;
}

void
Cmd_vote_f (gentity_t *ent)
{
  char parm[MAX_CVAR_VALUE_STRING];
  int ballot;
  int clientnum;
  int argn;

  clientnum = ent - g_entities;
  argn = 1;
  trap_Argv(argn++, parm, sizeof(parm));
  switch (*parm)
    {
      /* XXX: Biased towards English. */
      case 'Y':
      case 'y':
      case '1':
        ballot = VOTE_YES;
        break;
      default:
        ballot = VOTE_NO;
        break;
    }
  if (votebox_cast(&globalvote, clientnum, ballot))
    {
      TellClient("Vote recorded\n");
    }
  else
    {
      TellClient("Vote already cast.\n");
    }
}


int
Vote2_hook_svcmd (const char *cmd)
{
#define SVCMD(str, fn) if (!Q_stricmp(cmd,str)) { fn(); return qtrue; }
    SVCMD("votefilter_clear", Svcmd_votefilter_clear)
    SVCMD("votefilter_add", Svcmd_votefilter_add)
    SVCMD("votefilter_show", Svcmd_votefilter_show)
    SVCMD("votefilter_test", Svcmd_votefilter_test)
    return qfalse;
}
