/*
 | each.c (12 May 1998)
 | by Paul Clifford (paul@plasma.demon.co.uk)
 */

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

#include "common.h"
#include "each.h"


/*
 | Operand finalisation code
 */

static void
free_expression(expression *e) {
    if (e->finalise) e->finalise(e);
    free(e);
}

static void
finalise_boolean(expression *e) {
    free_expression(e->args[0].e);
    if (e->args[1].e) free_expression(e->args[1].e);
}


/*
 | Operand evaluation functions
 */

static int
eval_comma(const expression *e, file_info *f, const dir_position *dir) {
    const expression *larg = e->args[0].e;
    const expression *rarg = e->args[1].e;
    larg->evaluate(larg, f, dir);
    return(rarg->evaluate(rarg, f, dir));
}

static int
eval_or(const expression *e, file_info *f, const dir_position *dir) {
    const expression *larg = e->args[0].e;
    const expression *rarg = e->args[1].e;
    return(larg->evaluate(larg, f, dir) || rarg->evaluate(rarg, f, dir));
}

static int
eval_not(const expression *e, file_info *f, const dir_position *dir) {
    const expression *arg = e->args[0].e;
    return(!(arg->evaluate(arg, f, dir)));
}

static int
eval_and(const expression *e, file_info *f, const dir_position *dir) {
    const expression *larg = e->args[0].e;
    const expression *rarg = e->args[1].e;
    return(larg->evaluate(larg, f, dir) && rarg->evaluate(rarg, f, dir));
}


/*
 | Main expression parser
 */

static void
parse_error(const char *error) {
    fprintf(stderr, "parse error: %s\n", error);
    exit(EXIT_FAILURE);
}

static expression *
parse(parse_ctx *ctx, const int single) {
    expression *e = 0, *e2 = 0;
    const char *s;

    for (; ctx->i < ctx->argc;) {
        s = ctx->argv[(ctx->i)++];
        if (!strcmp(s, ")")) {
            if (ctx->level-- == 0) parse_error("Mismatched brackets");
            break;
        } else if (!strcmp(s, ",")) {
            expression *comma = xmalloc(sizeof(expression));
            if (!e) parse_error("Unexpected comma");
            comma->args[0].e = e;
            e = parse(ctx, 1);
            if (!e) parse_error("Invalid right hand expression for sequence operator");
            comma->args[1].e = e;
            comma->evaluate = &eval_comma;
            comma->finalise = &finalise_boolean;
            e = comma;
        } else if (!strcmp(s, "-o") || !strcmp(s, "-or")) {
            expression *or = xmalloc(sizeof(expression));
            if (!e) parse_error("Unexpected or");
            or->args[0].e = e;
            e = parse(ctx, 1);
            if (!e) parse_error("Invalid right hand expression for or");
            or->args[1].e = e;
            or->evaluate = &eval_or;
            or->finalise = &finalise_boolean;
            e = or;
        } else if (!strcmp(s, "!") || !strcmp(s, "-not")) {
            expression *not = xmalloc(sizeof(expression));
            e2 = parse(ctx, 1);
            if (!e2) parse_error("Invalid right hand expression for not");
            not->args[0].e = e2;
            not->args[1].e = 0;
            not->evaluate = &eval_not;
            not->finalise = &finalise_boolean;
            if (!e)
                e = not;
            else {
                expression *and = xmalloc(sizeof(expression));
                and->args[0].e = e;
                and->args[1].e = not;
                and->evaluate = &eval_and;
                and->finalise = &finalise_boolean;
                e = and;
            }
        } else {
            if (!strcmp(s, "(") || !strcmp(s, "-a") || !strcmp(s, "-and")) {
                if (!e && *s != '(') parse_error("Unexpected and");
                if (*s != '(')
                    e2 = parse(ctx, 1);
                else {
                    ctx->level++;
                    e2 = parse(ctx, 0);
                }
            } else
                e2 = parse_primary(s, ctx);
            if (!e) {
                if (!e2) parse_error("Empty expression!");
                e = e2;
            } else {
                expression *and = xmalloc(sizeof(expression));
                if (!e2) parse_error("Invalid right hand expression for and");
                and->args[0].e = e;
                and->args[1].e = e2;
                and->evaluate = &eval_and;
                and->finalise = &finalise_boolean;
                e = and;
            }
        }
        if (single) break;
    }
    if (ctx->i > ctx->argc) parse_error("Unexpected end of input");
    if (ctx->level > 0 && ctx->i == ctx->argc) parse_error("Mismatched brackets");
    return(e);
}


/*
 | Directory scanning
 */

static void
dirscan_init(dirscan_ctx *ctx, const char *dir) {
    ctx->offset = 0;
    ctx->dir = dir;
    ctx->nleft = 0;
}

static file_info *
dirscan_next(dirscan_ctx *ctx) {
    file_info *file;

    while (ctx->nleft == 0) {
        if (ctx->offset == -1) return(0); /* end of list */
        ctx->ptr = ctx->buffer;
        if (dirscan_do(ctx)) return(0);
    }

    file = (file_info *) ctx->ptr;
    file->prune = 0;

    for (ctx->ptr += 29; *(ctx->ptr++););
    ctx->ptr += 3; ctx->ptr = (const char *) ((int) ctx->ptr & ~3);
    ctx->nleft--;

    return(file);
}


/*
 | The program core
 */

static void
perform_find(dir_position *dir, const expression *e) {
    file_info *file;
    dirscan_ctx ctx;

    dirscan_init(&ctx, dir->path);

    while ((file = dirscan_next(&ctx)) != 0) {
        e->evaluate(e, file, dir);
        if (!file->prune && (file->type == 2 || file->type == 3)) {
            dir_position subdir;
            const int i = strlen(dir->path), j = strlen(file->name);

            subdir.base = dir->base;
            subdir.base_strlen = dir->base_strlen;
            subdir.path = xmalloc(i + j + 2);
            strcpy(subdir.path, dir->path);
            subdir.path[i] = '.';
            strcpy(subdir.path + i + 1, file->name);
            perform_find(&subdir, e);
            free(subdir.path);
        }
    }
}


/*
 | Program interface
 */

static void
show_usage(void) {
    fprintf(
        stderr,
        "each v0.01 (12 May 1998)\n"
        "by Paul Clifford (paul@plasma.demon.co.uk)\n\n"
        "Syntax: each <path> <expression>\n"
    );
    exit(EXIT_FAILURE);
}

int
main(const int argc, const char **argv) {
    expression *e;
    parse_ctx ctx;
    dir_position dir;

    if (argc < 3) show_usage();

    ctx.i = 2;
    ctx.argc = argc;
    ctx.argv = argv;
    ctx.level = 0;
    e = parse(&ctx, 0);
    if (!e) show_usage();

    if (!strcmp(argv[1], "."))
        dir.base = dir.path = "@";
    else
        dir.base = dir.path = (char *) argv[1];
    dir.base_strlen = strlen(dir.base);
    perform_find(&dir, e);

    free_expression(e);

    return(EXIT_SUCCESS);
}
