/* boolexp.c */
#include "copyright.h"

#ifdef WANT_ANSI
#ifdef __STDC__
#include <stddef.h>
#include <stdlib.h>
#endif /* __STDC__ */
#endif /* WANT_ANSI */

#include "db.h"
#include "match.h"
#include "mudconf.h"
#include "externs.h"
#include "interface.h"

#ifndef STANDALONE

/* ---------------------------------------------------------------------------
 * check_attr: indicate if attribute ATTR on player passes key when checked by
 * the object lockobj
 */

int check_attr (dbref player, dbref lockobj, ATTR *attr, char *key)
{
char	*buff;
dbref	aowner;
int	aflags, checkit;

	buff = atr_pget(player, attr->number, &aowner, &aflags);
	checkit = 0;

	if (See_attr(lockobj, player, attr, aowner)) {
		checkit = 1;
#ifdef ATR_NAME
	} else if (attr->number == A_NAME) {
		checkit = 1;
#endif
	}

	if (checkit && (!wild_match(key, buff, (char **)NULL, 0))) {
		checkit = 0;
	}

	free_lbuf(buff);
	return checkit;
}

int eval_boolexp(dbref player, dbref thing, struct boolexp * b)
{
dbref	aowner, obj;
int	aflags, c;
char	*key, *buff;
ATTR	*a;

	if (b == TRUE_BOOLEXP) return 1;

	switch (b->type) {
	case BOOLEXP_AND:
		return (eval_boolexp(player, thing, b->sub1) &&
			eval_boolexp(player, thing, b->sub2));
	case BOOLEXP_OR:
		return (eval_boolexp(player, thing, b->sub1) ||
			eval_boolexp(player, thing, b->sub2));
	case BOOLEXP_NOT:
		return !eval_boolexp(player, thing, b->sub1);
	case BOOLEXP_INDIR:
	/*
	 * BOOLEXP_INDIR (i.e. @) is a unary operation which is replaced at
	 * evaluation time by the lock of the object whose number is the
	 * argument of the operation.
	 */

		mudstate.lock_nest_lev++;
		if (mudstate.lock_nest_lev >= mudconf.lock_nest_lim) {
#ifndef STANDALONE
			STARTLOG(LOG_BUGS,"BUG","LOCK")
				log_name_and_loc(player);
				log_text((char *)": Lock exceeded recursion limit.");
			ENDLOG
			notify(player, "Sorry, broken lock!");
#else
			fprintf(stderr, "Lock exceeded recursion limit.\n");
#endif
			mudstate.lock_nest_lev--;
			return (0);
		}
		if ((b->sub1->type != BOOLEXP_CONST) || (b->sub1->thing < 0)) {
#ifndef STANDALONE
			STARTLOG(LOG_BUGS,"BUG","LOCK")
				log_name_and_loc(player);
				buff = alloc_mbuf("eval_boolexp.LOG.indir");
				sprintf(buff,
					": Lock had bad indirection (%c, type %d)",
					INDIR_TOKEN, b->sub1->type);
				log_text(buff);
				free_mbuf(buff);
			ENDLOG
			notify(player, "Sorry, broken lock!");
#else
			fprintf(stderr, "Broken lock.\n");
#endif
			mudstate.lock_nest_lev--;
			return (0);
		}
		key = atr_get(b->sub1->thing, A_LOCK, &aowner, &aflags);
		c = eval_boolexp_atr(player, thing, key);
		free_lbuf(key);
		mudstate.lock_nest_lev--;
		return (c);
	case BOOLEXP_CONST:
		return (b->thing == player ||
			member(b->thing, Contents(player)));
	case BOOLEXP_ATR:
		a = atr_num(b->thing);
		if (!a) return 0;	/* no such attribute */

		/* First check the object itself, then its contents */

		if (check_attr(player, thing, a, (char *)b->sub1)) return 1;
		DOLIST(obj, Contents(player)) {
			if (check_attr(obj, thing, a, (char *)b->sub1))
				return 1;
		}
		return 0;
	case BOOLEXP_IS:
		return (b->sub1->thing == player);
	case BOOLEXP_CARRY:
		return (member(b->sub1->thing, Contents(player)));
	case BOOLEXP_OWNER:
		return (Owner(b->sub1->thing) == Owner(player));
	default:
		abort();		/* bad type */
		return 0;
	}
}

int eval_boolexp_atr(dbref player, dbref thing, char *key)
{
  struct boolexp *b;
  int ret_value;
  b = parse_boolexp(player, key);
  if (b == NULL)
    ret_value = 1;
  else
    ret_value = eval_boolexp(player, thing, b);
  free_boolexp(b);
  return (ret_value);
}

#endif

/* If the parser returns TRUE_BOOLEXP, you lose */
/* TRUE_BOOLEXP cannot be typed in by the user; use @unlock instead */
static const char *parsebuf;
static char parsestore[LBUF_SIZE];
static dbref parse_player;

static void skip_whitespace()
{
  while (*parsebuf && isspace(*parsebuf))
    parsebuf++;
}

static struct boolexp *parse_boolexp_E();	/* defined below */

static struct boolexp *test_atr(char *s)
{
  ATTR *attrib;
  struct boolexp *b;
  char *buff, *s1;
  int anum;

  buff = alloc_lbuf("test_atr");
  strcpy(buff, s);
  for (s = buff; *s && (*s != ':'); s++) ;
  if (!*s) {
    free_lbuf(buff);
    return((struct boolexp *)NULL);
  }
  *s++ = '\0';
  /* see if left side is valid attribute.  Access to attr is checked on eval
   * Also allow numeric references to attributes.  It can't hurt us, and
   * lets us import stuff that stores attr locks by number instead of by
   * name.
   */
  if (!(attrib = atr_str(buff))) {

    /* Only #1 can lock on numbers */
    if (!God(parse_player)) return((struct boolexp *)NULL);

    s1 = buff;
    for (s1=buff; isdigit(*s1); s1++) ;
    if (*s1) {
      free_lbuf(buff);
      return((struct boolexp *)NULL);
    }
    anum = atoi(buff);
  } else {
    anum = attrib->number;
  }

  /* made it now make the parse tree node */
  b = alloc_bool("test_str");
  b->type = BOOLEXP_ATR;
  b->thing = (dbref)anum;
  b->sub1 = (struct boolexp *)strsave(s);
  free_lbuf(buff);
  return (b);
}

/* L -> (E); L -> object identifier */
static struct boolexp *
 parse_boolexp_L()
{
  struct boolexp *b;
  char *p, *buf;

  buf = NULL;
  skip_whitespace();
  switch (*parsebuf) {
    case '(':
      parsebuf++;
      b = parse_boolexp_E();
      skip_whitespace();
      if (b == TRUE_BOOLEXP || *parsebuf++ != ')') {
	free_boolexp(b);
	return TRUE_BOOLEXP;
      }
      break;
    default:
      /* must have hit an object ref */
      /* load the name into our buffer */
      buf = alloc_lbuf("parse_boolexp_L");
      p = buf;
      while (*parsebuf
	     && *parsebuf != AND_TOKEN
	     && *parsebuf != OR_TOKEN
	     && *parsebuf != ')') {
	*p++ = *parsebuf++;
      }
      /* strip trailing whitespace */
      *p-- = '\0';
      while (isspace(*p))
	*p-- = '\0';

      /* check for an attribute */
      if ((b = test_atr(buf)) != NULL) {
        free_lbuf(buf);
	return (b);
      }
      b = alloc_bool("parse_boolexp_L");
      b->type = BOOLEXP_CONST;

      /* do the match */
#ifndef STANDALONE
      init_match(parse_player, buf, TYPE_THING);
      match_neighbor();
      match_possession();
      match_me();
      match_absolute();
      match_player();
      b->thing = match_result();

      if (b->thing == NOTHING) {
	notify(parse_player,
	       tprintf("I don't see %s here.", buf));
	free_lbuf(buf);
	free_bool(b);
	return TRUE_BOOLEXP;
      }
      if (b->thing == AMBIGUOUS) {
	notify(parse_player,
	       tprintf("I don't know which %s you mean!", buf));
	free_lbuf(buf);
	free_bool(b);
	return TRUE_BOOLEXP;
      }
#else	/* STANDALONE ... had better be #<num> or we're hosed */
      if (buf[0] != '#') {
	free_lbuf(buf);
	free_bool(b);
	return TRUE_BOOLEXP;
      }
      b->thing = atol(&buf[1]);
      if (b->thing < 0) {
	free_lbuf(buf);
        free_bool(b);
        return TRUE_BOOLEXP;
      }
#endif
      free_lbuf(buf);
  }
  return b;
}

/* F -> !F; F -> @L; F -> =L; F -> +L; F -> $L */
/* The argument to ` must be type BOOLEXP_CONST */
static struct boolexp *
 parse_boolexp_F()
{
  struct boolexp *b2;
  skip_whitespace();
  switch (*parsebuf) {
    case NOT_TOKEN:
      parsebuf++;
      b2 = alloc_bool("parse_boolexp_F.not");
      b2->type = BOOLEXP_NOT;
      if ((b2->sub1 = parse_boolexp_F()) == TRUE_BOOLEXP) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else
	return (b2);
      /*NOTREACHED*/
      break;
    case INDIR_TOKEN:
      parsebuf++;
      b2 = alloc_bool("parse_boolexp_F.indir");
      b2->type = BOOLEXP_INDIR;
      b2->sub1 = parse_boolexp_L();
      if ((b2->sub1) == TRUE_BOOLEXP) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else if ((b2->sub1->type) != BOOLEXP_CONST) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else
	return (b2);
      /*NOTREACHED*/
      break;
    case IS_TOKEN:
      parsebuf++;
      b2 = alloc_bool("parse_boolexp_F.is");
      b2->type = BOOLEXP_IS;
      b2->sub1 = parse_boolexp_L();
      if ((b2->sub1) == TRUE_BOOLEXP) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else if ((b2->sub1->type) != BOOLEXP_CONST) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else
	return (b2);
      /*NOTREACHED*/
      break;
    case CARRY_TOKEN:
      parsebuf++;
      b2 = alloc_bool("parse_boolexp_F.carry");
      b2->type = BOOLEXP_CARRY;
      b2->sub1 = parse_boolexp_L();
      if ((b2->sub1) == TRUE_BOOLEXP) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else if ((b2->sub1->type) != BOOLEXP_CONST) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else
	return (b2);
      /*NOTREACHED*/
      break;
    case OWNER_TOKEN:
      parsebuf++;
      b2 = alloc_bool("parse_boolexp_F.owner");
      b2->type = BOOLEXP_OWNER;
      b2->sub1 = parse_boolexp_L();
      if ((b2->sub1) == TRUE_BOOLEXP) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else if ((b2->sub1->type) != BOOLEXP_CONST) {
	free_boolexp(b2);
	return (TRUE_BOOLEXP);
      } else
	return (b2);
      /*NOTREACHED*/
      break;
    default:
      return (parse_boolexp_L());
  }
}


/* T -> F; T -> F & T */
static struct boolexp *
 parse_boolexp_T()
{
  struct boolexp *b;
  struct boolexp *b2;
  if ((b = parse_boolexp_F()) != TRUE_BOOLEXP) {
    skip_whitespace();
    if (*parsebuf == AND_TOKEN) {
      parsebuf++;

      b2 = alloc_bool("parse_boolexp_T");
      b2->type = BOOLEXP_AND;
      b2->sub1 = b;
      if ((b2->sub2 = parse_boolexp_T()) == TRUE_BOOLEXP) {
	free_boolexp(b2);
	return TRUE_BOOLEXP;
      }
      b = b2;
    }
  }
  return b;
}
/* E -> T; E -> T | E */
static struct boolexp *
 parse_boolexp_E()
{
  struct boolexp *b;
  struct boolexp *b2;
  if ((b = parse_boolexp_T()) != TRUE_BOOLEXP) {
    skip_whitespace();
    if (*parsebuf == OR_TOKEN) {
      parsebuf++;

      b2 = alloc_bool("parse_boolexp_E");
      b2->type = BOOLEXP_OR;
      b2->sub1 = b;
      if ((b2->sub2 = parse_boolexp_E()) == TRUE_BOOLEXP) {
	free_boolexp(b2);
	return TRUE_BOOLEXP;
      }
      b = b2;
    }
  }
  return b;
}

struct boolexp *
 parse_boolexp(dbref player, const char *buf)
{
  strcpy(parsestore, buf);
  parsebuf = parsestore;
  parse_player = player;
  if ((buf == NULL) || (*buf == '\0'))
    return (TRUE_BOOLEXP);
  return parse_boolexp_E();
}