/* Formula program for FICS.  Written by Dave Herscovici
                                         (dhersco@math.nps.navy.mil)
			      Edited by DAV to include wild,non-std and untimed
				 lightning flags

   Operators allowed, in order of precedence:
      ! (not), - (unary),
      *, /,
      +, -,
      <, <=, =<, >, >=, =>, (<= and =< are equivalent, as are >= and =>)
      =, ==, !=, <>, (two ways of saying both equals and not equals)
      &, &&, (both "and")
      |, ||, (both "or").

   Parentheses () are also allowed, as is a pound sign '#' for comments.
   The program will divide by a fudge factor of .001 instead of 0.

   Variables allowed:
      time, inc, rating, myrating, rated, blitz, standard, lightning, registered,
      assesswin, assessdraw, assessloss, timeseal,
      ratingdiff = rating - myrating, private, wild, untimed, nonstandard,
      maxtime(n) = maximum time n moves will take (in seconds),
      mymaxtime(n) = same, but just count my time.
      f1, f2, f3, f4, f5, f6, f7, f8, f9.

   The code for translating blitz and standard variables may have to be
   redone.  F1 through f9 are user-defined formula variables.  They can
   be used to avoid having to retype your entire formula when you want
   to change one part of it, or to compensate for the lack of a 'mood'
   variable.

   For example:
      set f1 rated & time=5 & inc=0   # rated 5 minute games
      set f2 rating - myrating
      set f3 # this line is a random comment.
      set f4 f2>400   # I want a REAL fight

   Then you can type:
      set formula f1   # rated 5 min. games only
      set formula etime >= 10 & f2 > -100 # long games, decent competition
      set formula f1 & !f4
   or set formula f2 >= 0 | blitz
   depending on your mood.

   Further modifications could account for starting positions, time
   odds games, provisional or established opponents, etc.  Maybe f0
   should be reserved for your formula upon logging in; i.e. if f0
   is set, your formula is automatically set to f0 when you log in.
*/

#include <ctype.h>
#include "config.h"
#include "formula.h"
#include "playerdb.h"
#include "gamedb.h"
#include "command.h"
#include "common.h"
#include "stdinclude.h"
#include "utils.h"
#include "rmalloc.h"
#include "lists.h"
#include "network.h"
#include "ratings.h"

extern player parray[];

PRIVATE char *GetPlayersFormula (player *pWho, int clause)
{
  if (clause==MAX_FORMULA) return (pWho->formula);
  else return (pWho->formulaLines[clause]);
}

/* In MoveIndexPastString, *i is treated as the index into string[];
   this function returns 1 if the character string beginning with
   string[*i] match *text, and 0 otherwise.  In the former case, *i
   is incremented to move the index past *text.
*/
int MoveIndexPastString (char *string, int *i, char *text)
{
  int n = strlen(text);
  if (strncasecmp(text, string + *i, n)) return (0);
  *i += n;
  return (n);
}    /* end of function MoveIndexPastString. */

/* GetRating simply chooses between blitz, standard and ratings. */
int GetRating(player *p, int gametype)
{
    if (gametype == TYPE_BLITZ) return (p->b_stats.rating);
    else if (gametype == TYPE_STAND) return (p->s_stats.rating);
    else if (gametype == TYPE_WILD) return (p->w_stats.rating);
    else if (gametype == TYPE_LIGHT) return (p->l_stats.rating);
    else if (gametype == TYPE_BUGHOUSE) return (p->bug_stats.rating);
    else return 0;
}    /* end of function GetRating. */

int GetNumberInsideParens (game *g, int clause, int *i, int *token,
                           int eval)
{
  char *string = GetPlayersFormula(&parray[g->black], clause);
  int ret;

  while (string[*i] != '\0' && isspace(string[*i])) (*i)++;
  if (!MoveIndexPastString(string, i, "(")) return (ERR_BADCHAR);

  ret = CheckFormula(g, clause, i, OPTYPE_PAREN, token, eval);
  if (ret != ERR_NONE)
    return (ret);
  if (MoveIndexPastString(string, i, ")")) return (ERR_NONE);
  else return (ERR_PAREN);
}

int Maxtime (game *g, int numMoves, int numPlayers)
{
  int max;

  if ((g->bInitTime == g->wInitTime) && (g->bIncrement == g->wIncrement)) {
    max = numPlayers * (60 * g->wInitTime + numMoves * g->wIncrement);
    if ((g->type != TYPE_UNTIMED) && (g->wInitTime == 0))
      max +=10 * numPlayers; /* 0 x start with ten secs */

  } else if (numPlayers == 2) {
    max = 60 * (g->wInitTime + g->bInitTime)
            + numMoves * (g->wIncrement + g->bIncrement);
    if ((g->type != TYPE_UNTIMED) && (g->wInitTime == 0))
      max +=10; /* 0 x start with ten secs */
    if ((g->type != TYPE_UNTIMED) && (g->bInitTime == 0))
      max +=10; /* 0 x start with ten secs */

  } else {
    max = 60 * g->bInitTime + numMoves * g->bIncrement;
    if ((g->type != TYPE_UNTIMED) && (g->bInitTime == 0))
      max +=10; /* 0 x start with ten secs */
  } 

  return max;
}


/* The black player in game *g has been challenged.  S/he has a list
   of formulas, and we're checking the one given by the index clause
   (MAX_FORMULA denotes the actual formula; if clause is smaller,
   we're looking at one of the user-defined formulas).  We're at
   position *i in the formula.  If we find a legitimate variable there
   and eval is 1, VarToToken puts its value in *tok; if eval is 0,
   just move past the variable.  Returns 1 or 0, depending on whether
   a legitimate variable was found.
*/
int VarToToken (game *g, int clause, char *string, int *i, int *tok, int eval)
{
  player *me = &parray[g->black];
  player *you = &parray[g->white];
  int index=0, c;
  double dummy_sterr;

  /* We list possible variables with the longest names first. */
  if (MoveIndexPastString(string, i, "registered")) *tok=you->registered;
  else if (MoveIndexPastString(string, i, "ratingdiff"))
    *tok = GetRating(you, g->type) - GetRating(me, g->type);
  else if (MoveIndexPastString(string, i, "assessloss"))
  {
    if (g->rated)
      rating_sterr_delta(g->black, g->white, g->type,
                         time(0), RESULT_LOSS, tok, &dummy_sterr);
    else *tok = 0;
  }
  else if (MoveIndexPastString(string, i, "assessdraw"))
  {
    if (g->rated)
      rating_sterr_delta(g->black, g->white, g->type,
                         time(0), RESULT_DRAW, tok, &dummy_sterr);
    else *tok = 0;
  }
  else if (MoveIndexPastString(string, i, "assesswin"))
  {
    if (g->rated)
      rating_sterr_delta(g->black, g->white, g->type,
                         time(0), RESULT_WIN, tok, &dummy_sterr);
    else *tok = 0;
  }
  else if (MoveIndexPastString(string, i, "mymaxtime"))
  {
    if (GetNumberInsideParens(g, clause, i, tok, eval) != ERR_NONE)
      return (0);
    *tok = Maxtime (g, *tok, 1);
  }
#ifdef TIMESEAL
  else if (MoveIndexPastString(string, i, "timeseal"))
    *tok = con[you->socket].timeseal;
#endif
  else if (MoveIndexPastString(string, i, "myrating"))
    *tok = GetRating(me, g->type);
  else if (MoveIndexPastString(string, i, "computer"))
    *tok = in_list(-1, L_COMPUTER, you->name);
  else if (MoveIndexPastString(string, i, "standard"))
    *tok = (g->type == TYPE_STAND);
  else if (MoveIndexPastString(string, i, "private"))
    *tok = you->private;
  else if (MoveIndexPastString(string, i, "maxtime"))
  {
    if (GetNumberInsideParens(g, clause, i, tok, eval) != ERR_NONE) 
      return (0);
    *tok = Maxtime (g, *tok, 2);
  }
  else if (MoveIndexPastString(string, i, "abuser"))
    *tok = in_list(-1, L_ABUSER, you->name);
  else if (MoveIndexPastString(string, i, "rating"))
    *tok = GetRating(you, g->type);
  else if (MoveIndexPastString(string, i, "rated")) *tok=g->rated;
  else if (MoveIndexPastString(string, i, "blitz")) *tok = (g->type == TYPE_BLITZ);
  else if (MoveIndexPastString(string, i, "wild")) *tok = (g->type == TYPE_WILD);
  else if (MoveIndexPastString(string, i, "lightning")) *tok = (g->type == TYPE_LIGHT);
  else if (MoveIndexPastString(string, i, "nonstandard")) *tok = (g->type == TYPE_NONSTANDARD);
  else if (MoveIndexPastString(string, i, "untimed")) *tok = (g->type == TYPE_UNTIMED);
  else if (MoveIndexPastString(string, i, "time")) *tok=g->wInitTime;
  else if (MoveIndexPastString(string, i, "inc")) *tok=g->wIncrement;
  else if (tolower(string[*i])=='f' && isdigit(string[*i+1]) &&
           (c = (string[*i+1]-'1')) >= 0 && clause > c)
  {
    *i += 2;
    return (!CheckFormula (g, c, &index, OPTYPE_NONE, tok, eval));
  }
  else return (0);
  return (1);
}    /* end of function VarToToken. */

/* ScanForOp checks for an operator at position *i in string[]. */
int ScanForOp (char *string, int *i)
{
  while (string[*i] != '\0' && isspace(string[*i])) (*i)++;

  if (string[*i] == '\0') return (OP_NONE);
  if (string[*i] == '#') return (OP_NONE);
  if (string[*i] == ')') return (OP_RTPAREN);

  /* Two char operators and longer first. */
  if (MoveIndexPastString(string, i, "and")) return (OP_AND);
  if (MoveIndexPastString(string, i, "or")) return (OP_OR);
  if (MoveIndexPastString(string, i, "||")) return (OP_OR);
  if (MoveIndexPastString(string, i, "&&")) return (OP_AND);
  if (MoveIndexPastString(string, i, "==")) return (OP_EQ);
  if (MoveIndexPastString(string, i, "!=")) return (OP_NEQ);
  if (MoveIndexPastString(string, i, "<>")) return (OP_NEQ);
  if (MoveIndexPastString(string, i, ">=")) return (OP_GE);
  if (MoveIndexPastString(string, i, "=>")) return (OP_GE);
  if (MoveIndexPastString(string, i, "<=")) return (OP_LE);
  if (MoveIndexPastString(string, i, "=<")) return (OP_LE);

  /* One char operators now. */
  if (MoveIndexPastString(string, i, "|")) return (OP_OR);
  if (MoveIndexPastString(string, i, "&")) return (OP_AND);
  if (MoveIndexPastString(string, i, ">")) return (OP_GT);
  if (MoveIndexPastString(string, i, "<")) return (OP_LT);
  if (MoveIndexPastString(string, i, "=")) return (OP_EQ);
  if (MoveIndexPastString(string, i, "+")) return (OP_PLUS);
  if (MoveIndexPastString(string, i, "-")) return (OP_MINUS);
  if (MoveIndexPastString(string, i, "*")) return (OP_MULT);
  if (MoveIndexPastString(string, i, "/")) return (OP_DIV);
  return (OP_BAD);
}    /* end of function ScanForOp. */

/* OpType returns the precedence of the operator op. */
int OpType (int op)
{
  switch (op)
  {
    case OP_BAD:  return (OPTYPE_BAD);
    case OP_NONE:  return (OPTYPE_NONE);
    case OP_RTPAREN:  return (OPTYPE_RPAREN);
    case OP_OR:  return (OPTYPE_OR);
    case OP_AND:  return (OPTYPE_AND);
    case OP_EQ: case OP_NEQ:  return (OPTYPE_COMPEQ);
    case OP_GT: case OP_GE:  case OP_LT: case OP_LE:  return (OPTYPE_COMPGL);
    case OP_PLUS: case OP_MINUS:  return (OPTYPE_ADD);
    case OP_MULT: case OP_DIV:  return (OPTYPE_MULT);
    case OP_NEGATE: case OP_NOT:  return (OPTYPE_UNARY);
    default: return (OPTYPE_BAD);
  }
}    /* end of function OpType. */

/* In EvalBinaryOp, *left is the left operand; and op is the
   current operator.  *g and clause specify which formula string to
   look at (we're checking parray[g->black].formulaLines[clause]),
   and *i tells us where we are in the string.  We look for a right
   operand from position *i in the string, and place the expression
   (*left op right) in *left.  For example, if *left=6, op =
   OP_MULT, and we pull off right = 4, we replace *left with
   6*4=24.  Returns 0 if no error; otherwise indicates the error.
*/
int EvalBinaryOp (int *left, int op, game *g, int clause, int *i)
{
  int right, ret;
  if ((op==OP_OR) && (*left != 0))            /* Nothing to decide. */
  {
    *left = 1;
    return (CheckFormula (g, clause, i, OpType(op), &right, 0));
  }
  else if ((op==OP_AND) && (*left == 0))     /* Nothing to decide. */
    return (CheckFormula (g, clause, i, OpType(op), &right, 0));

  else {
    ret = CheckFormula (g, clause, i, OpType(op), &right, 1);
    if (ret != ERR_NONE) return (ret);
  }
  switch (op)
  {
    default:  case OP_BAD:  return (ERR_BADOP);
    case OP_NONE:  case OP_RTPAREN:  return (ERR_NONE);
    case OP_OR:  *left = (*left || right);  return (ERR_NONE);
    case OP_AND:  *left = (*left && right);  return (ERR_NONE);
    case OP_EQ:  *left = (*left == right);  return (ERR_NONE);
    case OP_NEQ:  *left = (*left != right);  return (ERR_NONE);
    case OP_GT:  *left = (*left > right);  return (ERR_NONE);
    case OP_GE:  *left = (*left >= right);  return (ERR_NONE);
    case OP_LT:  *left = (*left < right);  return (ERR_NONE);
    case OP_LE:  *left = (*left <= right);  return (ERR_NONE);
    case OP_PLUS:  *left += right;  return (ERR_NONE);
    case OP_MINUS:  *left -= right;  return (ERR_NONE);
    case OP_MULT:  *left *= right;  return (ERR_NONE);
    case OP_DIV:
      if (right != 0)
      {
        *left /= right;
        return (ERR_NONE);
      }
      else
      {
        pprintf(g->black, "Dividing by %lf instead or zero in formula.\n", 
FUDGE_FACTOR);
        *left /= FUDGE_FACTOR;
        return (ERR_NONE);
      }
  }
}    /* end of function EvalBinaryOp. */

/* If eval is 1, ScanForNumber evaluates the number at position *i
   in the formula given by *g and clause, and place this value in
   *token.  op_type is the precedence of the operator preceding the
   *i'th position.  If we come to an operator of higher precedence,
   we must keep going before we can leave this function.  If eval
   is 0, just move past the number we would evaluate.  Returns 0 if
   no error; otherwise return code indicates the error.
*/
int ScanForNumber (game *g, int clause, int *i, int op_type,
                   int *token, int eval)
{
  char *string = GetPlayersFormula (&parray[g->black], clause);
  char c;
  int ret;

  while ((string[*i] != '\0') && (isspace(string[*i]))) (*i)++;
  switch (c = string[*i])
  {
    case '\0':  case '#':
      if (op_type != OPTYPE_NONE) return (ERR_EOF);
      else *token=1;
      return (ERR_NONE);

    case ')':
      if (op_type != OPTYPE_PAREN) return (ERR_PAREN);
      else *token=1;
      return(ERR_NONE);

    case '(':  return GetNumberInsideParens(g,clause,i,token,eval);

    case '-':  case '!':
      ++(*i);
      if (c==string[*i]) return(ERR_UNARY);
      ret = ScanForNumber(g,clause,i,OPTYPE_UNARY,token,eval);
      if (ret != ERR_NONE)
        return (ret);
      if (c == '-') *token = -(*token);
      else if (c == '!') *token = !(*token);
      return (ERR_NONE);

    default:
      if (isdigit(string[*i]))
      {
        *token = 0;

        do *token = 10 * (*token) + string[(*i)++] - '0';
        while (isdigit(string[*i]));

        while (string[*i] != '\0' && isspace(string[*i])) (*i)++;
        if (MoveIndexPastString (string, i, "minutes"))
          *token *= 60;
        return (ERR_NONE);
      }
      else if (isalpha(string[*i]))
      {
        if (!VarToToken (g, clause, string, i, token, eval))
          return (ERR_BADVAR);
        return(ERR_NONE);
      }    
      else return (ERR_NONESENSE);
  }
}    /* end of function ScanForNumber. */

/* If eval is 1, CheckFormula looks for the next token in the
   formula given by *g, clause, and *i; usually this is the right
   side of an expression whose operator has precedence OpType; if
   eval is 0, just go to the end of an expression.  Return 0 if no
   error; otherwise, return error type.
*/
int CheckFormula (game *g, int clause, int *i, int op_type,
                  int *result, int eval)
{
  int token, ret, nextOp, lastPos;
  char *string = GetPlayersFormula (&parray[g->black], clause);

  if (string == NULL)
  {
    *result = 1;
    return (ERR_NONE);
  }
  ret = ScanForNumber (g, clause, i, op_type, &token, eval);
  if (ret != ERR_NONE)
    return (ret);
  lastPos = *i;
  nextOp = ScanForOp (string, i);
  while (OpType(nextOp) > op_type)    /* higher precedence. */
  {
    if (nextOp==OP_RTPAREN) return (ERR_PAREN);
    if (!eval)
      ret = CheckFormula(g, clause, i, OpType(nextOp), &token, 0);
    else ret = EvalBinaryOp (&token, nextOp, g, clause, i);
    if (ret != ERR_NONE) return (ret);
    lastPos = *i;
    nextOp = ScanForOp (string, i);
  }
  if (nextOp == OP_BAD) return(ERR_BADOP);
  *result = token;

  /* move back unless we're at the end or at a right paren, in which
     case we never went forward. */
  if (nextOp != OP_NONE && nextOp != OP_RTPAREN) *i=lastPos;
  return (ERR_NONE);
}    /* end of function CheckFormula. */

/* which clauses are relevant for a player's formula. */
PRIVATE int ChooseClauses(player *who, char *formula)
{
  int i, which, ret=0;

  if (formula == NULL)
    return ret;
  for (i=0; formula[i] != '\0' && formula[i] != '#'; i++) {
    if (formula[i] != 'f' || (i > 0 && isalnum(formula[i-1]))
        || !isdigit(formula[i+1]))
      continue;
    sscanf(&formula[i], "f%d", &which);
    ret |= (1 << (which - 1));
  }
  /* now scan clauses found as part of the formula. */
  for (i = MAX_FORMULA - 1; i >= 0; i--) {
    if (ret & (1 << i)) {
      ret |= ChooseClauses(who, who->formulaLines[i]);
    }
  }
  return ret;
}    /* end of function ChooseClauses. */

void ExplainFormula (game *g, textlist **clauses)
{
  int i, which, dummy_index, value;
  char txt[20];
  player *challenged = &parray[g->black];
  textlist **Cur = clauses;

  which = ChooseClauses(challenged, challenged->formula);

  for (i = 0; i < MAX_FORMULA; i++) {
    if ((which & (1 << i)) == 0)
      continue;
    dummy_index = 0;
    CheckFormula (g, i, &dummy_index, OPTYPE_NONE, &value, 1);
    sprintf(txt, "%d", value);
    SaveTextListEntry (Cur, txt, i);
    Cur = &(*Cur)->next;
  }
}

#if 0
void ExplainFormula (game *g, textlist *eval)
{
  int i, newPos, value;
  char Head[MAX_STRING_LENGTH],
       Tail[MAX_STRING_LENGTH];

  strcpy (strRet, GetPlayersFormula(&parray[g->black], clause));
  for (i=0; strRet[i] != '\0'; i++)
  {
    if (strRet[i] == '#')
    {
      strRet[i] = '\0';
      break;
    }
    if (!isalpha(strRet[i])) continue;
    newPos = i;
    if (!VarToToken(g, clause, strRet, &newPos, &value, 1))
      i = newPos;
    else
    {
      strcpy (Head, strRet);
      strcpy (Tail, strRet + newPos);
      Head[i] = '\0';

      sprintf(strRet, "%s%d%s", Head, value, Tail);
      while (isalpha(strRet[i])) i++;
    }
  }
}
#endif

/* GameMatchesFormula converts parameters to a game structure
   and passes a pointer to this game to CheckFormula for
   evaluation.  It returns the evaluated formula.
*/
PUBLIC int GameMatchesFormula (int w, int b, int wTime, int wInc, int bTime,
           int bInc, int rated, int gametype, textlist **clauseList)
{
  game g;
  int index=0, ret;

  g.white = w;  g.black = b;
  g.wInitTime = wTime;  g.bInitTime = bTime;
  g.wIncrement = wInc;  g.bIncrement = bInc;
  g.rated = rated;
  g.type = gametype;

  if (CheckFormula(&g, MAX_FORMULA, &index, OPTYPE_NONE, &ret, 1) != ERR_NONE)
    return (0);
  if (ret == 0) ExplainFormula (&g, clauseList);
  return (ret);
}    /* end of function GameMatchesFormula. */

/* SetValidFormula sets a clause of player p and creates a game structure
   to check whether that new formula is legitimate.  If so, return 1;
   otherwise, reset the formula and return 0.
*/
int SetValidFormula (int p, int clause, char *string)
{
  game g;
  int index=0, ret, err=ERR_NONE;
  char *Old=NULL, **Cur;
  player *me = &parray[p];

  if (clause==MAX_FORMULA)
    Cur = &me->formula;
  else
    Cur = &me->formulaLines[clause];

  Old = *Cur;

  if (string != NULL) {
      string = eatwhite(string);
      *Cur = (*string != '\0'  ?  xstrdup (string)  :  NULL);
  }
  else *Cur = NULL;

  if (*Cur==NULL) {
      if (Old != NULL) rfree(Old);
      return 1;
  }

  g.white = g.black = p;
  g.wInitTime = g.bInitTime = me->d_time;
  g.wIncrement = g.bIncrement = me->d_inc;
  g.rated = me->rated;
  g.type = TYPE_BLITZ;
  err = CheckFormula (&g, clause, &index, OPTYPE_NONE, &ret, 0);

  if (err != ERR_NONE) {
      /* Bad formula; reset it. */
       rfree(*Cur);
      *Cur = Old;
  } else {
      if (Old != NULL) rfree(Old);
  }
  return (err == ERR_NONE);
}

PUBLIC void ShowClauses (int p, int p1, textlist *clauses)
{
  textlist *Cur;

  if (clauses != NULL)
    pprintf(p, "\n");
  for (Cur = clauses; Cur != NULL; Cur = Cur->next) {
    pprintf(p, "f%d=%s: %s\n", Cur->index + 1, Cur->text,
               parray[p1].formulaLines[Cur->index]);
  }
}