/*
 * (C) Copyright 2005- 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.
 */


/*
  Author: B.Raoult
  Modified by Enrico Fucile
*/

#include "grib_api_internal.h"
#include <cmath>
#include <signal.h>
#include <cctype>
#include <stdlib.h>

static grib_math* readpower(grib_context* c, char** form, int* err);
static grib_math* readatom(grib_context* c, char** form, int* err);
static grib_math* readfactor(grib_context* c, char** form, int* err);
static grib_math* readterm(grib_context* c, char** form, int* err);
static grib_math* readtest(grib_context* c, char** form, int* err);
static grib_math* readlist(grib_context* c, char** form, int* n, int* err);
static grib_math* readand(grib_context* c, char** form, int* err);
static grib_math* reador(grib_context* c, char** form, int* err);
static void advance(char** form);
static char* opname(char* p, int n);
/*
static long op_eq(long a, long b)     {return a == b;}
static long op_ne(long a, long b)     {return a != b;}
static long op_lt(long a, long b)     {return a <  b;}
static long op_gt(long a, long b)     {return a >  b;}
static long op_and(long a, long b)    {return a && b;}
static long op_or(long a, long b)     {return a || b;}
static long op_ge(long a, long b)     {return a >= b;}
static long op_le(long a, long b)     {return a <= b;}

static long op_bit(long a, long b)    {return a&(1<<b);}
static long op_bitoff(long a, long b) {return !op_bit(a,b);}

static long op_not(long a) {return !a;}
static long op_neg(long a) {return -a;}

static double op_neg_d(double a) {return -a;}

static long op_pow(long a, long b) {return codes_power<double>(a,b);}
static long op_add(long a, long b) {return a+b;}
static long op_sub(long a, long b) {return a-b;}
static long op_div(long a, long b) {return a/b;}
static long op_mul(long a, long b) {return a*b;}
static long op_modulo(long a, long b) {return a%b;}

static double op_mul_d(double a, double b) {return a*b;}
static double op_div_d(double a, double b) {return a/b;}
static double op_add_d(double a, double b) {return a+b;}
static double op_sub_d(double a, double b) {return a-b;}

static double op_eq_d(double a, double b)     {return a == b;}
static double op_ne_d(double a, double b)     {return a != b;}
static double op_lt_d(double a, double b)     {return a <  b;}
static double op_gt_d(double a, double b)     {return a >  b;}
static double op_ge_d(double a, double b)     {return a >= b;}
static double op_le_d(double a, double b)     {return a <= b;}

static int op_eq_string(char* a,char* b) {
  if (a && b) return strcmp(a,b) == 0 ? 1 : 0;
  return 0;
}
static int op_ne_string(char* a,char* b) {
  if (a && b) return strcmp(a,b) == 0 ? 0 : 1;
  return 0;
}
static int op_lt_string(char* a,char* b) {
  if (a && b) return strcmp(a,b) < 0 ? 1 : 0;
  return 0;
}
static int op_gt_string(char* a,char* b) {
  if (a && b) return strcmp(a,b) > 0 ? 1 : 0;
  return 0;
}
static int op_ge_string(char* a,char* b) {
  if (a && b) return strcmp(a,b) >= 0 ? 1 : 0;
  return 0;
}
static int op_le_string(char* a,char* b) {
  if (a && b) return strcmp(a,b) <= 0 ? 1 : 0;
  return 0;
}
*/

typedef double real;
typedef int boolean;
typedef int err;
typedef real (*fop1)(real);
typedef real (*fop2)(real, real);

static void advance(char** form)
{
    (*form)++;
    while (isspace(**form))
        (*form)++;
}

static grib_math* readatom(grib_context* c, char** form, int* err)
{
    grib_math* p;
    int i;
    char buf[1024];

    switch (**form) {
        case '(':
            advance(form);
            p = reador(c, form, err);
            if (**form != ')') {
                grib_context_log(c, GRIB_LOG_ERROR, "Formula: missing )");
                *err = GRIB_INTERNAL_ERROR;
            }
            advance(form);
            break;

        case '-':
            p        = (grib_math*)grib_context_malloc(c, sizeof(grib_math));
            p->arity = 1;
            p->name  = strdup("neg");
            Assert(p->name);
            advance(form);
            p->left = readatom(c, form, err);
            break;

        case '!':
            p        = (grib_math*)grib_context_malloc(c, sizeof(grib_math));
            p->arity = 1;
            p->name  = strdup("neg");
            Assert(p->name);
            advance(form);
            p->left = readatom(c, form, err);
            break;

        case '\0':
            grib_context_log(c, GRIB_LOG_ERROR, "Formula: syntax error");
            *err = GRIB_INTERNAL_ERROR;
            return NULL;
            /*NOTREACHED*/
            break;

        default:
            i = 0;

            if (**form == '\'' || **form == '"') {
                char achar = *((*form)++);
                while (**form && **form != achar)
                    buf[i++] = *((*form)++);
                if (**form)
                    (*form)++;
            }
            else
                while (isalpha(**form) || isdigit(**form) || **form == '.' || **form == '_')
                    buf[i++] = *((*form)++);

            buf[i] = 0;
            if (isspace(**form))
                advance(form);

            p       = (grib_math*)grib_context_malloc(c, sizeof(grib_math));
            p->name = strdup(buf);
            Assert(p->name);
            p->left = 0;
            p->right = 0;

            switch (**form) {
                case '(':
                    advance(form);
                    p->arity = 0;
                    p->left  = readlist(c, form, &p->arity, err);
                    if (**form != ')') {
                        grib_context_log(c, GRIB_LOG_ERROR, "Formula: missing )");
                        *err = GRIB_INTERNAL_ERROR;
                    }
                    advance(form);
                    break;

                case '[':
                    advance(form);
                    p->arity = 0;
                    p->left  = readlist(c, form, &p->arity, err);
                    if (**form != ']') {
                        grib_context_log(c, GRIB_LOG_ERROR, "Formula: missing ]");
                        *err = GRIB_INTERNAL_ERROR;
                    }
                    p->arity = -p->arity;
                    advance(form);
                    break;

                default:
                    p->arity = 0;
                    break;
            }

            break;
    }

    return p;
}

static char* opname(char* p, int n)
{
    char buf[5];
    strncpy(buf, p, n);
    buf[n] = 0;
    return strdup(buf);
}

void print_math(grib_math* m)
{
    if (m) {
        putchar('(');
        print_math(m->left);
        printf("%s", m->name);
        print_math(m->right);
        putchar(')');
    }
}

static grib_math* readpower(grib_context* c, char** form, int* err)
{
    grib_math* p = readatom(c, form, err);

    while (**form == '^' || (**form == '*' && *(*form + 1) == '*')) {
        grib_math* q = (grib_math*)grib_context_malloc(c, sizeof(grib_math));
        q->right     = 0;
        q->left      = p;
        q->arity     = 2;

        if (**form == '*') {
            advance(form);
            **form = '^';
        }

        q->name = opname(*form, 1);
        advance(form);
        q->right = readatom(c, form, err);
        p        = q;
    }
    return p;
}

static grib_math* readlist(grib_context* c, char** form, int* n, int* err)
{
    grib_math* p;

    if (**form == ')')
        return NULL;

    p  = readtest(c, form, err);
    *n = 1;

    while (**form == ',') {
        grib_math* q = (grib_math*)grib_context_malloc(c, sizeof(grib_math));

        (*n)++;

        q->left = p;

        advance(form);

        q->right = readtest(c, form, err);

        p = q;
    }
    return p;
}

static grib_math* readfactor(grib_context* c, char** form, int* err)
{
    grib_math* p = readpower(c, form, err);

    while (**form == '*' || **form == '/') {
        grib_math* q = (grib_math*)grib_context_malloc(c, sizeof(grib_math));

        q->arity = 2;
        q->left  = p;
        q->name  = opname(*form, 1);

        advance(form);

        q->right = readpower(c, form, err);

        p = q;
    }
    return p;
}

static grib_math* readterm(grib_context* c, char** form, int* err)
{
    grib_math* p = readfactor(c, form, err);
    while (**form == '+' || **form == '-') {
        grib_math* q = (grib_math*)grib_context_malloc(c, sizeof(grib_math));

        q->arity = 2;
        q->left  = p;
        q->name  = opname(*form, 1);

        advance(form);

        q->right = readfactor(c, form, err);

        p = q;
    }
    return p;
}

static grib_math* readtest(grib_context* c, char** form, int* err)
{
    grib_math* p = readterm(c, form, err);
    while (**form == '<' || **form == '>' || **form == '=') {
        grib_math* q = (grib_math*)grib_context_malloc(c, sizeof(grib_math));
        char* x      = *form;
        int n        = 1;

        q->arity = 2;
        q->left  = p;

        advance(form);
        if (**form == '=' || **form == '>') {
            n = 2;
            advance(form);
        }

        q->name = opname(x, n);

        q->right = readterm(c, form, err);

        p = q;
    }
    return p;
}

static grib_math* readand(grib_context* c, char** form, int* err)
{
    grib_math* p = readtest(c, form, err);
    while (**form == '&') {
        grib_math* q = (grib_math*)grib_context_malloc(c, sizeof(grib_math));
        char* x      = *form;
        int n        = 1;

        q->arity = 2;
        q->left  = p;

        advance(form);
        if (**form == '&') {
            n = 2;
            advance(form);
        }

        q->name = opname(x, n);

        q->right = readtest(c, form, err);

        p = q;
    }
    return p;
}

static grib_math* reador(grib_context* c, char** form, int* err)
{
    grib_math* p = readand(c, form, err);
    while (**form == '|') {
        grib_math* q = (grib_math*)grib_context_malloc(c, sizeof(grib_math));
        char* x      = *form;
        int n        = 1;

        q->arity = 2;
        q->left  = p;

        advance(form);
        if (**form == '|') {
            n = 2;
            advance(form);
        }

        q->name = opname(x, n);

        q->right = readand(c, form, err);

        p = q;
    }
    return p;
}

// static grib_math* grib_math_clone(grib_context* c, grib_math* m)
// {
//     grib_math* n = NULL;
//     if (m) {
//         n        = (grib_math*)grib_context_malloc(c, sizeof(grib_math));
//         n->arity = m->arity;
//         n->name  = strdup(m->name);
//         Assert(n->name);
//         n->left  = grib_math_clone(c, m->left);
//         n->right = grib_math_clone(c, m->right);
//     }
//     return n;
// }

void grib_math_delete(grib_context* c, grib_math* m)
{
    grib_math *left = 0, *right = 0;
    left  = m->left;
    right = m->right;
    if (m->name)
        free(m->name);
    grib_context_free(c, m);
    if (left)
        grib_math_delete(c, left);
    if (right)
        grib_math_delete(c, right);
}

grib_math* grib_math_new(grib_context* c, const char* formula, int* err)
{
    grib_math* x = NULL;
    char* f      = NULL;
    char* fsave  = NULL;

    *err = 0;

    if (!formula) {
        *err = GRIB_INVALID_ARGUMENT;
        return NULL;
    }

    f = strdup(formula);
    Assert(f);
    fsave = f;

    x = reador(c, &f, err);
    if (*err != GRIB_SUCCESS)
        return NULL;

    if (*f) {
        grib_context_log(c, GRIB_LOG_ERROR,
                         "grib_math_new : Part of the formula was not processed: '%s'", f);
        *err = GRIB_INVALID_ARGUMENT;
        return NULL;
    }

    free(fsave);

    return x;
}
