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

#include "grib_api_internal.h"

#ifdef HAVE_NETCDF

#include <cmath>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>

#include <time.h>
#include <float.h>
#include <netcdf.h>

#include "grib_tools.h"
#include "eccodes_windef.h"

#ifdef ECCODES_ON_WINDOWS
#include <stdint.h>
#endif

const char* tool_description =
    "Convert GRIB file(s) to netCDF format."
    "\n\tNote: The GRIB geometry should be a regular lat/lon grid or a regular Gaussian grid"
    "\n\t(the key \"typeOfGrid\" should be \"regular_ll\" or \"regular_gg\")";
const char* tool_name        = "grib_to_netcdf";
const char* tool_online_doc  = "https://confluence.ecmwf.int/display/ECC/grib_to_netcdf";
const char* tool_usage       = "[options] -o output_file grib_file grib_file ... ";
static char argvString[2048] = {0,};

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

static grib_context* ctx           = NULL;
static double global_missing_value = 9.9692099683868690e+36; /* See GRIB-953 */

/*===============================================================================*/
/* request from mars client */
/*===============================================================================*/

#define NUMBER(x) (sizeof(x) / sizeof(x[0]))

typedef int err;
typedef off_t file_offset;

static int files = 0;

struct value
{
    struct value* next;
    char* name;
};
typedef struct value value;

struct request
{
    struct request* next;
    struct parameter* params;
    char* name;
    int order;
};
typedef struct request request;
/*  request part */
/* language part */

struct parameter
{
    struct parameter* next;
    struct value* values;
    char* name;
    int count;
};
typedef struct parameter parameter;

static const char* get_value(const request*, const char* name, int n);
static bool parsedate(const char* name, long* julian, long* second, bool* isjul);

void usage_and_exit(void)
{
    usage(); // this calls exit(1)
}

static bool eq_string(const char* l, const char* r)
{
    if (l && r) return strcmp(l, r) == 0;
    return false;
}

static bool eq_integer(const char* l, const char* r)
{
    if (l && r) return atol(l) == atol(r);
    return false;
}

static bool eq_null(const char* l, const char* r)
{
    return true;
}

static bool eq_coord(const char* l, const char* r)
{
    if (l && r) return strcmp(l, r) == 0;
    return false;
}

static bool eq_range(const char* l, const char* r)
{
    if (l && r) return strcmp(l, r) == 0;
    return false;
}

static bool eq_date(const char* l, const char* r)
{
    if (l && r) return strcmp(l, r) == 0;
    return false;
}

static bool eq_param(const char* l, const char* r)
{
    if (l && r) return strcmp(l, r) == 0;
    return false;
}

static bool eq_time(const char* l, const char* r)
{
    if (l && r) return strcmp(l, r) == 0;
    return false;
}

static value* new_value(const char* name)
{
    value* v = (value*)calloc(1, sizeof(value));
    ECCODES_ASSERT(v);
    v->name = grib_context_strdup(ctx, name);
    return v;
}

static parameter* find_parameter(const request* r, const char* parname)
{
    if (!parname)
        return 0;
    if (r) {
        parameter* p = r->params;
        while (p) {
            if (strcmp(p->name, parname) == 0)
                return p;
            p = p->next;
        }
    }
    return NULL;
}

static void ecc_reqmerge(parameter* pa, const parameter* pb, request* a)
{
    const value* vb = pb->values;

    if (strcmp(pa->name, pb->name) != 0)
        return;

    while (vb) {
        value* va      = pa->values;
        value* last    = 0;
        const char* nb = vb->name;
        bool add    = true;

        while (va) {
            if (strcmp(va->name, nb) == 0) {
                add = false;
                break;
            }

            last = va;
            va   = va->next;
        }

        if (add) {
            value* v = new_value(nb);
            if (last)
                last->next = v;
            else
                pa->values = v;
            pa->count = 0;
        }

        vb = vb->next;
    }
}

/* Fast version if a && b same */
static bool ecc_reqmerge1(request* a, const request* b)
{
    parameter* pa       = a->params;
    const parameter* pb = b->params;

    while (pa && pb) {
        if (strcmp(pa->name, pb->name) != 0)
            return false;

        ecc_reqmerge(pa, pb, a);

        pa = pa->next;
        pb = pb->next;
    }

    return (pa == NULL && pb == NULL);
}

static void free_one_value(value* p)
{
    grib_context_free(ctx, p->name);
    grib_context_free(ctx, p);
}

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

/* Convert the first part of the string 'p' to a number (x) and set n to its length. */
/* Return the rest of the string */
static const char* parse1(const char* p, int* x, int* n)
{
    *x = *n = 0;
    while (*p && isdigit(*p)) {
        (*x) *= 10;
        (*x) += *p - '0';
        (*n)++;
        p++;
    }
    return p;
}

static bool is_number(const char* name)
{
    const char* p = name;
    int x, n;

    if (p == 0 || *p == 0)
        return false;

    if (*p == '-')
        p++;
    else if (*p == '+')
        p++;

    p = parse1(p, &x, &n);
    if (n == 0 && *p != '.')
        return false;

    if (*p == '.') {
        p++;
        p = parse1(p, &x, &n);
    }

    if (*p == 'e' || *p == 'E') {
        p++;
        if (*p == '-')
            p++;
        else if (*p == '+')
            p++;
        p = parse1(p, &x, &n);
        if (n == 0)
            return false;
    }

    return *p == 0 ? true : false;
}
static parameter* new_parameter(char* name, value* v)
{
    parameter* p = (parameter*)calloc(1, sizeof(parameter));
    ECCODES_ASSERT(p);
    p->name   = grib_context_strdup(ctx, name);
    p->values = v;
    return p;
}

static void put_value(request* r, const char* parname, const char* valname, bool append, bool unique, bool ascending)
{
    parameter* p;
    value* v;

    if (!r)
        return;

    if ((p = find_parameter(r, parname)) != NULL) {
        if (append) {
            value *a = p->values, *b = NULL, *c = NULL;
            while (a) {
                b = a;
                if (unique) {
                    if (is_number(a->name) && is_number(valname)) {
                        if (atof(a->name) == atof(valname))
                            return;
                    }
                    else if (strcmp(a->name, valname) == 0)
                        return;
                }

                if (ascending) {
                    if (is_number(a->name)) {
                        if (atof(valname) < atof(a->name))
                            break;
                    }
                    else if (strcmp(valname, a->name) < 0)
                        break;
                }
                c = b;
                a = a->next;
            }
            v = new_value(grib_context_strdup(ctx, valname));
            if (ascending) {
                if (c) {
                    if (b && b != c)
                        v->next = b;
                    c->next = v;
                }
                else {
                    if (a)
                        v->next = a;
                    p->values = v;
                }
            }
            else {
                if (b)
                    b->next = v;
                else
                    p->values = v;
            }
            /* p->count++; */
            p->count = 0;
        }
        else {
            if (p->values) {
                free_all_values(p->values->next);
                p->values->next = NULL;
                /* p->count = 1; */
                p->count = 0;
                if (strcmp(p->values->name, valname) == 0)
                    return;
                else {
                    grib_context_free(ctx, p->values->name);
                    p->values->name = grib_context_strdup(ctx, valname);
                }
            }
            else {
                v         = new_value(grib_context_strdup(ctx, valname));
                p->values = v;
                /* p->count = 1; */
                p->count = 0;
            }
        }
    }
    else {
        parameter* q = NULL;
        parameter* s = r->params;
        v            = new_value(grib_context_strdup(ctx, valname));
        p            = new_parameter(grib_context_strdup(ctx, parname), v);
        while (s) {
            q = s;
            s = s->next;
        }
        if (q)
            q->next = p;
        else
            r->params = p;
    }
}

static void add_value(request* r, const char* parname, const char* fmt, ...)
{
    char buffer[1024];
    va_list list;

    va_start(list, fmt);
    vsnprintf(buffer, sizeof(buffer), fmt, list);
    va_end(list);

    put_value(r, parname, buffer, true, false, false);
}

static void ecc_reqmerge2(request* a, const request* b)
{
    const parameter* pb = b->params;

    while (pb) {
        parameter* pa = find_parameter(a, pb->name);

        if (pa == NULL) {
            value* v = pb->values;
            while (v) {
                put_value(a, pb->name, v->name, true, true, false);
                v = v->next;
            }
        }
        else {
            ecc_reqmerge(pa, pb, a);
        }

        pb = pb->next;
    }
}

static void reqmerge(request* a, const request* b)
{
    if (a && b) {
        if (!ecc_reqmerge1(a, b))
            ecc_reqmerge2(a, b);
    }
}

static void save_name(FILE* f, const char* name, int n)
{
    int i = 0, cnt = 0;

    if (name == NULL)
        name = "(null)";

    cnt = fprintf(f, "%s", name);

    for (i = cnt; i < n; i++)
        putc(' ', f);
}

static void save_one_value(FILE* f, value* r)
{
    save_name(f, r->name, 0);
}

static void save_all_values(FILE* f, value* r)
{
    while (r) {
        save_one_value(f, r);
        if (r->next)
            putc('/', f);
        r = r->next;
    }
}

static void save_all_parameters(FILE* f, parameter* r)
{
    while (r) {
        if (r->values) {
            fprintf(f, ",\n    ");
            save_name(f, r->name, 10);
            fprintf(f, " = ");
            save_all_values(f, r->values);
        }

        r = r->next;
    }
    putc('\n', f);
}

static void save_one_request(FILE* f, const request* r)
{
    if (r) {
        save_name(f, r->name, 0);
        save_all_parameters(f, r->params);
        putc('\n', f);
    }
}

static void save_all_requests(FILE* f, const request* r)
{
    while (r) {
        save_one_request(f, r);
        r = r->next;
    }
}

/* Not used for the moment
static void print_one_request(const request *r)
{
    save_one_request(stdout, r);
}
 */

static void print_all_requests(const request* r)
{
    save_all_requests(stdout, r);
}

static void free_one_parameter(parameter* p)
{
    grib_context_free(ctx, p->name);
    free_all_values(p->values);
    /*free_all_requests(p->interface);*/
    grib_context_free(ctx, p);
}

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

static void free_one_request(request* r)
{
    grib_context_free(ctx, r->name);
    free_all_parameters(r->params);
    grib_context_free(ctx, r);
}

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

static void set_value(request* r, const char* parname, const char* fmt, ...)
{
    char buffer[10240];
    va_list list;

    va_start(list, fmt);
    vsnprintf(buffer, sizeof(buffer), fmt, list);
    va_end(list);

    put_value(r, parname, buffer, false, false, false);
}

static err handle_to_request(request* r, grib_handle* g)
{
    grib_keys_iterator* ks;
    char name[256];
    char value[256];
    size_t len = sizeof(value);
    int e      = 0;

    if (!g)
        return -1;

    /* printf("------------\n"); */
    ks = grib_keys_iterator_new(g, GRIB_KEYS_ITERATOR_ALL_KEYS, "mars");

    while (grib_keys_iterator_next(ks)) {
        strcpy(name, grib_keys_iterator_get_name(ks));

        if ((e = grib_keys_iterator_get_string(ks, value, &len)) != GRIB_SUCCESS)
            grib_context_log(ctx, GRIB_LOG_ERROR, "Cannot get %s as string %d (%s)", name, e, grib_get_error_message(e));

        set_value(r, name, "%s", value);
        len = sizeof(value);
    }

    strcpy(name, "stepUnits");
    if ((e = grib_get_string(g, name, value, &len)) == GRIB_SUCCESS) {
        set_value(r, name, "%s", value);
    }
    else {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Cannot get %s as string (%s)", name, grib_get_error_message(e));
    }

    /*
     ECCODES_ASSERT(grib_get_long(g, "validityDate", &l ) == 0);
     set_value(r, "validityDate", "%ld", l);

     ECCODES_ASSERT(grib_get_long(g, "validityTime", &l ) == 0);
     set_value(r, "validityTime", "%ld", l);
     */

    len = sizeof(value);
    if (grib_get_string(g, "cfVarName", name, &len) == 0) {
        if (strcmp(name, "unknown") != 0) {
            set_value(r, "param", "%s", name);
        }
        else {
            len = sizeof(value);
            if (grib_get_string(g, "shortName", name, &len) == 0) {
                set_value(r, "param", "%s", name);
            }
        }
    }

    len = sizeof(value);
    if (grib_get_string(g, "name", name, &len) == 0) {
        if (strcmp(name, "unknown") != 0) {
            set_value(r, "_long_name", "%s", name);
        }
    }

    len = sizeof(value);
    if (grib_get_string(g, "units", name, &len) == 0) {
        if (strcmp(name, "unknown") != 0) {
            set_value(r, "_units", "%s", name);
        }
    }

    len = sizeof(value);
    if (grib_get_string(g, "cfName", name, &len) == 0) {
        if (strcmp(name, "unknown") != 0) {
            set_value(r, "_cf_name", "%s", name);
        }
    }

    grib_keys_iterator_delete(ks);
    return e;
}

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

typedef bool (*namecmp)(const char*, const char*);

typedef struct hypercube
{
    request* cube;
    request* r;
    request* iterator;
    char* set;
    int count;
    int size;
    int max;
    int* index_cache;
    int index_cache_size;
    namecmp* compare;
} hypercube;

typedef struct axis_t
{
    const char* name;
    namecmp compare;
} axis_t;

/* This should be c++ ... */

typedef enum field_state
{
    unknown,
    packed_mem,
    packed_file,
    expand_mem
} field_state;

typedef struct
{
    int refcnt;
    request* r;
} field_request;

/* One field .. */

typedef struct field
{
    int refcnt;

    field_state shape;

    grib_handle* handle;
    double* values;
    size_t value_count;

    /* if on file */

    file_offset offset;
    size_t length;
    grib_file* file;

    /* missing fields/values */

    /*bool is_missing;*/ /* field is missing */
    bool has_bitmap;     /* field has missing values (= bitmap) */

    field_request* r;

} field;

typedef struct fieldset
{
    int refcnt;

    /* if fields */

    int max;
    int count;

    field** fields;

} fieldset;

/*
 #define MISSING_VALUE(n)      ((n) == missing_value)
 #define MISSING_FIELD(f)      ((f)->missing)
 #define FIELD_HAS_BITMAP(f)   ((f)->bitmap)

 #define FASTNEW(type)         (type*)calloc(1, sizeof(type))
 #define grib_context_free(ctx,x)           grib_context_free(ctx,x)
 */

static field* get_field(fieldset* v, int n, field_state shape);
static hypercube* new_hypercube_from_mars_request(const request* r);
static void release_field(field* g);
static int count_axis(const hypercube* h);
static const char* get_axis(const hypercube* h, int pos);
static const char* get_axis(const hypercube* h, int pos);
static int cube_order(const hypercube* h, const request* r);
static void free_hypercube(hypercube* h);
static int ecc_cube_position(const hypercube* h, const request* r, bool remove_holes);

static value* clone_one_value(const value* p)
{
    value* q = (value*)calloc(1, sizeof(value));
    ECCODES_ASSERT(q);
    q->next = NULL;
    q->name = grib_context_strdup(ctx, p->name);
    return q;
}

static 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;
}

static parameter* clone_one_parameter(const parameter* p)
{
    parameter* q = (parameter*)calloc(1, sizeof(parameter));
    ECCODES_ASSERT(q);
    q->next   = NULL;
    q->name   = grib_context_strdup(ctx, p->name);
    q->values = clone_all_values(p->values);
    return q;
}

static 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;
}

static request* clone_one_request(const request* r)
{
    if (r) {
        request* p = (request*)calloc(1, sizeof(request));
        ECCODES_ASSERT(p);
        p->name = grib_context_strdup(ctx, r->name);

        p->params = clone_all_parameters(r->params);
        p->next   = NULL;
        return p;
    }
    return NULL;
}

static request* new_request(const char* name, parameter* p)
{
    request* r = (request*)calloc(1, sizeof(request));
    ECCODES_ASSERT(r);
    r->name   = grib_context_strdup(ctx, name);
    r->params = p;
    return r;
}

static request* empty_request(const char* name)
{
    return new_request(name ? name : "", NULL);
}

static field_request* new_field_request(request* r)
{
    field_request* g = (field_request*)grib_context_malloc_clear(ctx, sizeof(field_request));
    g->r             = clone_one_request(r);
    return g;
}

static void free_field_request(field_request* g)
{
    if (!g)
        return;
    g->refcnt--;
    if (g->refcnt <= 0) {
        free_all_requests(g->r);
        grib_context_free(ctx, g);
    }
}

static void free_field(field* g)
{
    if (!g)
        return;
    g->refcnt--;
    if (g->refcnt <= 0) {
        /*free_gribfile(g->file);*/
        free_field_request(g->r);
        if (g->values)
            grib_context_free(ctx, g->values);
        grib_handle_delete(g->handle);
        grib_context_free(ctx, g);
    }
}

static void free_fieldset(fieldset* v)
{
    if (!v)
        return;
    v->refcnt--;
    if (v->refcnt <= 0) {
        int i;
        grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: free_fieldset (%d fields) : ", v->count);

        for (i = 0; i < v->count; i++)
            free_field(v->fields[i]);

        grib_context_free(ctx, v->fields);
        grib_context_free(ctx, v);
    }
}

static field* new_field()
{
    return (field*)grib_context_malloc_clear(ctx, sizeof(field));
}

#define INIT_SIZE 1024
static void grow_fieldset(fieldset* v, int n)
{
    int m = v->count;
    int x = v->max;

    if (n < v->count)
        return;

    v->count = n;

    while (v->count >= v->max)
        if (v->max < INIT_SIZE)
            v->max = INIT_SIZE;
        else
            v->max += v->max / 2 + 1;

    if (v->max != x) {
        int i;
        if (v->fields == NULL) {
            v->fields = (field**)grib_context_malloc(ctx, sizeof(field*) * v->max);
            ECCODES_ASSERT(v->fields);
        }
        else {
            field** f = (field**)grib_context_malloc(ctx, sizeof(field*) * v->max);
            ECCODES_ASSERT(f);
            for (i = 0; i < m; i++)
                f[i] = v->fields[i];
            grib_context_free(ctx, v->fields);
            v->fields = f;
        }

        for (i = m; i < v->max; i++)
            v->fields[i] = NULL;
    }
}

static fieldset* new_fieldset(int n)
{
    fieldset* f = (fieldset*)calloc(1, sizeof(fieldset));
    ECCODES_ASSERT(f);
    grow_fieldset(f, n);
    return f;
}

static field* read_field(grib_file* file, file_offset pos, long length)
{
    field* g = new_field();

    g->file   = file;
    g->offset = pos;
    g->length = length;
    g->shape  = packed_file;

    return g;
}

static err set_field(fieldset* v, field* g, int pos)
{
    if (pos >= 0) {
        field* h;
        grow_fieldset(v, pos + 1);
        h              = v->fields[pos];
        v->fields[pos] = g;
        g->refcnt++;
        if (h)
            free_field(h);
    }
    return 0;
}

static void count_parval(parameter* p)
{
    int n    = 0;
    value* v = p->values;

    while (v) {
        n++;
        v = v->next;
    }

    p->count = n;
}

static int count_values(const request* r, const char* parname)
{
    parameter* p = find_parameter(r, parname);

    if (p == NULL)
        return 0;
    if (p->count)
        return p->count;

    count_parval(p);

    return p->count;
}
static const char* get_value(const request* r, const char* parname, int nth)
{
    parameter* p = find_parameter(r, parname);
    value* v;
    int i = 0;

    if (p == NULL)
        return NULL;

    if (!p->count)
        count_values(r, parname);

    v = p->values;

    while (v) {
        if (nth == i++)
            return v->name;
        v = v->next;
    }

    return NULL;
}

static err to_packed_mem(field* g)
{
    if (g->shape == packed_mem)
        return 0;

    if (g->shape == expand_mem) {
        if (g->values)
            grib_context_free(ctx, g->values);
        g->values      = NULL;
        g->value_count = 0;
        g->shape       = packed_mem;

        return 0;
    }

    if (g->shape == packed_file) {
    }
    return 0;
}

static err to_expand_mem(field* g)
{
    err e = 0;

    ECCODES_ASSERT(g);
    if (g->shape == expand_mem)
        return 0;

    if (g->shape == packed_file) {
        const void* dummy = NULL;

        grib_file* file = grib_file_open(g->file->name, "r", &e);
        if (!file || !file->handle) {
            grib_context_log(ctx, GRIB_LOG_ERROR | GRIB_LOG_PERROR, "%s", g->file->name);
            return -1;
        }

        fseeko(file->handle, g->offset, SEEK_SET);

        g->handle = grib_handle_new_from_file(ctx, file->handle, &e);
        ECCODES_ASSERT(g->handle);

        if (g->handle)
            grib_get_message(g->handle, &dummy, &g->length);

        grib_file_close(file->name, 0, &e);
        if (!g->handle)
            return -1;

        if (g->values)
            grib_context_free(ctx, g->values);
        g->values = NULL;
    }

    if (g->values == NULL) {
        size_t count = 0;
        long bitmap  = 0;

        if ((e = grib_get_size(g->handle, "values", &g->value_count))) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get number of values: %s", grib_get_error_message(e));
            return e;
        }

        count = g->value_count;

        if ((e = grib_set_double(g->handle, "missingValue", global_missing_value))) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot set missingValue: %s", grib_get_error_message(e));
            return e;
        }

        g->values = (double*)grib_context_malloc(ctx, sizeof(double) * g->value_count);
        if ((e = grib_get_double_array(g->handle, "values", g->values, &count))) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot decode values: %s", grib_get_error_message(e));
            return e;
        }

        if (count != g->value_count)
            grib_context_log(ctx, GRIB_LOG_FATAL, "ecCodes: value count mismatch %ld %ld", count, g->value_count);

        if ((e = grib_get_long(g->handle, "missingValuesPresent", &bitmap))) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get missingValuesPresent: %s", grib_get_error_message(e));
            return e;
        }

        g->has_bitmap = (bitmap != 0);

#ifdef COMEBACK
        set g->missing
#endif
    }

    g->shape = expand_mem;

    return e;
}

static void set_field_state(field* g, field_state shape)
{
    switch (shape) {
        case expand_mem:
            to_expand_mem(g);
            break;

        case packed_mem:
            to_packed_mem(g);
            break;

        case packed_file:
            release_field(g);
            break;

        default:
            grib_context_log(ctx, GRIB_LOG_FATAL, "Internal error %s %d", __FILE__, __LINE__);
            break;
    }
}

static field* get_field(fieldset* v, int n, field_state shape)
{
    field* g = v->fields[n];
    set_field_state(g, shape);
    return g;
}

static void release_field(field* g)
{
    if (g->file) {
        if (g->values)
            grib_context_free(ctx, g->values);
        g->values = NULL;
        g->shape  = packed_file;
        grib_handle_delete(g->handle);
        g->handle = NULL;
    }
}

static request* field_to_request(field* f)
{
    if (f->r == 0) {
        field_state state = f->shape;
        request* r        = empty_request(
#ifdef COMEBACK
            ((f->ksec1 == NULL) || (f->ksec1[2] != mars.computeflg)) ? "GRIB" : "COMPUTED");
#else
            "GRIB");
#endif

        set_field_state(f, packed_mem);
        handle_to_request(r, f->handle);
        set_field_state(f, state);

        f->r = new_field_request(r);
        free_all_requests(r);
    }
    return f->r->r;
}

static request* fieldset_to_request(fieldset* fs)
{
    int i;
    request* r = empty_request("GRIB");

    if (!fs) {
        free_one_request(r);
        return 0;
    }

    for (i = 0; i < fs->count; i++) {
        request* s = field_to_request(fs->fields[i]);
        reqmerge(r, s);
    }

    return r;
}

/*===============================================================================*/
/* hypercube from mars client */
/*===============================================================================*/

static bool eq_string(const char*, const char*);
static bool eq_integer(const char*, const char*);
static bool eq_range(const char*, const char*);
static bool eq_param(const char*, const char*);
static bool eq_coord(const char*, const char*);
static bool eq_date(const char*, const char*);
static bool eq_time(const char*, const char*);
static bool eq_null(const char*, const char*);

static axis_t global_axis[] = {

    /* From dhsbase.c 'check_grib' */
    {
        "class",
        eq_string,
    },
    {
        "type",
        eq_string,
    },
    {
        "stream",
        eq_string,
    },
    {
        "levtype",
        eq_string,
    },

    {
        "origin",
        eq_string,
    },
    {
        "product",
        eq_string,
    },
    {
        "section",
        eq_string,
    },
    {
        "method",
        eq_integer,
    },
    {
        "system",
        eq_integer,
    },

    /* testing */
    /* {"repres",      eq_null, }, */

    /* from field order */
    {
        "date",
        eq_date,
    },
    {
        "refdate",
        eq_date,
    },
    {
        "hdate",
        eq_date,
    },
    {
        "time",
        eq_time,
    },

    {
        "reference",
        eq_range,
    },
    {
        "step",
        eq_range,
    },
    {
        "fcmonth",
        eq_integer,
    },
    {
        "fcperiod",
        eq_range,
    },

    {
        "leadtime",
        eq_range,
    },
    {
        "opttime",
        eq_range,
    },

    {
        "expver",
        eq_string,
    },
    {
        "domain",
        eq_string,
    },

    {
        "diagnostic",
        eq_integer,
    },
    {
        "iteration",
        eq_integer,
    },

    {
        "quantile",
        eq_range,
    },
    {
        "number",
        eq_integer,
    },

    {
        "levelist",
        eq_coord,
    },
    {
        "latitude",
        eq_coord,
    },
    {
        "longitude",
        eq_coord,
    },
    {
        "range",
        eq_range,
    },

    {
        "param",
        eq_param,
    },

    {
        "ident",
        eq_integer,
    },
    {
        "obstype",
        eq_integer,
    },
    {
        "instrument",
        eq_integer,
    },

    {
        "frequency",
        eq_integer,
    },
    {
        "direction",
        eq_integer,
    },

    {
        "channel",
        eq_integer,
    },
};

static int axisindex(const char* name)
{
    size_t i = 0;
    for (i = 0; i < NUMBER(global_axis); i++) {
        if (STR_EQUAL(name, global_axis[i].name)){
            return (int)i;
        }
    }
    return -1;
}

static namecmp comparator(const char* name)
{
    static char* dontcompare = NULL;
    static bool first     = 1;
    int i                    = 0;

    if (first) {
        dontcompare = getenv("MARS_DONT_CHECK");
        first       = false;
    }

    if (dontcompare != NULL) {
        if (STR_EQUAL(dontcompare, name))
            return eq_null;
    }

    if ((i = axisindex(name)) != -1)
        return global_axis[i].compare;
    grib_context_log(ctx, GRIB_LOG_ERROR, "No comparator for %s", name);
    return eq_string;
}

/********************/
/* index accessors  */
/********************/

static int count_index(const hypercube* h)
{
    int i = 0, n = 0;

    for (i = 0; i < h->size; ++i)
        n += h->set[i];
    return n;
}

static int count_holes(const hypercube* h, int cnt)
{
    int i = 0, n = 0;

    for (i = 0; i < cnt; ++i)
        n += h->set[i];

    return (n == cnt) ? 0 : (cnt - n);
}

static void reset_index(hypercube* h, int v)
{
    memset(h->set, v, h->size);
}

static void set_index(hypercube* h, int index, int value)
{
    if (index < 0 || index >= h->count) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error, bad hypercube index %d", index);
        exit(1);
    }

    if (index >= h->max) {
        int old = h->max;
        while (index >= h->max)
            h->max += 4096;

        h->set = h->set ? (char*)grib_context_realloc(ctx, h->set, h->max) : (char*)grib_context_malloc(ctx, h->max);
        ECCODES_ASSERT(h->set);
        memset(h->set + old, 0, h->max - old);
    }

    if (index >= h->size)
        h->size = index + 1;

    h->set[index] = value;
}

/**************************/
/* End of index accessors */
/**************************/

/*******************/
/* axis accessors  */
/*******************/

static int count_axis(const hypercube* h)
{
    if (h && h->cube)
        return count_values(h->cube, "axis");

    return -1;
}

static const char* get_axis(const hypercube* h, int pos)
{
    const char* axis = NULL;
    if (pos < count_axis(h)) {
        axis = get_value(h->cube, "axis", pos);
    }
    return axis;
}

static void add_axis(hypercube* h, const char* axis)
{
    add_value(h->cube, "axis", "%s", axis);
}

static void unset_value(request* r, const char* parname)
{
    parameter *p, *q = NULL;
    if (!r)
        return;

    p = r->params;
    while (p) {
        if (STR_EQUAL(parname, p->name)) {
            if (q)
                q->next = p->next;
            else
                r->params = p->next;
            free_one_parameter(p);
            return;
        }
        q = p;
        p = p->next;
    }
}

static void reset_axis(hypercube* h)
{
    unset_value(h->cube, "axis");
}

static void valcpy(request* a, request* b, char* aname, char* bname)
{
    parameter* p;
    if (a && b && (p = find_parameter(b, bname))) {
        bool z = false;
        value* v  = p->values;
        while (v) {
            put_value(a, aname, v->name, z, false, false);
            z = true;
            v = v->next;
        }
    }
}

static void cube_values(hypercube* h, const char* p)
{
    valcpy(h->cube, h->r, (char*)p, (char*)p);
}

static int count_dimensions(const hypercube*, const char*);

static int set_axis(hypercube* h)
{
    int i     = 0;
    int count = (h && h->r) ? 1 : -1;

    reset_axis(h);
    for (i = (NUMBER(global_axis) - 1); i >= 0; --i) {
        int n = count_dimensions(h, global_axis[i].name);
        if (n > 1) {
            add_axis(h, global_axis[i].name);
            cube_values(h, global_axis[i].name);
            count *= n;
        }
    }

    return count;
}

/*************************/
/* End of axis accessors */
/*************************/

/*******************/
/* Cube dimensions */
/*******************/

static int count_dimensions(const hypercube* h, const char* axis)
{
    int dims = -1;
    if (h && h->r)
        dims = count_values(h->r, axis);
    return dims;
}

/**************************/
/* End of cube dimensions */
/**************************/

/**************************/
/* Auxiliary functions    */
/**************************/

static int count_hypercube(const request* r)
{
    int count = 1;
    size_t i = 0;
    for (i = 0; i < NUMBER(global_axis); ++i) {
        int c = count_values(r, global_axis[i].name);
        count *= c ? c : 1;
    }

    return count;
}

static int cube_order(const hypercube* h, const request* r)
{
    return ecc_cube_position(h, r, true);
}

static int cube_position(const hypercube* h, const request* r)
{
    return ecc_cube_position(h, r, false);
}

static void reserve_index_cache(hypercube* h, int size)
{
    if (size <= 0)
        return;

    if (h->index_cache != 0)
        grib_context_free(ctx, h->index_cache);
    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: Allocating hypercube index_cache: %d entries", size);
    h->index_cache = (int*)calloc(size, sizeof(int));
    ECCODES_ASSERT(h->index_cache);
    h->index_cache_size = size;
}

static int ecc_cube_position(const hypercube* h, const request* r, bool remove_holes)
{
    request* cube = h->cube;
    int c         = count_axis(h);
    int index     = 0;
    int i         = 0;
    int n         = 1;
    int ok        = 0;

    if (h->index_cache == 0 || h->index_cache_size != c)
        reserve_index_cache((hypercube*)h, c);

    for (i = 0; i < c; ++i) {
        const char* axis = get_axis(h, i);
        const char* v    = get_value(r, axis, 0);
        const char* w    = NULL;
        int dims         = count_dimensions(h, axis);
        int k            = 0;
        int count        = count_values(cube, axis);
        int last         = h->index_cache[i];

        for (k = 0; k < count; k++) {
            int j = (k + last) % count;
            w     = get_value(cube, axis, j);
            if (h->compare ? h->compare[i](w, v) : (strcmp(w, v) == 0)) {
                index += j * n;
                n *= dims;
                ok++;
                ((hypercube*)h)->index_cache[i] = j;
                break;
            }
            else
                grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: ecc_cube_position, %s, %s != %s [%scompare function available]", axis, w, v, h->compare ? "" : "no ");
        }
    }

    if (remove_holes) {
        int holes = 0;
        if (count_index(h) != h->size)
            holes = count_holes(h, index);
        index -= holes;
    }

    return (ok == c) ? index : -1;
}

static void cube_indexes(
    const hypercube* h, request* r,
    char** times_array, size_t times_array_size,
    int* indexes, int size)
{
    request* cube = h->cube;
    int c         = count_axis(h);
    int i         = 0;
    int index     = 0;
    int n         = 1;

    if (size < c) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error in cube_indexes. size=%d < axis=%d", size, c);
    }

    if (h->index_cache == 0 || h->index_cache_size != c)
        reserve_index_cache((hypercube*)h, c);

    for (i = 0; i < c; ++i) {
        const char* axis           = get_axis(h, i);
        const char* v              = get_value(r, axis, 0);
        const char* w              = NULL;
        int dims                   = count_dimensions(h, axis);
        int j                      = 0;
        int k                      = 0;
        int count                  = count_values(cube, axis);
        int last                   = h->index_cache[i];
        const bool is_time_axis = (STR_EQUAL(axis, "time"));
        if (is_time_axis) {
            ECCODES_ASSERT(times_array);
            ECCODES_ASSERT(times_array_size == count);
        }

        for (k = 0; k < count; k++) {
            j = (k + last) % count;
            if (is_time_axis) {
                /* GRIB-792: use fast lookup */
                ECCODES_ASSERT(j >= 0 && j < times_array_size);
                w = times_array[j];
                /* For testing:
                 * ECCODES_ASSERT( strcmp(w, get_value(cube, axis, j))==0 );
                 * */
            }
            else {
                /* slow access method */
                w = get_value(cube, axis, j);
            }

            if (h->compare ? h->compare[i](w, v) : (w == v)) {
                index += j * n;
                n *= dims;
                ((hypercube*)h)->index_cache[i] = j;
                break;
            }
        }
        indexes[i] = j;
    }
    (void)index;
}

/*********************************/
/* End of Auxiliary functions    */
/*********************************/

static hypercube* new_hypercube(const request* r)
{
    hypercube* h = (hypercube*)calloc(1, sizeof(hypercube));
    int total = 0, count = 0;
    size_t n        = 0;
    const char* val = 0;
    ECCODES_ASSERT(h);
    h->r    = clone_one_request(r);
    h->cube = empty_request("CUBE");

    h->count = total = count_hypercube(r);
    count            = set_axis(h);

    h->compare = 0;

    if ((total != count) || (count == 0)) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error while computing hypercube fields");
        grib_context_log(ctx, GRIB_LOG_ERROR, "Number of fields in request %d", total);
        grib_context_log(ctx, GRIB_LOG_ERROR, "Number of fields in hypercube %d", count);
    }
    set_index(h, count - 1, 1);
    memset(h->set, 1, count);

    /* This is expensive, but makes the iterator with only
       those parameters found as axis */
    h->iterator = empty_request(0);
    for (n = 0; n < NUMBER(global_axis); ++n)
        if ((val = get_value(h->r, global_axis[n].name, 0)) != NULL)
            set_value(h->iterator, global_axis[n].name, val);

    return h;
}

static void print_hypercube(const hypercube* h)
{
    ECCODES_ASSERT(h);
    if (!h) return;
    print_all_requests(h->r);
    print_all_requests(h->cube);
    grib_context_log(ctx, GRIB_LOG_INFO, "%d active out of %d fields described\n", count_index(h), h->size);
}

static void free_hypercube(hypercube* h)
{
    free_all_requests(h->r);
    free_all_requests(h->cube);
    free_all_requests(h->iterator);
    grib_context_free(ctx, h->index_cache);
    grib_context_free(ctx, h->compare);
    grib_context_free(ctx, h->set);
    grib_context_free(ctx, h);
}

struct stuff_1
{
    hypercube* c;
    request* r;
};

static void reqcb_1(const request* r, int count, axis_t* names, char* vals[], void* data)
{
    struct stuff_1* s = (struct stuff_1*)data;
    int i;

    for (i = 0; i < count; i++)
        if (vals[i])
            set_value(s->r, names[i].name, vals[i]);

    set_index(s->c, cube_position(s->c, s->r), 1);
}

typedef void (*loopproc)(const request*, int, axis_t*, char**, void*);

static void names_loop(const request* r, loopproc proc, void* data);

static hypercube* new_hypercube_from_mars_request(const request* r)
{
    int i;
    int n;

    struct stuff_1 s;

//     const request *lang = mars_language_from_request(r);
//     int count = 0;
//     count = init_axis(lang);
//     grib_context_log(ctx,GRIB_LOG_DEBUG,"cube %s",r->kind);
//     /* print_all_requests(mars_language_from_request(r)); */
//     grib_context_log(ctx,GRIB_LOG_INFO,"NUMBER(axis): %d, number axisnew: %d",NUMBER(axis),count);

    s.c = new_hypercube(r);
    s.r = clone_one_request(r);

    reset_index(s.c, 0);
    names_loop(r, reqcb_1, &s);

    free_one_request(s.r);

    /* add single parameters */

    for (i = 0; i < NUMBER(global_axis); i++) {
        int m = count_values(r, global_axis[i].name);
        if (m == 1) {
            add_value(s.c->cube, "axis", global_axis[i].name);
            set_value(s.c->cube, global_axis[i].name, get_value(r, global_axis[i].name, 0));
        }
    }

    n = count_values(s.c->cube, "axis");
    if (n) {
        s.c->compare = (namecmp*)calloc(n, sizeof(namecmp));
        ECCODES_ASSERT(s.c->compare);
    }

    for (i = 0; i < n; i++)
        s.c->compare[i] = comparator(get_value(s.c->cube, "axis", i));

    return s.c;
}

/* This one doesn't have single parameters in CUBE */
static hypercube* new_simple_hypercube_from_mars_request(const request* r)
{
    int i;
    int n;

    struct stuff_1 s;
    s.c = new_hypercube(r);
    s.r = clone_one_request(r);

    reset_index(s.c, 0);

    names_loop(r, reqcb_1, &s);

    free_one_request(s.r);
    n = count_values(s.c->cube, "axis");
    if (n) {
        s.c->compare = (namecmp*)calloc(n, sizeof(namecmp));
        ECCODES_ASSERT(s.c->compare);
    }

    for (i = 0; i < n; i++)
        s.c->compare[i] = comparator(get_value(s.c->cube, "axis", i));

    return s.c;
}

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

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

/* TODO:
 - Print usage in log file
 - consider FCMONTH and Climatology
 - Build logic to create validationtime when only one of DATE or TIME or STEP have multiple values:
 for example: date=-1, time=12, step=24/48
 - parametrise the type of data for each axis (function fill_netcdf_dimensions & define_netcdf_dimensions)
 Now, all are INT but TIME could be float
 - allow user specified scale_factor
 - insert 'user input request'
 - ORIGIN
 - Monthly means seem not to ignore STEP (data server, era40, eg retrieve 39/142)
 */

typedef struct ncatt
{
    char name[1024];
    char* long_name;
    char* units;
    char* short_name;
    char* standard_name;
    request* metadata;
    nc_type nctype;
} ncatt_t;

typedef struct filter_type
{
    fieldset* fset;
    hypercube* filter;
    int count;
    double scale_factor;
    double add_offset;
    double missing;
    bool bitmap;
    ncatt_t att;
    request* filter_request;
    bool scale;
} dataset_t;

/*
 * typedef struct ncfile {
 *     dataset_t *filters;
 *     int ncid;
 * } ncfile_t;
*/

typedef struct ncoptions
{
    bool usevalidtime; /* Whether to use valid TIME only or not */
    bool auto_refdate; /* Automatic Reference Date */
    long refdate;         /* Reference date */
    const char* version;

    char* title;
    char* history;
    char* unlimited;
    bool checkvalidtime;
    request* mars_description;
    bool mmeans;      /* Whether this dataset is Monthly Means */
    bool climatology; /* Whether this dataset is climatology */
    bool shuffle;
    long deflate;
} ncoptions_t;

ncoptions_t setup;

#define NC_TYPES 7
struct nc_types_values
{
    double nc_type_max;
    double nc_type_min;
    double nc_type_missing;
} nc_type_values[NC_TYPES] = {
    /* In some occasions, SHRT_MIN-2 for the minimum value, makes ncview display
 missing values for -32766, while NC_FILL_SHORT=-32767, and SHRT_MIN=-32768 */
    { 0, 0, 0 },                                  /* NC_NAT,   'Not A Type' (c.f. NaN) */
    { 0x7f, NC_FILL_BYTE + 1, NC_FILL_BYTE },     /* NC_BYTE,   signed 1 byte integer */
    { 0xff, NC_FILL_CHAR + 1, NC_FILL_CHAR },     /* NC_CHAR,   ISO/ASCII character */
    { 0x7fff, NC_FILL_SHORT + 1, NC_FILL_SHORT }, /* NC_SHORT,  signed 2 byte integer */
    { 0x7ffffff, NC_FILL_INT + 1, NC_FILL_INT },  /* NC_INT,    signed 4 byte integer */
    { FLT_MAX, -FLT_MAX, NC_FILL_FLOAT },         /* NC_FLOAT,  single precision floating point number */
    { DBL_MAX, -DBL_MAX, NC_FILL_DOUBLE },        /* NC_DOUBLE, double precision floating point number */
};

static long fcmonth2days(long date, long months)
{
    long julianfrom = grib_date_to_julian(date);
    long years      = (long)(months / 12);
    long leap       = (long)(years / 4) - (long)(years / 100);
    long to         = years * 365 + (months % 12) * 32 + 1 + leap; /* FCMONTH can't be > 28 */
    long julianto   = julianfrom + to;
    long days       = 0;

    long dd       = date % 100;
    long dateto   = grib_julian_to_date(julianto);
    long nextdate = (dateto / 100) * 100 + dd;

    julianto = grib_date_to_julian(nextdate);
    days     = julianto - julianfrom;

    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: date: %ld + %ld months = %ld days", date, months, days);

    return days;
}

static long request_fields(request* r)
{
    long cnt     = 1;
    parameter* p = r->params;
    while (p) {
        if (p->name[0] != '_') {
            cnt *= count_values(r, p->name);
        }
        p = p->next;
    }
    return cnt;
}

/* Transform MARS FCMONTHs into number of months from base date.
 For example, FCMONTH=1 is current month == 0 */
static void fcmonth2nbmonths(request* r)
{
    long n = count_values(r, "fcmonth");

    if (n == 0)
        return;
    n = atol(get_value(r, "fcmonth", 0));
    set_value(r, "fcmonth", "%ld", n - 1);
}

static long monthnumber(const char* m)
{
    const char* months[] = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
    int i                = 0;

    while (i < 12)
        if (strcmp(m, months[i++]) == 0)
            return i;
    grib_context_log(ctx, GRIB_LOG_ERROR, "Error. Translation for MONTH='%s' not found", m);
    return -1;
}

static int check_stepUnits(const char* step_units_str)
{
    /* Only hours, minutes and seconds supported */
    if (STR_EQUAL(step_units_str, "h") ||
        STR_EQUAL(step_units_str, "m") ||
        STR_EQUAL(step_units_str, "s")) {
        return GRIB_SUCCESS;
    }
    return GRIB_WRONG_STEP_UNIT;
}

/* The argument represents 1 field */
static void validation_time(request* r)
{
    long date        = 0;
    long time        = 0;
    double step      = 0;
    long fcmonthdays = 0;
    long fcmonth     = 0;
    double v;
    long julian            = 0;
    const char* step_units = NULL;

    long nstep    = count_values(r, "step");
    long ndate    = count_values(r, "date");
    long ntime    = count_values(r, "time");
    long nfcmonth = count_values(r, "fcmonth");

    static long julianrefdate = 0;

    if (nstep > 1 || ndate > 1 || ntime > 1 || nfcmonth > 1) {
        if (nstep > 1)
            grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error. Field has more than 1 STEP");
        if (ntime > 1)
            grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error. Field has more than 1 TIME");
        if (ndate > 1)
            grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error. Field has more than 1 DATE");
        if (nfcmonth > 1)
            grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error. Field has more than 1 FCMONTH");
        print_all_requests(r);
        exit(1);
    }

    if (nstep)
        step = atof(get_value(r, "step", 0));

    if (ndate) {
        const char* p         = get_value(r, "date", 0);
        const char* marsClass = get_value(r, "class", 0);
        if (eq_string(marsClass, "s2")) {
            /* S2S Data. See GRIB-699 and GRIB-762 */
            const char* hdate = get_value(r, "hdate", 0);
            grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: S2S Data");
            if (hdate) {
                p = hdate; /* This is a hindcast */
            }
        }
        if (is_number(p))
            date = atol(p);
        else {
            long second = 0;
            bool isjul, date_ok;
            date_ok = parsedate(p, &julian, &second, &isjul);
            if (!date_ok)
                grib_context_log(ctx, GRIB_LOG_ERROR, "Failed to parse date: '%s'", p);
            date = grib_julian_to_date(julian);
        }

        /* Climatology */
        if (strlen(p) == 3) {
            if (setup.usevalidtime)
                grib_context_log(ctx, GRIB_LOG_ERROR, "Climatology data. Setting usevalidtime=OFF");
            setup.auto_refdate = false;
            setup.usevalidtime = false;
            setup.climatology  = true;
        }
    }

    if (ntime)
        time = atol(get_value(r, "time", 0));

    if (nfcmonth) {
        fcmonth = atol(get_value(r, "fcmonth", 0));
        /* FCMONTH needs base DATE */
        if (date == 0) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error: FCMONTH needs DATE");
            exit(1);
        }
        fcmonthdays = fcmonth2days(date, fcmonth);
    }

    julian     = grib_date_to_julian(date);
    step_units = get_value(r, "stepUnits", 0);
    if (step_units) {
        if (check_stepUnits(step_units) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR,
                             "Cannot convert stepUnits of '%s'. Only hours, minutes and seconds supported.", step_units);
        }
        if (STR_EQUAL("m", step_units)) {
            step /= 60;
        }
        else if (STR_EQUAL("s", step_units)) {
            step /= 3600;
        }
    }
    v = julian * 24.0 + fcmonthdays * 24.0 + time / 100.0 + step * 1.0;
    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: date=%ld, julian=%ld, fcmonthdays=%ld, time=%ld, step=%g, validation=%.3f", date, julian, fcmonthdays, time, step, v);
    set_value(r, "_validation", "%lf", v);
    set_value(r, "_juliandate", "%ld", julian);

    if (!julianrefdate)
        julianrefdate = grib_date_to_julian(setup.refdate);

    set_value(r, "_validationtime", "%lf", v - julianrefdate * 24.0);

    /* Remove minutes from TIME */
    if (ntime)
        set_value(r, "time", "%.1lf", time / 100.0);
}

static void free_nc_options()
{
    if (setup.title)
        grib_context_free(ctx, setup.title);
    if (setup.history)
        grib_context_free(ctx, setup.history);
    if (setup.unlimited)
        grib_context_free(ctx, setup.unlimited);
    if (setup.mars_description)
        free_all_requests(setup.mars_description);
}

static void get_nc_options(const request* user_r)
{
    const char* checkvalidtime_env = NULL;
    const char* validtime          = get_value(user_r, "usevalidtime", 0);
    const char* refdate            = get_value(user_r, "referencedate", 0);
    const char* shuffle            = get_value(user_r, "shuffle", 0);
    const char* deflate            = get_value(user_r, "deflate", 0);

    const char* title     = get_value(user_r, "title", 0);
    const char* history   = get_value(user_r, "history", 0);
    const char* unlimited = get_value(user_r, "unlimited", 0);

    setup.shuffle      = shuffle ? (strcmp(shuffle, "true") == 0) : false;
    setup.deflate      = deflate ? ((strcmp(deflate, "none") == 0) ? -1 : atol(deflate)) : -1;
    setup.usevalidtime = validtime ? (strcmp(validtime, "true") == 0) : false;
    setup.refdate      = refdate ? atol(refdate) : 19000101;
    setup.auto_refdate = refdate ? (strcmp(get_value(user_r, "referencedate", 0), "AUTOMATIC") == 0) : false;

    setup.title     = title ? grib_context_strdup(ctx, (title)) : NULL;
    setup.history   = history ? grib_context_strdup(ctx, (history)) : NULL;
    setup.unlimited = unlimited ? grib_context_strdup(ctx, ((unlimited))) : NULL;

    setup.checkvalidtime   = true;
    checkvalidtime_env     = getenv("GRIB_TO_NETCDF_CHECKVALIDTIME");
    if (checkvalidtime_env) {
        const long v = atol(checkvalidtime_env);
        if (v == 0) setup.checkvalidtime = false;
    }
    setup.mars_description = empty_request("MARS");
}

static nc_type translate_nctype(const char* name)
{
    if (!name)
        return NC_SHORT;

    if (STR_EQUAL(name, "NC_BYTE"))
        return NC_BYTE;

    if (STR_EQUAL(name, "NC_SHORT"))
        return NC_SHORT;

    if (STR_EQUAL(name, "NC_INT"))
        return NC_INT;

    if (STR_EQUAL(name, "NC_FLOAT"))
        return NC_FLOAT;

    if (STR_EQUAL(name, "NC_DOUBLE"))
        return NC_DOUBLE;

    grib_context_log(ctx, GRIB_LOG_ERROR, "Unknown netCDF type '%s'. Using NC_SHORT", name);
    return NC_SHORT;
}

static void check_err(const char* function, const int stat, const int line)
{
    if (stat != NC_NOERR) {
        /* (void) fprintf(stderr, "line %d of %s: %s\n", line, tool_name, nc_strerror(stat)); */
        (void)fprintf(stderr, "\n%s ERROR: line %d, %s: %s\n",
                      tool_name, line, function, nc_strerror(stat));
        if (stat == NC_EVARSIZE) {
            (void)fprintf(stderr,
                          "\nCannot create netCDF classic format, dataset is too large!\n"
                          "Try splitting the input GRIB(s).\n");
        }
        exit(1);
    }
}

#define DIM_ID 1
static int set_dimension(int ncid, const char* name, int n, int xtype, const char* units, const char* long_name)
{
    int var_id = 0;
    int stat   = 0;
    int dim_id = DIM_ID;
    int dim_vec[DIM_ID];

    if ( setup.unlimited && (STR_EQUAL(name, setup.unlimited)) )
        n = NC_UNLIMITED;

    stat = nc_def_dim(ncid, name, n, &dim_id);
    check_err("nc_def_dim", stat, __LINE__);

    dim_vec[0] = dim_id;
    stat       = nc_def_var(ncid, name, (nc_type)xtype, 1, dim_vec, &var_id);
    check_err("nc_def_var", stat, __LINE__);

    if (units != NULL) {
        stat = nc_put_att_text(ncid, var_id, "units", strlen(units), units);
        check_err("nc_put_att_text", stat, __LINE__);
    }

    if (long_name != NULL) {
        stat = nc_put_att_text(ncid, var_id, "long_name", strlen(long_name), long_name);
        check_err("nc_put_att_text", stat, __LINE__);
    }

    return var_id;
}

static int check_grid(field* f)
{
    err e = 0;
    char grid_type[80];
    size_t size = sizeof(grid_type);

    if ((e = grib_get_string(f->handle, "typeOfGrid", grid_type, &size)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get typeOfGrid %s", grib_get_error_message(e));
        return e;
    }

    if ( !STR_EQUAL(grid_type, "regular_ll") && !STR_EQUAL(grid_type, "regular_gg") ) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Grid type = %s", grid_type);
        grib_context_log(ctx, GRIB_LOG_ERROR, "First GRIB is not on a regular lat/lon grid or on a regular Gaussian grid. Exiting.\n");
        return GRIB_GEOCALCULUS_PROBLEM;
    }
    return e;
}

static int get_num_latitudes_longitudes(grib_handle* h, size_t* nlats, size_t* nlons)
{
    err e = 0;
    char grid_type[80];
    size_t size = sizeof(grid_type);

    if (grib_get_string(h, "typeOfGrid", grid_type, &size) == GRIB_SUCCESS &&
        strcmp(grid_type, "regular_ll") == 0) {
        /* Special shortcut for regular lat/on grids */
        long n;
        ECCODES_ASSERT(!grib_is_missing(h, "Ni", &e));
        if ((e = grib_get_long(h, "Ni", &n)) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Ni: %s", grib_get_error_message(e));
            return e;
        }
        *nlons = n;

        if ((e = grib_get_long(h, "Nj", &n)) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Nj: %s", grib_get_error_message(e));
            return e;
        }
        *nlats = n;
    }
    else {
        if ((e = grib_get_size(h, "distinctLatitudes", nlats)) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get distinctLatitudes: %s", grib_get_error_message(e));
            return e;
        }
        if ((e = grib_get_size(h, "distinctLongitudes", nlons)) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get distinctLongitudes: %s", grib_get_error_message(e));
            return e;
        }
    }
    return e;
}

static int def_latlon(int ncid, fieldset* fs)
{
    int n        = 0;
    size_t nlats = 0, nlons = 0;
    err e = 0;

    field* g = get_field(fs, 0, expand_mem);

    DEBUG_ASSERT(check_grid(g) == GRIB_SUCCESS);

    if ((e = get_num_latitudes_longitudes(g->handle, &nlats, &nlons)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get lat/lon info: %s", grib_get_error_message(e));
        return e;
    }

    /* Define longitude */
    n = (int)nlons;
    set_dimension(ncid, "longitude", n, NC_FLOAT, "degrees_east", "longitude");

    /* Define latitude */
    n = nlats;
    set_dimension(ncid, "latitude", n, NC_FLOAT, "degrees_north", "latitude");

    /* g->purge_header = true; */
    release_field(g);

    return e;
}

static int put_latlon(int ncid, fieldset* fs)
{
    int var_id = 0;
    size_t i   = 0;
    size_t n   = 0;
    int stat   = 0;
    err e      = 0;

    field* g = get_field(fs, 0, expand_mem);

    double* dvalues = NULL;
    float* fvalues  = NULL;
    long nv         = 0;

    size_t ni;
    size_t nj;

#if 0
    /* Get info in degrees */
    if((e = grib_get_double(g->handle, "iDirectionIncrementInDegrees", &ew_stride)) != GRIB_SUCCESS)
    {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get iDirectionIncrementInDegrees: %s", grib_get_error_message(e));
        return e;
    }

    if((e = grib_get_double(g->handle, "jDirectionIncrementInDegrees", &ns_stride)) != GRIB_SUCCESS)
    {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get jDirectionIncrementInDegrees %s", grib_get_error_message(e));
        return e;
    }

    /* Define longitude */
    if((e = grib_get_long(g->handle, "Ni", &ni)) != GRIB_SUCCESS)
    {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Ni %s", grib_get_error_message(e));
        return e;
    }
    /* Define latitude */
    if((e = grib_get_long(g->handle, "Nj", &nj)) != GRIB_SUCCESS)
    {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Nj %s", grib_get_error_message(e));
        return e;
    }

#endif
    if ((e = get_num_latitudes_longitudes(g->handle, &nj, &ni)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: put_latlon: cannot get distinctLatitudes: %s", grib_get_error_message(e));
        return e;
    }

    /* Compute max. # values and allocate */
    nv = ni;
    if (nv < nj)
        nv = nj;

    fvalues = (float*)grib_context_malloc(ctx, sizeof(float) * nv);
    dvalues = (double*)grib_context_malloc(ctx, sizeof(double) * nv);

    /* longitude */
    n    = ni;
    stat = nc_inq_varid(ncid, "longitude", &var_id);
    check_err("nc_inq_varid", stat, __LINE__);
    if ((e = grib_get_double_array(g->handle, "distinctLongitudes", dvalues, &n)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: put_latlon: cannot get distinctLongitudes: %s", grib_get_error_message(e));
        return e;
    }
    if (n != ni) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Number of distinctLongitudes is not the same as Ni (%zu!=%zu)",n,ni);
        return GRIB_GEOCALCULUS_PROBLEM;
    }

    for (i = 0; i < n; i++) {
        fvalues[i] = dvalues[i];
    }

    stat = nc_put_var_float(ncid, var_id, fvalues);
    check_err("nc_put_var_float", stat, __LINE__);

    /* latitude */
    n    = nj;
    stat = nc_inq_varid(ncid, "latitude", &var_id);
    check_err("nc_inq_varid", stat, __LINE__);
    if ((e = grib_get_double_array(g->handle, "distinctLatitudes", dvalues, &n)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: put_latlon: cannot get distinctLatitudes: %s", grib_get_error_message(e));
        return e;
    }
    if (n != nj) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Number of distinctLatitudes is not the same as Nj (%zu!=%zu)",n,nj);
        return GRIB_GEOCALCULUS_PROBLEM;
    }

    for (i = 0; i < n; i++) {
        fvalues[i] = dvalues[i];
    }
    stat = nc_put_var_float(ncid, var_id, fvalues);
    check_err("nc_put_var_float", stat, __LINE__);

    /* g->purge_header = true; */
    release_field(g);
    grib_context_free(ctx, fvalues);
    grib_context_free(ctx, dvalues);

    return e;
}

static int compute_scale(dataset_t* subset)
{
    double max            = -DBL_MAX;
    double min            = DBL_MAX;
    double median         = 0;
    long i                = 0;
    size_t j              = 0;
    int64_t scaled_max    = 0;
    int64_t scaled_min    = 0;
    int64_t scaled_median = 0;
    double ao = 0.0, sf = 0.0;
    double x;

    char test_scaled_max    = 0;
    char test_scaled_min    = 0;
    char test_scaled_median = 0;

    err e = 0;

    fieldset* fs = subset->fset;
    int idx      = subset->att.nctype;

    for (i = 0; i < fs->count; i++) {
        field* g = get_field(fs, i, expand_mem);
        size_t len;
        static double* vals    = NULL;
        static size_t vals_len = 0;

        if ((e = grib_get_size(g->handle, "values", &len)) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get size of values: %s", grib_get_error_message(e));
            return e;
        }

        if (len > vals_len) {
            if (vals)
                grib_context_free(ctx, vals);
            vals     = (double*)grib_context_malloc(ctx, sizeof(double) * len);
            vals_len = len;
        }
        if ((e = grib_get_double_array(g->handle, "values", vals, &len)) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get values: %s", grib_get_error_message(e));
            return e;
        }

        if (g->has_bitmap) {
            subset->bitmap = true;
            for (j = 0; j < len; ++j) {
                if (vals && vals[j] != global_missing_value) {
                    if (vals[j] > max) max = vals[j];
                    if (vals[j] < min) min = vals[j];
                }
            }
        }
        else {
            for (j = 0; j < len; ++j) {
                if (vals) {
                    if (vals[j] > max) max = vals[j];
                    if (vals[j] < min) min = vals[j];
                }
            }
        }
        /* g->purge_header = true; */
        release_field(g);
    }

    median = (max + min) / 2.0;

    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: max_int: %lf, min_int: %lf", nc_type_values[idx].nc_type_max, nc_type_values[idx].nc_type_min);

    sf = (double)((max - min) / (double)(nc_type_values[idx].nc_type_max - nc_type_values[idx].nc_type_min));
    ao = ((max + min) - sf * (nc_type_values[idx].nc_type_min + nc_type_values[idx].nc_type_max)) / 2;

    if (min == max) {
        sf = 1.0; /* Prevent divide by zero later. Constant field grib has max == min */
    }

    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: idx is: %d", idx);
    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: max: %lf, min: %lf, median: %lf, scale factor: %lf, add_offset: %lf", max, min, median, sf, ao);

    x = ((median - ao));
    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: x=%lf", x);
    x /= sf;
    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: x=%lf", x);

    scaled_max    = rint((max - ao) / sf);
    scaled_min    = rint((min - ao) / sf);
    scaled_median = rint((median - ao) / sf);

    if (scaled_max > nc_type_values[idx].nc_type_max) {
        grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: scaled_max (=%lld) > nc_type_max (=%lf). Set sf to 1.0",
                         (long long)scaled_max, nc_type_values[idx].nc_type_max);
        sf = 1.0; /* ECC-685 */
    }

    test_scaled_max    = (char)scaled_max;
    test_scaled_min    = (char)scaled_min;
    test_scaled_median = (char)scaled_median;

    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: scaled_max: %lld, scaled_min: %lld, scaled_median: %lld, x: %lf",
                     (long long)scaled_max, (long long)scaled_min, (long long)scaled_median, x);

    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: test_scaled_max: %x, test_scaled_min: %x, test_scaled_median: %x",
                     test_scaled_max, test_scaled_min, test_scaled_median);

    max    = scaled_max * sf + ao;
    min    = scaled_min * sf + ao;
    median = scaled_median * sf + ao;

    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: max: %lf, min: %lf, median: %lf", max, min, median);

    subset->scale_factor = sf;
    subset->add_offset   = ao;

    return 0;
}

static int nc_put_att_type(int ncid, int varid, const char* name, nc_type nctype, int n, double value)
{
    int r = 0;
    switch (nctype) {
        case NC_BYTE: {
            unsigned char val_uchar = (unsigned char)value;
            r                       = nc_put_att_uchar(ncid, varid, name, nctype, n, &val_uchar);
            break;
        }
        case NC_SHORT: {
            short int val_short = (short int)value;
            r                   = nc_put_att_short(ncid, varid, name, nctype, n, &val_short);
            break;
        }
        case NC_INT: {
            int val_int = (int)value;
            r           = nc_put_att_int(ncid, varid, name, nctype, n, &val_int);
            break;
        }
        case NC_FLOAT: {
            float val_flt = (float)value;
            r             = nc_put_att_float(ncid, varid, name, nctype, n, &val_flt);
            break;
        }
        case NC_DOUBLE: {
            double val_dbl = (double)value;
            r              = nc_put_att_double(ncid, varid, name, nctype, n, &val_dbl);
            break;
        }
        default:
            grib_context_log(ctx, GRIB_LOG_ERROR, "nc_put_att_type(...): Unknown netCDF type '%d'", nctype);
            break;
    }
    return r;
}

static int nc_put_vara_type(int ncid, int varid, const size_t start[], const size_t count[], void* valuesp, nc_type nctype)
{
    int r = 0;
    switch (nctype) {
        case NC_BYTE:
            r = nc_put_vara_uchar(ncid, varid, start, count, (unsigned char*)valuesp);
            break;
        case NC_SHORT:
            r = nc_put_vara_short(ncid, varid, start, count, (short int*)valuesp);
            break;
        case NC_INT:
            r = nc_put_vara_int(ncid, varid, start, count, (int*)valuesp);
            break;
        case NC_FLOAT:
            r = nc_put_vara_float(ncid, varid, start, count, (float*)valuesp);
            break;
        case NC_DOUBLE:
            r = nc_put_vara_double(ncid, varid, start, count, (double*)valuesp);
            break;
        default:
            grib_context_log(ctx, GRIB_LOG_ERROR, "nc_put_vara_type(...): Unknown netCDF type '%d'", nctype);
            break;
    }
    return r;
}

static void scale_bitmap(double* vals, long n, void* data, dataset_t* subset)
{
    int i          = 0;
    nc_type nctype = subset->att.nctype;

    /* if(!subset->bitmap) {
        grib_context_log(ctx,GRIB_LOG_DEBUG,"No scale of bitmap required");
        return;
     } */
    if (n > 0 && !vals) {
        ECCODES_ASSERT(!"scale_bitmap: n > 0 but vals == NULL");
        return;
    }

    switch (nctype) {
        case NC_BYTE: {
            unsigned char* vscaled = (unsigned char*)data;
            for (i = 0; i < n; ++i) {
                if (vals[i] == global_missing_value) {
                    vscaled[i] = (unsigned char)subset->missing;
                }
            }
            break;
        }

        case NC_SHORT: {
            short int* vscaled = (short int*)data;
            for (i = 0; i < n; ++i) {
                if (vals[i] == global_missing_value) {
                    vscaled[i] = (short int)subset->missing;
                }
            }
            break;
        }

        case NC_INT: {
            int* vscaled = (int*)data;
            for (i = 0; i < n; ++i) {
                if (vals[i] == global_missing_value) {
                    vscaled[i] = (int)subset->missing;
                }
            }
            break;
        }

        case NC_FLOAT: {
            float* vscaled = (float*)data;
            for (i = 0; i < n; ++i) {
                if (vals[i] == global_missing_value) {
                    vscaled[i] = (float)subset->missing;
                }
            }
            break;
        }

        case NC_DOUBLE: {
            double* vscaled = (double*)data;
            for (i = 0; i < n; ++i) {
                if (vals[i] == global_missing_value) {
                    vscaled[i] = (double)subset->missing;
                }
            }
            break;
        }

        default:
            grib_context_log(ctx, GRIB_LOG_ERROR, "scale(...): Unknown netCDF type %d", nctype);
            break;
    }
}

static void scale(double* vals, long n, void* data, dataset_t* g)
{
    int i               = 0;
    nc_type nctype      = g->att.nctype;
    double scale_factor = g->scale_factor;
    double add_offset   = g->add_offset;

    /*
    if(!subset->scale)
    {
     grib_context_log(ctx,GRIB_LOG_DEBUG,"No scale required");
     return;
    }
    */
    DEBUG_ASSERT(vals);
    DEBUG_ASSERT(n > 0);
    if (!vals) return;

    switch (nctype) {
        case NC_BYTE: {
            unsigned char* vscaled = (unsigned char*)data;
            for (i = 0; i < n; ++i) {
                if (!g->bitmap || (vals[i] != global_missing_value)) {
                    double d = rint((vals[i] - add_offset) / scale_factor);
                    if (!(d >= nc_type_values[nctype].nc_type_min && d <= nc_type_values[nctype].nc_type_max)) {
                        grib_context_log(ctx, GRIB_LOG_ERROR, "Scaling for type NC_BYTE failed");
                        return;
                    }
                    vscaled[i] = d;
                }
            }
            break;
        }

        case NC_SHORT: {
            short int* vscaled = (short int*)data;
            for (i = 0; i < n; ++i) {
                if (!g->bitmap || (vals[i] != global_missing_value)) {
                    double d = 0;
                    ECCODES_ASSERT(scale_factor > 0);
                    d = rint((vals[i] - add_offset) / scale_factor);
                    if (!(d >= nc_type_values[nctype].nc_type_min && d <= nc_type_values[nctype].nc_type_max)) {
                        grib_context_log(ctx, GRIB_LOG_ERROR, "Scaling for type NC_SHORT failed");
                        return;
                    }
                    vscaled[i] = d;
                }
            }
            break;
        }

        case NC_INT: {
            int* vscaled = (int*)data;
            for (i = 0; i < n; ++i) {
                if (!g->bitmap || (vals[i] != global_missing_value)) {
                    double d = rint((vals[i] - add_offset) / scale_factor);
                    if (!(d >= nc_type_values[nctype].nc_type_min && d <= nc_type_values[nctype].nc_type_max)) {
                        grib_context_log(ctx, GRIB_LOG_ERROR, "Scaling for type NC_INT failed");
                        return;
                    }
                    vscaled[i] = d;
                }
            }
            break;
        }

        case NC_FLOAT: {
            float* vscaled = (float*)data;
            for (i = 0; i < n; ++i) {
                if (!g->bitmap || (vals[i] != global_missing_value)) {
                    double d = vals[i];
                    if (!(d >= nc_type_values[nctype].nc_type_min && d <= nc_type_values[nctype].nc_type_max)) {
                        grib_context_log(ctx, GRIB_LOG_ERROR, "Scaling for type NC_FLOAT failed");
                        return;
                    }
                    vscaled[i] = d;
                }
            }
            break;
        }

        case NC_DOUBLE: {
            double* vscaled = (double*)data;
            for (i = 0; i < n; ++i) {
                if (!g->bitmap || (vals[i] != global_missing_value)) {
                    double d = vals[i];
                    if(!(d >= nc_type_values[nctype].nc_type_min && d <= nc_type_values[nctype].nc_type_max)) {
                        grib_context_log(ctx, GRIB_LOG_ERROR, "Scaling for type NC_DOUBLE failed");
                        return;
                    }
                    vscaled[i] = d;
                }
            }
            break;
        }

        default:
            grib_context_log(ctx, GRIB_LOG_ERROR, "scale(...): Unknown netCDF type %d", nctype);
            break;
    }
}

/* Return array of strings which are the "time" values */
static char** create_times_array(const request* cube, size_t* size)
{
    char** result         = NULL;
    const char* time_axis = "time"; /* special case */
    parameter* the_param  = find_parameter(cube, time_axis);
    *size                 = 0;
    if (the_param) {
        size_t i = 0, num_values = 0;
        value* va = NULL;

        if (!the_param->count)
            count_values(cube, time_axis);

        /* Go thru all values to count how many there are */
        va = the_param->values;
        while (va) {
            ++num_values;
            va = va->next;
        }
        /* Create and populate array */
        result = (char**)grib_context_malloc(ctx, sizeof(char*) * num_values);
        va     = the_param->values;
        while (va) {
            result[i++] = va->name;
            va          = va->next;
        }
        *size = num_values;
    }
    return result;
}

static int put_data(hypercube* h, int ncid, const char* name, dataset_t* subset)
{
    int i      = 0;
    int stat   = 0;
    int dataid = 0;
    int naxis  = count_axis(h);
    size_t start[NC_MAX_DIMS];
    size_t count[NC_MAX_DIMS];
    char** times_array      = NULL;
    size_t times_array_size = 0;
    fieldset* fs            = subset->fset;
    field* f                = get_field(fs, 0, expand_mem);

    void* vscaled       = NULL;
    size_t vscaled_length = 0;

    long ni;
    long nj;
    err e = 0;

    /* Define longitude */
    if ((e = grib_get_long(f->handle, "Ni", &ni)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Ni: %s", grib_get_error_message(e));
        return e;
    }
    /* Define latitude */
    if ((e = grib_get_long(f->handle, "Nj", &nj)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Nj: %s", grib_get_error_message(e));
        return e;
    }

    /* Start filling dimensions at first value */
    for (i = 0; i < 2 + naxis; ++i)
        start[i] = 0;

    /* Count dimensions per axis */
    for (i = 0; i < naxis; ++i)
        count[naxis - i - 1] = 1;

    count[naxis]     = nj; /* latitude */
    count[naxis + 1] = ni; /* longitude */

    /* f->purge_header = true; */
    release_field(f);

    stat = nc_inq_varid(ncid, name, &dataid);
    check_err("nc_inq_varid", stat, __LINE__);

    /* GRIB-792: Build fast array storing values for the "time" axis. */
    /* This is for performance reasons */
    times_array = create_times_array(h->cube, &times_array_size);

    for (i = 0; i < fs->count; i++) {
        field* g = get_field(fs, i, expand_mem);
        size_t len;
        static double* vals    = NULL;
        static size_t vals_len = 0;
        bool missing        = 0;

        request* r;
        int j = 0;
        int idx[1024];
        int idxsize = 1024;

        if ((e = grib_get_size(g->handle, "values", &len)) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get size of values: %s", grib_get_error_message(e));
            return e;
        }

        if (len > vals_len) {
            if (vals)
                grib_context_free(ctx, vals);
            vals     = (double*)grib_context_malloc(ctx, sizeof(double) * len);
            vals_len = len;
        }
        if ((e = grib_get_double_array(g->handle, "values", vals, &len)) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get values: %s", grib_get_error_message(e));
            return e;
        }

        // bool missing = (g->ksec4[0] < 0); /* If negative number of values, field is missing */

        r = field_to_request(g);
        if (!missing) {
            /* Reserved the maximum memory needed */
            /* This should only be done once, as all fields have the same geometry */
            if ((vscaled_length == 0) || (vscaled_length < sizeof(double) * len)) {
                if (vscaled)
                    grib_context_free(ctx, vscaled);
                vscaled        = (void*)grib_context_malloc(ctx, sizeof(double) * len);
                vscaled_length = sizeof(double) * len;
            }

            scale(vals, len, vscaled, subset);
            if (subset->bitmap)
                scale_bitmap(vals, len, vscaled, subset);

            if ((e = grib_get_long(g->handle, "Ni", &ni)) != GRIB_SUCCESS) {
                grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Ni: %s", grib_get_error_message(e));
                return e;
            }
            /* Define latitude */
            if ((e = grib_get_long(g->handle, "Nj", &nj)) != GRIB_SUCCESS) {
                grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Nj: %s", grib_get_error_message(e));
                return e;
            }

            if (nj != count[naxis] || ni != count[naxis + 1]) {
                grib_context_log(ctx, GRIB_LOG_ERROR, "GRIB message %d has different resolution\n", i + 1);
                grib_context_log(ctx, GRIB_LOG_ERROR, "lat=%ld, long=%ld instead of lat=%ld, long=%ld\n", nj, ni, count[naxis], count[naxis + 1]);
                exit(1);
            }

            cube_indexes(h, r, times_array, times_array_size, idx, idxsize);
            for (j = 0; j < naxis; ++j)
                start[naxis - j - 1] = idx[j];

            grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: Put data from field %d", i);

            stat = nc_put_vara_type(ncid, dataid, start, count, vscaled, subset->att.nctype);
            check_err("nc_put_vara_type", stat, __LINE__);
        }

        /* g->purge_header = true; */
        release_field(g);
    }

    grib_context_free(ctx, vscaled);
    grib_context_free(ctx, times_array);
    return 0;
}

static void set_always_a_time(hypercube* h, request* data_r)
{
    if (setup.usevalidtime && count_values(data_r, "time") == 1) {
        set_value(h->cube, "time", "%.2lf", atof(get_value(data_r, "_validationtime", 0)));
        add_value(h->cube, "axis", "time");

        {
            int i = 0;
            int n = count_values(h->cube, "axis");
            if (n) {
                h->compare = (namecmp*)calloc(n, sizeof(namecmp));
                ECCODES_ASSERT(h->compare);
            }

            for (i = 0; i < n; i++)
                h->compare[i] = comparator(get_value(h->cube, "axis", i));
        }
    }
}

static int define_netcdf_dimensions(hypercube* h, fieldset* fs, int ncid, dataset_t* subsets, int subsetcnt, const request* data_r)
{
    const request* cube = h->cube;
    int naxis           = count_axis(h);
    int i               = 0;
    int stat            = 0;
    int n               = 0;
    int var_id          = 0; /* Variable ID */
    int dims[1024];

    size_t chunks[NC_MAX_DIMS] = {0,}; /* For chunking */
    err e = 0;

    long ni;
    long nj;

    field* f = get_field(fs, 0, expand_mem);

    if ((e = check_grid(f)) != GRIB_SUCCESS) {
        release_field(f);
        return e;
    }

    if ((e = grib_get_long(f->handle, "Ni", &ni)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Ni: %s", grib_get_error_message(e));
        return e;
    }
    if ((e = grib_get_long(f->handle, "Nj", &nj)) != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "ecCodes: cannot get Nj: %s", grib_get_error_message(e));
        return e;
    }
    release_field(f);

    /* Count dimensions per axis */
    for (i = 0; i < naxis; ++i)
        chunks[naxis - i - 1] = 1;

    chunks[naxis]     = nj; /* latitude */
    chunks[naxis + 1] = ni; /* longitude */

    /* START DEFINITIONS */

    /* Define latitude/longitude dimensions */
    e = def_latlon(ncid, fs);
    if (e != GRIB_SUCCESS)
        return e;

    /* Define netCDF dimensions */
    for (i = 0; i < naxis; ++i) {
        int nctype       = NC_INT;
        const char* axis = get_axis(h, i);
        const char* units      = NULL;
        char u[10240];
        const char* lowaxis = (axis);
        const char* longname = (char*)lowaxis;
        n                   = count_values(cube, axis);

        if (count_values(data_r, "levtype") > 1) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "Cannot handle fields for different levtypes.\n");
            grib_context_log(ctx, GRIB_LOG_ERROR, "Please split input data into different files. Exiting!\n");
            exit(1);
        }
        if (count_values(data_r, "stepUnits") > 1) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "Cannot handle fields for different stepUnits.\n");
            grib_context_log(ctx, GRIB_LOG_ERROR, "Please split input data into different files. Exiting!\n");
            exit(1);
        }

        if (strcmp(axis, "levelist") == 0) {
            const char* levtype = get_value(data_r, "levtype", 0);
            if (levtype) {
                if (strcmp("pl", levtype) == 0) {
                    units    = "millibars";
                    longname = "pressure_level";
                }

                if (strcmp("ml", levtype) == 0) {
                    longname = "model_level_number";
                }
            }

            lowaxis = "level";
        }

        if (strcmp(axis, "number") == 0) {
            longname = "ensemble_member";
        }

        if (strcmp(axis, "date") == 0) {
            snprintf(u, sizeof(u), "days since %ld-%02ld-%02ld 00:00:0.0", setup.refdate / 10000, (setup.refdate % 10000) / 100, (setup.refdate % 100));
            units    = u;
            longname = "Base_date";
            if (setup.climatology) {
                snprintf(u, sizeof(u), "months");
                units = u;
            }
        }

        if (strcmp(axis, "time") == 0) {
            bool onedtime = (count_values(cube, "date") == 0 && count_values(cube, "step") == 0);
            snprintf(u, sizeof(u), "hours since 0000-00-00 00:00:00.0");
            longname = "reference_time";
            if (setup.usevalidtime || onedtime) {
                snprintf(u, sizeof(u), "hours since %ld-%02ld-%02ld 00:00:00.0", setup.refdate / 10000, (setup.refdate % 10000) / 100, (setup.refdate % 100));
                longname = "time";
            }
            if (setup.climatology) {
                snprintf(u, sizeof(u), "hours");
            }
            units = u;
            /* nctype = NC_FLOAT; */
        }

        if (strcmp(axis, "step") == 0) {
            units    = "hours";
            longname = "time_step";
            if (count_values(cube, "date") == 0 && count_values(cube, "time") == 0) {
                const char* d = get_value(data_r, "date", 0);
                const char* t = get_value(data_r, "time", 0);
                long date     = d ? atol(d) : 0;
                long hour     = t ? atol(t) : 0;
                long min      = t ? 60 * (atof(t) - hour) : 0;
                snprintf(u, sizeof(u), "hours since %ld-%02ld-%02ld %02ld:%02ld:00.0", date / 10000, (date % 10000) / 100, (date % 100), hour, min);
                units = u;
            }
        }

        if ((strcmp(axis, "fcmonth") == 0)) {
            const char* date = get_value(data_r, "date", 0);
            char ymd[32]     = "";

            if (date) {
                strncat(ymd, date, 4);
                strcat(ymd, "-");
                strncat(ymd, date + 4, 2);
                strcat(ymd, "-");
                /* udunits is a bit tricky with month being 30.4 days */
                /* ncview doesn't display properly */
                strcat(ymd, "01");
            }
            else {
                snprintf(ymd, sizeof(ymd), "00-00-00");
            }
            snprintf(u, sizeof(u), "months since %s 00:00:00.0", ymd);
            units    = u;
            longname = "time";
        }

        var_id = set_dimension(ncid, lowaxis, n, nctype, units, longname);

        if (strcmp(axis, "time") == 0) {
            if (setup.usevalidtime) {
                const char* cal = "gregorian";

                if (setup.mmeans) {
                    const char* period = "0000-01-00 00:00:00";
                    stat         = nc_put_att_text(ncid, var_id, "avg_period", strlen(period), period);
                    check_err("nc_put_att_text", stat, __LINE__);
                }

                stat = nc_put_att_text(ncid, var_id, "calendar", strlen(cal), cal);
                check_err("nc_put_att_text", stat, __LINE__);
            }
        }
    }

    /* Define data dimension */
    n = 1 + 1 + naxis; /* longitude + latitude + # axis */
    for (i = 0; i < n; ++i)
        dims[i] = n - i - 1;

    for (i = 0; i < subsetcnt; ++i) {
        printf("%s: Defining variable '%s'.\n", tool_name, subsets[i].att.name);

        stat = nc_def_var(ncid, subsets[i].att.name, subsets[i].att.nctype, n, dims, &var_id);
        check_err("nc_def_var", stat, __LINE__);

        if (setup.deflate > -1) {
#ifdef NC_NETCDF4
            stat = nc_def_var_chunking(ncid, var_id, NC_CHUNKED, chunks);
            check_err("nc_def_var_chunking", stat, __LINE__);

            /* Set compression settings for a variable */
            stat = nc_def_var_deflate(ncid, var_id, setup.shuffle, 1, setup.deflate);
            check_err("nc_def_var_deflate", stat, __LINE__);
#else
            (void)chunks;
            grib_context_log(ctx, GRIB_LOG_ERROR, "Deflate option only supported in netCDF4");
#endif
        }
        if (subsets[i].scale) {
            compute_scale(&subsets[i]);
            stat = nc_put_att_double(ncid, var_id, "scale_factor", NC_DOUBLE, 1, &subsets[i].scale_factor);
            check_err("nc_put_att_double", stat, __LINE__);

            stat = nc_put_att_double(ncid, var_id, "add_offset", NC_DOUBLE, 1, &subsets[i].add_offset);
            check_err("nc_put_att_double", stat, __LINE__);
        }

        stat = nc_put_att_type(ncid, var_id, "_FillValue", subsets[i].att.nctype, 1, nc_type_values[subsets[i].att.nctype].nc_type_missing);
        check_err("nc_put_att_type", stat, __LINE__);
        stat = nc_put_att_type(ncid, var_id, "missing_value", subsets[i].att.nctype, 1, nc_type_values[subsets[i].att.nctype].nc_type_missing);
        check_err("nc_put_att_type", stat, __LINE__);

        if (subsets[i].att.units) {
            const char* txt = subsets[i].att.units;
            stat            = nc_put_att_text(ncid, var_id, "units", strlen(txt), txt);
            check_err("nc_put_att_text", stat, __LINE__);
        }

        if (subsets[i].att.long_name) {
            const char* txt = subsets[i].att.long_name;
            stat            = nc_put_att_text(ncid, var_id, "long_name", strlen(txt), txt);
            check_err("nc_put_att_text", stat, __LINE__);
        }

        if (subsets[i].att.short_name) {
            const char* txt = subsets[i].att.short_name;
            stat            = nc_put_att_text(ncid, var_id, "short_name", strlen(txt), txt);
            check_err("nc_put_att_text", stat, __LINE__);
        }

        if (subsets[i].att.standard_name) {
            const char* txt = subsets[i].att.standard_name;
            stat            = nc_put_att_text(ncid, var_id, "standard_name", strlen(txt), txt);
            check_err("nc_put_att_text", stat, __LINE__);
        }

        // if(subsets[i].att.other)
        // {
        //     const char *txt = subsets[i].att.long_name;
        //     stat = nc_put_att_text(ncid, var_id, "other",strlen(txt),txt);
        //     check_err("nc_put_att_text", stat,__LINE__,__FILE__);
        // }

        if (subsets[i].att.metadata) {
            parameter* p = subsets[i].att.metadata->params;

            while (p) {
                const char* txt = p->values->name;
                stat            = nc_put_att_text(ncid, var_id, p->name, strlen(txt), txt);
                check_err("nc_put_att_text", stat, __LINE__);

                p = p->next;
            }
        }
    }

    /* Dimension-less variable for MARS request */
    if ((0)) /* reset when we have proper & fast mars_description */
    {
        /* parameter *p = data_r->params; */
        parameter* p = setup.mars_description->params;
        stat         = nc_def_var(ncid, "MARS", NC_CHAR, 0, 0, &var_id);
        check_err("nc_def_var", stat, __LINE__);

        /* Store request for those parameters with single value */
        while (p) {
            /* if((p->name[0] != '_') && (p->count == 1)) */
            if (p->name[0] != '_') {
                char par[1024];
                char val[1024000] = "";
                snprintf(par, sizeof(par), "%s", (p->name));
#if 0
                value2string(p->values,val);
#else
                snprintf(val, sizeof(val), "%s", (p->values->name));
#endif
                stat = nc_put_att_text(ncid, var_id, par, strlen(val), (val));
                if (stat != NC_NOERR) {
                    printf("Error setting request for %s = %s\n", par, val);
                }
                check_err("nc_put_att_text", stat, __LINE__);
            }
            p = p->next;
        }
    }

    /* Global attributes */
    {
        char timestamp[80];
        time_t now;
        /* char *convention = "MARS;CF"; */
        const char* convention = "CF-1.6";
        char history[10240];
        /* char *institution = "ECMWF Meteorological Archival and Retrieval System"; */

        /* Convention */
        stat = nc_put_att_text(ncid, NC_GLOBAL, "Conventions", strlen(convention), convention);
        check_err("nc_put_att_text", stat, __LINE__);

        /* Use history provided or Timestamp */
        if (setup.history) {
            snprintf(history, sizeof(history), "%s", setup.history);
        }
        else {
            int major    = ECCODES_MAJOR_VERSION;
            int minor    = ECCODES_MINOR_VERSION;
            int revision = ECCODES_REVISION_VERSION;

            time(&now);
            strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S GMT", gmtime(&now));
            snprintf(history, sizeof(history), "%s by grib_to_netcdf-%d.%d.%d: %s", timestamp, major, minor, revision, argvString);
        }
        stat = nc_put_att_text(ncid, NC_GLOBAL, "history", strlen(history), history);
        check_err("nc_put_att_text", stat, __LINE__);

        //stat = nc_put_att_text(ncid, NC_GLOBAL, "source",strlen(setup.source),setup.source);
        //check_err(stat,__LINE__,__FILE__);
        //stat = nc_put_att_text(ncid, NC_GLOBAL, "institution",strlen(institution),institution);
        //check_err(stat,__LINE__,__FILE__);

        if (setup.title) {
            stat = nc_put_att_text(ncid, NC_GLOBAL, "title", strlen(setup.title), setup.title);
            check_err("nc_put_att_text", stat, __LINE__);
        }
    }
    return e;
}

static size_t string_to_unique_number(const char* axis, const char* str)
{
    size_t result = 0;
    if (strcmp(axis, "type") == 0) {
        /* TODO: not ideal but capture the most common MARS types */
        if      (strcmp(str, "an") == 0) return 2;
        else if (strcmp(str, "fc") == 0) return 9;
        else if (strcmp(str, "cf") == 0) return 10;
        else if (strcmp(str, "pf") == 0) return 11;
        else if (strcmp(str, "em") == 0) return 17;
        else if (strcmp(str, "es") == 0) return 18;
        else if (strcmp(str, "ep") == 0) return 30;
        else if (strcmp(str, "4i") == 0) return 33;
        else if (strcmp(str, "4g") == 0) return 8;
        else if (strcmp(str, "ia") == 0) return 3;
        else if (strcmp(str, "efi") == 0) return 27;
    }
    /* Fallback general case: Use hashing */
    result = 5381;
    while (*str) {
        result = 33 * result ^ (unsigned char)*str++;
    }
    return result;
}

static int fill_netcdf_dimensions(hypercube* h, fieldset* fs, int ncid)
{
    const request* cube = h->cube;
    int naxis           = count_axis(h);
    int i               = 0;
    int var_id          = 0;
    int stat            = 0;

    /* Put latitude/longitude values */
    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: Put latitude/longitude values");
    put_latlon(ncid, fs);

    /* Put netCDF axis values */
    grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: Put netcdf axis values");
    for (i = 0; i < naxis; ++i) {
        int j               = 0;
        const char* axis    = get_axis(h, i);
        int n               = count_values(cube, axis);
        int* values         = (int*)grib_context_malloc(ctx, sizeof(int) * n);
        const char* lowaxis = (axis);

        if (!values) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "fill_netcdf_dimensions: cannot allocate %ld bytes", sizeof(int) * n);
            exit(1);
        }

        if (strcmp("levelist", axis) == 0)
            lowaxis = "level";

        if (strcmp("date", axis) == 0) {
            if (setup.climatology)
                for (j = 0; j < n; ++j)
                    values[j] = monthnumber(get_value(cube, axis, j));
            else
                for (j = 0; j < n; ++j)
                    values[j] = grib_date_to_julian(atol(get_value(cube, axis, j))) - grib_date_to_julian(setup.refdate);
        }
        else {
            for (j = 0; j < n; ++j) {
                long lv        = 0;
                const char* sv = get_value(cube, axis, j);
                if (is_number(sv)) {
                    lv = atol(sv); /* Detect error? */
                }
                else {
                    /* ECC-725: Convert string-valued dimension to integer
                     * e.g. mars type or stream */
                    lv = string_to_unique_number(axis, sv);
                }
                values[j] = lv;
            }
        }

        stat = nc_inq_varid(ncid, (lowaxis), &var_id);
        check_err("nc_inq_varid", stat, __LINE__);

        /* if ( strcmp("time", axis) == 0 && setup.unlimited != NULL && strcmp(setup.unlimited, "time") == 0 && setup.usevalidtime) */
        /* GRIB-437, GRIB-625 Special treatment of RECORD (unlimited) dimension */
        /* See "The NetCDF C Interface Guide" Section 6.23 */
        if (setup.unlimited != NULL && strcmp(setup.unlimited, axis) == 0) {
            /* This is tricky. I'm not sure it works when this dimension is not outer dimension */
            size_t start[NC_MAX_DIMS];
            size_t count[NC_MAX_DIMS];
            nc_type dim_type = 0;

            start[0] = 0;
            count[0] = n;

            stat = nc_inq_vartype(ncid, var_id, &dim_type); /* get the type of this dimension */
            check_err("nc_inq_vartype", stat, __LINE__);

            stat = nc_put_vara_type(ncid, var_id, start, count, values, dim_type);
            check_err("nc_put_vara_type", stat, __LINE__);
        }
        else {
            stat = nc_put_var_int(ncid, var_id, values);
            check_err("nc_put_var_int", stat, __LINE__);
        }

        grib_context_free(ctx, values);
    }

    return 0;
}

static void remove_param(request* r, void* data, const char* p)
{
    request* config = (request*)data;
    const char* ignore;
    int i = 0;

    while ((ignore = get_value(config, p, i++)) != NULL) {
        unset_value(r, ignore);
    }
}

static void print_ignored_keys(FILE* f, request* data)
{
    const char* ignore = NULL;
    int i              = 0;
    while ((ignore = get_value(data, "ignore", i)) != NULL) {
        if (i == 0) {
            fprintf(f, "%s: Ignoring key(s): %s", tool_name, ignore);
        }
        else {
            fprintf(f, ", %s", ignore);
        }
        ++i;
    }
    if (i > 0)
        fprintf(f, "\n");
}

#define NO_TABLE -1
#define NO_PARAM 0

static void paramtable(const char* p, long* param, long* table, bool paramIdMode)
{
    const char* q = p;
    int len       = strlen(p);

    *param = atol(p);

    while (p && (*p != '.') && ((p - q) < len))
        ++p;

    if ((*p == '.'))
        *table = atol(++p);

    /* This version is grib_api... It should rely on what grib_api returns,
       either param.table or paramId
     */
    if (paramIdMode) {
        /* Special case for param=228015 => 15.228 */
        if ((*param != NO_PARAM) && (*table == NO_TABLE) && (len == 6)) {
            char tbl[4];
            char par[4];
            p = q;
            strncpy(tbl, p, 3);
            tbl[3] = '\0';
            strncpy(par, p + 3, 3);
            par[3] = '\0';
            *param = atol(par);
            *table = atol(tbl);
        }
    }
}

static void find_nc_attributes(const request* subset_r, const request* user_r, ncatt_t* att, const request* config_r, const request* data_r)
{
    const char* split         = NULL;
    int j                     = 0;
    bool set_param_as_name = true;
    long datatable            = 0; /* = atol(get_value(data_r,"_CODETABLE2",0)); */

    if (count_values(user_r, "split") == 0)
        strcat(att->name, "data");

    while ((split = get_value(user_r, "split", j++)) != NULL) {
        if (strcmp(split, "param") != 0) {
            if (count_values(data_r, split) > 1)
                set_param_as_name = false;
        }
    }

    j = 0;
    while ((split = get_value(user_r, "split", j++)) != NULL) {
        bool found    = false;
        request* cfg     = (request*)config_r;
        bool is_param = strcmp(split, "param") == 0;
        /* Only use this parameter in the name if there is more
           than one value in the original request or if param */
        bool setname = ((count_values(data_r, split) > 1) || (is_param && set_param_as_name));

        while (cfg) {
            const char* cfgname = get_value(cfg, "NAME", 0);
            const char* cfgval  = get_value(cfg, "VALUE", 0);
            const char* dataval = NULL;
            int i               = 0;

            if (strcmp(split, cfgname) == 0) {
                while ((dataval = get_value(subset_r, cfgname, i++)) != NULL) {
                    const char* tablestr = get_value(cfg, "TABLE2", 0);
                    long cfgtable        = (is_param && tablestr != NULL) ? atol(get_value(cfg, "TABLE2", 0)) : -1;

                    long cfgparam  = atol(cfgval);
                    long dataparam = atol(dataval);
                    paramtable(dataval, &dataparam, &datatable, false);

                    /* If it is not param and they're EXACTLY equal or
                       being param, they're the same parameter and table */
                    if ((!is_param && (strcmp(dataval, cfgval) == 0)) || (is_param && (dataparam == cfgparam) && (datatable == cfgtable || (datatable == 0 && (cfgtable == 128))))) {
                        const char* val      = NULL;
                        const char* metafile = NULL;

                        if ((val = get_value(cfg, "accuracy", 0)) != NULL)
                            att->nctype = translate_nctype(val);
                        att->long_name  = grib_context_strdup(ctx, (get_value(cfg, "LONG_NAME", 0)));
                        att->short_name = grib_context_strdup(ctx, (get_value(cfg, "SHORT_NAME", 0)));
                        att->units      = grib_context_strdup(ctx, (get_value(cfg, "UNITS", 0)));

                        /* Check if there is more metadata for this variable */
                        if ((metafile = get_value(cfg, "METADATA", 0)) != NULL) {
                            static const char* metadata_dir = NULL;
                            char metapath[1024];

                            if (!metadata_dir)
                                metadata_dir = getenv("METADATA_DIR");

                            snprintf(metapath, sizeof(metapath), "%s/%s", metadata_dir ? metadata_dir : ".", metafile);
                            att->metadata = 0; /* read_request_file(metapath); */
                        }

                        if (setname) {
                            const char* pname = get_value(cfg, "DATA", 0);
                            if (strlen(att->name))
                                strcat(att->name, "_");
                            strcat(att->name, pname);
                        }
                        found = true;

                        grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: Var. name found: '%s'", att->name);
                    }
                }
            }
            cfg = cfg->next;
        }

        /* We have not found configuration for this attribute */
        if (!found) {
            const char* val = get_value(subset_r, split, 0);
            if (val && (setname)) {
                if (strlen(att->name))
                    strcat(att->name, "_");
                strcat(att->name, val);
            }
        }
    }

    /* NetCDF does not allow variable names to start with a digit */
    if (!isalpha(att->name[0])) {
        char buf[1048];
        const char* val = get_value(subset_r, "param", 0);
        snprintf(buf, sizeof(buf), "%s_%s", (val ? val : "p"), att->name);
        strcpy(att->name, buf);
    }
}

static request* first;
static request* last;

static void reqcb(const request* r, int count, axis_t* names, char* vals[], void* data)
{
    request* w = clone_one_request(r);
    int i;
    /*request **d = (request**)data;*/

    int* n = (int*)data;

    w->order = (*n)++;
    for (i = 0; i < count; i++) {
        if (vals[i])
            put_value(w, names[i].name, vals[i], false, false, false);
    }

    if (first == NULL)
        first = w;
    else
        last->next = w;
    last = w;
}

static bool chk_152(int count, axis_t* names, char* vals[])
{
    return true;
}

static void loop(const request* r, bool ml, int index, int count, axis_t* strings, char* values[], loopproc callback, void* data)
{
    if (index < count) {
        parameter* p = find_parameter(r, strings[index].name);

        (void)count_values(r, strings[index].name); /* force list expansion */

        if (p) {
            value* v = p->values;

            while (v) {
                values[index] = v->name;
                loop(r, ml, index + 1, count, strings, values, callback, data);
                v = v->next;
            }
        }
        else {
            values[index] = NULL;
            loop(r, ml, index + 1, count, strings, values, callback, data);
        }
    }
    else if (!ml || chk_152(count, strings, values))
        callback(r, count, strings, values, data);
}

static void values_loop(const request* r, int count, axis_t* parnames, loopproc callback, void* data)
{
    char** values = (char**)grib_context_malloc(ctx, sizeof(char*) * count);
    const char* p = get_value(r, "levtype", 0);
    bool ml    = (bool)(p && (strcmp(p, "ml") == 0));

    if (ml) {
        p = get_value(r, "expect", 0);
        if (p && atol(p) != 0) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "EXPECT provided, special treatment of LNSP");
            grib_context_log(ctx, GRIB_LOG_ERROR, "and other single level parameters disabled");
            ml = false;
        }
    }

    loop(r, ml, 0, count, parnames, values, callback, data);

    grib_context_free(ctx, values);
}

static void names_loop(const request* r, loopproc proc, void* data)
{
    values_loop(r, NUMBER(global_axis), global_axis, proc, data);
}

static request* unwind_one_request(const request* r)
{
    int n = 0;
    first = last = NULL;
    names_loop(r, reqcb, &n);
    return first;
}

static int split_fieldset(fieldset* fs, request* data_r, dataset_t** subsets, const request* user_r, const request* config_r)
{
    const char* split  = NULL;
    int count          = 1;
    int i              = 0;
    request* s         = NULL;
    request* u         = NULL;
    dataset_t* filters = NULL;
    nc_type nctype     = translate_nctype(get_value(user_r, "accuracy", 0));

    s = empty_request("filter");
    while ((split = get_value(user_r, "split", i++)) != NULL) {
        int cnt = count_values(data_r, split);
        if (cnt) {
            count *= count_values(data_r, split);
            valcpy(s, data_r, (char*)split, (char*)split);
        }
    }

    u = unwind_one_request(s);

    free_all_requests(s);

    filters = (dataset_t*)calloc(count, sizeof(dataset_t));
    ECCODES_ASSERT(filters);

    s = u;
    for (i = 0; i < count; ++i) {
        ECCODES_ASSERT(s);
        filters[i].filter         = new_hypercube_from_mars_request(s);
        filters[i].fset           = new_fieldset(1);
        filters[i].count          = 0;
        filters[i].filter_request = clone_one_request(s);
        filters[i].bitmap         = false;
        /* filters[i].mmeans  = false; */

        s = s->next;
    }

    for (i = 0; i < fs->count; ++i) {
        bool ok = false;
        field* f   = get_field(fs, i, packed_mem);
        request* g = field_to_request(f);
        int j      = 0;

        while (!ok && (j < count)) {
            ok = (cube_order(filters[j].filter, g) != -1);
            if (ok) {
                const char* p;
                set_field(filters[j].fset, f, filters[j].count++);
                filters[j].bitmap |= f->has_bitmap;
                if ((p = get_value(f->r->r, "_units", 0)) != NULL) {
                    filters[j].att.units = grib_context_strdup(ctx, p);
                }
                if ((p = get_value(f->r->r, "_long_name", 0)) != NULL) {
                    filters[j].att.long_name = grib_context_strdup(ctx, p);
                }
                if ((p = get_value(f->r->r, "_cf_name", 0)) != NULL) {
                    filters[j].att.standard_name = grib_context_strdup(ctx, p);
                }
            }
            j++;
        }
        if (!ok) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "Internal error. Lost field %d while splitting fieldset", i);
            print_all_requests(g);
            grib_context_log(ctx, GRIB_LOG_ERROR, "count is %d", count);
            grib_context_log(ctx, GRIB_LOG_ERROR, "First cube is:");
            print_hypercube(filters[0].filter);
            exit(1);
        }
        /* f->purge_header = true; */
        release_field(f);
    }

    for (i = 0; i < count; ++i) {
        filters[i].att.nctype = nctype;
        filters[i].scale      = true;
        filters[i].missing    = nc_type_values[nctype].nc_type_missing;
        find_nc_attributes(filters[i].filter_request, user_r, &(filters[i].att), config_r, data_r);
        grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: filter[%d] found.- Var. name '%s', nctype: %d, found nctype: %d", i, filters[i].att.name, nctype, filters[i].att.nctype);

        if (strlen(filters[i].att.name) == 0) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "Variable name not found");
            exit(1);
        }

        /* Check if we need scaling */
        switch (filters[i].att.nctype) {
            case NC_FLOAT:
            case NC_DOUBLE:
                filters[i].scale = false;
                break;
            default:
                filters[i].scale = true;
                break;
        }
    }

    free_all_requests(u);
    *subsets = filters;
    return count;
}

static void free_subsets(dataset_t* subsets, int count)
{
    int i = 0;

    for (i = 0; i < count; ++i) {
        grib_context_free(ctx, subsets[i].att.units);
        grib_context_free(ctx, subsets[i].att.long_name);
        grib_context_free(ctx, subsets[i].att.short_name);
        grib_context_free(ctx, subsets[i].att.standard_name);
        free_all_requests(subsets[i].att.metadata);
        free_hypercube(subsets[i].filter);
        free_fieldset(subsets[i].fset);
        free_all_requests(subsets[i].filter_request);
    }
    grib_context_free(ctx, subsets);
}

/* Return the number of the given month or -1 if it fails to match */
static int convert_month(const char* pMonthString)
{
    if (strcmp(pMonthString, "jan") == 0) return 1;
    if (strcmp(pMonthString, "feb") == 0) return 2;
    if (strcmp(pMonthString, "mar") == 0) return 3;
    if (strcmp(pMonthString, "apr") == 0) return 4;
    if (strcmp(pMonthString, "may") == 0) return 5;
    if (strcmp(pMonthString, "jun") == 0) return 6;
    if (strcmp(pMonthString, "jul") == 0) return 7;
    if (strcmp(pMonthString, "aug") == 0) return 8;
    if (strcmp(pMonthString, "sep") == 0) return 9;
    if (strcmp(pMonthString, "oct") == 0) return 10;
    if (strcmp(pMonthString, "nov") == 0) return 11;
    if (strcmp(pMonthString, "dec") == 0) return 12;
    return -1; /*Failed*/
}

static bool parsedate(const char* name, long* julian, long* second, bool* isjul)
{
    const char* p = name;
    int n;
    int y = 0, m = 0, d = 0, H = 0, M = 0, S = 0;

    *julian = *second = 0;
    *isjul            = false;

    if (p == 0 || *p == 0)
        return false;

    /* Special ERA Interim grib1 date format: jul-21, sep-02 etc
     * See GRIB-416
     */
    if (isalpha(*p)) {
        char month[32];
        int day = 0;
        n = sscanf(p, "%[^-]-%d", month, &day);
        /* Matched two items (month and day) and month is 3 letters */
        if (n == 2 && strlen(month) == 3) {
            y = 1900; /* no year specified */
            m = convert_month(month);
            if (m == -1)
                return false;
            *julian = grib_date_to_julian(y * 10000 + m * 100 + day);
            *second = 0;
            return true;
        }
    }

    /* year */
    p = parse1(p, &y, &n);
    if (n != 2 && n != 4) /* year string must be 2 or 4 characters long: 93 or 1993 */
        return false;
    if (*p++ != '-')
        return false;

    /* month */
    p = parse1(p, &m, &n);
    if (n == 2) {
        /* day */
        if (*p++ != '-')
            return false;
        p = parse1(p, &d, &n);
        if (n != 2)
            return false;
    }
    else if (n == 3) {
        long j = grib_date_to_julian(y * 10000 + 101) + m - 1;
        j      = grib_julian_to_date(j);
        /* julian day */;
        d      = j % 100;
        m      = (j % 10000) / 100;
        *isjul = true;
    }
    else
        return false;

    if (m == 0 || m > 12) {
        return false; /* month out of range */
    }

    while (*p && isspace(*p))
        p++;

    /* hour */
    p = parse1(p, &H, &n);
    if (n != 0) {
        if (n != 2)
            return false;

        /* minute */
        if (*p++ != ':')
            return false;

        p = parse1(p, &M, &n);
        if (n != 2)
            return false;

        if (*p != 0) {
            /* second */
            if (*p++ != ':')
                return false;
            p = parse1(p, &S, &n);
            if (n != 2)
                return false;
        }
    }

    *julian = grib_date_to_julian(y * 10000 + m * 100 + d);
    *second = H * 3600 + M * 60 + S;

    return *p == 0 ? true : false;
}

static bool check_dimension_name(const char* dim)
{
    /* Dimension name must begin with an alphabetic character, followed by zero
     * or more alphanumeric characters including the underscore */
    int i = 0, len = 0;
    if (!dim)
        return false;

    len = strlen(dim);
    if (len == 0)
        return false;
    if (!isalpha(dim[0]))
        return false;

    for (i = 1; i < len; ++i) {
        const char c = dim[i];
        const int ok = isalnum(c) || c == '_';
        if (!ok)
            return false;
    }
    return true;
}

static int get_creation_mode(int option_kind)
{
    /* Return the mode flag for nc_create based   */
    /* on the kind of netCDF user wants to create */
    int creation_mode = NC_CLOBBER;
    switch (option_kind) {
        case NC_FORMAT_CLASSIC:
            printf("%s: Creating classic file format.\n", tool_name);
            break;
        case NC_FORMAT_64BIT:
            creation_mode |= NC_64BIT_OFFSET;
            printf("%s: Creating large (64 bit) file format.\n", tool_name);
            break;
#ifdef NC_NETCDF4
        case NC_FORMAT_NETCDF4:
            creation_mode |= NC_NETCDF4;
            printf("%s: Creating netCDF-4/HDF5 format.\n", tool_name);
            break;
        case NC_FORMAT_NETCDF4_CLASSIC:
            creation_mode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
            printf("%s: Creating netCDF-4 classic model file format.\n", tool_name);
            break;
#else
        case NC_FORMAT_NETCDF4:
        case NC_FORMAT_NETCDF4_CLASSIC:
            grib_context_log(ctx, GRIB_LOG_ERROR, "%s not built with netCDF4, cannot create netCDF-4 files.", tool_name);
            exit(1);
            break;
#endif
        default:
            fprintf(stderr, "Bad value (%d) for -k option\n", option_kind);
            exit(1);
            break;
    }
    return creation_mode;
}
/*=====================================================================*/

grib_option grib_options[] = {
    { "I:", "key1,key2,...", "\n\t\tIgnore keys. Default method,type,stream,refdate,hdate\n", 0, 1, "method,type,stream,refdate,hdate" },
    { "S:", "key1,key2,...", "\n\t\tSplit according to keys. Default param,expver\n", 0, 1, "param,expver" },
    { "R:", "date", "\n\t\tReference date in the format YYYYMMDD. Default value 19000101.\n", 0, 1, "19000101" },
    { "D:", "NC_DATATYPE",
      "\n\t\tType of data. Possible values NC_BYTE, NC_SHORT, NC_INT, NC_FLOAT, NC_DOUBLE."
      "\n\t\tDefault NC_SHORT\n",
      0, 1, "NC_SHORT" },
    { "T", 0, "Don't use time of validity.\n", 0, 1, 0 },
    { "f", 0, 0, 0, 1, 0 },
    { "o:", "output_file", "\n\t\tThe name of the netCDF output file.\n", 1, 1, 0 },
    { "V", 0, 0, 0, 1, 0 },
    { "M", 0, 0, 0, 1, 0 },
    { "k:", "kind",
      "\n\t\tSpecifies the kind of file to be created. Possible values are:"
      "\n\t\t1 -> netCDF classic file format"
      "\n\t\t2 -> netCDF 64 bit classic file format (Default)"
      "\n\t\t3 -> netCDF-4 file format"
      "\n\t\t4 -> netCDF-4 classic model file format\n",
      0, 1, "2" },
    { "d:", "level",
      "\n\t\tDeflate data (compression level). Only for netCDF-4 output format."
      "\n\t\tPossible values [0,9]. Default None."
      "\n\t\tChunking strategy based on GRIB message.\n",
      0, 1, "6" },
    { "s", 0, "Shuffle data before deflation compression.\n", 0, 1, 0 },
    { "u:", "dimension", "\n\t\tSet dimension to be an unlimited dimension.\n", 0, 1, "time" },
    { "h", 0, 0, 0, 1, 0 },
};

int grib_options_count    = sizeof(grib_options) / sizeof(grib_option);
static fieldset* fs       = NULL;
static request* data_r    = NULL;
request* user_r           = NULL;
static int option_kind    = 2; /* By default NetCDF3, 64-bit offset */
static int deflate_option = 0;

/* Table of formats for legal -k values. Inspired by nccopy */
struct KindValue
{
    const char* name;
    int kind;
} legalkinds[] = {
    { "1", NC_FORMAT_CLASSIC },
    { "classic", NC_FORMAT_CLASSIC },

    /* The 64-bit offset kind */
    { "2", NC_FORMAT_64BIT },
    { "64-bit-offset", NC_FORMAT_64BIT },

    /* NetCDF-4 HDF5 format */
    { "3", NC_FORMAT_NETCDF4 },
    { "hdf5", NC_FORMAT_NETCDF4 },
    { "netCDF-4", NC_FORMAT_NETCDF4 },
    { "netCDF4", NC_FORMAT_NETCDF4 },
    { "enhanced", NC_FORMAT_NETCDF4 },

    /* NetCDF-4 HDF5 format, but using only nc3 data model */
    { "4", NC_FORMAT_NETCDF4_CLASSIC },
    { "hdf5-nc3", NC_FORMAT_NETCDF4_CLASSIC },
    { "netCDF-4 classic model", NC_FORMAT_NETCDF4_CLASSIC },
    { "netCDF4_classic", NC_FORMAT_NETCDF4_CLASSIC },
    { "enhanced-nc3", NC_FORMAT_NETCDF4_CLASSIC },

    /* null terminate*/
    { NULL, 0 }
};

int main(int argc, char* argv[])
{
    int i = 0;

    /* GRIB-413: Collect all program arguments into a string */
    const size_t maxLen = sizeof(argvString);
    size_t currLen      = 0;
    for (i = 0; i < argc; ++i) {
        currLen += strlen(argv[i]);
        if (currLen >= maxLen - 1)
            break;
        strcat(argvString, argv[i]);
        if (i != argc - 1)
            strcat(argvString, " ");
    }
    return grib_tool(argc, argv);
}

int grib_tool_before_getopt(grib_runtime_options* options)
{
    return 0;
}

int grib_tool_init(grib_runtime_options* options)
{
    char* p            = NULL;
    char* list         = NULL;
    ctx                = grib_context_get_default();
    options->onlyfiles = 1;
    fs                 = new_fieldset(0);
    data_r             = empty_request(0);
    user_r             = empty_request(0);

    printf("%s: Version ", tool_name);
    grib_print_api_version(stdout);
    printf("\n");

    if (grib_options_on("D:")) {
        set_value(user_r, "accuracy", grib_options_get_option("D:"));
    }
    else {
        set_value(user_r, "accuracy", "NC_SHORT");
    }

    /* Option -S: Split according to keys */
    if (grib_options_on("S:")) {
        char* lasts         = NULL;
        list = grib_options_get_option("S:");
        p    = strtok_r(list, ",", &lasts);
        set_value(user_r, "split", p);
        p = strtok_r(NULL, ",", &lasts);
        while (p != NULL) {
            add_value(user_r, "split", p);
            p = strtok_r(NULL, ",", &lasts);
        }
    }
    else {
        set_value(user_r, "split", "param");
        add_value(user_r, "split", "expver");
    }

    /* Option -I: Ignore keys */
    if (grib_options_on("I:")) {
        char* lasts = NULL;
        list = grib_options_get_option("I:");
        p    = strtok_r(list, ",", &lasts);
        set_value(user_r, "ignore", p);
        p = strtok_r(NULL, ",", &lasts);
        while (p != NULL) {
            add_value(user_r, "ignore", p);
            p = strtok_r(NULL, ",", &lasts);
        }
    }
    else {
        set_value(user_r, "ignore", "method");
        add_value(user_r, "ignore", "type");
        add_value(user_r, "ignore", "stream");
        add_value(user_r, "ignore", "refdate");
        add_value(user_r, "ignore", "hdate");
    }

    if (grib_options_on("T"))
        set_value(user_r, "usevalidtime", "false");
    else
        set_value(user_r, "usevalidtime", "true");

    if (grib_options_on("k:")) {
        struct KindValue* kvalue = NULL;
        const char* kind_name = grib_options_get_option("k:");
        for (kvalue = legalkinds; kvalue->name; kvalue++) {
            if (strcmp(kind_name, kvalue->name) == 0) {
                option_kind = kvalue->kind; /* Found the right kind */
                break;
            }
        }
        if (kvalue->name == NULL) {
            fprintf(stderr, "Invalid value for -k option: %s\n", kind_name);
            fprintf(stderr, "Please use one of:\n");
            for (kvalue = legalkinds; kvalue->name; kvalue++) {
                if (is_number(kvalue->name))
                    fprintf(stderr, "\t%s\n", kvalue->name);
            }
            usage_and_exit();
        }
    }

    if (grib_options_on("d:")) {
        if (option_kind == 3 || option_kind == 4) { /* netCDF-4 */
            const char* theArg = grib_options_get_option("d:");
            if (!is_number(theArg) || atol(theArg) < 0 || atol(theArg) > 9) {
                fprintf(stderr, "Invalid deflate option: %s (must be 0 to 9)\n", theArg);
                usage_and_exit();
            }
            set_value(user_r, "deflate", theArg);
            deflate_option = 1;
        }
        else {
            fprintf(stderr, "Invalid deflate option for non netCDF-4 output formats\n");
            usage_and_exit();
        }
    }
    else {
        set_value(user_r, "deflate", "none");
    }

    if (grib_options_on("s")) {
        if (deflate_option)
            set_value(user_r, "shuffle", "true");
        else {
            fprintf(stderr, "Invalid shuffle option. Deflate option needed.\n");
            usage_and_exit();
        }
    }
    else
        set_value(user_r, "shuffle", "false");

    if (grib_options_on("R:")) {
        const char* theArg = grib_options_get_option("R:");
        if (!is_number(theArg)) {
            fprintf(stderr, "Invalid reference date: %s\n", theArg);
            usage_and_exit();
        }
        set_value(user_r, "referencedate", theArg);
    }
    else {
        set_value(user_r, "referencedate", "19000101");
    }

    if (grib_options_on("u:")) {
        const char* theDimension = grib_options_get_option("u:");
        if (!check_dimension_name(theDimension)) {
            fprintf(stderr, "Invalid dimension: \"%s\"\n", theDimension);
            exit(1);
        }
        set_value(user_r, "unlimited", theDimension);
    }

    get_nc_options(user_r);

    return 0;
}

int grib_tool_new_filename_action(grib_runtime_options* options, const char* filename)
{
    char buf[1024] = {0,};
    int e           = 0;
    int i           = 0;
    grib_handle* h  = NULL;
    grib_file* file = NULL;

    printf("%s: Processing input file '%s'.\n", tool_name, filename);

    file = grib_file_open(filename, "r", &e);
    if (!file || !file->handle)
        return e;

    fseeko(file->handle, 0, SEEK_SET);

    files++;

    while ((h = grib_handle_new_from_file(ctx, file->handle, &e)) != NULL) {
        long length;
        field* g;
        request* r;

        /* process only GRIB for the moment*/
        size_t size = sizeof(buf);
        ECCODES_ASSERT(grib_get_string(h, "identifier", buf, &size) == 0);
        if (strcmp(buf, "GRIB")) {
            grib_handle_delete(h);
            continue;
        }
        ECCODES_ASSERT(grib_get_long(h, "totalLength", &length) == 0);

        g = read_field(file, h->offset, length);

        r = empty_request("");
        if (handle_to_request(r, h) != GRIB_SUCCESS) {
            grib_context_log(ctx, GRIB_LOG_ERROR, "Failed to convert GRIB handle to a request");
        }

        /* Keep full MARS description */
        /* copy = clone_one_request(r); */
        /* reqmerge(setup.mars_description,copy); */
        /* reqmerge(setup.mars_description,r); */

        if (i == 1) {
            const char* mmeans = get_value(r, "_MONTHLY_MEANS", 0);
            setup.mmeans       = mmeans ? (atol(mmeans) == 1) : false;
        }
        fcmonth2nbmonths(r);

        if (!setup.refdate) {
            if (setup.auto_refdate)
                setup.refdate = atol(get_value(r, "date", 0));
            else {
                const char* p = get_value(user_r, "referencedate", 0);
                if (is_number(p))
                    setup.refdate = atol(p);
                else {
                    long julian = 0, second = 0;
                    bool isjul;
                    parsedate(p, &julian, &second, &isjul);
                    setup.refdate = grib_julian_to_date(julian);
                }
            }
        }

        validation_time(r);
        if (setup.usevalidtime) {
            unset_value(r, "date");
            unset_value(r, "time");
            unset_value(r, "step");
            unset_value(r, "fcmonth");

            set_value(r, "time", "%.2lf", atof(get_value(r, "_validationtime", 0)));
        }

        g->r = new_field_request(r);

        set_field(fs, g, i++);

        reqmerge(data_r, r);
        free_all_requests(r);

        grib_handle_delete(h);
    }

    if (e != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "%s (message %d)", grib_get_error_message(e), i);
    }

    int e2 = 0;
    grib_file_close(file->name, 0, &e2);
    if (e2 != GRIB_SUCCESS) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Failed to close file %s (%s)", file->name, grib_get_error_message(e2));
    }

    {
        /* Now do some checks */
        request* temp_data_r = fieldset_to_request(fs);
        if (setup.checkvalidtime) {
            int cnt = request_fields(temp_data_r);
            if (fs->count != i || (cnt < i)) {
                grib_context_log(ctx, GRIB_LOG_ERROR, "Wrong number of fields");
                grib_context_log(ctx, GRIB_LOG_ERROR, "File contains %d GRIBs, %d left in internal description, %d in request", i, fs->count, cnt);
                grib_context_log(ctx, GRIB_LOG_ERROR, "The fields are not considered distinct!\n");
                /*grib_context_log(ctx, GRIB_LOG_ERROR, "MARS description");*/
                /*print_all_requests(setup.mars_description);*/
                if (ctx->debug) {
                    grib_context_log(ctx, GRIB_LOG_ERROR, "Internal description");
                    print_all_requests(temp_data_r);
                }
                if (grib_options_on("T")) {
                    grib_context_log(ctx, GRIB_LOG_ERROR, "Hint: This may be due to several fields having the same date, time and step.");
                }
                else {
                    grib_context_log(ctx, GRIB_LOG_ERROR, "Hint: This may be due to several fields having the same validity time.");
                    grib_context_log(ctx, GRIB_LOG_ERROR, "Try using the -T option (Do not use time of validity)");
                }
                exit(1);
            }
        }
        free_all_requests(temp_data_r);
    }

    return e;
}

int grib_tool_new_file_action(grib_runtime_options* options, grib_tools_file* file)
{
    return 0;
}

int grib_tool_new_handle_action(grib_runtime_options* options, grib_handle* h)
{
    return 0;
}

int grib_tool_skip_handle(grib_runtime_options* options, grib_handle* h)
{
    return 0;
}

int grib_tool_finalise_action(grib_runtime_options* options)
{
    request* config_r  = NULL;
    hypercube* dims    = NULL;
    dataset_t* subsets = NULL;
    int count;
    int i;
    int ncid;
    int stat;
    int err           = 0;
    int creation_mode = NC_CLOBBER;

    if (options->outfile == NULL || options->outfile->name == NULL) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "No output file. Exiting!");
        usage_and_exit();
    }

    if (fs->count == 0) {
        grib_context_log(ctx, GRIB_LOG_ERROR, "Input does not contain any field. Exiting!");
        return -1;
    }

    printf("%s: Found %d GRIB field%s in %d file%s.\n", tool_name, fs->count, fs->count > 1 ? "s" : "", files, files > 1 ? "s" : "");

    if (ctx->debug) {
        grib_context_log(ctx, GRIB_LOG_INFO, "Request representing %d fields ", fs->count);
        print_all_requests(data_r);
    }

    /* Split the SOURCE from request into as many datasets as specified */
    count = split_fieldset(fs, data_r, &subsets, user_r, config_r);
    remove_param(data_r, (void*)user_r, "ignore");
    remove_param(data_r, (void*)user_r, "split");
    print_ignored_keys(stdout, user_r);

    dims = new_simple_hypercube_from_mars_request(data_r);
    if (ctx->debug) {
        grib_context_log(ctx, GRIB_LOG_INFO, "Hypercube");
        print_hypercube(dims);
    }

    /* In case there is only 1 DATE+TIME+STEP, set at least 1 time as axis */
    set_always_a_time(dims, data_r);

    /* Create netCDF file */

    printf("%s: Creating netCDF file '%s'\n", tool_name, options->outfile->name);
    printf("%s: NetCDF library version: %s\n", tool_name, nc_inq_libvers());

    creation_mode = get_creation_mode(option_kind);
    stat          = nc_create(options->outfile->name, creation_mode, &ncid);
    if (stat != NC_NOERR) {
        char msg[1024];
        snprintf(msg, sizeof(msg), "nc_create: '%s'", options->outfile->name);
        check_err(msg, stat, __LINE__);
    }

    /* Define netCDF dataset */
    err = define_netcdf_dimensions(dims, fs, ncid, subsets, count, data_r);
    if (err != GRIB_SUCCESS) {
        stat = nc_close(ncid);
        check_err("nc_close", stat, __LINE__);
        stat = nc_delete(options->outfile->name);
        check_err("nc_delete", stat, __LINE__);

        exit(1);
    }

    /* End definitions */
    stat = nc_enddef(ncid);
    check_err("nc_enddef", stat, __LINE__);

    /* Fill dimensions */
    fill_netcdf_dimensions(dims, fs, ncid);

    /* Put data values */
    for (i = 0; i < count; ++i) {
        if (subsets[i].fset) {
            char dataset[100];
            snprintf(dataset, sizeof(dataset), subsets[i].att.name, i + 1);
            put_data(dims, ncid, dataset, &subsets[i]);
        }
        else {
            grib_context_log(ctx, GRIB_LOG_ERROR, "Fieldset %d is empty!!", i + 1);
        }
    }

    stat = nc_close(ncid);
    check_err("nc_close", stat, __LINE__);

    free_all_requests(data_r);
    free_hypercube(dims);
    free_fieldset(fs);
    free_subsets(subsets, count);

    free_nc_options();
    printf("%s: Done.\n", tool_name);

    return 0;
}

int grib_no_handle_action(grib_runtime_options* options, int err)
{
    fprintf(dump_file, "\t\t\"ERROR: unreadable message\"\n");
    return 0;
}


#else
#include <stdio.h>
int main(int argc, char** argv)
{
    printf("\n");
    printf("grib_to_netcdf:\n");
    printf("\n");
    printf(" ecCodes was not compiled with NETCDF enabled\n");
    printf("\n");
    exit(1);
}
#endif
