/*
 * © 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.
 */

#include <assert.h>
#include <ctype.h>
#include "lang.h"
#include "mars.h"

#define PERFECT_MATCH 10000

static err langerr = 0;

void free_one_value(value* p) {
    strfree(p->name);
    FREE(p);
}

void free_all_values(value* p) {
    while (p) {
        value* q = p->next;
        free_one_value(p);
        p = q;
    }
}

void free_one_parameter(parameter* p) {
    strfree(p->name);
    free_all_values(p->values);
    free_all_values(p->current_values);
    free_all_values(p->default_values);
    free_all_values(p->ibm_values);
    free_all_requests(p->subrequest);
    free_all_requests(p->interface);
    FREE(p);
}

void free_all_parameters(parameter* p) {
    while (p) {
        parameter* q = p->next;
        free_one_parameter(p);
        p = q;
    }
}

void free_one_request(request* r) {
    strfree(r->name);
    strfree(r->kind);
    strfree(r->info);
    free_all_parameters(r->params);
    FREE(r);
}

void free_all_requests(request* p) {
    while (p) {
        request* q = p->next;
        free_one_request(p);
        p = q;
    }
}

/*==================================================================*/


value* clone_one_value(const value* p) {
    value* q = NEW_CLEAR(value);
    q->next  = NULL;
    q->name  = strcache(p->name);
    return q;
}

value* clone_all_values(const value* p) {
    if (p) {
        value* q = clone_one_value(p);
        q->next  = clone_all_values(p->next);
        /* q->alias = cone_value(p->alias); */
        return q;
    }
    return NULL;
}

parameter* clone_one_parameter(const parameter* p) {
    parameter* q      = NEW_CLEAR(parameter);
    q->next           = NULL;
    q->name           = strcache(p->name);
    q->values         = clone_all_values(p->values);
    q->current_values = clone_all_values(p->current_values);
    q->default_values = clone_all_values(p->default_values);
    q->ibm_values     = clone_all_values(p->ibm_values);
    q->subrequest     = clone_all_requests(p->subrequest);
    q->interface      = clone_all_requests(p->interface);
    return q;
}

parameter* clone_all_parameters(const parameter* p) {
    if (p) {
        parameter* q = clone_one_parameter(p);
        q->next      = clone_all_parameters(p->next);
        return q;
    }
    return NULL;
}

request* clone_one_request(const request* r) {
    if (r) {
        request* p = NEW_CLEAR(request);
        p->name    = strcache(r->name);
        p->info    = strcache(r->info);
        p->kind    = strcache(r->kind);
        p->params  = clone_all_parameters(r->params);
        p->next    = NULL;
        return p;
    }
    return NULL;
}

request* clone_all_requests(const request* p) {
    if (p) {
        request* q = clone_one_request(p);
        q->next    = clone_all_requests(p->next);
        return q;
    }
    return NULL;
}
/*==================================================================*/
static unsigned char charmap[] = {

#if 0
	'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
	'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
	'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
	'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
	'\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
	'\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
	'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
	'\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
	'\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
	'\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
	'\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
	'\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',
	'\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
	'\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
	'\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
	'\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
	'\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
	'\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
	'\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
	'\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
	'\300', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
	'\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
	'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
	'\370', '\371', '\372', '\333', '\334', '\335', '\336', '\337',
	'\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
	'\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
	'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
	'\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
#else
    0x00,
    0x01,
    0x02,
    0x03,
    0x04,
    0x05,
    0x06,
    0x07,
    0x08,
    0x09,
    0x0a,
    0x0b,
    0x0c,
    0x0d,
    0x0e,
    0x0f,
    0x10,
    0x11,
    0x12,
    0x13,
    0x14,
    0x15,
    0x16,
    0x17,
    0x18,
    0x19,
    0x1a,
    0x1b,
    0x1c,
    0x1d,
    0x1e,
    0x1f,
    ' ' /*   */,
    '!' /* ! */,
    '"' /* " */,
    '#' /* # */,
    '$' /* $ */,
    '%' /* % */,
    '&' /* & */,
    0x27,
    '(' /* ( */,
    ')' /* ) */,
    '*' /* * */,
    '+' /* + */,
    ',' /* , */,
    '-' /* - */,
    '.' /* . */,
    '/' /* / */,
    '0' /* 0 */,
    '1' /* 1 */,
    '2' /* 2 */,
    '3' /* 3 */,
    '4' /* 4 */,
    '5' /* 5 */,
    '6' /* 6 */,
    '7' /* 7 */,
    '8' /* 8 */,
    '9' /* 9 */,
    ':' /* : */,
    ';' /* ; */,
    '<' /* < */,
    '=' /* = */,
    '>' /* > */,
    '?' /* ? */,
    '@' /* @ */,
    'A' /* A */,
    'B' /* B */,
    'C' /* C */,
    'D' /* D */,
    'E' /* E */,
    'F' /* F */,
    'G' /* G */,
    'H' /* H */,
    'I' /* I */,
    'J' /* J */,
    'K' /* K */,
    'L' /* L */,
    'M' /* M */,
    'N' /* N */,
    'O' /* O */,
    'P' /* P */,
    'Q' /* Q */,
    'R' /* R */,
    'S' /* S */,
    'T' /* T */,
    'U' /* U */,
    'V' /* V */,
    'W' /* W */,
    'X' /* X */,
    'Y' /* Y */,
    'Z' /* Z */,
    '[' /* [ */,
    0x5c,
    ']' /* ] */,
    '^' /* ^ */,
    ' ' /* _ */,
    '`' /* ` */,
    'A' /* a */,
    'B' /* b */,
    'C' /* c */,
    'D' /* d */,
    'E' /* e */,
    'F' /* f */,
    'G' /* g */,
    'H' /* h */,
    'I' /* i */,
    'J' /* j */,
    'K' /* k */,
    'L' /* l */,
    'M' /* m */,
    'N' /* n */,
    'O' /* o */,
    'P' /* p */,
    'Q' /* q */,
    'R' /* r */,
    'S' /* s */,
    'T' /* t */,
    'U' /* u */,
    'V' /* v */,
    'W' /* w */,
    'X' /* x */,
    'Y' /* y */,
    'Z' /* z */,
    '{' /* { */,
    '|' /* | */,
    '}' /* } */,
    '~' /* ~ */,
    0x7f,
    0x80,
    0x81,
    0x82,
    0x83,
    0x84,
    0x85,
    0x86,
    0x87,
    0x88,
    0x89,
    0x8a,
    0x8b,
    0x8c,
    0x8d,
    0x8e,
    0x8f,
    0x90,
    0x91,
    0x92,
    0x93,
    0x94,
    0x95,
    0x96,
    0x97,
    0x98,
    0x99,
    0x9a,
    0x9b,
    0x9c,
    0x9d,
    0x9e,
    0x9f,
    0xa0,
    0xa1,
    0xa2,
    0xa3,
    0xa4,
    0xa5,
    0xa6,
    0xa7,
    0xa8,
    0xa9,
    0xaa,
    0xab,
    0xac,
    0xad,
    0xae,
    0xaf,
    0xb0,
    0xb1,
    0xb2,
    0xb3,
    0xb4,
    0xb5,
    0xb6,
    0xb7,
    0xb8,
    0xb9,
    0xba,
    0xbb,
    0xbc,
    0xbd,
    0xbe,
    0xbf,
    0xc0,
    0xc1,
    0xc2,
    0xc3,
    0xc4,
    0xc5,
    0xc6,
    0xc7,
    0xc8,
    0xc9,
    0xca,
    0xcb,
    0xcc,
    0xcd,
    0xce,
    0xcf,
    0xd0,
    0xd1,
    0xd2,
    0xd3,
    0xd4,
    0xd5,
    0xd6,
    0xd7,
    0xd8,
    0xd9,
    0xda,
    0xdb,
    0xdc,
    0xdd,
    0xde,
    0xdf,
    0xe0,
    0xe1,
    0xe2,
    0xe3,
    0xe4,
    0xe5,
    0xe6,
    0xe7,
    0xe8,
    0xe9,
    0xea,
    0xeb,
    0xec,
    0xed,
    0xee,
    0xef,
    0xf0,
    0xf1,
    0xf2,
    0xf3,
    0xf4,
    0xf5,
    0xf6,
    0xf7,
    0xf8,
    0xf9,
    0xfa,
    0xfb,
    0xfc,
    0xfd,
    0xfe,
    0xff,
#endif
};


static int count_matches(const char* a, const char* b, int exact) {
    int match         = 0;
    register char* cm = (char*)charmap;


    if (b[1] == 0) /* Allow words starting with a @, *, ... */
        switch (b[0]) {

            case '@':
                return PERFECT_MATCH;

            case '*':
                if (is_number(a))
                    return PERFECT_MATCH;
                else if (isrange(a)) /* <------------ come back here !!! */
                    return PERFECT_MATCH;
                else if (isdate(a)) /* <------------ come back here !!! */
                    return PERFECT_MATCH;
                else if (istime(a)) /* <------------ come back here !!! */
                    return PERFECT_MATCH;
                else
                    return 0;
                /*NOTREACHED*/
                break;

            case '"':
            case '\'':
                if (*a == '"' || *a == '\'')
                    return PERFECT_MATCH;
                else
                    return 0;

                /*NOTREACHED*/
                break;

            case '/':
                /* obsolete ... */
                break;
        }

    /* match for DAY 1, WEEK 6, ... */
    if (b[0] == '*' && b[1] != 0) {
        char buf[80];
        int value;
        if (sscanf(a, "%s %d", buf, &value) == 2) {
            if (count_matches(buf, b + 1, exact)) {
                return PERFECT_MATCH;
            }
        }
    }

    while (*a && *b && cm[*a] == cm[*b]) {
        match++;
        a++;
        b++;
    }

    /*
    if(*a != 0) return 0;
    if(*b == 0) return PERFECT_MATCH;
    */

    if (*a == 0 && *b == 0)
        return PERFECT_MATCH;

    if (exact)
        return 0;

    return match;
}


void update_step_list(int p, int n, int by, value* v) {
    value* nv;
    parameter* np;
    value* save;
    value* first = 0;
    value* last  = 0;
    char buf[80];

    sprintf(buf, "%d", p * 24);
    first = nv = new_value(strcache(buf));

    nv->next = new_value(strcache("TO"));
    nv       = nv->next;

    sprintf(buf, "%d", (int)((p + n - by / 24.0) * 24));
    nv->next = new_value(strcache(buf));
    nv       = nv->next;

    nv->next = new_value(strcache("BY"));
    nv       = nv->next;

    sprintf(buf, "%d", by);
    nv->next = new_value(strcache(buf));
    nv       = nv->next;


    np        = new_parameter(strcache("STEP"), first);
    np->count = 5;
    /* print_all_requests(new_request(strcache("STEP"),np)); */

    check_for_to_by_list(np);

    /* print_all_requests(new_request(strcache("STEP"),np)); */

    nv = first = np->values;
    while (nv) {
        last = nv;
        nv   = nv->next;
    }

    last->next  = v->next;
    v->next     = first->next;
    first->next = 0;

    strfree(v->name);
    v->name = strcache(first->name);

    free_all_parameters(np);
}

static err replace_value(value* v, long val) {
    char buf[80];
    sprintf(buf, "%ld", val);
    strfree(v->name);
    v->name = strcache(buf);
    return 0;
}

static err expand_steps_day(value* v, int val, request* r) {
    return replace_value(v, val * 24);
}

static err expand_steps_week(value* v, int val, request* r) {
    return replace_value(v, val * 7 * 24);
}

static err expand_steps_verify(value* v, int val, request* r) {
    const char* date = get_value(r, "DATE", 0);
    const char* time = get_value(r, "TIME", 0);
    long d1, d2;

    if (count_values(r, "DATE") != 1) {
        marslog(LOG_EROR, "Only one DATE can be given when STEPs are be specified as VERIFY");
        return -2;
    }

    if (count_values(r, "TIME") != 1) {
        marslog(LOG_EROR, "Only one TIME can be given when STEPs are be specified as VERIFY");
        return -2;
    }

    d1 = mars_date_to_julian(atol(date));
    d2 = mars_date_to_julian(val);
    /* t = atol(time); if(t>100) t /= 100; */

    return replace_value(v, (d2 - d1) * 24);
}

static err expand_steps_month(value* v, int val, request* r) {
    const char* date = get_value(r, "DATE", 0);
    long d1, d2, d0;
    int i;
    int n            = 0;
    int p            = 0;
    const char* time = get_value(r, "TIME", 0);

    if (count_values(r, "DATE") != 1) {
        marslog(LOG_EROR, "Only one DATE can be given when STEPs are be specified in MONTH");
        return -2;
    }

    d2 = d1 = mars_date_to_julian(atol(date) / 100 * 100 + 01);
    for (i = 0; i < val; i++) {
        d2 = mars_julian_to_date(d2 + 32, mars.y2k);
        d2 = mars_date_to_julian(d2 / 100 * 100 + 1);
    }

    p = d2 - d1;

    printf("validate %ld", mars_julian_to_date(mars_date_to_julian(atol(date)) + p, mars.y2k));

    return replace_value(v, p * 24);
}

static err expand_steps(request* r) {
    const char* step;
    parameter* p = find_parameter(r, "STEP");
    value* v;

    if (!p)
        return 0;

    v = p->values;
    while (v) {
        char buf[80];
        int val;
        if (sscanf(v->name, "%s %d", buf, &val) == 2) {
            if (EQ(buf, "DAY")) {
                if (expand_steps_day(v, val, r) != 0)
                    return -2;
            }
            else if (EQ(buf, "WEEK")) {
                if (expand_steps_week(v, val, r) != 0)
                    return -2;
            }
            else if (EQ(buf, "VERIFY")) {
                if (expand_steps_verify(v, val, r) != 0)
                    return -2;
            }
            else if (EQ(buf, "MONTH")) {
                if (expand_steps_month(v, val, r) != 0)
                    return -2;
            }
            else {
                marslog(LOG_EROR, "Unknow value for STEP: %s", buf);
                return -2;
            }
        }
        v = v->next;
    }
    return 0;
}


void check_for_date(parameter* p, value* r) {
    char buf[80];

    if (EQ(p->name, "DATE")) {

        if (is_number(r->name)) {
            int n = atoi(r->name);
            int m;


            /* special case for monthly means */

            if (n > 0 && (n % 100) == 0 && n >= 100000)
                m = mars_julian_to_date(mars_date_to_julian(n + 1), mars.y2k) - 1;
            else if (n > 0 && n < 100000)
                /* special case for 'by' value */
                m = n;
            else {
                m = mars_julian_to_date(mars_date_to_julian(n), mars.y2k);
            }

            if (n != m) {
                sprintf(buf, "%d", m);
                strfree(r->name);
                r->name = strcache(buf);
            }
        }
        else if (isdate(r->name)) {
            int m;
            long julian, second;
            boolean isjul;
            parsedate(r->name, &julian, &second, &isjul);
            if (second)
                marslog(LOG_WARN, "HH:MM:SS lost in %s", r->name);
            if (!isjul) {
                m = mars_julian_to_date(julian, mars.y2k);
                sprintf(buf, "%d", m);
                strfree(r->name);
                r->name = strcache(buf);
            }
        }
    }
}

void check_for_time(parameter* p, value* r) {
    char buf[80];

    if (EQ(p->name, "TIME")) {

        if (is_number(r->name)) {
            if (strlen(r->name) <= 2) {
                int n = atoi(r->name);
                sprintf(buf, "%04d", 100 * n);
                strfree(r->name);
                r->name = strcache(buf);
            }
        }
        else if (istime(r->name)) {
            int h, m, s;
            parsetime(r->name, &h, &m, &s);
            if (s)
                marslog(LOG_WARN, "seconds ignored in %s", r->name);
            sprintf(buf, "%02d%02d", h, m);
            strfree(r->name);
            r->name = strcache(buf);
        }
    }
}

static void match_values(value* l, value* r, value** match1, value** match2, int* best_match, const parameter* p) {
    const char* exact_match = get_value(p->interface, "exact_match", 0);
    int exact               = exact_match && exact_match[0] == 't';

    /* if(exact) print_all_requests(p->interface); */

    while (l) {
        value* o = l;

        if (l->ref)
            match_values(l->ref, r, match1, match2, best_match, p);
        else
            while (o) {
                int match = count_matches(r->name, o->name, exact);

                if (exact) /* this will imply match of full string */
                {
                    if (match == PERFECT_MATCH) {
                        *best_match = match;
                        *match2     = l;
                        *match1     = NULL;
                    }
                }
                else {
                    if (match == *best_match) {
                        /* only if it is not already this value */
                        if (*match2 != l) {
                            *match1 = *match2;
                            *match2 = l;
                        }
                    }

                    if (match > *best_match) {
                        *best_match = match;
                        *match2     = l;
                        *match1     = NULL;
                    }
                }

                o = o->other_names;
            }
        l = l->next;
    }
}

static boolean expand_value(parameter* p, value* r, value* lang, parameter* plang) {
    value* match1  = NULL;
    value* match2  = NULL;
    int best_match = 0;
    value *a, *b;

    /* printf(" -----------> START MATCHING FOR %s %s\n", p->name, r->name); */

    match_values(lang, r, &match1, &match2, &best_match, plang);

    if (!best_match) {
        value* v = lang;

        marslog(LOG_EROR, "undefined value : %s for parameter %s", r->name, p->name);

        if (mars.verbose) {
            marslog(LOG_EROR, "Values are : ");


            while (v) {
                value* o = v;
                while (o) {
                    const char* n = o->name;
                    switch (n[0]) {
                        case '*':
                            if (n[1]) {
                                printf("%s N", n + 1);
                            }
                            else
                                printf("any number");
                            break;

                        case '"':
                        case '\'':
                            printf("any string");
                            break;

                        case '/':
                            printf("a list");
                            break;

                        default:
                            printf("%s", n);
                            break;
                    }
                    if (o->other_names)
                        printf(" or ");
                    o = o->other_names;
                }
                putchar('\n');
                v = v->next;
            }
        }


        /* if don't fail use default values */
        if (mars.expflags & EXPAND_DONT_FAIL) {
            langerr = 0;
            marslog(LOG_WARN, "Ignoring parameter %s, using defaults", p->name);
            if (EQ(lang->name, "*")) /* avoid crashing */
            {
                strfree(r->name);
                r->name = strcache("1");
            }
            else {
                free_all_values(r);
                r = clone_one_value(lang);
            }

            return false;
        }
        langerr = -1;
        return false;
    }

    if (match1 && match2 && (match1 != match2) && best_match != PERFECT_MATCH) {
        marslog(LOG_EROR, "Ambiguous : %s could be %s or %s",
                r->name,
                match1->name,
                match2->name);
        langerr = -1;
        return false;
    }

    if (match2->name[0] == '*' && match2->name[1] != 0 && strlen(match2->name) > 2) /* Metview simple formula uses '* ' */
    {
        /* case of DAY 1, WEEK 2, ... */
        char buf[80];
        long value;

        sscanf(r->name, "%s %ld", buf, &value);
        sprintf(buf, "%s %ld", match2->name + 1, value);
        strfree(r->name);
        r->name = strcache(buf);
    }
    else if (match2->name[0] == '*' && match2->name[1] == 0) {
        /* remove ending . in numbers */

        int len = strlen(r->name) - 1;
        if (len > 0 && (r->name[len] == '.')) {
            char buf[80];
            strcpy(buf, r->name);
            strfree(r->name);
            buf[len] = 0;
            r->name  = strcache(buf);
        }
    }
    else if (/* match2->name[1] != 0 || */
             (match2->name[0] != '"' && match2->name[0] != '\'' && match2->name[0] != '@')) {

        /* find last alias */

        a = match2;
        b = match2;

        if (mars.expflags & EXPAND_LAST_NAME)
            while (a) {
                b = a;
                a = a->other_names;
            }
        else if (mars.expflags & EXPAND_2ND_NAME)
            if (a->other_names)
                b = a->other_names;

        strfree(r->name);
        r->name = strcache(b->name);

        if (b->expand != NULL && ((mars.expflags & EXPAND_SUB_LISTS) != 0)) {
            value* x = p->values;
            value* y = 0;
            value* z = clone_all_values(b->expand);

            if (p->ibm_values == 0)
                p->ibm_values = clone_all_values(p->values);

            while (x != r) {
                y = x;
                x = x->next;
            }

            if (y)
                y->next = z;
            else
                p->values = z;

            y = 0;
            while (z != NULL) {
                y = z;
                z = z->next;
            }

            y->next = r->next;
            r->next = 0;
            free_one_value(r);

            return true;
        }
    }

    if (mars.expflags & EXPAND_DATE)
        check_for_date(p, r);
    if (mars.expflags & EXPAND_TIME)
        check_for_time(p, r);

    return false;
}

static parameter* match_parameter(parameter* r, parameter* lang, boolean verbose) {
    parameter* l      = lang;
    parameter* match1 = NULL;
    parameter* match2 = NULL;
    int best_match    = 0;

    while (l) {
        int match = count_matches(r->name, l->name, 0);

        if (match == best_match) {
            match1 = match2;
            match2 = l;
        }

        if (match > best_match) {
            best_match = match;
            match2     = l;
            match1     = NULL;
        }

        l = l->next;
    }


    if (!best_match) {
        if (verbose)
            marslog(LOG_EROR, "Undefined parameter: %s", r->name);
        return NULL;
    }

    if (match1 && match2) {

        const char* prio1 = get_value(match1->interface, "priority", 0);
        const char* prio2 = get_value(match2->interface, "priority", 0);

        boolean prio = prio1 && prio2 && atol(prio1) != atol(prio2);

        if (verbose)
            marslog(prio ? LOG_DBUG : LOG_EROR,
                    "Ambiguous parameter: %s could be %s or %s",
                    r->name,
                    match1->name,
                    match2->name);

        if (prio) {
            if (atol(prio1) > atol(prio2))
                match2 = match1;
            if (verbose)
                marslog(LOG_DBUG, "Assuming that '%s' means '%s'", r->name, match2->name);
        }
        else {
            return NULL;
        }
    }

    return match2;
}


static void expand_parameter(parameter* r, parameter* lang) {
    parameter* match = match_parameter(r, lang, true);
    value* p;

    if (match == NULL) {
        langerr = -1;
        return;
    }

    strfree(r->name);
    r->name = strcache(match->name);

    parameter* plang = lang;
    while (plang) {
        if (strcmp(plang->name, match->name) == 0)
            break;
        plang = plang->next;
    }

    p = r->values;
    while (p) {
        if (expand_value(r, p, match->values, plang))
            p = r->values;
        else
            p = p->next;
    }

    free_all_values(match->current_values);
    free_all_values(match->ibm_values);

    match->current_values = clone_all_values(r->values);
    match->ibm_values     = clone_all_values(r->ibm_values);

    match->subrequest = clone_all_requests(r->subrequest);
}


static request* strip_off_values(request* lang, request* r) {
    parameter* p = r->params;
    parameter* l = lang->params;
    parameter* q = NULL;

    while (p) {
        value* v     = p->current_values;
        int flg      = 0;
        int count    = 0;
        parameter* s = p->next;

        free_all_values(p->values);
        p->values         = p->current_values;
        p->current_values = NULL;

        free_all_values(p->default_values);
        p->default_values = clone_all_values(p->ibm_values);

        if (v == NULL) {
            count = 1;
            flg   = 1;
        }
        while (v) {
            if (v->name == NULL) {
                count = 1;
                flg   = 1;
                break;
            }
            else if (mars.expflags & EXPAND_NO_OFF) {
                if (EQ(v->name, "O"))
                    flg++;
                else if (EQ(v->name, "OFF"))
                    flg++;
                else if (EQ(v->name, "\"\""))
                    flg++;
                else if (EQ(v->name, ""))
                    flg++;
            }

            count++;

            v = v->next;
        }

        if (mars.expflags & EXPAND_NO_DEFAULT) {
            value* d = l->default_values;
            value* v = p->values;

            while (d && v) {
                if (d->name != v->name)
                    break;
                d = d->next;
                v = v->next;
            }

            if (v == NULL && d == NULL) {
                count = 1;
                flg   = 1;
            }
        }

        /* if(p->values == NULL) { count = 1; flg = 1; } */

        if ((mars.expflags & (EXPAND_NO_OFF | EXPAND_NO_DEFAULT)) && (count == 1) && flg) {
            if (q)
                q->next = p->next;
            else
                r->params = p->next;
            free_one_parameter(p);
        }
        else
            q = p;

        p = s;
        l = l->next;
    }

    if (mars.expflags & EXPAND_STEP) {
        if (expand_steps(r) != 0)
            return 0;
    }

    if (mars.expflags & EXPAND_LISTS) {
        parameter* p = r->params;
        while (p) {
            count_values(r, p->name);
            p = p->next;
        }
    }

    return r;
}

static boolean probe_parameters(parameter* p, parameter* lang) {
    while (p) {
        if (*p->name != '_') {
            parameter* match;
            if ((match = match_parameter(p, lang, mars.debug))) {
                value* v = p->values;
                while (v) {
                    value* match1  = NULL;
                    value* match2  = NULL;
                    int best_match = 0;

                    match_values(match->values, v, &match1, &match2, &best_match, match);
                    /* Undefined value */

                    if (!best_match) {
                        marslog(LOG_DBUG, "No best match for %s", p->name);
                        return false;
                    }

                    /* ambiguous */
                    if (match1 && match2 && (match1 != match2) && best_match != PERFECT_MATCH) {
                        marslog(LOG_DBUG, "Two best match for %s %s %s", p->name, match1->name, match2->name);
                        return false;
                    }

                    v = v->next;
                }
            }
            else {
                marslog(LOG_DBUG, "No match for %s", p->name);
                return false;
            }
        }
        p = p->next;
    }
    return true;
}

static request* match_verb(const request* r, request* lang) {
    request* l = lang;
    request* matches[1024];
    int best_match = 0;
    int top        = 0;
    int use        = -1;
    boolean tell   = false;

    while (l) {
        int match = count_matches(r->name, l->name, 0);

        if (match == best_match) {
            if (top == NUMBER(matches) - 1)
                marslog(LOG_EXIT, "Match stack too small");
            matches[top++] = l;
        }

        if (match > best_match) {
            top            = 0;
            matches[top++] = l;
            best_match     = match;
        }

        l = l->next;
    }

    if (!best_match) {
        marslog(LOG_EROR, "Undefined verb: %s", r->name);
        return NULL;
    }

    if (top > 1) {
        int i;
        /* Try to resolve ambiguity */
        boolean ok[1024];
        int n = 0;
        use   = -1;
        for (i = 0; i < top; i++) {
            marslog(LOG_DBUG, "Probing %s (%s)", matches[i]->name, matches[i]->kind);
            if ((ok[i] = probe_parameters(r->params, matches[i]->params))) {
                if (use < 0)
                    use = i;
                n++;
                marslog(LOG_DBUG, "ok");
            }
            else {
                marslog(LOG_DBUG, "fail");
            }
        }

        if (n > 1) {
            marslog(LOG_DBUG, "Ambiguous verb: '%s' could be:", r->name);
            for (i = 0; i < top; i++)
                if (ok[i])
                    marslog(LOG_DBUG, "  %s (%s)", matches[i]->name, matches[i]->kind);
            tell = true;
        }
    }

    if (use < 0)
        use = 0;

    if (tell)
        marslog(LOG_DBUG, "Choosing %s (%s)", matches[use]->name, matches[use]->kind);
    return matches[use];
}

static request* expand_one_request(const request* r, request* lang) {
    request* match = match_verb(r, lang);
    parameter* p   = r->params;

    if (!match) {
        langerr = -1;
        return NULL;
    }

    while (p) {
        if (*p->name != '_') {
            /* printf("EXPAND PARAMETER %s\n", p->name); */
            expand_parameter(p, match->params);
        }
        p = p->next;
    }

    return strip_off_values(match, clone_one_request(match));
}

static void chk_defaults(request* r) {
    while (r) {
        parameter* p = r->params;
        while (p) {
            value* v = p->default_values;
            while (v) {
                if (expand_value(p, v, p->values, p))
                    v = p->default_values;
                else
                    v = v->next;
            }
            p = p->next;
        }
        r = r->next;
    }
}


static value* resolve(request* lang, char* ref, char* name) {
    request* r = lang;

    while (r) {
        if (r->name == ref) {
            parameter* p = r->params;
            while (p) {
                if (p->name == name)
                    return p->values;
                p = p->next;
            }
        }
        r = r->next;
    }

    marslog(LOG_EXIT, "Reference not found parameter %s of verb %s", name, ref);
    return 0;
}


static void resolve_references(request* lang, request* r) {
    while (r) {
        parameter* p = r->params;
        while (p) {
            value* v = p->values;
            while (v) {
                if (v->ref)
                    v->ref = resolve(lang, (char*)v->ref, v->name);
                v = v->next;
            }
            p = p->next;
        }
        r = r->next;
    }
}

request* read_language_file(const char* name) {
    extern request* parser_lang;
    request* r;

    if (parser(name, false) != NOERR) {
        free_all_requests(parser_lang);
        parser_lang = NULL;
        return NULL;
    }
    r           = parser_lang;
    parser_lang = NULL;
    resolve_references(r, r);
    chk_defaults(r);
    reset_language(r);

    return r;
}

static request* expand0(request* lang, rule* test, const request* r) {
    if (r) {
        request* q = expand_one_request(r, lang);

        if (!check_one_request(test, q))
            langerr = -1;

        if (q)
            q->next = expand0(lang, test, r->next);
        return q;
    }
    return NULL;
}

long expand_flags(long flags) {
    long old      = mars.expflags;
    mars.expflags = flags;
    return old;
}

request* expand_all_requests(request* lang, rule* test, const request* r) {
    request* s;
    langerr = 0;
    s       = expand0(lang, test, r);
    if (langerr)
        free_all_requests(s);
    return langerr ? NULL : s;
}

void reset_language(request* lang) {
    request* r = lang;
    while (r) {
        parameter* p = r->params;
        while (p) {
            free_all_values(p->current_values);
            free_all_requests(p->subrequest);
            p->subrequest     = 0;
            p->current_values = clone_all_values(p->default_values);
            p                 = p->next;
        }
        r = r->next;
    }
}

static void loopuk_loop(value* v, lookupproc c, void* d) {
    while (v) {
        if (v->ref)
            loopuk_loop(v->ref, c, d);
        else
            c(v->name, d);
        v = v->next;
    }
}

request* closest_verb(request* lang, const char* name) {
    request* l = lang;
    int best   = 1;
    request* r = NULL;

    while (l) {
        int match = count_matches(name, l->name, 0);
        if (match > best) {
            r    = l;
            best = match;
        };
        l = l->next;
    }

    return r;
}

parameter* closest_parameter(request* lang, const char* name) {
    parameter* l = lang ? lang->params : NULL;
    parameter* w = NULL;
    int best     = 1;

    while (l) {
        int match = count_matches(name, l->name, 0);
        if (match > best) {
            w    = l;
            best = match;
        };
        l = l->next;
    }

    return w;
}

void loopuk_language(request* lang, const char* v, const char* p, lookupproc c, void* d) {
    request* r = closest_verb(lang, v);

    if (r == NULL)
        return;

    /* just params */

    if (p == NULL) {
        parameter* w = r->params;
        while (w) {
            c(w->name, d);
            w = w->next;
        }
    }
    else {
        parameter* w = closest_parameter(r, p);
        if (w)
            loopuk_loop(w->values, c, d);
    }
}

static request* trim(request* lang, request* r) {
    while (lang) {
        if (lang->name == r->name) {
            parameter* p = r->params;
            request* s   = empty_request(r->name);
            parameter* t = NULL;

            while (p) {
                parameter* q = lang->params;
                while (q) {
                    if (q->name == p->name) {
                        parameter* u = clone_one_parameter(p);
                        if (t)
                            t->next = u;
                        else
                            s->params = u;
                        t = u;
                        break;
                    }

                    q = q->next;
                }

                p = p->next;
            }
            return s;
        }
        lang = lang->next;
    }
    return NULL;
}

request* trim_all_requests(request* lang, request* r) {
    request* s = NULL;
    request* t = NULL;
    request* u = NULL;

    while (r) {
        u = trim(lang, r);
        if (u) {
            if (t)
                t->next = u;
            else
                s = u;
            t = u;
        }

        r = r->next;
    }
    return s;
}

request* expand_mars_request(const request* r) {
    return expand_all_requests(mars_language(), mars_rules(), r);
}

request* mars_language_from_request(const request* r) {
    request* lang = mars_language();
    while (lang) {
        if (lang->name == r->name && lang->kind == r->kind)
            return lang;
        lang = lang->next;
    }
    return NULL;
}

void copy_to_ibm_values(request* r, const char* name) {
    parameter* p = find_parameter(r, name);
    if (p) {
        free_all_values(p->default_values);
        p->default_values = clone_all_values(p->values);
    }
}

void move_to_ibm_values(request* source, request* dest, const char* name) {
    parameter* p = find_parameter(source, name);
    parameter* q = find_parameter(dest, name);
    if (p && q) {
        free_all_values(q->default_values);
        q->default_values = clone_all_values(p->values);
    }
}
