/*
 * © Copyright 1996-2012 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/*

B.Raoult
ECMWF Oct-93

*/

#include "lang.h"
#include "mars.h"

void print_conditions(condition* c) {
    static char* opnames[] = {
        "val",
        "%or",
        "%and",
        "%not",
        "=",
        "<",
        ">",
        "<>",
        ">=",
        "<=",
        "%in",
    };
    value* v;

    if (c) {
        putchar('[');

        switch (c->op) {

            case t_val:
                v = (value*)c->left;
                printf("%s\n", v->name);
                break;

            case t_func:
                printf("%s(%s)\n", (char*)c->left, (char*)c->right);
                break;

            case t_not:
                printf("%%not ");
                print_conditions(c->left);
                break;

            default:
                print_conditions(c->left);
                printf(" %s ", opnames[c->op]);
                print_conditions(c->right);
                break;
        }

        putchar(']');
    }
}

static boolean bad_param;

static int compvalues(request* r, condition* t) {
    condition* t1 = t->left;
    condition* t2 = t->right;
    char* name;
    const char* par;
    char* val;

    if (t1->op != t_val || t2->op != t_val)
        marslog(LOG_EXIT, "Bad test");

    name = ((value*)(t1->left))->name;


    par = get_value(r, name, 0);

    if (par == NULL) {
        bad_param = true;
        return -1;
    }

    val = ((value*)(t2->left))->name;


    if (is_number(par) && is_number(val)) {
        if (EQ(name, "DATE"))
            return mars_date_to_julian(atol(par)) - mars_date_to_julian(atol(val));
        else
            return atof(par) - atof(val);
    }
    return strcmp(par, val);
}


static int complist(request* r, condition* t) {
    marslog(LOG_WARN, "IN TEST NOT IMP.");
    return 0;
}

boolean old_expver_func(request* r, char* n, char* a) {

    /* TODO: Create a global option... */
    const char* c = get_value(r, "CLASS", 0);
    const char* e = no_quotes(get_value(r, "EXPVER", 0));
    char path[1024];
    char line[1024];
    FILE* f;

    if (a)
        c = a;

    if (!c) {
        marslog(LOG_WARN, "old_expver_func: CLASS is not defined");
        return false;
    }
    if (!e) {
        marslog(LOG_WARN, "old_expver_func: EXPVER is not defined");
        return false;
    }

    sprintf(path, "%s/etc/old_expver.%s", getenv("MARS_HOME") ? getenv("MARS_HOME") : ".", lowcase(c));

    f = fopen(path, "r");
    if (!f) {
        marslog(LOG_EXIT | LOG_PERR, "old_expver_func: cannot open %s", path);
        return false;
    }

    while (fgets(line, sizeof(line), f)) {
        if (strncmp(e, line, 4) == 0)
            return true;
    }

    fclose(f);

    return false;
}


boolean call_func(request* c, char* f, char* a) {
    /* For now, hardcoded ... */
    if (strcmp(f, "old_expver") == 0)
        return old_expver_func(c, f, a);
    marslog(LOG_EXIT, "Invalid function in chk [%s](%s)", f, a ? a : "null");
    return false;
}

boolean condition_check(request* c, condition* t) {
    boolean b;
    char *p, *q;

    switch (t->op) {
        case t_or:
            b = condition_check(c, t->left) || condition_check(c, t->right);
            break;

        case t_and:
            b = condition_check(c, t->left) && condition_check(c, t->right);
            break;

        case t_not:
            b = !condition_check(c, t->left);
            break;

        case t_val:
            b = count_values(c, ((value*)(t->left))->name) != 0;
            break;

        case t_func:
            p = (char*)t->left;
            q = (char*)t->right;
            return call_func(c, p, q);
            break;

        case t_eq:
            b = compvalues(c, t) == 0;
            break;

        case t_lt:
            b = compvalues(c, t) < 0;
            break;

        case t_gt:
            b = compvalues(c, t) > 0;
            break;

        case t_ne:
            b = compvalues(c, t) != 0;
            break;

        case t_ge:
            b = compvalues(c, t) >= 0;
            break;

        case t_le:
            b = compvalues(c, t) <= 0;
            break;

        case t_in:
            b = complist(c, t);
            break;
    }

    /*
    if(mars.debug)
    {
        print_conditions(t);
        putchar('\n');
        marslog(LOG_DBUG," -> %s",b?"true":"false");
    }
    */

    return b;
}

rule* read_check_file(const char* fname) {
    extern rule* parser_ruls;
    rule* r;
    if (parser(fname, false) != NOERR) {
        free_rule(parser_ruls);
        parser_ruls = NULL;
        return NULL;
    }
    r           = parser_ruls;
    parser_ruls = NULL;
    return r;
}


void print_actions(action* a) {
}

void print_rules(rule* r) {
    while (r) {
        printf("%%if ");
        print_conditions(r->test);
        printf(" %%then\n");
        print_actions(r->doit);
        putchar('\n');
        r = r->next;
    }
}

boolean doaction(rule* c, request* r, action* a) {
    FILE* f      = 0;
    request* env = 0;
    char* s;
    parameter* p;
    value* v;
    boolean b;
    boolean x = true;

    while (a) {
        switch (a->op) {
            case a_set:
                p = (parameter*)a->param;
                marslog(LOG_DBUG, "Setting param %s to:",
                        p->name);

                if (mars.debug) {
                    printf("%s\n", p->values->name);
                    putchar('\n');
                }

                v = p->values;
                b = false;
                while (v) {
                    if (b)
                        add_value(r, p->name, v->name);
                    else
                        set_value(r, p->name, v->name);
                    b = true;
                    v = v->next;
                }
                break;

            case a_unset:
                s = (char*)a->param;
                marslog(LOG_DBUG, "Unsetting param %s", s);
                unset_value(r, s);
                add_value(r, "_UNSET", "%s", s);
                break;

            case a_warning:
                s = (char*)a->param;
                marslog(LOG_WARN, "%s", no_quotes(s));
                break;

            case a_info:
                s = (char*)a->param;
                marslog(LOG_INFO, "%s", no_quotes(s));
                break;

            case a_mail:
                env = get_environ();
                s   = (char*)a->param;
                f   = mail_open(mars.dhsmail, "%s", no_quotes(s));
                mail_request(f, "MARS Request:", r);
                mail_request(f, "MARS Environment:", env);
                mail_close(f);
                break;

            case a_mailuser:
                notify_user(s, r);
                break;

            case a_error:
                s = (char*)a->param;
                marslog(LOG_EROR, "%s", no_quotes(s));
                x = false;
                break;

            case a_exit:
                s = (char*)a->param;
                marslog(LOG_EXIT, "%s", no_quotes(s));
                break;

            case a_fail:
                x = false;
                marslog(LOG_DBUG, "Fail");
                if (mars.debug) {
                    print_one_request(r);
                    putchar('\n');
                    print_conditions(c->test);
                    putchar('\n');
                }
                break;
        }
        a = a->next;
    }

    return x;
}

boolean check_one_request(rule* c, request* r) {
    boolean b = true;

    if (r == NULL)
        return true;

    set_value(r, "_VERB", r->name);
    if (mars.appl)
        set_value(r, "_APPL", mars.appl);

    while (c) {
        bad_param = false;

        if (condition_check(r, c->test) && !bad_param)
            b = doaction(c, r, c->doit) && b;
        c = c->next;
    }


    return b;
}

rule* new_rule(condition* c, action* a) {
    rule* r = NEW_CLEAR(rule);
    r->test = c;
    r->doit = a;
    return r;
}

condition* new_condition(testop op, condition* l, condition* r) {
    condition* c = NEW_CLEAR(condition);
    c->op        = op;
    c->left      = l;
    c->right     = r;
    return c;
}

action* new_action(actop op, void* parm) {
    action* a = NEW_CLEAR(action);
    a->op     = op;
    a->param  = parm;
    return a;
}

condition* clone_condition(condition* c) {
    if (!c)
        return NULL;

    if (c->op == t_func)
        return new_condition(t_func,
                             (condition*)strcache((char*)c->left),
                             (condition*)strcache((char*)c->right));
    else if (c->op == t_val)
        return new_condition(t_val,
                             (condition*)clone_all_values((value*)(c->left)),
                             NULL);
    else
        return new_condition(c->op,
                             clone_condition(c->left),
                             clone_condition(c->right));
}

void free_action(action* a) {
    while (a) {
        action* b = a->next;

        switch (a->op) {

            case a_set:
                free_all_parameters((parameter*)a->param);
                break;

            case a_unset:
            case a_warning:
            case a_info:
            case a_mail:
            case a_mailuser:
            case a_error:
            case a_exit:
                strfree((char*)a->param);
                break;

            case a_fail:
                break;

            default:
                marslog(LOG_EXIT, "free_action : unknown action");
                break;
        }
        FREE(a);
        a = b;
    }
}

void free_condition(condition* c) {
    if (c) {
        if (c->op == t_func) {
            strfree((char*)c->left);
            strfree((char*)c->right);
        }
        else if (c->op == t_val)
            free_all_values((value*)(c->left));
        else {
            free_condition(c->left);
            free_condition(c->right);
        }
        FREE(c);
    }
}

void free_rule(rule* r) {
    while (r) {
        rule* n = r->next;
        free_condition(r->test);
        free_action(r->doit);
        FREE(r);
        r = n;
    }
}
