/*
 * Code by Scott Bigham
 */

/*
 * Helper functions for "process_pref_file()" to evaluate conditional
 * expressions
 *
 * The following expression syntax is recognized:
 *
 *    expr | expr         Logical OR
 *    expr & expr         Logical AND
 *    ! expr              Logical NOT
 *    ( expr )            Grouping
 *    "string"            True if player name exactly matches string
 *                        (case-sensitive)
 *    token               True if token matches (case-sensitive) the
 *                        player's race, class or sex or the three-
 *                        letter system identifier the Angband executable
 *                        was built with
 *    0                   False
 *    1                   True
 *
 * For instance, this expression is true on an X11 system for all male
 * characters not named Fred, and for all hobbit characters except rogues:
 *
 *    (x11 | xaw) & ((!"Fred" & Male) | (Hobbit & !Rogue))
 *
 * Input:
 *   sp: pointer to current position in expression string
 *
 * Output:
 *   1 for true, 0 for false, <0 for error
 *
 * Should probably do:
 *   SYSTEM="mac" & !(RACE="Gnome")
 */

/* Convenient macro to skip white space */
#define SKIP_WS \
        do { while (**sp && isspace(**sp)) (*sp)++; } while (0)

/* Forward declaration */
static int process_pref_file_or_expr(char **);

/*
 * Handle 0, 1, token, !expr, (expr) and "playername"
 */
static int process_pref_file_primary_expr(char **sp)
{
    int v = -5;
    char *s1, *s2;

    SKIP_WS;
    if (**sp == '0' || **sp == '1') {
        v = (**sp == '1');
        (*sp)++;
    } else if (**sp == '(') {
        (*sp)++;
        v = process_pref_file_or_expr(sp);
        if (**sp == ')')
            (*sp)++;
        else
            v = -1;
    } else if (**sp == '!') {
        (*sp)++;
        v = process_pref_file_primary_expr(sp);
        v = (v < 0 ? v : !v);
    } else if (**sp == '"') {
        s1 = *sp + 1;
        s2 = s1 + 1;
        while (*s2 && *s2 != '"')
            s2++;
        if (*s2 != '"')
            v = -2;
        else
            v = !strncmp(s1, player_name, s2 - s1);
        *sp = s2 + 1;
    } else if (isalpha(**sp)) {
        s1 = s2 = *sp;
        while (isalnum(*s2) || *s2 == '-')
            s2++;
        v = (!strncmp(s1, ANGBAND_SYS, (s2 - s1))   ||
             !strncmp(s1, rp_ptr->title, (s2 - s1)) ||
             !strncmp(s1, cp_ptr->title, (s2 - s1)) ||
             !strncmp(s1, sp_ptr->title, (s2 - s1)));
        *sp = s2;
    } else {
        v = -3;
    }

    return v;
}

/*
 * Handle expr & expr [ & expr ... ]
 */
static int process_pref_file_and_expr(char **sp)
{
    int v, v2;

    v = process_pref_file_primary_expr(sp);
    if (v < 0) return v;

    SKIP_WS;
    while (**sp && **sp == '&') {
        (*sp)++;
        v2 = process_pref_file_primary_expr(sp);
        if (v2 < 0) return v2;
        v = (v && v2);
        SKIP_WS;
    }

    return v;
}

/*
 * Handle expr | expr [ | expr ... ]
 */
static int process_pref_file_or_expr(char **sp)
{
    int v, v2;

    v = process_pref_file_and_expr(sp);
    if (v < 0) return v;

    SKIP_WS;
    while (**sp && **sp == '|') {
        (*sp)++;
        v2 = process_pref_file_and_expr(sp);
        if (v2 < 0) return v2;
        v = (v || v2);
        SKIP_WS;
    }

    return v;
}

static int process_pref_file_expr(char **sp)
{
    int v;

    v = process_pref_file_or_expr(sp);
    SKIP_WS;
    if (**sp != '\0') v = -4;
    return v;
}

#undef SKIP_WS

/*
 * Process the "user pref file" with the given name
 *
 * See the function above for a list of legal "commands".
 *
 * We also accept the special "?" and "%" directives, which
 * allow conditional evaluation and filename inclusion.
 */
errr process_pref_file(cptr name)
{
    FILE *fp;

    char buf[1024];

    int num = -1;

    errr err = 0;

    bool bypass = FALSE;


    /* Build the filename */
    path_build(buf, 1024, ANGBAND_DIR_USER, name);

    /* Open the file */
    fp = my_fopen(buf, "r");

    /* No such file */
    if (!fp) return (-1);


    /* Process the file */
    while (0 == my_fgets(fp, buf, 1024))
    {
        /* Count lines */
        num++;


        /* Skip "empty" lines */
        if (!buf[0]) continue;

        /* Skip "blank" lines */
        if (isspace(buf[0])) continue;

        /* Skip comments */
        if (buf[0] == '#') continue;


        /* Process "?:<expr>" */
        if ((buf[0] == '?') && (buf[1] == ':'))
        {
            int  v;
            char *s;
            
            /* Start */
            s = buf + 2;

            /* Parse the expr */
            v = process_pref_file_expr(&s);

            /* Check for error */
            if (v < 0) {
                err = v;
                break;
            }

            /* Set flag */
            bypass = !v;
            
            /* Continue */
            continue;
        }

        /* Apply conditionals */
        if (bypass) continue;


        /* Process "%:<file>" */
        if (buf[0] == '%')
        {
            /* Process that file if allowed */
            (void)process_pref_file(buf + 2);

            /* Continue */
            continue;
        }


        /* Process the line */
        err = process_pref_file_aux(buf);
        
        /* Oops */
        if (err) break;
    }


    /* Error */
    if (err)
    {
        /* Useful error message */
        msg_format("Error %d in line %d of file '%s'.", err, num, name);
        msg_format("Parsing '%s'", buf);
    }

    /* Close the file */
    my_fclose(fp);

    /* Result */
    return (err);
}

