/*
 | primaries.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 "kernel.h"

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


static void primary_syntax(const char *, const char *);


/*
 | Wildcard matching
 */

static void
match_error(void) {
    fprintf(stderr, "Premature end of search pattern in character class\n");
    exit(EXIT_FAILURE);
}

static int
match(const char *pattern, const char *str) {
    for (; *pattern; pattern++, str++) {
        if (*pattern == '\\') {
            pattern++;
            if (!*pattern || tolower(*pattern) != tolower(*str)) return(0);
        } else if (*pattern == '*') {
            while (*(++pattern) == '*');
            if (!*pattern) return(1); /* quick exit when pattern ends with '*' */
            do { if (match(pattern, str)) return(1); } while (*str++);
            return(0);
        } else if (*pattern == '[') {
            int not, matched = 0;
            char previous = 0;
            if (!*(++pattern)) match_error();
            not = (*pattern == '!' || *pattern == '^');
            if (not) pattern++;
            for (; *pattern && *pattern != ']'; pattern++) {
                if (*pattern == '-' && previous) {
                    if (!*(++pattern))  match_error();
                    if (*pattern == ']') {
                        if (*str == '-') matched = 1;
                        break;
                    } else {
                        if (*str >= previous && *str <= *pattern) matched = 1;
                    }
                    previous = 0;
                } else {
                    if ((previous = *pattern) == *str) matched = 1;
                }
            }
            if (!*pattern) match_error();
            if (!matched ^ not) return(0);
        } else if (!*str)
            return(0);
        else if (*pattern == '#' || *pattern == '?')
            continue;
        else if (tolower(*pattern) != tolower(*str))
            return(0);
    }
    return(*str == 0);
}


/*
 | Primary evaluation code
 */

static int
eval_name(const expression *e, file_info *f, const dir_position *dir) {
    IGNORE(dir);

    return(match(e->args[0].s, f->name));
}

static int
eval_path(const expression *e, file_info *f, const dir_position *dir) {
    int i = strlen(dir->path);
    const int j = strlen(f->name);
    char *path = xmalloc(i + j + 2);

    strcpy(path, dir->path);
    path[i] = '.';
    strcpy(path + i + 1, f->name);
    i = match(e->args[0].s, path);
    free(path);
    return(i);
}

static int
eval_print(const expression *e, file_info *f, const dir_position *dir) {
    const int i = strlen(dir->path), j = strlen(f->name);
    char *path = xmalloc(i + j + 2);
    IGNORE(e);

    strcpy(path, dir->path);
    path[i] = '.';
    strcpy(path + i + 1, f->name);
    printf("%s\n", path);
    free(path);
    return(1);
}

static int
eval_print0(const expression *e, file_info *f, const dir_position *dir) {
    const int i = strlen(dir->path), j = strlen(f->name);
    char *path = xmalloc(i + j + 2);
    IGNORE(e);

    strcpy(path, dir->path);
    path[i] = '.';
    strcpy(path + i + 1, f->name);
    printf("%s\0", path);
    free(path);
    return(1);
}

static int
eval_prune(const expression *e, file_info *f, const dir_position *dir) {
    IGNORE(e); IGNORE(dir);

    f->prune = 1;
    return(1);
}

static int
eval_type(const expression *e, file_info *f, const dir_position *dir) {
    const int type = e->args[0].n;
    IGNORE(dir);

    if (e->args[1].n) {
        if (f->type == 2) return(0);
        return(type == ((f->load_adr >> 8) & 0xfff));
    } else {
        return((f->type & type) == type);
    }
}

static void
build_command(char *cmnd, const int cmnd_size, const expression *e, const file_info *f, const dir_position *dir) {
    const int i = strlen(dir->path), j = strlen(f->name), k = dir->base_strlen;
    char *args = xmalloc((i + 1 + j + 1) + ((i - k) + j + 1) + (j + 1)), *x;

    /* %0 = dir.leaf, %1 = subdir.leaf, %2 = leaf */
    x = args + sprintf(args, "%s.%s", dir->path, f->name);
    sprintf(x + 1, "%s %s", args + k + 1, f->name);
    x[0] = ' ';

    substitute_args(args, cmnd, cmnd_size, e->args[0].s, e->args[1].n);
    free(args);
}

static int
eval_exec(const expression *e, file_info *f, const dir_position *dir) {
    char command[1024];

    build_command(command, 1024, e, f, dir);
    switch (system(command)) {
        case 0:
            return(1);
        case -2:
            fprintf(stderr, "each: couldn't execute '%s': %s\n", command, _kernel_last_oserror()->errmess);
        default:
            return(0);
    }
}

static int
eval_ok(const expression *e, file_info *f, const dir_position *dir) {
    char command[1024];

    build_command(command, 1024, e, f, dir);
    printf("Execute '%s' (Y/N/Quiet/Abandon) ? ", command);
    switch (tolower(read_character())) {
        case 'y':
            printf("Y\n");
            break;
        case 'q':
            ((expression *) e)->evaluate = &eval_exec;
            printf("Q\n");
            break;
        case 'a':
            printf("A\n");
            exit(EXIT_FAILURE);
        default:
            printf("N\n");
            return(0);
    }
    switch (system(command)) {
        case 0:
            return(1);
        case -2:
            fprintf(stderr, "each: couldn't execute '%s': %s\n", command, _kernel_last_oserror()->errmess);
        default:
            return(0);
    }
}

static int
eval_size(const expression *e, file_info *f, const dir_position *dir) {
    int size = f->length;
    IGNORE(dir);

    if (e->args[2].n == 9)
        /* round up the size if checking block sizes */
        size = (size + 511) >> 9;
    else
        size >>= e->args[2].n;

    if (e->args[0].n > 0)
        return(size > e->args[1].n);
    else if (e->args[0].n < 0)
        return(size < e->args[1].n);
    return(size == e->args[1].n);
}

static int
eval_perm(const expression *e, file_info *f, const dir_position *dir) {
    int attr = f->attributes;
    IGNORE(dir);

    if (e->args[0].n)
        return((attr & e->args[1].n) == e->args[1].n);
    else {
        attr &= e->args[2].n;
        return(attr == e->args[1].n);
    }
}

static int
eval_newer(const expression *e, file_info *f, const dir_position *dir) {
    IGNORE(dir);

    return(compare_datestamp(f->exec_adr, f->load_adr, e->args[0].n, e->args[1].n) > 0);
}

static int
eval_time(const expression *e, file_info *f, const dir_position *dir) {
    int day_low, day_high;
    IGNORE(dir);

    extract_day(f->exec_adr, f->load_adr & 0xff, &day_low, &day_high);
    if (e->args[0].n > 0)
        return(compare_datestamp(day_low, day_high, e->args[1].n, e->args[2].n) < 0);
    else if (e->args[0].n < 0)
        return(compare_datestamp(day_low, day_high, e->args[1].n, e->args[2].n) >= 0);
    else
        return(compare_datestamp(day_low, day_high, e->args[1].n, e->args[2].n) == 0);
}


/*
 | Primary finalisation code
 */

static void
finalise_command(expression *e) {
    free((void *) e->args[0].s);
}


/*
 | Primary construction code
 */

static void
construct_type(const char *s, parse_ctx *ctx, expression *e) {
    const char *type;

    if (ctx->i >= ctx->argc) primary_syntax(s, "<f|d|i|filetype>");
    type = ctx->argv[ctx->i++];
    if (type[1] == 0) {
        e->args[1].n = 0;
        if (*type == 'f') e->args[0].n = 1;
        else if (*type == 'd') e->args[0].n = 2;
        else if (*type == 'i') e->args[0].n = 3;
        else primary_syntax(s, "<f|d|i|filetype>");
    } else {
        e->args[1].n = 1;
        if (convert_filetype(type, &e->args[0].n)) {
            fprintf(stderr, "Unknown filetype '%s'\n", type);
            exit(EXIT_FAILURE);
        }
    }
    e->evaluate = &eval_type;
}

static void
construct_command(const char *s, parse_ctx *ctx, expression *e) {
    int length = 0, i;
    const char *arg = ctx->argv[ctx->i];
    char *command;

    for (i = ctx->i; i < ctx->argc; i++) {
        arg = ctx->argv[i];
        if (!strcmp(arg, ";")) break;
        length += strlen(arg) + 1;
    }
    if (strcmp(arg, ";") || length == 0) primary_syntax(s, "<command string> ;");
    command = xmalloc(length); *command = 0;
    for (i = 0; ctx->i < ctx->argc; i++) {
        arg = ctx->argv[ctx->i++];
        if (!strcmp(arg, "{}")) arg = "%0";
        if (!strcmp(arg, ";")) break;
        if (i) strcat(command, " ");
        strcat(command, arg);
    }

    e->args[0].s = command;
    e->args[1].n = length;
    if (s[1] == 'e')
        e->evaluate = &eval_exec;
    else
        e->evaluate = &eval_ok;
    e->finalise = &finalise_command;
}

static void
construct_size(const char *s, parse_ctx *ctx, expression *e) {
    const char *size;
    char units;
    char *tail;

    if (ctx->i >= ctx->argc) primary_syntax(s, "[+|-]<size>[c|B|k|M]");
    size = ctx->argv[ctx->i++];
    if (size[0] == '+') {
        e->args[0].n = 1;
        size++;
    } else if (size[0] == '-') {
        e->args[0].n = -1;
        size++;
    } else {
        e->args[0].n = 0;
    }

    e->args[1].n = (int) strtol(size, &tail, 10);
    units = tolower(*tail);
    if (units == 'c' || units == 'b')
        e->args[2].n = 0;
    else if (units == 'k')
        e->args[2].n = 10;
    else if (units == 'm')
        e->args[2].n = 20;
    else if (units == '\0')
        e->args[2].n = 9;
    else
        primary_syntax(s, "[+|-]<size>[c|B|k|M]");
    e->evaluate = &eval_size;
}

static void
construct_perm(const char *s, parse_ctx *ctx, expression *e) {
    const char *arg;
    int permission = 0;

    if (ctx->i >= ctx->argc) primary_syntax(s, "<permission string>");
    arg = ctx->argv[ctx->i++];
    if (*arg != '-')
        e->args[0].n = 0;
    else {
        e->args[0].n = 1;
        arg++;
    }
    if (isdigit(*arg)) {
        int temp = (int) strtoul(arg, 0, 8);
        if (temp & 0400) permission |= (1 << 0);
        if (temp & 0200) permission |= (1 << 1);
        if (temp & 0004) permission |= (1 << 4);
        if (temp & 0002) permission |= (1 << 5);
        e->args[2].n = 0x33;
    } else {
        if (strchr(arg, 'R')) permission |= (1 << 0);
        if (strchr(arg, 'W')) permission |= (1 << 1);
        if (strchr(arg, 'L')) permission |= (1 << 3);
        if (strchr(arg, 'r')) permission |= (1 << 4);
        if (strchr(arg, 'w')) permission |= (1 << 5);
        e->args[2].n = 0x3b;
    }
    e->args[1].n = permission;
    e->evaluate = &eval_perm;
}

static void
construct_time(const char *s, parse_ctx *ctx, expression *e) {
    const char *days;

    if (ctx->i >= ctx->argc) primary_syntax(s, "[+|-]<days>");
    days = ctx->argv[ctx->i++];
    if (*days == '+') {
        e->args[0].n = 1;
        days++;
    } else if (*days == '-') {
        e->args[0].n = -1;
        days++;
    } else
        e->args[0].n = 0;
    get_previous_day((int) strtol(days, 0, 10), &e->args[1].n, &e->args[2].n);
    e->evaluate = &eval_time;
}


/*
 | Primary parser
 */

static void
primary_syntax(const char *op, const char *syntax) {
    fprintf(stderr, "primary syntax: %s %s\n", op, syntax);
    exit(EXIT_FAILURE);
}

expression *
parse_primary(const char *s, parse_ctx *ctx) {
    expression *e = xmalloc(sizeof(expression));

    e->finalise = 0;

    if (!strcmp(s, "-name")) {
        if (ctx->i >= ctx->argc) primary_syntax(s, "<search pattern>");
        e->args[0].s = ctx->argv[ctx->i++];
        e->evaluate = &eval_name;
    } else if (!strcmp(s, "-path")) {
        if (ctx->i >= ctx->argc) primary_syntax(s, "<search pattern>");
        e->args[0].s = ctx->argv[ctx->i++];
        e->evaluate = &eval_path;
    } else if (!strcmp(s, "-print")) {
        e->evaluate = &eval_print;
    } else if (!strcmp(s, "-print0")) {
        e->evaluate = &eval_print0;
    } else if (!strcmp(s, "-prune")) {
        e->evaluate = &eval_prune;
    } else if (!strcmp(s, "-type")) {
        construct_type(s, ctx, e);
    } else if (!strcmp(s, "-exec") || !strcmp(s, "-ok")) {
        construct_command(s, ctx, e);
    } else if (!strcmp(s, "-size")) {
        construct_size(s, ctx, e);
    } else if (!strcmp(s, "-perm")) {
        construct_perm(s, ctx, e);
    } else if (!strcmp(s, "-newer")) {
        const char *filename;
        if (ctx->i >= ctx->argc) primary_syntax(s, "<filename>");
        filename = ctx->argv[ctx->i++];
        if (read_datestamp(&e->args[0].n, &e->args[1].n, filename)) {
            fprintf(stderr, "Unable to read datestamp from '%s'\n", filename);
            exit(EXIT_FAILURE);
        }
        e->evaluate = &eval_newer;
    } else if (!strcmp(s, "-time") || !strcmp(s, "-atime") || !strcmp(s, "-ctime") || !strcmp(s, "-mtime")) {
        construct_time(s, ctx, e);
    } else {
        fprintf(stderr, "Unknown primary '%s'\n", s);
        exit(EXIT_FAILURE);
    }

    return(e);
}
