/*                         E X I S T S . C
 * BRL-CAD
 *
 * Copyright (c) 2008-2024 United States Government as represented by
 * the U.S. Army Research Laboratory.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this file; see the file named COPYING for more
 * information.
 */
/** @file libged/exists.c
 *
 * The exist command.
 *
 * Based on public domein code from:
 * NetBSD: test.c, v 1.38 2011/08/29 14:51:19 joerg
 *
 * test(1); version 7-like -- author Erik Baalbergen
 * modified by Eric Gisin to be used as built-in.
 * modified by Arnold Robbins to add SVR3 compatibility
 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
 * modified by J.T. Conklin for NetBSD.
 *
 */

/* exists accepts the following grammar:
   oexpr   ::= aexpr | aexpr "-o" oexpr ;
   aexpr   ::= nexpr | nexpr "-a" aexpr ;
   nexpr   ::= primary | "!" primary
   primary ::= unary-operator operand
   | operand binary-operator operand
   | operand
   | "(" oexpr ")"
   ;
   unary-operator ::= "-c"|"-e"|"-n"|"-p"|"-v";

   binary-operator ::= "="|"!="|"-beq"|"-bne"|"-bge"|"-bgt"|"-ble"|"-blt";
   operand ::= <any legal BRL-CAD object name>
*/

#include "common.h"

#include <stdlib.h>
#include <string.h>

#include "bu/cmd.h"

#include "../ged_private.h"

#ifndef __arraycount
#  define __arraycount(__x)       (sizeof(__x) / sizeof(__x[0]))
#endif

/* expression handling logic */

enum token {
    EOI,
    OEXIST,
    OCOMB,
    ONULL,
    ONNULL,
    OPRIM,
    OBVOL,
    EXTEQ,
    EXTNE,
    EXTGT,
    EXTLT,
    BVOLEQ,
    BVOLNE,
    BVOLGE,
    BVOLGT,
    BVOLLE,
    BVOLLT,
    UNOT,
    BAND,
    BOR,
    LPAREN,
    RPAREN,
    OPERAND
};


enum token_types {
    UNOP,
    BINOP,
    BUNOP,
    BBINOP,
    PAREN
};


struct t_op {
    const char *op_text;
    short op_num, op_type;
};


/* The following option structures need to be kept in sorted order for
 * bsearch - be sure any new entries are in the right numerical order
 * per bu_strcmp (or the ASCII character values, in the single char
 * case.
 */
static const struct t_op cop[] = {
    {"!",   UNOT,   BUNOP},
    {"(",   LPAREN, PAREN},
    {")",   RPAREN, PAREN},
    {"<",   EXTLT,  BINOP},
    {"=",   EXTEQ,  BINOP},
    {">",   EXTGT,  BINOP},
};


static const struct t_op cop2[] = {
    {"!=",  EXTNE,  BINOP},
};


static const struct t_op mop4[] = {
    {"beq", BVOLEQ,  BINOP},
    {"bge", BVOLGE,  BINOP},
    {"bgt", BVOLGT,  BINOP},
    {"ble", BVOLLE,  BINOP},
    {"blt", BVOLLT,  BINOP},
    {"bne", BVOLNE,  BINOP},
};


static const struct t_op mop2[] = {
    {"N",   ONNULL,  UNOP},
    {"a",   BAND,    BBINOP},
    {"c",   OCOMB,   UNOP},
    {"e",   OEXIST,  UNOP},
    {"n",   ONULL,   UNOP},
    {"o",   BOR,     BBINOP},
    {"p",   OPRIM,   UNOP},
    {"v",   OBVOL,   UNOP},
};


struct exists_data {
    char **t_wp;
    struct t_op const *t_wp_op;
    struct bu_vls *message;
    struct ged *gedp;
    int no_op;
};
#define EXISTS_DATA_INIT_ZERO {NULL, NULL, NULL, NULL, 0}

static int oexpr(enum token, struct exists_data *);
static int aexpr(enum token, struct exists_data *);
static int nexpr(enum token, struct exists_data *);
static int primary(enum token, struct exists_data *);
static int binop(struct exists_data *);
static int isoperand(struct exists_data *);

int db_object_exists(struct exists_data *);
int db_object_exists_and_non_null(struct exists_data *);

#define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text

static int
compare1(const void *va, const void *vb)
{
    const unsigned char *a = (unsigned char *)va;
    const unsigned char *b = VTOC(vb);

    return a[0] - b[0];
}


static int
compare3(const void *va, const void *vb)
{
    const char *a = (const char *)va;
    const char *b = (const char *)VTOC(vb);
    return bu_strcmp(a, b);
}


static const struct t_op *
findop(const char *s)
{
    if (s[0] == '-') {
	if (s[1] == '\0')
	    return NULL;
	if (s[2] == '\0')
	    return (const struct t_op *)bsearch(s + 1, mop2, __arraycount(mop2), sizeof(*mop2), compare1);
	else if (s[4] != '\0')
	    return NULL;
	else
	    return (const struct t_op *)bsearch(s + 1, mop4, __arraycount(mop4), sizeof(*mop4), compare3);
    } else {
	if (s[1] == '\0')
	    return (const struct t_op *)bsearch(s, cop, __arraycount(cop), sizeof(*cop), compare1);
	else if (BU_STR_EQUAL(s, cop2[0].op_text))
	    return cop2;
	else
	    return NULL;
    }
}


static enum token
t_lex(char *s, struct exists_data *ed)
{
    struct t_op const *op;

    ed->no_op = 0;
    if (s == NULL) {
	ed->t_wp_op = NULL;
	return EOI;
    }

    if ((op = findop(s)) != NULL) {
	if (!((op->op_type == UNOP && isoperand(ed)) ||
	      (op->op_num == LPAREN && *(ed->t_wp+1) == 0))) {
	    ed->t_wp_op = op;
	    return (enum token)op->op_num;
	}
    }
    if (strlen(*(ed->t_wp)) > 0 && !op && !ed->t_wp_op) {
	ed->t_wp_op = findop("-N");
	ed->no_op = 1;
	return (enum token)ed->t_wp_op->op_num;
    } else {
	ed->t_wp_op = NULL;
    }
    return OPERAND;
}


static int
oexpr(enum token n, struct exists_data *ed)
{
    int res;

    res = aexpr(n, ed);
    if (*(ed->t_wp) == NULL)
	return res;
    if (t_lex(*++(ed->t_wp), ed) == BOR) {
	ed->t_wp_op = NULL;
	return oexpr(t_lex(*++(ed->t_wp), ed), ed) || res;
    }
    (ed->t_wp)--;
    return res;
}


static int
aexpr(enum token n, struct exists_data *ed)
{
    int res;

    res = nexpr(n, ed);
    if (*(ed->t_wp) == NULL)
	return res;
    if (t_lex(*++(ed->t_wp), ed) == BAND) {
	ed->t_wp_op = NULL;
	return aexpr(t_lex(*++(ed->t_wp), ed), ed) && res;
    }
    (ed->t_wp)--;
    return res;
}


static int
nexpr(enum token n, struct exists_data *ed)
{

    if (n == UNOT)
	return !nexpr(t_lex(*++(ed->t_wp), ed), ed);
    return primary(n, ed);
}


static int
isoperand(struct exists_data *ed)
{
    struct t_op const *op;
    char *s, *t;

    if ((s  = *((ed->t_wp)+1)) == 0)
	return 1;
    if ((t = *((ed->t_wp)+2)) == 0)
	return 0;
    if ((op = findop(s)) != NULL)
	return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0');
    return 0;
}


/* The code below starts the part that still needs reworking for the
 * new geometry based tokens/logic */
static int
primary(enum token n, struct exists_data *ed)
{
    enum token nn;
    int res;

    if (n == EOI)
	return 0;               /* missing expression */
    if (n == LPAREN) {
	ed->t_wp_op = NULL;
	if ((nn = t_lex(*++(ed->t_wp), ed)) == RPAREN)
	    return 0;       /* missing expression */
	res = oexpr(nn, ed);
	if (t_lex(*++(ed->t_wp), ed) != RPAREN) {
	    bu_vls_printf(ed->message , "closing paren expected");
	    return 0;
	}
	return res;
    }
    if (ed->t_wp_op && ed->t_wp_op->op_type == UNOP) {
	/* unary expression */
	if (!ed->no_op) {
	    if (*++(ed->t_wp) == NULL) {
		bu_vls_printf(ed->message , "argument expected");
		return 0;
	    }
	}
	switch (n) {
	    case OCOMB:
		bu_log("comb case");
		return 0;
	    case OEXIST:
		return db_object_exists(ed);
		/*return db_lookup();*/
	    case ONULL:
		bu_log("null case");
		return 0;
	    case ONNULL:
		/* default case */
		return db_object_exists_and_non_null(ed);
	    case OPRIM:
		bu_log("primitive case");
		return 0;
	    case OBVOL:
		bu_log("bounding volume case");
		return 0;
	    default:
		/* not reached */
		return 0;
	}
    }

    if (t_lex(ed->t_wp[1], ed), ed->t_wp_op && ed->t_wp_op->op_type == BINOP) {
	return binop(ed);
    }

    return 0;
}


static int
binop(struct exists_data *ed)
{
    const char /**opnd1, */*opnd2;
    struct t_op const *op;

    /* opnd1 = *(ed->t_wp); */
    (void) t_lex(*++(ed->t_wp), ed);
    op = ed->t_wp_op;

    opnd2 = *++(ed->t_wp);
    if (opnd2 == NULL) {
	bu_vls_printf(ed->message , "argument expected");
	return 0;
    }

    switch (op->op_num) {
	case EXTEQ:
	    bu_log("extern eq case");
	    return 0;
	case EXTNE:
	    bu_log("extern neq case");
	    return 0;
	case EXTLT:
	    bu_log("extern lt case");
	    return 0;
	case EXTGT:
	    bu_log("extern gt case");
	    return 0;
	case BVOLEQ:
	    bu_log("vol eq case");
	    return 0;
	case BVOLNE:
	    bu_log("vol neq case");
	    return 0;
	case BVOLGE:
	    bu_log("vol geq case");
	    return 0;
	case BVOLGT:
	    bu_log("vol gt case");
	    return 0;
	case BVOLLE:
	    bu_log("vol leq case");
	    return 0;
	case BVOLLT:
	    bu_log("vol lt case");
	    return 0;
	default:
	    return 0;
	    /* NOTREACHED */
    }
}


/* test functions */
int db_object_exists(struct exists_data *ed)
{
    struct directory *dp = NULL;
    dp = db_lookup(ed->gedp->dbip, *(ed->t_wp), LOOKUP_QUIET);
    if (dp) return 1;
    return 0;
}


int db_object_exists_and_non_null(struct exists_data *ed)
{
    int result;
    result = db_object_exists(ed);
    if (result) {
	/* db_lookup passes: todo - check for null database object */
	return result;
    } else {
	/* db_lookup fails - no go */
	return result;
    }
}


/**
 * Checks for the existence of a specified object.
 */
int
ged_exists_core(struct ged *gedp, int argc, const char *argv_orig[])
{
    /* struct directory *dp;*/
    static const char *usage = "object";
    struct exists_data ed = EXISTS_DATA_INIT_ZERO;
    struct bu_vls message = BU_VLS_INIT_ZERO;
    int result;
    char **argv = bu_argv_dup(argc, argv_orig);

    GED_CHECK_DATABASE_OPEN(gedp, BRLCAD_ERROR);
    GED_CHECK_ARGC_GT_0(gedp, argc, BRLCAD_ERROR);

    /* initialize result */
    bu_vls_trunc(gedp->ged_result_str, 0);

    /* must be wanting help */
    if (argc == 1) {
	bu_vls_printf(gedp->ged_result_str, "Usage: %s %s", argv_orig[0], usage);
	return GED_HELP;
    }

    ed.t_wp = &argv[1];
    ed.gedp = gedp;
    ed.t_wp_op = NULL;
    ed.message = &message;
    result = oexpr(t_lex(*(ed.t_wp), &ed), &ed);

    if (result)
	bu_vls_printf(gedp->ged_result_str, "1");
    else
	bu_vls_printf(gedp->ged_result_str, "0");

    if (bu_vls_strlen(ed.message) > 0) {
	bu_vls_printf(gedp->ged_result_str, "%s", bu_vls_addr(ed.message));
	bu_vls_free(&message);
	return BRLCAD_ERROR;
    }

    bu_vls_free(&message);
    if (*(ed.t_wp) != NULL && *++(ed.t_wp) != NULL) {
	return BRLCAD_ERROR;
    } else {
	return BRLCAD_OK;
    }
}


#ifdef GED_PLUGIN
#include "../include/plugin.h"
struct ged_cmd_impl exists_cmd_impl = {
    "exists",
    ged_exists_core,
    GED_CMD_DEFAULT
};

const struct ged_cmd exists_cmd = { &exists_cmd_impl };
const struct ged_cmd *exists_cmds[] = { &exists_cmd, NULL };

static const struct ged_plugin pinfo = { GED_API,  exists_cmds, 1 };

COMPILER_DLLEXPORT const struct ged_plugin *ged_plugin_info(void)
{
    return &pinfo;
}
#endif /* GED_PLUGIN */

/*
 * Local Variables:
 * mode: C
 * tab-width: 8
 * indent-tabs-mode: t
 * c-file-style: "stroustrup"
 * End:
 * ex: shiftwidth=4 tabstop=8
 */
