/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <math.h>
#include <ctype.h>
#include <assert.h>
#include <fenv.h>

#include <algorithm>
#include <iostream>
#include <map>
#include <memory>
#include <numeric>
#include <string>
#include <vector>

#include "macro.h"
#include "cbufr.h"
#include "script.h"
#include "methods.h"

#include "MvException.h"
#include "MvFieldSet.h"
#include "MvGrid.h"
#include "MvMiscellaneous.h"
#include "MvPath.hpp"
#include "MvSci.h"
#include "GribVertical.h"

#define NO_MVGRID 0

/* save every 10 fields */
#define SAVE_EVERY_N_FIELDS 10


// used in function GridLatLonsFunction
enum eGridLatLonsType
{
    GLL_LATS = 0,
    GLL_LONS
};

// used in function GribDateFunction
enum eGribDateType
{
    GDT_BASE = 0,
    GDT_VALID
};


// used in SubGribFunction
enum eGribIndexType
{
    GINDEX_NUMBERS,
    GINDEX_VECTOR
};


enum eG2To1
{
    G2TO1_DIRECTION,
    G2TO1_SPEED
};

static math Math = {
    0,
    0,
    0,
    0};

static const char* parameterKey = "paramId";

hypercube* CGrib::get_cube()
{
    if (cube)
        return cube;

    request* s = empty_request(0);

    for (int i = 0; i < fs->count; i++) {
        field* g = get_field(fs, i, packed_mem);
        request* r = empty_request(0);
        // grib_to_request(r,g->buffer,g->length);
        /*int e = */ handle_to_request(r, g->handle, nullptr);
        release_field(g);
#if 0

		hypercube* c = new_hypercube(r);

		if(cube)
		{
			hypercube* a = add_cube(cube,c);
			free_hypercube(cube);
			free_hypercube(c);
			c = a;
		}

		cube = c;
#else
        reqmerge(s, r);
#endif

        free_all_requests(r);
    }

    cube = new_hypercube(s);


    return cube;
}

void CGrib::Dump2()
{
    std::cout << "\n";
    if (fs->count > 1)
        std::cout << "[\n";

    for (int i = 0; i < fs->count; i++) {
        field* g = get_field(fs, i, packed_mem);

        //		-- Q&D fix (040625/vk):
        //		-- field g has not been expanded and g == nullptr
        //		-- g->buffer is deleted if we expand, and g->buffer is needed later,
        //		-- thus, where is ksec1[2] equivalent original GRIB octet:
        //		-- ksec[2] == 6th octet in the original Section 1
        //		-- Sec0 is 8 octets + Sec1 6th octet = 14th octet from the start
        //		request *r = empty_request((g->ksec1[2] != mars.computeflg)

        // request *r = empty_request((g->buffer[13] != mars.computeflg)
        //	? "GRIB" : "COMPUTED");
        // grib_to_request(r,g->buffer,g->length);

        request* r = empty_request("GRIB");  //-- simplified gribapi port
        /*int e = */ handle_to_request(r, g->handle, nullptr);
        release_field(g);
        Perl(r);
        std::cout << "\n";
        free_all_requests(r);
    }

    std::cout << "\n";
    if (fs->count > 1)
        std::cout << "]\n";
}

int CGrib::Write(FILE* f)
{
    err e = 0;
    int i;

    for (i = 0; i < fs->count; i++) {
        field* g = fs->fields[i];
        set_field_state(g, packed_mem);
        e = e ? e : write_field(f, g);
        release_field(g);
    }

    return e;
}

void CGrib::Print(void)
{
    std::cout << '<' << fs->count << " field";
    if (fs->count > 1)
        std::cout << 's';
    std::cout << '>';
}

void CGrib::SetSubValue(Value& v, int arity, Value* arg)
{
    if (!Check(1, v, arity, arg, tgrib, 1, tnumber))
        return;

    int n;
    arg[0].GetValue(n);

    fieldset* f;
    v.GetValue(f);

    if (n < 1 || n > fs->count) {
        Error("CGrib::SetSubValue: Fieldset index [%d] is out of range (fieldset is %d long)",
              n, fs->count);
        return;
    }

    if (f->count > 1) {
        Error(
            "CGrib::SetSubValue: Cannot assign fieldset with more than 1 field "
            "(Fieldset is %d long)",
            f->count);
        return;
    }

    if (f->count == 0) {
        Error("CGrib::SetSubValue: Cannot assign empty fieldset");
        return;
    }

    set_field(fs, f->fields[0], n - 1);
}


/*******************************************************************************
 *
 * Function      : FieldsetContainsMissingValues
 *
 * Description   : Returns true if any of the fieldset's fields contain
 *                 missing values (determined by checking the .bitmap member).
 *
 ******************************************************************************/

static boolean FieldsetContainsMissingValues(fieldset* fs)
{
    int i;
    field* f;
    boolean bContainsMissingVals = false;

    for (i = 0; i < fs->count; i++) {
        f = fs->fields[i];

        if (FIELD_HAS_MISSING_VALS(f)) {
            bContainsMissingVals = true;
            break;
        }
    }

    return bContainsMissingVals;
}

/* End of function "FieldsetContainsMissingValues" */


/*******************************************************************************
 *
 * Function      : SetFieldElementToMissingValue
 *
 * Description   : Sets the element at the given index of the field to
 *                 'missing value'. Also sets the 'bitmap' member to true.
 *
 ******************************************************************************/

static inline void SetFieldElementToMissingValue(field* f, int i)
{
    f->values[i] = mars.grib_missing_value;
    f->bitmap = true;
}

/* End of function "SetFieldElementToMissingValue" */


/*******************************************************************************
 * Function      : GetIndexedFieldWithAtLeastPackedMem
 *
 * Description   : Returns the i'th field from the given fieldset, and ensures
 *                 that its shape is at least 'packed_mem'. If it is currently
 *                 expand_mem, then there is no point in changing it back to
 *                 packed_mem.
 *
 ******************************************************************************/

static field* GetIndexedFieldWithAtLeastPackedMem(fieldset* fs, int i)
{
    field* f;

    if (i >= fs->count || i < 0) {
        marslog(LOG_WARN, "GetIndexedFieldWithAtLeastPackedMem: index %d not valid (%d fields in fieldset)", i, fs->count);
        return nullptr;
    }

    f = fs->fields[i];

    if (f->shape == packed_file)
        set_field_state(f, packed_mem);

    // if packed_mem or expand_mem, then it's already ok


    return f;
}

/* End of function "GetIndexedFieldWithAtLeastPackedMem" */

// the input field has to be in packed_mem state at least
class AtLeastPackedMemExpander
{
public:
    AtLeastPackedMemExpander(field*);
    ~AtLeastPackedMemExpander();

protected:
    field* field_{nullptr};
    field_state oriState_{unknown};
};

AtLeastPackedMemExpander::AtLeastPackedMemExpander(field* f) :
    field_(f)
{
    if (field_) {
        oriState_ = field_->shape;
        if (oriState_ == packed_file) {
            set_field_state(field_, packed_mem);
        }
        assert(field_->shape == packed_mem || field_->shape == expand_mem);
    }
}

AtLeastPackedMemExpander::~AtLeastPackedMemExpander()
{
    if (field_) {
        set_field_state(field_, oriState_);
    }
}

//=============================================================================
// Functions for parsing arguments

static void getListArgAsArrayOfNumbers(Value* arg, int start, int num, double* d)
{
    CList* l;
    arg[start].GetValue(l);
    for (int i = 0; i < num; i++)
        (*l)[i].GetValue(d[i]);
}


static void getNumberArgsAsArrayOfNumbers(Value* arg, int start, int num, double* d)
{
    for (int i = 0; i < num; i++)
        arg[i + start].GetValue(d[i]);
}

//=============================================================================

class SubGribFunction : public Function
{
public:
    SubGribFunction(const char* n) :
        Function(n) {}
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);

private:
    eGribIndexType indexType_{GINDEX_NUMBERS};
};

int SubGribFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2 && arity != 3 && arity != 4)
        return false;


    if (arg[0].GetType() != tgrib)
        return false;


    // indexing can either be a set of numbers or a single vector
    if (arg[1].GetType() == tvector)  // vector
    {
        if (arity > 2)
            return false;
        else {
            indexType_ = GINDEX_VECTOR;
            return true;
        }
    }
    else  // number(s)
    {
        indexType_ = GINDEX_NUMBERS;

        for (int i = 1; i < arity; i++)
            if (arg[i].GetType() != tnumber)
                return false;
    }

    return true;
}

extern int vcnt;

Value SubGribFunction::Execute(int arity, Value* arg)
{
    if (indexType_ == GINDEX_NUMBERS) {
        int from = 0, to = 0, step = 0;
        bool toSupplied = false;
        fieldset* v;

        arg[0].GetValue(v);
        arg[1].GetValue(from);

        if (arity > 2) {
            arg[2].GetValue(to);
            toSupplied = true;
        }


        if (arity > 3)
            arg[3].GetValue(step);

        int baseIndex = Context::BaseIndex();  // = 1(Macro) or 0(Python)
        int indexOffset = 1 - baseIndex;       // = 0(Macro) or 1(Python)
        if (from < 1 || from > v->count)
            return Error("Fieldset index must be from %d to %d. %d was supplied and is out of range.",
                         baseIndex, v->count - indexOffset, from - indexOffset);

        if (toSupplied && ((to < 1) || (to > v->count)))
            return Error("Fieldset index must be from %d to %d. %d (second index) was supplied and is out of range.",
                         baseIndex, v->count - indexOffset, to - indexOffset);

        fieldset* w = sub_fieldset(v, from, to, step);
        if (!w)
            return Error("fs[]: Cannot extract sub-fieldset");

        return Value(w, true);
    }

    else  // vector indexing (i.e. the index is itself a vector of indexes)
    {
        // code partly taken from MARS/field.c/sub_fieldset()
        fieldset* v;
        CVector* vi;
        arg[0].GetValue(v);
        arg[1].GetValue(vi);

        fieldset* w = new_fieldset(vi->Count());

        for (int i = 0; i < vi->Count(); i++) {
            int index = vi->getIndexedValue(i);
            if (index < 1 || index > v->count)
                return Error("index %d(%d) is out of range. Fieldset size is %d", i + 1, index, v->count);

            field* g = v->fields[index - 1];
            w->fields[i] = g;
            g->refcnt++;
        }

        return Value(w, true);
    }
}

//=============================================================================

class MergeGribFunction : public Function
{
public:
    MergeGribFunction(const char* n) :
        Function(n) {}
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int MergeGribFunction::ValidArguments(int arity, Value* arg)
{
    if (arity < 1)
        return false;

    for (int i = 0; i < arity; i++)
        if (arg[i].GetType() != tgrib)
            return false;
    return true;
}

Value MergeGribFunction::Execute(int arity, Value* arg)
{
    int cnt = arity;
    bool anyFromFilter = false;
    fieldset* w = nullptr;
    fieldset* z;
    while (cnt--) {
        CGrib* v;
        arg[cnt].GetValue(v);
        anyFromFilter = anyFromFilter || v->fromFilter();

        z = w;
        w = merge_fieldsets(v->GetFieldset(), w);
        if (z)
            free_fieldset(z);
    }

    return Value(w, anyFromFilter);
}

//=============================================================================

class CubeFunction : public Function
{
public:
    CubeFunction(const char* n) :
        Function(n, 2, tgrib, trequest) {}
    virtual Value Execute(int arity, Value* arg);
};

Value CubeFunction::Execute(int, Value* arg)
{
    CGrib* v;
    arg[0].GetValue(v);
    request* r;
    arg[1].GetValue(r);

    hypercube* c = v->get_cube();
    return Value(cube_order(c, r));
}

//=============================================================================
// Base class for functions taking a fieldset as the first argument and performing gridpoint based
// computations using the lat/lon coordinates of a given gridpoint only. Computations where the
// grid geometry is also needed (e.g. gradient) do not belong to this category.
class GeoLocationBasedFunction : public Function
{
public:
    GeoLocationBasedFunction(const char* n) :
        Function(n) {}
    Value Execute(int arity, Value* arg) override;

protected:
    virtual void extractArguments(int arity, Value* arg) = 0;
    virtual bool compute(MvGridPtr) = 0;
    virtual void updateResult(field*);
};

Value GeoLocationBasedFunction::Execute(int arity, Value* arg)
{
    metview::MarsComputeFlagDisabler mflagDisabler;

    // the first argument is always a fieldset
    assert(arity >= 1);
    fieldset* v = nullptr;
    arg[0].GetValue(v);
    assert(v);

    try {
        extractArguments(arity, arg);

        // create the resulting fieldset with empty fields
        fieldset* result = new_fieldset(v->count);

        for (int i = 0; i < v->count; i++) {
            auto grd = MvGridPtr(MvGridFactory(v->fields[i]));
            if (!grd->hasLocationInfo()) {
                return Error("%s: unimplemented or spectral data - unable to extract location data", Name());
            }

            assert(grd->fieldPtr()->shape == expand_mem);

            // this indicates that we do not want to keep the modified values
            grd->setForgetValuesOnDestroy(true);

            // we store the resulting data in the input grid, this will not be saved back!!
            bool hasMissing = compute(grd);

            // create the resulting field and save it
            auto fRes = copy_field(v->fields[i], true);
            fRes->bitmap = (hasMissing) ? 1 : 0;
            assert(fRes->shape == expand_mem);

            updateResult(fRes);

            set_field(result, fRes, i);
            assert(fRes->shape == expand_mem);
            save_fieldset(result);
            assert(fRes->shape == packed_file);
        }

        return Value(result);
    }
    catch (MvException& e) {
        return Error("%s: %s", Name(), e.what());
    }

    return Value();
}

void GeoLocationBasedFunction::updateResult(field* fld)
{
    // mark fields as derived
    MvGridBase::setLong(fld, "generatingProcessIdentifier", 254);
}

//=============================================================================

class GenerateFunction : public Function
{
public:
    GenerateFunction(const char* n) :
        Function(n) { info = "Generates fields"; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int GenerateFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2 && arity != 3)
        return false;
    if (arg[0].GetType() != tgrib)
        return false;
    if (arg[1].GetType() != tstring)
        return false;
    return true;
}

Value GenerateFunction::Execute(int arity, Value* arg)
{
    nontested_eccodes_port("GenerateFunction::Execute");

    // re-write using MvGrid => works with all fields!
    fieldset* v;
    arg[0].GetValue(v);

    const char* nam;
    arg[1].GetValue(nam);

    Value param[4];
    param[0] = Value(0.0);
    param[1] = Value(0.0);
    param[2] = Value(0.0);
    if (arity == 3)
        param[3] = arg[2];

    arity++;

    Function* f = Owner()->WhichFunction(nam, arity, param);
    if (!f)
        return Error("Function %s not found", nam);

    fieldset* w = copy_fieldset(v, v->count, false);


    for (int i = 0; i < v->count; i++) {
        field* g = get_field(v, i, expand_mem);
        field* h = get_field(w, i, expand_mem);

        std::unique_ptr<MvGridBase> grd(MvGridFactory(g));
        if (!grd->hasLocationInfo())
            return Error("GenerateFunction: unimplemented or spectral data - unable to extract location data");
        std::unique_ptr<MvGridBase> grdOut(MvGridFactory(h));

        bool cont = true;
        while (cont)  //-- process current field
        {
            param[0] = Value(grd->lat_y());
            param[1] = Value(grd->lon_x());
            param[2] = Value(grd->value());

            Value u = f->Execute(arity, param);

            double d;
            u.GetValue(d);
            grdOut->value(d);

            grdOut->advance();
            cont = grd->advance();
        }

        if (((i + 1) % 10) == 0)
            save_fieldset(w);

        release_field(g);
    }

    return Value(w);

#if 0
		if( grd->getLong("gridDefinition") != GRIB_LAT_LONG ) // s2->data_rep != GRIB_LAT_LONG)
			return Error("generate: Field is not lat/long");

		double x0   = grd->getDouble("longitudeOfFirstGridPoint // sec2_[cGridFirstLon] / cGridScaling;"); // s2->limit_west /GRIB_FACTOR;
		double y0   = grd->getDouble("latitudeOfFirstGridPoint // sec2_[cGridFirstLon] / cGridScaling;"); // s2->limit_north/GRIB_FACTOR;
		double maxx = grd->getDouble("longitudeOfLastGridPoint // sec2_[cGridFirstLon] / cGridScaling;"); // s2->limit_east /GRIB_FACTOR;

		if(x0 > maxx) x0 -= 360;

		double dx = grd->getDouble("numberOfPointsAlongAParallel"); //  s2->grid_ew/GRIB_FACTOR;
		double dy = grd->getDouble("numberOfPointsAlongAMeridian"); // -s2->grid_ns/GRIB_FACTOR;

		double x  = x0;
		double y  = y0;

		for( int j=0; j<grd->("value_count"); ++j ) //  j<g->sec4len;j++)
		{
			param[0] = Value(y);
			param[1] = Value(x);
			param[2] = Value( (*grd)[j] ) // (double)g->rsec4[j]);

			Value u = f->Execute(arity,param);

/*
			if(u.GetType() != tnumber)
			{
				char *kind;
				u.GetType(&kind);
				return Error(
					"Bad type (%s) returned by function %s"
					" (should be a number)", kind, nam);
			}
*/

			double d;
			u.GetValue(d);
			grd->value(j) = d; // h->rsec4[j] = d;

			x += dx; if(x > maxx) { x = x0; y += dy; }

		}

		if( ((i+1) % 10) == 0) save_fieldset(w);

		release_field(g);
	}
#endif
}

//=============================================================================

class MaskFunction : public GeoLocationBasedFunction
{
public:
    MaskFunction(const char* n) :
        GeoLocationBasedFunction(n) { info = "Generates masks for fieldsets"; }
    int ValidArguments(int arity, Value* arg) override;

protected:
    void extractArguments(int arity, Value* arg) override;
    bool compute(MvGridPtr grd) override;

private:
    bool setMissing_{false};
    int adjustedArity_{0};
    std::string incorrectOption_;
    MvGeoBox geoArea_;
};

int MaskFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    metview::checkStringOption("missing", arity, arg, setMissing_, incorrectOption_);
    adjustedArity_ = arity;

    switch (arity) {
        case 5:
            if (arg[0].GetType() != tgrib)
                return false;
            for (i = 1; i < 5; i++)
                if (arg[i].GetType() != tnumber)
                    return false;
            break;

        case 2:
            if (arg[0].GetType() != tgrib)
                return false;
            if (arg[1].GetType() != tlist)
                return false;

            arg[1].GetValue(l);
            if (l->Count() != 4)
                return false;
            for (i = 0; i < 4; i++)
                if ((*l)[i].GetType() != tnumber)
                    return false;
            break;

        default:
            return false;
    }

    return true;
}

void MaskFunction::extractArguments(int arity, Value* arg)
{
    assert(arity >= 1);

    double d[4];  // n,w,s,e
    arity = adjustedArity_;

    if (!incorrectOption_.empty()) {
        throw MvException("if supplied, the option parameter must be 'missing'; it is '" +
                          incorrectOption_ + "'");
    }

    if (arity < 4) {
        getListArgAsArrayOfNumbers(arg, 1, 4, &d[0]);
    }
    else {
        getNumberArgsAsArrayOfNumbers(arg, 1, 4, d);
    }
    double n = d[0], w = d[1], s = d[2], e = d[3];
    while (w > e) {
        w -= 360.0;
    }
    geoArea_ = MvGeoBox(n, w, s, e);
}

bool MaskFunction::compute(MvGridPtr grd)
{
    bool hasMissing = false;
    do {
        if (setMissing_) {
            if (!geoArea_.isInside(grd->lat_y(), grd->lon_x())) {
                grd->value(mars.grib_missing_value);
                hasMissing = true;
            }
        }
        else {
            grd->value(geoArea_.isInside(grd->lat_y(), grd->lon_x()));
        }
    } while (grd->advance());
    return hasMissing;
}

//=============================================================================
//----------------------   rmask - "round mask" alias "radius mask"

class RMaskFunction : public GeoLocationBasedFunction
{
public:
    RMaskFunction(const char* n) :
        GeoLocationBasedFunction(n) { info = "Generates masks based on a radius around a point for fieldsets"; }
    int ValidArguments(int arity, Value* arg) override;

protected:
    void extractArguments(int arity, Value* arg) override;
    bool compute(MvGridPtr grd) override;

private:
    bool setMissing_{false};
    int adjustedArity_{0};
    std::string incorrectOption_;
    MvLocationHub centre_;    // mask center, lat/lon
    double cosOfRadius_{1.};  // cosine of the angular distance representing the radius
};

int RMaskFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    metview::checkStringOption("missing", arity, arg, setMissing_, incorrectOption_);
    adjustedArity_ = arity;

    switch (arity) {
        case 4:  //-- rmask( fieldset, lat, lon, radius )
            if (arg[0].GetType() != tgrib)
                return false;

            for (i = 1; i < 4; i++)
                if (arg[i].GetType() != tnumber)
                    return false;
            break;

        case 2:  //-- rmask( fieldset, [ lat, lon, radius ] )
            if (arg[0].GetType() != tgrib)
                return false;

            if (arg[1].GetType() != tlist)
                return false;

            arg[1].GetValue(l);
            if (l->Count() != 3)
                return false;

            for (i = 0; i < 3; i++)
                if ((*l)[i].GetType() != tnumber)
                    return false;
            break;

        default:
            return false;
    }

    return true;
}

void RMaskFunction::extractArguments(int arity, Value* arg)
{
    assert(arity >= 1);
    arity = adjustedArity_;
    if (!incorrectOption_.empty()) {
        throw MvException("if supplied, the option parameter must be 'missing'; it is '" +
                          incorrectOption_ + "'");
    }

    double d[3];
    if (arity < 4) {
        getListArgAsArrayOfNumbers(arg, 1, 3, &d[0]);  //-- extract 3 numeric values from a list
    }
    else {
        getNumberArgsAsArrayOfNumbers(arg, 1, 3, d);
    }

    centre_ = MvLocationHub(d[0], d[1]);

    // the input radius is in metres. We convert to the cosine of the
    // angular distance (in radius) it represents on the surface of the Earth.
    cosOfRadius_ = std::cos(MvSci::metresToRadians(d[2]));
}

bool RMaskFunction::compute(MvGridPtr grd)
{
    bool hasMissing = false;
    do {
        double dst = centre_.cosOfDistance(grd->lat_y(), grd->lon_x());
        // cosine is monotone descreasing in [0, pi]!.So the larger the value
        // the smaller the distance!
        bool outside = (dst < cosOfRadius_);
        if (setMissing_) {
            if (outside) {
                grd->value(mars.grib_missing_value);
                hasMissing = true;
            }
        }
        else {
            grd->value(outside ? 0.0 : 1.0);
        }
    } while (grd->advance());
    return hasMissing;
}

//=============================================================================

class PolyMaskFunction : public GeoLocationBasedFunction
{
public:
    PolyMaskFunction(const char* n) :
        GeoLocationBasedFunction(n) { info = "Generates polygon masks for fieldsets"; }
    int ValidArguments(int arity, Value* arg) override;

protected:
    void extractArguments(int arity, Value* arg) override;
    bool compute(MvGridPtr grd) override;

private:
    bool setMissing_{false};
    int adjustedArity_{0};
    std::string incorrectOption_;
    std::vector<eckit::geometry::polygon::LonLatPolygon> polygons_;
};

int PolyMaskFunction::ValidArguments(int arity, Value* arg)
{
    metview::checkStringOption("missing", arity, arg, setMissing_, incorrectOption_);
    adjustedArity_ = arity;

    if (arity == 3) {
        if (arg[0].GetType() != tgrib) {
            return false;
        }
        if (arg[1].GetType() == tvector && arg[2].GetType() == tvector) {
            return true;
        }
        if (arg[1].GetType() == tlist && arg[2].GetType() == tlist) {
            return true;
        }
    }
    return false;
}

void PolyMaskFunction::extractArguments(int arity, Value* arg)
{
    assert(arity >= 1);
    arity = adjustedArity_;
    if (!incorrectOption_.empty()) {
        throw MvException("if supplied, the option parameter must be 'missing'; it is '" +
                          incorrectOption_ + "'");
    }

    polygons_.clear();
    metview::buildPolygons(&arg[1], &arg[2], polygons_);
}

bool PolyMaskFunction::compute(MvGridPtr grd)
{
    bool hasMissing = false;
    do {
        eckit::geometry::Point2 p(grd->lon_x(), grd->lat_y());
        bool inside = false;
        for (auto const& poly : polygons_) {
            if (poly.contains(p)) {
                inside = true;
                break;
            }
        }
        if (setMissing_) {
            if (!inside) {
                grd->value(mars.grib_missing_value);
                hasMissing = true;
            }
        }
        else {
            grd->value(inside ? 1.0 : 0.0);
        }
    } while (grd->advance());
    return hasMissing;
}


//=============================================================================
//-----------------------------------------------------   distance  (020816/vk)

class GridDistanceFunction : public Function
{
public:
    GridDistanceFunction(const char* n) :
        Function(n) { info = "Computes the distances of all the gridpoints from a point"; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int GridDistanceFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    switch (arity) {
        case 3:  //-- distance( fieldset, lat, lon )
            if (arg[0].GetType() != tgrib)
                return false;

            for (i = 1; i < 3; i++)
                if (arg[i].GetType() != tnumber)
                    return false;

            break;

        case 2:  //-- distance( fieldset, [ lat, lon ] )
            if (arg[0].GetType() != tgrib)
                return false;

            if (arg[1].GetType() != tlist)
                return false;

            arg[1].GetValue(l);
            if (l->Count() != 2)
                return false;

            for (i = 0; i < 2; i++)
                if ((*l)[i].GetType() != tnumber)
                    return false;

            break;

        default:
            return false;
    }

    return true;
}

Value GridDistanceFunction::Execute(int arity, Value* arg)
{
    fieldset* v;
    double d[2];
    int i;

    arg[0].GetValue(v);  //-- get parameters...

    if (arity == 2)
        getListArgAsArrayOfNumbers(arg, 1, 2, &d[0]);  //-- extract 2 numeric values from a list
    else
        getNumberArgsAsArrayOfNumbers(arg, 1, 2, d);

    MvLocation ref_point(d[0], d[1]);  //-- reference point, lat/lon
    MvLocation gridpointLoc;           //-- for grid point locations

    fieldset* z = copy_fieldset(v, v->count, false);

    for (i = 0; i < v->count; i++)  //-- for all fields in a fieldset
    {
        std::unique_ptr<MvGridBase> newGrd(MvGridFactory(z->fields[i]));

        if (!newGrd->hasLocationInfo())
            return Error("distance: unimplemented or spectral data - unable to extract location data");

        do  //-- process current field
        {
            gridpointLoc.set(newGrd->lat_y(), newGrd->lon_x());
            double distance_m = gridpointLoc.distanceInMeters(ref_point);
            newGrd->value(distance_m);
        } while (newGrd->advance());
    }

    //-- do we need to release 'z' ???
    return Value(z);
}

//=============================================================================

class GridBearingFunction : public GeoLocationBasedFunction
{
public:
    GridBearingFunction(const char* n) :
        GeoLocationBasedFunction(n) { info = "Computes the bearings for all the gridpoints with respect to a reference point"; }
    int ValidArguments(int arity, Value* arg) override;

protected:
    void extractArguments(int arity, Value* arg) override;
    bool compute(MvGridPtr grd) override;

    double latRef_{0.};
    double lonRef_{0.};
};

int GridBearingFunction::ValidArguments(int arity, Value* arg)
{
    CList* l = nullptr;
    switch (arity) {
        case 3:  //-- bearing( fieldset, lat, lon )
        {
            if (arg[0].GetType() != tgrib)
                return false;

            for (int i = 1; i < 3; i++) {
                if (arg[i].GetType() != tnumber)
                    return false;
            }
            break;
        }

        case 2:  //-- bearing( fieldset, [ lat, lon ] )
        {
            if (arg[0].GetType() != tgrib)
                return false;

            if (arg[1].GetType() != tlist)
                return false;

            arg[1].GetValue(l);
            if (l->Count() != 2)
                return false;

            for (int i = 0; i < 2; i++)
                if ((*l)[i].GetType() != tnumber)
                    return false;

            break;
        }

        default:
            return false;
    }

    return true;
}
void GridBearingFunction::extractArguments(int arity, Value* arg)
{
    assert(arity >= 1);
    double d[2];
    if (arity == 2) {
        getListArgAsArrayOfNumbers(arg, 1, 2, &d[0]);
    }
    else {
        getNumberArgsAsArrayOfNumbers(arg, 1, 2, d);
    }

    latRef_ = d[0];
    lonRef_ = d[1];
}

bool GridBearingFunction::compute(MvGridPtr grd)
{
    if (lonRef_ > 180.) {
        lonRef_ -= 360.;
    }

    double cosLatRef = std::cos(MvSci::degToRad(latRef_));
    double sinLatRef = std::sin(MvSci::degToRad(latRef_));
    bool hasMissing = false;
    do {
        double bearing = 0.;
        double lon = grd->lon_x();
        double lat = grd->lat_y();
        const double eps = 1E-9;

        if (lon > 180.) {
            lon -= 360.;
        }
        double d_lon = MvSci::degToRad(lon - lonRef_);

        // the bearing is constant on the same latitude
        if (fabs(lat - latRef_) < eps) {
            // same location
            if (fabs(lon - lonRef_) < eps) {
                grd->value(mars.grib_missing_value);
                hasMissing = true;
            }
            else {
                bearing = (d_lon <= 0.) ? 270. : 90.;
                grd->value(bearing);
            }
        }
        else {
            lat = MvSci::degToRad(lat);

            // bearing in degrees, x axis points to East, anti-clockwise
            bearing = atan2(cosLatRef * sin(lat) - sinLatRef * cos(lat) * cos(d_lon),
                            sin(d_lon) * cosLatRef);

            // transforms to the required coordinate system: x axis points to North, clockwise
            bearing = M_PI_2 - bearing;
            if (bearing < 0) {
                bearing += 2.0 * M_PI;
            }
            grd->value(MvSci::radToDeg(bearing));
        }
    } while (grd->advance());
    return hasMissing;
}

//=============================================================================

class DistributionFunction : public Function
{
public:
    DistributionFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Calculate EPS distributions";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value DistributionFunction::Execute(int, Value* arg)
{
    // nontested_eccodes_port("DistributionFunction::Execute");

#if 0
	fieldset *v;


	arg[0].GetValue(v);

	int save = mars.computeflg;
	mars.computeflg = 0;
	int acc = mars.accuracy;

	fieldset *z = copy_fieldset(v,v->count,true);

	std::vector<field*> f(z->count);
	f[0] = get_field(z,0,expand_mem);
	fortint size = f[0]->sec4len;
	for (int i = 1; i < z->count ;i++)
	{
		f[i] = get_field(z,i,expand_mem);
		if (f[i] == 0)
			return Error("distribution: cannot read field");
		fortint s = f[i]->sec4len;
		if (s != size)
			return Error("distribution: the fields do not have the same size");

	}

	std::vector<fortfloat> array(z->count);
	for (int i = 0; i < size ;i++)
	{
		for (int j = 0; j < z->count; j++)
			array[j] = f[j]->rsec4[i];
		std::sort(array.begin(),array.end());
		for (int j = 0; j < z->count; j++)
			f[j]->rsec4[i] = array[j];
	}

	for (int i = 0; i < z->count; i++)
	{
		// Ensemble distribution
		f[i]->ksec1[39-1] = 23;

		// Number
		f[i]->ksec1[42-1] = i + 1;
	}

   	mars.accuracy = f[0]->ksec4[1];

	Value res(z);
	mars.computeflg = save;
    mars.accuracy = acc;

	return res;
#else
    fieldset* v;

    arg[0].GetValue(v);

    int save = mars.computeflg;
    mars.computeflg = 0;
    int acc = mars.accuracy;

    fieldset* z = copy_fieldset(v, v->count, true);

    std::vector<field*> f(z->count);
    f[0] = get_field(z, 0, expand_mem);
    size_t size = f[0]->value_count;
    for (int i = 1; i < z->count; i++) {
        f[i] = get_field(z, i, expand_mem);
        if (f[i] == 0)
            return Error("distribution: cannot read field");
        size_t s = f[i]->value_count;
        if (s != size)
            return Error("distribution: the fields do not have the same size");
    }

    std::vector<double> array(z->count);
    for (size_t i = 0; i < size; i++) {
        for (int j = 0; j < z->count; j++)
            array[j] = f[j]->values[i];
        std::sort(array.begin(), array.end());
        for (int j = 0; j < z->count; j++)
            f[j]->values[i] = array[j];
    }

    int err = 0;
    for (int i = 0; i < z->count; i++) {
        // Ensemble distribution
        // f[i]->ksec1[39-1] = 23;
        grib_set_long(f[i]->handle, "mars.type", 23);
        // Number
        // f[i]->ksec1[42-1] = i + 1;
        // err = grib_set_long( f[i]->handle, "mars.number", i+1 );
        grib_set_long(f[i]->handle, "perturbationNumber", i + 1);
    }

    // mars.accuracy = f[0]->ksec4[1];
    long acc0;
    //	err = grib_get_long( f[0]->handle, "setDecimalPrecision", &acc0 );
    err = grib_get_long(f[0]->handle, "numberOfBitsContainingEachPackedValue", &acc0);
    if (err == 0)
        mars.accuracy = (int)acc0;
    else
        std::cerr << ">>> DistributionFunction::Execute() - unable to get 'numberOfBitsContainingEachPackedValue'"
                  << std::endl;

    Value res(z);
    mars.computeflg = save;
    mars.accuracy = acc;

    return res;
#endif
}


//=============================================================================

/*******************************************************************************
 *
 * Function      : GribHeaderFunctionR
 *
 * Description   : Reads an element from the GRIB header.
 *
 ******************************************************************************/

class GribHeaderFunctionR : public Function
{
    eGribHeaderType type;

public:
    GribHeaderFunctionR(const char* n, eGribHeaderType t) :
        Function(n, 2, tgrib, tstring),
        type(t)
    {
        info = "Reads GRIB headers using ecCodes keys";
    }
    Value Execute(int arity, Value* arg) override;
};


/*
*  GetGribHeaderValue: Used by GribHeaderFunctionR and GribHeaderFunctionRGeneric
   to obtain the value of a particular key.
*/

Value GetGribHeaderValue(field* fld, const char* key, eGribHeaderType type)
{
    Value value;

    try {
        switch (type) {
            case GRIB_LONG: {
                long n = MvGridBase::getLong(fld, key, true, true);
                value = n;
                break;
            }

            case GRIB_DOUBLE: {
                double r = MvGridBase::getDouble(fld, key, true, true);
                value = r;
                break;
            }

            case GRIB_STRING: {
                std::string str = MvGridBase::getString(fld, key, true, true);
                value = str.c_str();
                break;
            }

            case GRIB_LONG_ARRAY: {
                long* lvals = nullptr;
                long num_vals = MvGridBase::getLongArray(fld, key, &lvals, true, true);

                if ((lvals != nullptr) && (num_vals > 0)) {
                    auto* v = new CVector(num_vals);
                    for (int j = 0; j < num_vals; j++) {
                        v->setIndexedValue(j, lvals[j]);
                    }
                    value = v;
                    free(lvals);
                }

                break;
            }

            case GRIB_DOUBLE_ARRAY: {
                double* dvals = nullptr;
                long num_vals = MvGridBase::getDoubleArray(fld, key, &dvals, true, true);
                if ((dvals != nullptr) && (num_vals > 0)) {
                    auto* v = new CVector(num_vals);
                    for (int j = 0; j < num_vals; j++) {
                        v->setIndexedValue(j, dvals[j]);
                    }
                    value = v;
                    free(dvals);
                }
                break;
            }

            default: {
                return Value();
            }
        }
    }
    catch (MvException& e) {
        // no need for warning message, as getXXX already issues one
        return Value();
    }

    return value;
}


Value GribHeaderFunctionR::Execute(int, Value* arg)
{
    fieldset* fs = nullptr;
    const char* key;

    arg[0].GetValue(fs);   // get the fieldset variable
    arg[1].GetValue(key);  // get the GRB API key name variable

    auto* l = new CList(fs->count);

    for (int i = 0; i < fs->count; i++) {
        // the input field has to be in packed_mem state at least
        AtLeastPackedMemExpander fx(fs->fields[i]);
        Value value = GetGribHeaderValue(fs->fields[i], key, type);
        (*l)[i] = value;
    }

    return (l->Count() > 1) ? Value(l) : Value((*l)[0]);
}


/*******************************************************************************
 *
 * Function      : GribHeaderFunctionRGeneric
 *
 * Description   : Reads an element from the GRIB header.
 *
 ******************************************************************************/

class GribHeaderFunctionRGeneric : public Function
{
    enum GroupingType
    {
        GROUP_BY_FIELD = 0,
        GROUP_BY_KEY
    };


public:
    GribHeaderFunctionRGeneric(const char* n) :
        Function(n, 2, tgrib, tstring)
    {
        info = "Reads GRIB headers using ecCodes keys";
    }
    Value Execute(int arity, Value* arg) override;
    int ValidArguments(int arity, Value* arg) override;
};


int GribHeaderFunctionRGeneric::ValidArguments(int arity, Value* arg)
{
    // valid arguments are:
    //  fieldset, list
    //  fieldset, list, string

    if (arity != 2 && arity != 3)
        return false;
    if (arg[0].GetType() != tgrib || arg[1].GetType() != tlist)
        return false;
    if (arity == 3 && arg[2].GetType() != tstring)
        return false;

    return true;
}


Value GribHeaderFunctionRGeneric::Execute(int arity, Value* arg)
{
    fieldset* fs;
    CList* keys;
    const char* groupingString;
    GroupingType grouping = GROUP_BY_FIELD;  // default behaviour

    arg[0].GetValue(fs);    // get the fieldset variable
    arg[1].GetValue(keys);  // get the list of GRB API key names

    if (arity == 3) {
        arg[2].GetValue(groupingString);
        if (groupingString && !strcmp(groupingString, "key"))
            grouping = GROUP_BY_KEY;
        else if (groupingString && !strcmp(groupingString, "field"))
            grouping = GROUP_BY_FIELD;
        else
            return Error("%s: 3rd parameter should be 'field' or 'key'; %s is not valid.", Name(), groupingString);
    }

    CList* fieldValues = nullptr;
    CList* keyValues = nullptr;

    if (grouping == GROUP_BY_FIELD)
        fieldValues = new CList(fs->count);  // top-level list is the fields
    else
        keyValues = new CList(keys->Count());  // top-level list is the keys


    for (int i = 0; i < fs->count; i++) {
        // the input field has to be in packed_mem state at least
        AtLeastPackedMemExpander fx(fs->fields[i]);

        if (grouping == GROUP_BY_FIELD) {
            keyValues = new CList(keys->Count());  // for each field there will be a list of key values
        }

        for (int k = 0; k < keys->Count(); k++) {
            eGribHeaderType dataType = GRIB_STRING;  // default type
            char typeString[5] = "";
            const char* origKey;
            char* key;
            (*keys)[k].GetValue(origKey);
            key = strdup(origKey);

            if (grouping == GROUP_BY_KEY) {
                if (i == 0)  // first field, group-by-key
                {
                    fieldValues = new CList(fs->count);
                    (*keyValues)[k] = fieldValues;
                }
                else {
                    (*keyValues)[k].GetValue(fieldValues);
                }
            }


            // the keys are formatted as 'key[:t]' where t specifies
            // the type of data we are supposed to return. If omitted,
            // we will return as a string

            size_t len = strlen(key);
            for (size_t p = len - 1; p > 0; p--) {
                if (key[p] == ':') {
                    if (p < len - 1) {
                        strcpy(typeString, &key[p + 1]);
                        key[p] = '\0';
                        break;
                    }
                }
            }

            // convert the type string into our enumerated type
            // typeString=="n" means to return the native type for that key
            if (typeString[0] != '\0') {
                if (!strcmp(typeString, "n")) {
                    dataType = MvGridBase::getNativeType(fs->fields[i], key);
                    // if we can't get the native type, get as string and let GetGribHeaderValue handle the error
                    if (dataType == GRIB_TYPE_ERROR)
                        dataType = GRIB_STRING;
                }
                else if (!strcmp(typeString, "l"))
                    dataType = GRIB_LONG;
                else if (!strcmp(typeString, "d"))
                    dataType = GRIB_DOUBLE;
                else if (!strcmp(typeString, "s"))
                    dataType = GRIB_STRING;
                else if (!strcmp(typeString, "da"))
                    dataType = GRIB_DOUBLE_ARRAY;
                else if (!strcmp(typeString, "la"))
                    dataType = GRIB_LONG_ARRAY;
                else {
                    free(key);
                    return Error("%s: type specifier must be one of 'l', 'd', 's', 'da', 'la'. It is '%s'", Name(), typeString);
                }
            }

            Value value = GetGribHeaderValue(fs->fields[i], key, dataType);

            if (grouping == GROUP_BY_FIELD)
                (*keyValues)[k] = value;
            else
                (*fieldValues)[i] = value;

            free(key);

        }  // for each key

        if (grouping == GROUP_BY_FIELD)
            (*fieldValues)[i] = keyValues;

    }  // for each field


    return (grouping == GROUP_BY_FIELD) ? fieldValues : keyValues;
}


//=============================================================================

/*******************************************************************************
 *
 * Function      : GribHeaderFunctionW
 *
 * Description   : Writes an element to the GRIB header.
 *
 ******************************************************************************/

class GribHeaderFunctionW : public Function
{
    eGribHeaderType type;

public:
    GribHeaderFunctionW(const char* n, eGribHeaderType t) :
        Function(n, 2, tgrib, tlist),
        type(t)
    {
        info = "Writes GRIB headers using ecCodes keys";
    }

    int ValidArguments(int arity, Value* arg) override;
    Value Execute(int arity, Value* arg) override;

protected:
    bool repack_{false};
    std::string incorrectOption_;
    int adjustedArity_{0};
};

int GribHeaderFunctionW::ValidArguments(int arity, Value* arg)
{
    metview::checkStringOption("repack", arity, arg, repack_, incorrectOption_);
    adjustedArity_ = arity;

    if (arity == 2) {
        if (arg[0].GetType() == tgrib && arg[1].GetType() == tlist)
            return true;
    }
    return false;
}

Value GribHeaderFunctionW::Execute(int arity, Value* arg)
{
    fieldset* v = nullptr;
    CList* l = nullptr;

    assert(arity >= 2);
    arity = adjustedArity_;
    if (!incorrectOption_.empty()) {
        throw MvException("if supplied, the option parameter must be 'repack'; it is '" +
                          incorrectOption_ + "'");
    }

    arg[0].GetValue(v);
    arg[1].GetValue(l);

    if ((l->Count() % 2) != 0) {
        return Error("%s: the list does not contain an even number of values", Name());
    }

    int save = mars.computeflg;
    mars.computeflg = 0;
    int acc = mars.accuracy;

    // create the resulting fieldset with empty fields
    fieldset* result = new_fieldset(v->count);

    // for each field, apply the changes
    for (int i = 0; i < v->count; i++) {
        field* fRes = nullptr;
        {
            // the input field has to be in packed_mem state at least
            AtLeastPackedMemExpander fx(v->fields[i]);
            // create the resulting fieldset with empty fields
            fRes = copy_field(v->fields[i], true);
            assert(fRes);
            assert(fRes->shape == packed_mem || fRes->shape == expand_mem);
        }

        if (!fRes) {
            return Error("%s: could not create output field", Name());
        }

        if (repack_) {
            set_field_state(fRes, expand_mem);
        }

        switch (type) {
            case GRIB_LONG: {
                for (int j = 0; j < l->Count(); j += 2) {
                    long value;
                    const char* key;
                    (*l)[j].GetValue(key);
                    (*l)[j + 1].GetValue(value);
                    MvGridBase::setLong(fRes, key, value);
                }
                break;
            }

            case GRIB_DOUBLE: {
                for (int j = 0; j < l->Count(); j += 2) {
                    double value;
                    const char* key;
                    (*l)[j].GetValue(key);
                    (*l)[j + 1].GetValue(value);
                    MvGridBase::setDouble(fRes, key, value);
                }
                break;
            }

            case GRIB_STRING: {
                for (int j = 0; j < l->Count(); j += 2) {
                    const char* value;
                    const char* key;
                    (*l)[j].GetValue(key);
                    (*l)[j + 1].GetValue(value);
                    std::string valueString = std::string(value);
                    MvGridBase::setString(fRes, key, valueString);
                }
                break;
            }

            case GRIB_LONG_ARRAY: {
                for (int j = 0; j < l->Count(); j += 2) {
                    CVector *value;
                    const char* key;
                    (*l)[j].GetValue(key);
                    (*l)[j + 1].GetValue(value);
                    int num_vals = value->Count();
                    long *vals_array = new long[num_vals];
                    for (int j = 0; j < num_vals; j++) {
                        vals_array[j] = static_cast<long>(value->getIndexedValue(j));
                    }
                    MvGridBase::setLongArray(fRes, key, vals_array, num_vals);
                    delete [] vals_array;
                }
                break;
            }

            default:
                return Error("GribHeaderFunctionW: bad key type (%d)", type);
        }

        // write result
        set_field(result, fRes, i);
        assert(fRes->shape == packed_mem || fRes->shape == expand_mem);
        save_fieldset(result);
        assert(fRes->shape == packed_file);
    }

    Value x(result);
    mars.computeflg = save;
    mars.accuracy = acc;
    return x;
}

//=============================================================================

/*******************************************************************************
 *
 * Function      : GribHeaderFunctionWGeneric
 *
 * Description   : Writes elements of any type to the GRIB header.
 *
 ******************************************************************************/

class GribHeaderFunctionWGeneric : public Function
{
public:
    GribHeaderFunctionWGeneric(const char* n) :
        Function(n, 2, tgrib, tlist)
    {
        info = "Writes GRIB headers using ecCodes keys";
    }

    int ValidArguments(int arity, Value* arg) override;
    Value Execute(int arity, Value* arg) override;

protected:
    bool repack_{false};
    std::string incorrectOption_;
    int adjustedArity_{0};
};

int GribHeaderFunctionWGeneric::ValidArguments(int arity, Value* arg)
{
    metview::checkStringOption("repack", arity, arg, repack_, incorrectOption_);
    adjustedArity_ = arity;

    if (arity == 2) {
        if (arg[0].GetType() == tgrib && arg[1].GetType() == tlist)
            return true;
    }
    return false;
}

Value GribHeaderFunctionWGeneric::Execute(int arity, Value* arg)
{
    fieldset* v = nullptr;
    CList* l = nullptr;

    assert(arity >= 2);
    arity = adjustedArity_;
    if (!incorrectOption_.empty()) {
        throw MvException("if supplied, the option parameter must be 'repack'; it is '" +
                          incorrectOption_ + "'");
    }

    arg[0].GetValue(v);
    arg[1].GetValue(l);

    if ((l->Count() % 2) != 0) {
        return Error("%s: the list does not contain an even number of values", Name());
    }

    int save = mars.computeflg;
    mars.computeflg = 0;
    int acc = mars.accuracy;

    // create the resulting fieldset with empty fields
    fieldset* result = new_fieldset(v->count);

    // for each field, apply the changes
    for (int i = 0; i < v->count; i++) {
        field* fRes = nullptr;
        {
            AtLeastPackedMemExpander fx(v->fields[i]);
            // create the resulting fieldset with empty fields
            fRes = copy_field(v->fields[i], true);
            assert(fRes);
            assert(fRes->shape == packed_mem || fRes->shape == expand_mem);
        }

        if (!fRes) {
            return Error("%s: could not create output field", Name());
        }

        if (repack_) {
            set_field_state(fRes, expand_mem);
        }

        // for each key/value pair, set them in the field
        for (int j = 0; j < l->Count(); j += 2) {
            const char* key = nullptr;
            vtype valueType;

            (*l)[j].GetValue(key);
            valueType = (*l)[j + 1].GetType();

            switch (valueType) {
                case tstring: {
                    const char* value;
                    (*l)[j + 1].GetValue(value);
                    std::string valueString = std::string(value);
                    MvGridBase::setString(fRes, key, valueString);
                    break;
                }

                case tnumber: {
                    double value;
                    (*l)[j + 1].GetValue(value);

                    // try to figure out if it's an integer (long) or double
                    double epsilon = 0.000000001;  // a bit arbitrary
                    long numAsInt = (long)(value + epsilon);
                    if (fabs(numAsInt - value) < epsilon) {
                        // close enough - set as integer
                        MvGridBase::setLong(fRes, key, numAsInt);
                    }
                    else {
                        // set as double
                        MvGridBase::setDouble(fRes, key, value);
                    }
                    break;
                }

                default:
                    return Error("grib_set: bad value type - should be string or number", valueType);
            }
        }

        // write result
        set_field(result, fRes, i);
        assert(fRes->shape == packed_mem || fRes->shape == expand_mem);
        save_fieldset(result);
        assert(fRes->shape == packed_file);
    }

    Value x(result);
    mars.computeflg = save;
    mars.accuracy = acc;
    return x;
}


//=============================================================================

class GribMinMaxFunction : public Function
{
    boolean min;

public:
    GribMinMaxFunction(const char* n, boolean m) :
        Function(n, 1, tgrib),
        min(m) {}
    virtual Value Execute(int arity, Value* arg);
};

Value GribMinMaxFunction::Execute(int, Value* arg)
{
    fieldset* v;
    arg[0].GetValue(v);
    boolean b_any_missing = false;


    fieldset* z = copy_fieldset(v, 1, false);
    field* h = get_field(z, 0, expand_mem);
    field* g = get_field(v, 0, expand_mem);


    for (size_t j = 0; j < g->value_count; j++)
        h->values[j] = g->values[j];

    release_field(g);


    b_any_missing = FieldsetContainsMissingValues(v);

    for (int i = 1; i < v->count; i++) {
        field* g = get_field(v, i, expand_mem);

        /* For performance, we have two versions of these comparison loops -
           one that considers missing values (only called if there are any)
           and one that does not (called if there are none). */

        if (min)  // Get the minimum
        {
            if (b_any_missing)  // slower version, avoiding missing values
            {
                for (size_t j = 0; j < g->value_count; j++) {
                    if (!MISSING_VALUE(g->values[j]) && !MISSING_VALUE(h->values[j])) {
                        if (g->values[j] < h->values[j])
                            h->values[j] = g->values[j];
                    }
                    else {
                        SetFieldElementToMissingValue(h, j);
                    }
                }
            }

            else  // faster version, ignoring missing values
            {
                for (size_t j = 0; j < g->value_count; j++)
                    if (g->values[j] < h->values[j])
                        h->values[j] = g->values[j];
            }
        }
        else  // Get the maximum
        {
            if (b_any_missing)  // slower version, avoiding missing values
            {
                for (size_t j = 0; j < g->value_count; j++) {
                    if (!MISSING_VALUE(g->values[j]) && !MISSING_VALUE(h->values[j])) {
                        if (g->values[j] > h->values[j])
                            h->values[j] = g->values[j];
                    }
                    else {
                        SetFieldElementToMissingValue(h, j);
                    }
                }
            }

            else  // faster version, ignoring missing values
            {
                for (size_t j = 0; j < g->value_count; j++)
                    if (g->values[j] > h->values[j])
                        h->values[j] = g->values[j];
            }
        }

        release_field(g);
    }

    release_field(h);
    return Value(z);
}

//=============================================================================
class DumpGribFunction : public Function
{
public:
    DumpGribFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Dumps a fieldset";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value DumpGribFunction::Execute(int, Value* arg)
{
    Value x;
    fieldset* v;
    CList* l = nullptr;

    arg[0].GetValue(v);

    if (v->count > 1)
        l = new CList(v->count);

    for (int i = 0; i < v->count; i++) {
        field* g = get_field(v, i, packed_mem);
        request* r = empty_request(0);
        // grib_to_request(r,g->buffer,g->length);
        /*int e = */ handle_to_request(r, g->handle, nullptr);
        release_field(g);

        if (v->count > 1 && l)
            (*l)[i] = Value(r);
        else
            x = Value(r);
        free_all_requests(r);
    }

    if (v->count > 1)
        x = Value(l);

    return x;
}

//=============================================================================
class SortGribFunction : public Function
{
    using reqp = request*;
    using grbp = field*;
    using chrp = char*;

public:
    SortGribFunction(const char* n) :
        Function(n)
    {
        info = "Sorts a fieldset";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);

protected:
    static void sort(char*, request**, int*, int, char*);
    static char** extract(Value&, int&);
    static char** up(int, const char* = "<");
};

void SortGribFunction::sort(char* parm,
                            request** reqs,
                            int* indix,
                            int count,
                            char* order)
{
    int down = (*order == '>');
    double cmp;

    int i = 0;

    while (i < count - 1) {
        request* r1 = reqs[indix[i]];
        const char* p1 = get_value(r1, parm, 0);
        if (p1 == 0)
            p1 = "";

        int j = i;
        do {
            j++;
            request* r2 = reqs[indix[j]];
            const char* p2 = get_value(r2, parm, 0);
            if (p2 == 0)
                p2 = "";

            if (is_number(p1) && is_number(p2))
                cmp = atof(p1) - atof(p2);
            else
                cmp = strcmp(p1, p2);
            if (down)
                cmp = -cmp;


        } while (cmp <= 0 && j < count - 1);

        if (cmp > 0) {
            int x = indix[j];
            for (int k = j; k > i; k--)
                indix[k] = indix[k - 1];
            indix[i] = x;
        }
        else
            i++;
    }
}

static char* _up(const char* t)
{
    char buf[1024];
    strncpy(buf, t, sizeof(buf) - 1);
    char* s = buf;
    while (*s) {
        if (islower(*s))
            *s = toupper(*s);
        s++;
    }
    return strcache(buf);
}

char** SortGribFunction::extract(Value& v, int& n)
{
    if (v.GetType() == tstring) {
        const char* s;
        v.GetValue(s);
        n = 1;

        chrp* p = new chrp[n];
        p[0] = _up(s);
        return p;
    }

    // It's a list
    CList* l;
    v.GetValue(l);

    n = l->Count();

    chrp* p = new chrp[n];
    for (int i = 0; i < n; i++) {
        const char* s;
        (*l)[i].GetValue(s);
        p[i] = _up(s);
    }
    return p;
}

char** SortGribFunction::up(int n, const char* x)
{
    chrp* p = new chrp[n];
    for (int i = 0; i < n; i++)
        p[i] = strcache(x);
    return p;
}

int SortGribFunction::ValidArguments(int arity, Value* arg)
{
    if (arity < 1 || arity > 3) {
        return false;
    }
    if (arg[0].GetType() != tgrib) {
        return false;
    }
    if (arity > 1) {
        if (arg[1].GetType() != tstring && arg[1].GetType() != tlist)
            return false;
        if (arity > 2) {
            if (arg[2].GetType() != tstring && arg[2].GetType() != tlist)
                return false;

            if (arg[2].GetType() == tlist) {
                if (arg[1].GetType() == tstring)
                    return false;

                CList *l1 = nullptr, *l2 = nullptr;

                arg[1].GetValue(l1);
                arg[2].GetValue(l2);

                if (l2->Count() != 1 && l2->Count() != l1->Count()) {
                    return false;
                }
            }
        }
    }
    return true;
}

/* This should be the one in request.c in libmars */

static char* _names[] = {
    (char*)"DATE",
    (char*)"TIME",
    (char*)"STEP",
    (char*)"NUMBER",
    (char*)"LEVELIST",
    (char*)"PARAM",
};

Value SortGribFunction::Execute(int arity, Value* arg)
{
    fieldset* v = nullptr;
    arg[0].GetValue(v);

    if (v == nullptr) {
        return Error("%s: invalid input fieldset", Name());
    }

    auto** r = new reqp[v->count];
    auto* n = new int[v->count];
    auto** g = new grbp[v->count];

    for (int i = 0; i < v->count; i++) {
        g[i] = get_field(v, i, packed_mem);
        n[i] = i;
        r[i] = empty_request(0);
        handle_to_request(r[i], g[i]->handle, nullptr);
        release_field(g[i]);
    }

    char** names = nullptr;
    char** order = nullptr;
    int count = 0;

    switch (arity) {
        case 1:
            names = _names;
            count = NUMBER(_names);
            order = up(count);
            break;

        case 2:
            names = extract(arg[1], count);
            order = up(count);
            break;

        case 3:
            names = extract(arg[1], count);
            if (arg[2].GetType() == tstring) {
                const char* p = nullptr;
                arg[2].GetValue(p);
                order = up(count, p);
            }
            else {
                order = extract(arg[2], count);
            }
            break;
        default:
            delete[] r;
            delete[] n;
            delete[] g;
            return Error("%s: invalid sort parameters", Name());
    }

    int pos = count;
    while (--pos >= 0) {
        sort(names[pos], r, n, v->count, order[pos]);
    }

    for (int i = 0; i < v->count; i++) {
        free_all_requests(r[i]);
    }

    fieldset* result = new_fieldset(v->count);
    for (int i = 0; i < v->count; i++) {
        auto fOri = get_field(v, n[i], packed_mem);
        auto fRes = copy_field(fOri, false);
        set_field(result, fRes, i);
        save_fieldset(result);
        release_field(fOri);
        assert(fOri->shape == packed_file);
        assert(fRes->shape == packed_file);
    }

    delete[] r;
    delete[] n;
    delete[] g;

    for (int i = 0; i < count; i++) {
        strfree(order[i]);
        if (arity != 1) {
            strfree(names[i]);
        }
    }

    delete[] order;
    if (arity != 1) {
        delete[] names;
    }

    return Value(result);
}

//=============================================================================

class LatLonTransformFunction : public GeoLocationBasedFunction
{
public:
    using DoubleFuncPtr1 = double (*)(double);
    LatLonTransformFunction(const char* n, DoubleFuncPtr1 method, bool checkPole) :
        GeoLocationBasedFunction(n), method_(method), checkPole_(checkPole)
    {
        info = "Generates a field with the cosine of the gridpoint latitudes";
    }
    int ValidArguments(int arity, Value* arg) override;

protected:
    void extractArguments(int, Value*) override {}
    bool compute(MvGridPtr grd) override;

    DoubleFuncPtr1 method_;
    bool checkPole_{false};
};

int LatLonTransformFunction::ValidArguments(int arity, Value* arg)
{
    return (arity == 1 && arg[0].GetType() == tgrib);
}

bool LatLonTransformFunction::compute(MvGridPtr grd)
{
    bool hasMissing = false;
    double latLimit = 90. - 1E-7;
    do {
        if (!checkPole_) {
            grd->value(method_(MvSci::degToRad(grd->lat_y())));
        }
        else {
            if (fabs(grd->lat_y()) > latLimit) {
                grd->value(mars.grib_missing_value);
                hasMissing = true;
            }
            else {
                grd->value(method_(MvSci::degToRad(grd->lat_y())));
            }
        }
    } while (grd->advance());
    return hasMissing;
}


class CosLatFunction : public LatLonTransformFunction
{
public:
    CosLatFunction(const char* n) :
        LatLonTransformFunction(n, std::cos, false)
    {
        info = "Generates a field with the cosine of the gridpoint latitudes";
    }
};

class SinLatFunction : public LatLonTransformFunction
{
public:
    SinLatFunction(const char* n) :
        LatLonTransformFunction(n, std::sin, false)
    {
        info = "Generates a field with the sine of the gridpoint latitudes";
    }
};

class TanLatFunction : public LatLonTransformFunction
{
public:
    TanLatFunction(const char* n) :
        LatLonTransformFunction(n, std::tan, true)
    {
        info = "Generates a field with the tangent of the gridpoint latitudes";
    }
};

//=============================================================================

class SolarZenithAngleFunction : public GeoLocationBasedFunction
{
public:
    SolarZenithAngleFunction(const char* n) :
        GeoLocationBasedFunction(n)
    {
        info = "Computes the solar zenith angle in each gridpoint";
    }
    int ValidArguments(int arity, Value* arg) override;

protected:
    void extractArguments(int arity, Value* arg) override;
    bool compute(MvGridPtr grd) override;
    void updateResult(field*) override;

protected:
    bool toCosine_{false};
    int adjustedArity_{0};
    std::string incorrectOption_;
};

int SolarZenithAngleFunction::ValidArguments(int arity, Value* arg)
{
    metview::checkStringOption("to_cosine", arity, arg, toCosine_, incorrectOption_);
    adjustedArity_ = arity;

    switch (arity) {
        case 1:
            if (arg[0].GetType() != tgrib)
                return false;
            break;
        default:
            return false;
    }

    return true;
}

void SolarZenithAngleFunction::extractArguments(int arity, Value* /*arg*/)
{
    assert(arity >= 1);
    if (!incorrectOption_.empty()) {
        throw MvException("if supplied, the option parameter must be 'missing'; it is '" +
                          incorrectOption_ + "'");
    }
}

bool SolarZenithAngleFunction::compute(MvGridPtr grd)
{
    bool hasMissing = false;

    // solar declination
    // TODO: what to do if dt is invalid?
    // in the first place how can we detect that it is invalid?

    double dt = grd->validityDateTime();
    MvDate vdt{dt};
    auto declInRad = MvSci::solarDeclinationInRad(dt);

    // hour angle at Greenwich Meridian
    double hourAngleGM = static_cast<double>(vdt.Hour()) * 15. - 180.;
    if (declInRad < -2 || fabs(hourAngleGM) > 180.001) {
        grd->setAllValuesToMissing(false);
        hasMissing = true;
    }
    else {
        do {
            if (!MISSING_VALUE(grd->value())) {
                auto rv = MvSci::cosineSolarZenithAngle(grd->lat_y(), grd->lon_x(),
                                                        declInRad, hourAngleGM);
                if (toCosine_) {
                    grd->value(rv);
                }
                else {
                    grd->value(MvSci::radToDeg(acos(rv)));
                }
            }
            else {
                grd->value(mars.grib_missing_value);
                hasMissing = true;
            }
        } while (grd->advance());
    }
    return hasMissing;
}

void SolarZenithAngleFunction::updateResult(field* fld)
{
    if (toCosine_) {
        MvGridBase::setLong(fld, "paramId", 214001);
    }
    else if (MvGridBase::getLong(fld, "edition") == 2) {
        MvGridBase::setLong(fld, "paramId", 260225);
    }
    MvGridBase::setLong(fld, "generatingProcessIdentifier", 128);
}

//=============================================================================

class AbsVortFunction : public GeoLocationBasedFunction
{
public:
    AbsVortFunction(const char* n) :
        GeoLocationBasedFunction(n)
    {
        info = "Computes the absolute vorticity";
    }
    int ValidArguments(int arity, Value* arg) override;

protected:
    void extractArguments(int /*arity*/, Value* /*arg*/) override {}
    bool compute(MvGridPtr grd) override;
    void updateResult(field*) override;
};

int AbsVortFunction::ValidArguments(int arity, Value* arg)
{
    return (arity == 1 && arg[0].GetType() == tgrib);
}

bool AbsVortFunction::compute(MvGridPtr grd)
{
    bool hasMissing = false;
    const double omega = 2. * 7.29211508E-5;

    do {
        if (!MISSING_VALUE(grd->value())) {
            grd->value(omega * std::sin(MvSci::degToRad(grd->lat_y())) + grd->value());
        }
        else {
            grd->value(mars.grib_missing_value);
            hasMissing = true;
        }
    } while (grd->advance());

    return hasMissing;
}

void AbsVortFunction::updateResult(field* fld)
{
    // 3014 = "absv"
    MvGridBase::setLong(fld, "paramId", 3041);
    MvGridBase::setLong(fld, "generatingProcessIdentifier", 128);
}

//=============================================================================

class BoundingBoxFunction : public Function
{
public:
    BoundingBoxFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Returns the bounding box as a [west,north,east,south] list for each field";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value BoundingBoxFunction::Execute(int /*arity*/, Value* arg)
{
    fieldset* fs;
    arg[0].GetValue(fs);  // get the fieldset variable

    auto* l = new CList(fs->count);  // initialise the resulting list

    for (int i = 0; i < fs->count; i++) {
        field* h = GetIndexedFieldWithAtLeastPackedMem(fs, i);
        auto* mvf = new MvField(h);
        std::vector<double> v;
        if (mvf->mvGrid()) {
            mvf->mvGrid()->boundingBox(v);
            auto* vec4 = new CVector(v);
            (*l)[i] = vec4;
        }
        else {
            (*l)[i] = Value();
        }
    }

    // if only one field, then just return a single list, otherwise return a list of lists
    if (l->Count() > 1)
        return Value(l);
    else {
        Value single((*l)[0]);
        delete l;
        return single;
    }

    return Value();
}


//=============================================================================

class GridValsFunction : public Function
{
    bool deprecated;
    const char* newName;

public:
    GridValsFunction(const char* n, bool d, const char* nn = nullptr) :
        Function(n, 1, tgrib),
        deprecated(d),
        newName(nn)
    {
        info = "Returns the grid point values as a vector (or list of vectors).";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value GridValsFunction::Execute(int /*arity*/, Value* arg)
{
    DeprecatedMessage(deprecated, "fieldset", newName);

    fieldset* fs;
    int i;
    CList* l = nullptr;
    Value d;

    arg[0].GetValue(fs);  // the first argument is the fieldset


    // if more than 1 field, then the result will be a list of vectors

    if (fs->count > 1)
        l = new CList(fs->count);


    for (i = 0; i < fs->count; i++)  // for each field...
    {
        field* g = get_field(fs, i, expand_mem);
        auto* z = new CVector(g->value_count);

        if (MISSING_VALUE(z->MissingValueIndicator()))  // is the vector missing value the same as GRIB?
        {
            // optimised - no need to handle missing values separately
            z->CopyValuesFromDoubleArray(0, g->values, 0, g->value_count);
        }
        else {
            for (size_t j = 0; j < g->value_count; j++)
                if (MISSING_VALUE(g->values[j]))
                    z->setIndexedValueToMissing(j);
                else
                    z->setIndexedValue(j, g->values[j]);
        }

        release_field(g);
        if (fs->count > 1 && l)
            (*l)[i] = Value(z);
        else
            d = Value(z);
    }

    if (fs->count > 1)
        return Value(l);

    return d;
}


//=============================================================================

class GridLatLonsFunction : public Function
{
    eGridLatLonsType type;
    bool deprecated;
    const char* newName;

public:
    GridLatLonsFunction(const char* n, eGridLatLonsType t, bool d, const char* nn = nullptr) :
        Function(n, 1, tgrib),
        type(t),
        deprecated(d),
        newName(nn)
    {
        if (type == GLL_LATS)
            info = "Returns the grid point latitudes as a vector (or list of vectors)";
        else if (type == GLL_LONS)
            info = "Returns the grid point longitudes as a vector (or list of vectors)";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value GridLatLonsFunction::Execute(int /*arity*/, Value* arg)
{
    DeprecatedMessage(deprecated, "fieldset", newName);

    fieldset* fs;
    int i;
    CList* l = 0;
    Value d;

    arg[0].GetValue(fs);  // the first argument is the fieldset


    // if more than 1 field, then the result will be a list of vectors

    if (fs->count > 1)
        l = new CList(fs->count);


    for (i = 0; i < fs->count; i++)  // for each field...
    {
        std::unique_ptr<MvGridBase> grid(MvGridFactory(fs->fields[i]));
        if (!grid->hasLocationInfo())
            return Error("gridlats/gridlons: unimplemented or spectral data - unable to extract location data");

        auto* z = new CVector(grid->length());

        for (int j = 0; j < grid->length(); ++j) {
            if (type == GLL_LATS)
                z->setIndexedValue(j, grid->lat_y());
            else
                z->setIndexedValue(j, grid->lon_x());
            grid->advance();  // move to the next point
        }

        if (fs->count > 1)
            (*l)[i] = Value(z);
        else
            d = Value(z);
    }


    if (fs->count > 1)
        return Value(l);

    return d;
}


//=============================================================================

class SetGridValsFunction : public Function
{
    bool deprecated{false};
    const char* newName;
    bool option_given{false};  // did the user supply a string option parameter?
public:
    SetGridValsFunction(const char* n, bool d, const char* nn = nullptr) :
        Function(n),
        deprecated(d),
        newName(nn)
    {
        info = "Sets the grid point values from a vector (or list of vectors).";
    }
    virtual Value Execute(int arity, Value* arg);
    int ValidArguments(int arity, Value* arg);
};


int SetGridValsFunction::ValidArguments(int arity, Value* arg)
{
    option_given = false;

    if (arity != 2 && arity != 3)
        return false;

    if (arg[0].GetType() != tgrib)  //-- 1. argument: fieldset
        return false;

    if (arg[1].GetType() != tlist && arg[1].GetType() != tvector)  //-- 2. argument: list or vector
        return false;

    if (arity == 3) {
        if (arg[2].GetType() == tstring) {
            option_given = true;
            return true;
        }
        else
            return false;
    }

    return true;
}


// SetGridValsFunction
// input is a fieldset and either a vector or a list of vectors
//   if a single vector, then its values are applied to all the fields;
//   if a list, then the number of component vectors must be the same as
//     the number of fields, list[1] goes into fs[1], and so on.

Value SetGridValsFunction::Execute(int /*arity*/, Value* arg)
{
    DeprecatedMessage(deprecated, "fieldset", newName);

    fieldset* fs;
    CVector* v_in;
    int i;
    CList* list = 0;
    Value d;
    argtype input_type = tvector;
    const char* option_name;
    bool resize = false;

    if (option_given) {
        arg[2].GetValue(option_name);
        if (strcmp(option_name, "resize")) {
            return Error("set_gridvals: if supplied, the third parameter must be 'resize'; it is '%s'", option_name);
        }
        else {
            resize = true;
        }
    }


    arg[0].GetValue(fs);  // the first argument is the fieldset


    if (arg[1].GetType() == tlist) {
        arg[1].GetValue(list);  // the first argument is a list of vectors

        if (list->Count() != fs->count)  // only perform if num vectors is num fields
        {
            return Error("set_gridvals: list of input vectors (%d) should have same number as fieldset has fields (%d).", list->Count(), fs->count);
        }

        input_type = tlist;
    }
    else {
        arg[1].GetValue(v_in);  // the first argument is a single vector
    }


    fieldset* z = copy_fieldset(fs, fs->count, false);  // the output fieldset - intitially a copy of the input


    for (i = 0; i < fs->count; i++)  // for each input field...
    {
        CVector* v;

        if (input_type == tvector) {
            v = v_in;  // use the same vector for all fields
        }
        else  // must be tlist; get the i'th element and check it's a vector
        {
            Value& val = (*list)[i];

            if (val.GetType() == tvector)
                val.GetValue(v);
            else
                return Error("set_gridvals: element (%d) of input list is not a vector.", i + 1);
        }


        // now that 'v' is a vector, check that it has the same number of elements as the field
        // - is this check necessary? Perhaps the user might want to only push a certain
        //   number of points not the whole lot...

        field* h = get_field(z, i, expand_mem);

        // if the user wants to resize the array of values in the GRIB, then we will allow that here

        if (resize) {
            h->value_count = v->Count();
            release_mem(h->values);
            h->values = (double*)reserve_mem(sizeof(double) * h->value_count);
        }
        else if (v->Count() != (signed)h->value_count)  // otherwise, issue an error
        {
            return Error("set_gridvals: input vector has %d points, field has %d - they should be the same.", v->Count(), h->value_count);
        }


        // now push its values into the field

        for (size_t j = 0; j < h->value_count; j++) {
            if (v->isIndexedValueMissing(j))
                h->values[j] = mars.grib_missing_value;
            else
                h->values[j] = v->getIndexedValue(j);

            if (!h->bitmap && MISSING_VALUE(h->values[j]))  // ensure the bitmap flag is set
                h->bitmap = true;
        }

        release_field(h);
    }


    return Value(z);  // return the resultant fieldset
}


//=============================================================================

class GribDateFunction : public Function
{
    eGribDateType type;

public:
    GribDateFunction(const char* n, eGribDateType t) :
        Function(n, 1, tgrib),
        type(t)
    {
        if (type == GDT_BASE)
            info = "Returns the base date(s) of a given fieldset";
        else if (type == GDT_VALID)
            info = "Returns the valid date(s) of a given fieldset";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value GribDateFunction::Execute(int, Value* arg)
{
    fieldset* fs;
    arg[0].GetValue(fs);  // get the fieldset variable

    auto* l = new CList(fs->count);  // initialise the resulting list

    for (int i = 0; i < fs->count; i++) {
        field* h = GetIndexedFieldWithAtLeastPackedMem(fs, i);
        auto* mvfield = new MvField(h);
        double baseDate = mvfield->yyyymmddFoh();
        double dateAsNumber = baseDate;
        Date d(dateAsNumber);
        if (type == GDT_VALID)  // valid_date requested? then add the step
        {
            double stepFoh = mvfield->stepFoh();
            d = d + stepFoh;
        }

        (*l)[i] = d;
    }


    // if only one field, then just return a single date, otherwise return the list
    if (l->Count() > 1)
        return Value(l);
    else {
        Value single((*l)[0]);
        delete l;
        return single;
    }
}

//=============================================================================

class AccumulateFunction : public Function
{
    boolean average;

public:
    AccumulateFunction(const char* n, boolean a) :
        Function(n, 1, tgrib),
        average(a)
    {
        if (average)
            info = "Averages the values in a given field";
        else
            info = "Adds up the values in a given field";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value AccumulateFunction::Execute(int, Value* arg)
{
    fieldset* v;
    int i;
    int nNumValidPoints;
    CList* l = nullptr;
    double d;
    Value returnValue;

    arg[0].GetValue(v);

    if (v->count > 1)
        l = new CList(v->count);

    for (i = 0; i < v->count; i++) {
        field* g = get_field(v, i, expand_mem);

        d = 0;
        nNumValidPoints = 0;

        for (size_t j = 0; j < g->value_count; j++) {
            if (!MISSING_VALUE(g->values[j])) {
                d += g->values[j];
                nNumValidPoints++;
            }
        }


        if (average) {
            if (nNumValidPoints != 0)
                returnValue = Value(d / nNumValidPoints);
            else
                returnValue = Value();  // nil
        }
        else {
            if (nNumValidPoints == 0)
                returnValue = Value();  // nil
            else
                returnValue = Value(d);
        }


        release_field(g);

        if (v->count > 1 && l)
            (*l)[i] = returnValue;
    }

    if (v->count > 1)
        return Value(l);

    return returnValue;
}

//============================================================

class LookupFunction : public Function
{
public:
    LookupFunction(const char* n) :
        Function(n, 2, tgrib, tlist)
    {
        info = "Builds an output fieldset using the values in the first as indices into the second";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value LookupFunction::Execute(int, Value* arg)
{
    fieldset* v;
    CList* l;

    arg[0].GetValue(v);
    arg[1].GetValue(l);

    fieldset* z = copy_fieldset(v, v->count, false);

    int count = l->Count();
    auto* values = new double[l->Count()];
    int i;
    for (i = 0; i < count; i++)
        (*l)[i].GetValue(values[i]);

    for (i = 0; i < v->count; i++) {
        field* g = get_field(v, i, expand_mem);
        field* h = get_field(z, i, expand_mem);

        for (size_t j = 0; j < g->value_count; j++) {
            int n = (int)(g->values[j]);
            if (n < 0 || n >= count) {
                delete[] values;
                return Error("lookup: value out of range");
            }
            h->values[j] = values[n];
        }

        release_field(g);
        if (((i + 1) % 10) == 0)
            save_fieldset(z);
    }

    save_fieldset(z);

    delete[] values;
    return Value(z);
}

//=============================================================================
//============================================================

class LookupFunction2 : public Function
{
public:
    LookupFunction2(const char* n) :
        Function(n, 2, tgrib, tgrib) {}
    virtual Value Execute(int arity, Value* arg);
};

Value LookupFunction2::Execute(int, Value* arg)
{
    fieldset* v;
    fieldset* w;
    int i;

    arg[0].GetValue(v);
    arg[1].GetValue(w);

    fieldset* z = copy_fieldset(v, v->count, false);
    int count = w->count;

    int prev = -1;
    field* k = nullptr;

    for (i = 0; i < v->count; i++) {
        field* g = get_field(v, i, expand_mem);
        field* h = get_field(z, i, expand_mem);

        for (size_t j = 0; j < g->value_count; j++) {
            int n = (int)(g->values[j]);
            if (n < 0 || n >= count) {
                return Error("lookup: value out of range");
            }

            if (n != prev) {
                k = get_field(w, n, expand_mem);
                prev = n;
                if (j >= k->value_count) {
                    return Error("lookup: fields mismatch");
                }
            }
            h->values[j] = k->values[j];
        }

        release_field(g);
        if (((i + 1) % 10) == 0)
            save_fieldset(z);
    }

    if (k) {
        release_field(k);
    }

    save_fieldset(z);

    return Value(z);
}

//============================================================


/*
  FindIndexesFunction

  Example: if these are our inputs:

  GRIB:  10, 20, 30, 40     VECTOR:  | 5, 10, 15, 20, 25, 30  |
         15, 25, 35, 45
          8, 4,  20, 11


  then our output would be a new GRIB, with values equal to the input values'
  positions in the input vector (zero-based, for consistency with 'lookup'
  function).
  The input vector MUST be sorted in ascending order.
  Values outside the range of the vector's values will be set to the min(0)
  or max index.
  A value lying between two values in the vector will use the index of the nearest
  value; if equidistant, then the higher value is used.

  GRIB:  1, 3, 5, 5
         2, 4, 5, 5
         1, 0, 3, 1

  A missing value in the input field will result in a missing value in
  the output field.

*/


class FindIndexesFunction : public Function
{
    bool option_given{false};  // did the user supply a string option parameter?

public:
    FindIndexesFunction(const char* n) :
        Function(n)
    {
        info = "Builds an output fieldset containing each gridpoint's indexed position in the given vector";
    }
    virtual int ValidArguments(int arity, Value* arg);
    virtual Value Execute(int arity, Value* arg);
};

int FindIndexesFunction::ValidArguments(int arity, Value* arg)
{
    option_given = false;

    if (arity != 2 && arity != 3)
        return false;
    if (arg[0].GetType() != tgrib)
        return false;
    if (arg[1].GetType() != tvector)
        return false;

    if (arity == 3) {
        if (arg[2].GetType() == tstring) {
            option_given = true;
            return true;
        }
        else
            return false;
    }

    return true;
}

Value FindIndexesFunction::Execute(int, Value* arg)
{
    fieldset* fs;
    CVector* v;
    int i;
    const char* option_name;
    bool interpolate = false;

    arg[0].GetValue(fs);
    arg[1].GetValue(v);


    if (option_given) {
        arg[2].GetValue(option_name);
        if (strcmp(option_name, "interpolate")) {
            return Error("indexes: if supplied, the third parameter must be 'interpolate'; it is '%s'", option_name);
        }
        else {
            interpolate = true;
        }
    }

    fieldset* z = copy_fieldset(fs, fs->count, false);

    for (i = 0; i < fs->count; i++) {
        field* f = get_field(fs, i, expand_mem);  // input field
        field* g = get_field(z, i, expand_mem);   // output field

        for (size_t j = 0; j < f->value_count; j++) {
            int vindex;
            double result = v->Count() - 1;  // if we don't find it, then choose the last value
            double d = f->values[j];

            if (!MISSING_VALUE(d)) {
                // find this point's position in the vector

                for (vindex = 0; vindex < v->Count(); vindex++) {
                    double dvector = v->getIndexedValue(vindex);

                    // have we gone past this value in the vector?

                    if (dvector >= d) {
                        // only at the first value in the vector? if so, cap at index 0
                        if (vindex == 0) {
                            result = 0;
                        }
                        else {
                            // find the closest point - the current or the previous one

                            double dvectorprev = v->getIndexedValue(vindex - 1);
                            double distPrev = d - dvectorprev;

                            if (interpolate) {
                                // result is previous index plus proportion of the way to the next one
                                result = (vindex - 1) + (distPrev / (dvector - dvectorprev));
                            }
                            else {
                                double distNext = dvector - d;
                                if (distPrev < distNext)
                                    result = vindex - 1;
                                else
                                    result = vindex;
                            }
                        }

                        // we found the value, so we can exit this inner loop and move
                        // to the next point in the GRIB field

                        break;
                    }
                }

                g->values[j] = result;  // put the resulting index into the output field
            }

            else  // input field point is missing value, so set result to missing
            {
                SetFieldElementToMissingValue(g, j);
            }
        }

        release_field(f);

        if (((i + 1) % 10) == 0)
            save_fieldset(z);
    }

    save_fieldset(z);

    return Value(z);
}
//=============================================================================

class IntegrateFunction : public Function
{
public:
    IntegrateFunction(const char* n) :
        Function(n)
    {
        info = "Computes the average weighted by the gridcell area for each field in fieldset";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int IntegrateFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    if (arity < 1)
        return false;
    if (arg[0].GetType() != tgrib)
        return false;

    switch (arity) {
        case 5:
            for (i = 1; i < 5; i++)
                if (arg[i].GetType() != tnumber)
                    return false;
            break;

        case 2:
            switch (arg[1].GetType()) {
                case tlist:
                    arg[1].GetValue(l);
                    if (l->Count() != 4)
                        return false;
                    for (i = 0; i < 4; i++)
                        if ((*l)[i].GetType() != tnumber)
                            return false;
                    return true;

                case tgrib:
                    return true;

                default:
                    return false;
            }

            /* break; */

        case 1:
            break;

        default:
            return false;
    }
    return true;
}


Value IntegrateFunction::Execute(int arity, Value* arg)
{
    fieldset* v;
    fieldset* z = 0;
    int i;
    CList* l;

    double d[4];

    double& n = d[0];
    double& w = d[1];
    double& s = d[2];
    double& e = d[3];

    n = 90;
    s = -90;
    w = 0;
    e = 360;

    arg[0].GetValue(v);

    if (arity == 2) {
        if (arg[1].GetType() == tgrib) {
            arg[1].GetValue(z);
        }
        else {
            getListArgAsArrayOfNumbers(arg, 1, 4, &d[0]);
        }
    }
    else if (arity != 1)
        getNumberArgsAsArrayOfNumbers(arg, 1, 4, d);

    while (w > e)
        w -= 360.0;
    MvGeoBox geoArea(n, w, s, e);


    if (z && z->count != 1 && z->count != v->count)
        return Error(
            "integrate: wrong number of fields in mask argument %d, "
            "it should be 1 or %d",
            z->count, v->count);

    if (v->count > 1)
        l = new CList(v->count);

    Value returnVal;

    for (i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->hasLocationInfo())
            return Error("integrate: unimplemented or spectral data - unable to extract location data");

        std::unique_ptr<MvGridBase> maskgrd;
        if (z) {
            maskgrd = std::unique_ptr<MvGridBase>(MvGridFactory(z->fields[z->count > 1 ? i : 0]));
        }

        if (maskgrd.get() && !maskgrd->isEqual(grd.get()))
            return Error("integrate: field and mask don't match");

        double wght = 0;
        double sum = 0;
        int pcnt = 0;

        for (int j = 0; j < grd->length(); ++j) {
            int pointOk;
            if (maskgrd.get())
                pointOk = grd->hasValue() && maskgrd->value() != 0.0;
            else
                pointOk = grd->hasValue() && geoArea.isInside(grd->lat_y(), grd->lon_x());

            if (pointOk) {
                double w1 = grd->weight();  //-- for dbg
                double v1 = grd->value();   //-- for dbg

                wght += w1;
                sum += w1 * v1;
                ++pcnt;
            }

            grd->advance();
            if (maskgrd.get())
                maskgrd->advance();
        }


        if (wght) {
            double val = sum / wght;
            returnVal = Value(val);
        }
        else {
            returnVal = Value();  // nil
            marslog(LOG_WARN, "integrate: unable to integrate the field");
        }

        if (v->count > 1)  // multiple fields? add value to a list
            (*l)[i] = returnVal;

        // std::cout << " integrated over " << pcnt << " points" << std::endl;
    }  // end 'for each field'

    if (v->count > 1)
        return Value(l);

    return returnVal;
}

class MinMaxAreaFunction : public Function
{
    boolean min;

public:
    MinMaxAreaFunction(const char* n, boolean m) :
        Function(n),
        min(m)
    {
        if (min)
            info = "Computes the minimum value in a given area";
        else
            info = "Computes the maximum value in a given area";
    }

    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};


int MinMaxAreaFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;
    if (arg[0].GetType() != tgrib)
        return false;
    if (arg[1].GetType() != tlist)
        return false;

    return true;
}


Value MinMaxAreaFunction::Execute(int, Value* arg)
{
    fieldset* v;
    int i;
    CList* l;

    double d[4];

    double& n = d[0];
    double& w = d[1];
    double& s = d[2];
    double& e = d[3];

    arg[0].GetValue(v);


    arg[1].GetValue(l);
    for (i = 0; i < 4; i++)
        (*l)[i].GetValue(d[i]);

    while (w > e)
        w -= 360.0;
    MvGeoBox geoArea(n, w, s, e);


    double val = DBL_MAX;
    boolean first_found = true;
    int pointOk;

    for (i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->hasLocationInfo())
            return Error("minvalue/maxvalue: unimplemented or spectral data - unable to extract location data");

        for (int j = 0; j < grd->length(); ++j) {
            pointOk = grd->hasValue() && geoArea.isInside(grd->lat_y(), grd->lon_x());

            if (pointOk) {
                if (first_found) {
                    val = grd->value();
                    first_found = false;
                }

                if (min) {
                    if (grd->value() < val)
                        val = grd->value();
                }
                else {
                    if (grd->value() > val)
                        val = grd->value();
                }
            }

            grd->advance();
        }
    }

    if (val == DBL_MAX)
        return Value();  // nil, because there were no valid values
    else
        return Value(val);
}
//=============================================================================

class InterpolateFunction : public Function
{
public:
    InterpolateFunction(const char* n) :
        Function(n)
    {
        info = "Interpolates field values to the specified location";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int InterpolateFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    if (arity < 1)
        return false;
    if (arg[0].GetType() != tgrib)
        return false;

    switch (arity) {
        case 3:
            if (arg[1].GetType() == tnumber && arg[2].GetType() == tnumber)
                return true;
            if (arg[1].GetType() == tvector && arg[2].GetType() == tvector)
                return true;
            return false;  // not both numbers or vectors? Then not valid.
            break;

        case 2:
            switch (arg[1].GetType()) {
                case tlist:
                    arg[1].GetValue(l);
                    if (l->Count() != 2)
                        return false;
                    for (i = 0; i < 2; i++)
                        if ((*l)[i].GetType() != tnumber)
                            return false;
                    return true;

                default:
                    return false;
            }

            /* break; */

        default:
            return false;
    }
    return true;
}


Value InterpolateFunction::Execute(int arity, Value* arg)
{
    extern double interpolate(field * Grib, double y, double x);

    fieldset* v;
    int i;
    CList* l;

    double d[2];         // if single coordinates
    double& lat = d[0];  // if single coordinates
    double& lon = d[1];  // if single coordinates
    CVector* vd[2];      // if vector of coordinates
    CVector* vlat;       // if vector of coordinates
    CVector* vlon;       // if vector of coordinates
    CVector* vvals;      // if vector of coordinates
    double a;
    Value returnVal;
    vtype input_type = tnumber;


    arg[0].GetValue(v);

    if (arity == 2) {
        getListArgAsArrayOfNumbers(arg, 1, 2, &d[0]);
    }
    else {
        input_type = arg[1].GetType();

        if (input_type == tnumber)  // get 2 coordinates as numbers
            getNumberArgsAsArrayOfNumbers(arg, 1, 2, d);

        else  // get 2 vectors of  coordinates
        {
            for (i = 0; i < 2; i++)
                arg[i + 1].GetValue(vd[i]);

            vlat = vd[0];
            vlon = vd[1];

            if (vlat->Count() != vlon->Count()) {
                return Error("interpolate: latitude (%d) and longitude (%d) vectors are of different size; they must be the same.",
                             vlat->Count(), vlon->Count());
            }
        }
    }


    // if a single input coordinate, then put it into 1-element vectors so that we can
    // use the same code to access the coordinates whether they are given as numbers
    // or vectors
    if (input_type == tnumber) {
        vlat = new CVector(1);
        vlon = new CVector(1);

        vlat->setIndexedValue(0, lat);
        vlon->setIndexedValue(0, lon);
    }


    if (v->count > 1)
        l = new CList(v->count);

    for (i = 0; i < v->count; i++) {
#if NO_MVGRID
        field* g = get_field(v, i, expand_mem);
        gribsec2_ll* s2 = (gribsec2_ll*)&g->ksec2[0];

        if (s2->data_rep != GRIB_LAT_LONG)
            return Error("interpolate: Field is not lat/long");

        a = interpolate(g, lat, lon);


        // a will be DBL_MAX if there is no valid corresponding grid value.

        if (a == DBL_MAX)
            returnVal = Value();  // nil
        else
            returnVal = Value(a);


        if (v->count > 1)
            (*l)[i] = returnVal;

        release_field(g);
#else

        if (input_type == tvector)  // create a new output vector for this field?
            vvals = new CVector(vlat->Count());

        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));

        if (!grd->hasLocationInfo()) {
            return Error("interpolate: unimplemented or spectral data - unable to extract location data");
        }


        for (long j = 0; j < vlat->Count(); j++) {
            double vlatval = vlat->getIndexedValue(j);
            double vlonval = vlon->getIndexedValue(j);
            a = grd->interpolatePoint(vlatval, vlonval);


            // a will be DBL_MAX if there is no valid corresponding grid value.

            if (a == DBL_MAX)
                if (input_type == tvector)
                    vvals->setIndexedValueToMissing(j);
                else
                    returnVal = Value();  // nil
            else if (input_type == tvector)
                vvals->setIndexedValue(j, a);
            else
                returnVal = Value(a);
        }  // end input coordinate loop


        if (input_type == tvector)
            returnVal = Value(vvals);


        if (v->count > 1) {
            (*l)[i] = returnVal;
        }
#endif
    }


    // cleanup
    if (input_type == tnumber) {
        delete vlat;
        delete vlon;
    }


    if (v->count > 1)
        return Value(l);

    return returnVal;
}


//=============================================================================

class SurroundingPointsFunction : public Function
{
public:
    SurroundingPointsFunction(const char* n) :
        Function(n), optionString_("all")
    {
        info = "Returns the indexes of the four surrounding grid points";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);

protected:
    std::string optionString_;
};

int SurroundingPointsFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    if (arity < 1)
        return false;
    if (arg[0].GetType() != tgrib)
        return false;

    // the last arg can be an optional string parameter
    if (arg[arity - 1].GetType() == tstring) {
        const char* tmpCh;
        arg[arity - 1].GetValue(tmpCh);
        if (!tmpCh || strcmp(tmpCh, optionString_.c_str()) != 0) {
            return false;
        }
        arity--;
    }

    switch (arity) {
        case 3:
            if (arg[1].GetType() == tnumber && arg[2].GetType() == tnumber)
                return true;
            if (arg[1].GetType() == tvector && arg[2].GetType() == tvector)
                return true;
            return false;  // not both numbers or vectors? Then not valid.
            break;

        case 2:
            switch (arg[1].GetType()) {
                case tlist:
                    arg[1].GetValue(l);
                    if (l->Count() != 2)
                        return false;
                    for (i = 0; i < 2; i++)
                        if ((*l)[i].GetType() != tnumber)
                            return false;
                    return true;

                default:
                    return false;
            }

            /* break; */

        default:
            return false;
    }
    return true;
}


/* ComparePointsByValue - helper function used for sorting */
bool ComparePointsByValue(const MvGridPoint& left, const MvGridPoint& right)
{
    return left.value_ < right.value_;
}

Value SurroundingPointsFunction::Execute(int arity, Value* arg)
{
    fieldset* v;
    int i, j;
    CList* l;

    double d[2];              // if single coordinate
    double& lat = d[0];       // if single coordinate
    double& lon = d[1];       // if single coordinate
    CVector* vd[2];           // if vector of coordinates
    CVector* vlat = nullptr;  // if vector of coordinates
    CVector* vlon = nullptr;  // if vector of coordinates
    CList* lvals;             // if list of vectors of coordinates
    Value returnVal;
    bool collectAll = false;
    vtype input_type = tnumber;


    arg[0].GetValue(v);

    if (arg[arity - 1].GetType() == tstring) {
        const char* tmpCh;
        arg[arity - 1].GetValue(tmpCh);
        if (tmpCh && strcmp(tmpCh, optionString_.c_str()) == 0) {
            collectAll = true;
        }
        arity--;
    }

    if (arity == 2)  // fieldset + list of single lat,lon
        getListArgAsArrayOfNumbers(arg, 1, 2, &d[0]);
    else {
        input_type = arg[1].GetType();

        if (input_type == tnumber)  // get 2 coordinates as numbers
            getNumberArgsAsArrayOfNumbers(arg, 1, 2, d);

        else  // get 2 vectors of coordinates
        {
            for (i = 0; i < 2; i++)
                arg[i + 1].GetValue(vd[i]);

            vlat = vd[0];
            vlon = vd[1];

            if (vlat == nullptr || vlon == nullptr) {
                return Error("Surrounding_points_indexes: no latute or longutude vector is specified");
            }

            if (vlat->Count() != vlon->Count()) {
                return Error("surrounding_points_indexes: latitude (%d) and longitude (%d) vectors are of different size; they must be the same.",
                             vlat->Count(), vlon->Count());
            }
        }
    }


    // if a single input coordinate, then put it into 1-element vectors so that we can
    // use the same code to access the coordinates whether they are given as numbers
    // or vectors
    if (input_type == tnumber) {
        vlat = new CVector(1);
        vlon = new CVector(1);

        vlat->setIndexedValue(0, lat);
        vlon->setIndexedValue(0, lon);
    }


    // if more than one field, then the result will be a list
    if (v->count > 1)
        l = new CList(v->count);

    int baseIndex = Context::BaseIndex();
    for (i = 0; i < v->count; i++) {
        if (input_type == tvector)  // create a new output vector for this field?
            lvals = new CList(vlat->Count());
        else
            lvals = new CList(1);

        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));

        if (!grd->hasLocationInfo()) {
            return Error("surrounding_points_indexes: unimplemented or spectral data - unable to extract location data");
        }

        for (j = 0; j < vlat->Count(); j++) {
            double vlatval = vlat->getIndexedValue(j);
            double vlonval = vlon->getIndexedValue(j);
            std::vector<MvGridPoint> surroundingPoints;
            if (grd->surroundingGridpoints(vlatval, vlonval, surroundingPoints, collectAll, true)) {
                size_t npoints = surroundingPoints.size();
                assert(npoints > 0);
                auto* vec4 = new CVector(npoints);
                for (size_t i = 0; i < npoints; i++) {
                    vec4->setIndexedValue(i, surroundingPoints[i].index_ + baseIndex);
                }
                (*lvals)[j] = Value(vec4);
            }
            else {
                (*lvals)[j] = Value();  // nil
            }
        }  // end input coordinate loop

        if (input_type == tvector)
            returnVal = lvals;
        else
            returnVal = Value((*(lvals))[0]);

        if (v->count > 1) {
            (*l)[i] = returnVal;
        }
    }  // end fieldset loop


    if (v->count > 1)
        return Value(l);

    return returnVal;
}


//=============================================================================

class NearestGridpointFunction : public Function
{
    bool loc_info;
    std::string optionString_;

public:
    NearestGridpointFunction(const char* n, bool loc_info) :
        Function(n),
        loc_info(loc_info),
        optionString_("valid")
    {
        info = "Returns the nearest grid point value from a field";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int NearestGridpointFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    if (arity < 2)
        return false;
    if (arg[0].GetType() != tgrib)
        return false;

    // the last arg can be an optional string parameter
    if (arg[arity - 1].GetType() == tstring) {
        const char* tmpCh;
        arg[arity - 1].GetValue(tmpCh);
        if (!tmpCh || strcmp(tmpCh, optionString_.c_str()) != 0) {
            return false;
        }
        arity--;
    }

    switch (arity) {
        case 3:
            if (arg[1].GetType() == tnumber && arg[2].GetType() == tnumber)
                return true;
            if (arg[1].GetType() == tvector && arg[2].GetType() == tvector && !loc_info)
                return true;
            return false;  // not both numbers or vectors? Then not valid.
            break;

        case 2:
            switch (arg[1].GetType()) {
                case tlist:
                    arg[1].GetValue(l);
                    if (l->Count() != 2)
                        return false;
                    for (i = 0; i < 2; i++)
                        if ((*l)[i].GetType() != tnumber)
                            return false;
                    return true;
            }
            return false;

        default:
            return false;
    }
    return true;
}


Value NearestGridpointFunction::Execute(int arity, Value* arg)
{
    fieldset* v;
    int i, j;
    CList* l;
    request* r = empty_request(nullptr);

    double d[2];         // if single coordinates
    double& lat = d[0];  // if single coordinates
    double& lon = d[1];  // if single coordinates
    CVector* vd[2];      // if vector of coordinates
    CVector* vlat;       // if vector of coordinates
    CVector* vlon;       // if vector of coordinates
    CVector* vvals;      // if vector of coordinates
    bool nearestValid = false;

    Value returnVal, returnLocVal;
    vtype input_type = tnumber;

    arg[0].GetValue(v);

    if (arg[arity - 1].GetType() == tstring) {
        const char* tmpCh;
        arg[arity - 1].GetValue(tmpCh);
        if (tmpCh && strcmp(tmpCh, optionString_.c_str()) == 0) {
            nearestValid = true;
        }
        arity--;
    }

    if (arity == 2) {
        getListArgAsArrayOfNumbers(arg, 1, 2, &d[0]);
    }
    else {
        input_type = arg[1].GetType();

        if (input_type == tnumber)  // get 2 coordinates as numbers
            getNumberArgsAsArrayOfNumbers(arg, 1, 2, d);

        else  // get 2 vectors of  coordinates
        {
            for (i = 0; i < 2; i++)
                arg[i + 1].GetValue(vd[i]);

            vlat = vd[0];
            vlon = vd[1];

            if (vlat->Count() != vlon->Count()) {
                return Error("nearest_gridpoint: latitude (%d) and longitude (%d) vectors are of different size; they must be the same.",
                             vlat->Count(), vlon->Count());
            }
        }
    }

    // if a single input coordinate, then put it into 1-element vectors so that we can
    // use the same code to access the coordinates whether they are given as numbers
    // or vectors
    if (input_type == tnumber) {
        vlat = new CVector(1);
        vlon = new CVector(1);

        vlat->setIndexedValue(0, lat);
        vlon->setIndexedValue(0, lon);
    }

    // if more than one field, then the result will be a list
    if (v->count > 1 || loc_info)
        l = new CList(v->count);

    MvGridPoint gp;

    for (i = 0; i < v->count; i++) {
        if (input_type == tvector)  // create a new output vector for this field?
            vvals = new CVector(vlat->Count());

        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));

        if (!grd->hasLocationInfo()) {
            return Error("nearest_gridpoint: unimplemented or spectral data - unable to extract location data");
        }

        for (j = 0; j < vlat->Count(); j++) {
            double vlatval = vlat->getIndexedValue(j);
            double vlonval = vlon->getIndexedValue(j);
            gp = grd->nearestGridpoint(vlatval, vlonval, nearestValid);

            if (loc_info) {
                if (gp.value_ == DBL_MAX)    // no grid point (e.g. out of sub-area)
                    returnLocVal = Value();  // nil because there is no nearest grid point
                else {
                    set_value(r, "latitude", "%g", gp.loc_.latitude());
                    set_value(r, "longitude", "%g", gp.loc_.longitude());
                    set_value(r, "index", "%d", gp.index_ + Context::BaseIndex());                                  // +1 to make it 1-based?
                    set_value(r, "distance", "%g", gp.loc_.distanceInMeters(MvLocation(vlatval, vlonval)) / 1000);  // in km

                    if (gp.value_ == mars.grib_missing_value)
                        unset_value(r, "value");  // nil
                    else
                        set_value(r, "value", "%g", gp.value_);
                    returnLocVal = Value(r);
                }
            }
            else  // just return the value, not any location information
            {
                bool ismissing = ((gp.value_ == DBL_MAX) ||                 // no grid point
                                  (gp.value_ == mars.grib_missing_value));  // grid point exists, but missing value
                if (ismissing) {
                    if (input_type == tvector)
                        vvals->setIndexedValueToMissing(j);
                    else
                        returnVal = Value();  // nil
                }
                else {
                    if (input_type == tvector)
                        vvals->setIndexedValue(j, gp.value_);
                    else
                        returnVal = Value(gp.value_);
                }
            }

        }  // end input coordinate loop


        if (input_type == tvector)
            returnVal = Value(vvals);


        if (loc_info) {
            (*l)[i] = returnLocVal;
        }
        else if (v->count > 1) {
            (*l)[i] = returnVal;
        }

    }  // end fieldset loop


    if (input_type == tnumber)  // cleanup
    {
        delete vlat;
        delete vlon;
    }


    if (v->count > 1 || loc_info)
        return Value(l);

    return returnVal;
}

//=============================================================================

class FindFunction : public Function
{
public:
    FindFunction(const char* n) :
        Function(n)
    {
        info = "Finds values in field";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int FindFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    if (arity < 2)
        return false;

    if (arg[0].GetType() != tgrib)
        return false;

    if (arg[1].GetType() != tnumber && arg[1].GetType() != tlist)
        return false;

    if (arg[1].GetType() == tlist) {
        arg[1].GetValue(l);
        if (l->Count() != 2)
            return false;

        for (i = 0; i < 2; i++) {
            if ((*l)[i].GetType() != tnumber)
                return false;
        }
    }

    switch (arity) {
        case 6:
            for (i = 2; i < 6; i++)
                if (arg[i].GetType() != tnumber)
                    return false;
            break;

        case 3:
            switch (arg[2].GetType()) {
                case tlist:
                    arg[2].GetValue(l);
                    if (l->Count() != 4)
                        return false;
                    for (i = 0; i < 4; i++)
                        if ((*l)[i].GetType() != tnumber)
                            return false;
                    return true;

                case tgrib:
                    return true;

                default:
                    return false;
            }

            /* break; */

        case 2:
            break;

        default:
            return false;
    }
    return true;
}


Value FindFunction::Execute(int arity, Value* arg)
{
    fieldset* z = 0;
    int i;
    CList* l;

    double min = 1;
    double max = 1;

    double d[4];
    double& n = d[0];
    double& w = d[1];
    double& s = d[2];
    double& e = d[3];

    fieldset* v;
    arg[0].GetValue(v);

    if (arg[1].GetType() == tlist) {
        CList* l;
        arg[1].GetValue(l);
        (*l)[0].GetValue(min);
        (*l)[1].GetValue(max);
    }
    else {
        arg[1].GetValue(min);
        max = min;
    }

    n = 90;
    s = -90;
    w = 0;
    e = 360;

    if (arity == 3) {
        if (arg[2].GetType() == tgrib) {
            arg[2].GetValue(z);
        }
        else {
            getListArgAsArrayOfNumbers(arg, 2, 4, &d[0]);
        }
    }
    else if (arity != 2)
        for (i = 0; i < 4; i++)
            arg[i + 2].GetValue(d[i]);

    while (w > e)
        w -= 360.0;

    MvGeoBox geoArea(n, w, s, e);

    CList* p = 0;

    if (v->count > 1)
        l = new CList(v->count);

    for (i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->hasLocationInfo())
            return Error("find: unimplemented or spectral data - unable to extract location data");

        std::unique_ptr<MvGridBase> maskgrd;
        if (z) {
            maskgrd = std::unique_ptr<MvGridBase>(MvGridFactory(z->fields[z->count > 1 ? i : 0]));
        }

        if (maskgrd.get() && !maskgrd->isEqual(grd.get()))
            return Error("find: field and mask don't match");

        auto** q = new CList*[grd->length()];
        int cnt = 0;
        int j;

        for (j = 0; j < grd->length(); ++j) {
            bool b;

            if (maskgrd.get())
                b = (maskgrd->value() != 0.0);
            else
                b = geoArea.isInside(grd->lat_y(), grd->lon_x());

            b = b && grd->hasValue();

            if (b) {
                if (grd->value() >= min && grd->value() <= max) {
                    auto* z = new CList(2);
                    (*z)[0] = grd->lat_y();
                    (*z)[1] = grd->lon_x();
                    q[cnt++] = z;
                }
            }

            grd->advance();
            if (maskgrd.get())
                maskgrd->advance();
        }


        auto* a = new CList(cnt);
        for (j = 0; j < cnt; j++)
            (*a)[j] = q[j];

        p = a;

        delete[] q;

        if (v->count > 1)
            (*l)[i] = Value(a);
    }

    if (v->count > 1)
        return Value(l);

    return Value(p);
}

//=============================================================================
// new function added 1998-11-09/br (overwriting 'find')
// renamed as 'pick'  1999-03-24/vk (and restore original 'find')
// renamed as 'gfind' 2003-04-10/vk ('pick' was never activated!)
//
//  geopoints = gfind( fieldset, value, tolerance = 0 )
//

class GFindFunction : public Function
{
public:
    GFindFunction(const char* n) :
        Function(n)
    {
        info = "Finds values in field and returns the result as geopoints";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int GFindFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2 && arity != 3)
        return false;

    if (arg[0].GetType() != tgrib)
        return false;

    if (arg[1].GetType() != tnumber)
        return false;

    if (arity == 3 && arg[2].GetType() != tnumber)
        return false;

    return true;
}

Value GFindFunction::Execute(int arity, Value* arg)
{
    char* path = marstmp();
    FILE* f = fopen(path, "w");
    if (!f)
        return Error("gfind: cannot open %s", path);

    fprintf(f, "#GEO\n");
    fprintf(f, "#DATA\n");


    fieldset* v;
    double value = 0;
    double epsilon = 0;


    arg[0].GetValue(v);
    arg[1].GetValue(value);
    if (arity == 3)
        arg[2].GetValue(epsilon);

    for (int i = 0; i < v->count; i++) {
        MvField mvf(v->fields[i]);
        MvFieldExpander mvf_expand(mvf);  // to ensure values are expanded in memory
        MvGridBase* grd = mvf.mvGrid();
        if (!grd->hasLocationInfo())
            return Error("gfind: unimplemented or spectral data - unable to extract location data");

        for (int j = 0; j < grd->length(); ++j) {
            bool pointOk = grd->hasValue();

            if (pointOk) {
                double d = grd->value() - value;
                if (d < 0)
                    d = -d;

                if (d <= epsilon) {
                    double verifDate = mvf.yyyymmddFoh() + mvf.stepFoh();
                    Date d(verifDate);

                    fprintf(f, "%g\t%g\t%g\t%d\t%d\t%g\n",
                            grd->lat_y(),
                            grd->lon_x(),
                            mvf.level(),
                            d.YyyyMmDd(),
                            d.Hour() * 100 + d.Minute(),
                            grd->value());
                }
            }

            grd->advance();
        }
    }

    fclose(f);

    return Value(new CGeopts(path, 1));
}

//=============================================================================

class GeoOnMlFunction : public Function
{
public:
    GeoOnMlFunction(const char* n) :
        Function(n)
    {
        info = "Computes geopotential on model levels";
    }
    Value Execute(int arity, Value* arg) override;
    int ValidArguments(int arity, Value* arg) override;
};

int GeoOnMlFunction::ValidArguments(int arity, Value* arg)
{
    if (arity == 4) {
        for (int i = 0; i < 4; i++) {
            if (arg[i].GetType() != tgrib) {
                return false;
            }
        }
        return true;
    }
    return false;
}

Value GeoOnMlFunction::Execute(int /*arity*/, Value* arg)
{
    fieldset* t = nullptr;
    fieldset* q = nullptr;
    fieldset* lnsp = nullptr;
    fieldset* zs = nullptr;

    arg[0].GetValue(t);
    arg[1].GetValue(q);
    arg[2].GetValue(lnsp);
    arg[3].GetValue(zs);

    try {
        return metview::geopotentialOnMl(t, q, lnsp, zs);
        //        metview::MlToPlInter inter = metview::MlToPlInter(fs, lnsp);
        //        inter.setTargetVc(pres);
        //        return Value(inter.compute());
    }
    catch (MvException& e) {
        return Error("%s: %s", Name(), e.what());
    }
    return Value();
}


//=============================================================================

class MLToPLInterpolateFunction : public Function
{
public:
    MLToPLInterpolateFunction(const char* n) :
        Function(n)
    {
        info = "Performs interpolation from model levels to pressure levels";
    }
    Value Execute(int arity, Value* arg) override;
    int ValidArguments(int arity, Value* arg) override;
};

int MLToPLInterpolateFunction::ValidArguments(int arity, Value* arg)
{
    if (arity == 3) {
        if (arg[0].GetType() == tgrib && arg[1].GetType() == tgrib &&
            (arg[2].GetType() == tlist || arg[2].GetType() == tvector)) {
            return true;
        }
    }
    return false;
}

Value MLToPLInterpolateFunction::Execute(int /*arity*/, Value* arg)
{
    fieldset* lnsp = nullptr;
    fieldset* fs = nullptr;

    arg[0].GetValue(lnsp);
    arg[1].GetValue(fs);

    std::vector<double> pres;
    if (arg[2].GetType() == tlist) {
        CList* l = nullptr;
        arg[2].GetValue(l);
        for (int i = 0; i < l->Count(); i++) {
            double v;
            (*l)[i].GetValue(v);
            pres.push_back(v);
        }
    }
    else if (arg[2].GetType() == tvector) {
        CVector* v = nullptr;
        arg[2].GetValue(v);
        for (int i = 0; i < v->Count(); i++) {
            pres.push_back((*v)[i]);
        }
    }

    try {
        metview::MlToPlInter inter = metview::MlToPlInter(fs, lnsp);
        inter.setTargetVc(pres);
        return Value(inter.compute());
    }
    catch (MvException& e) {
        return Error("%s: %s", Name(), e.what());
    }
    return Value();
}

//=============================================================================

class MLToHLInterpolateFunction : public Function
{
public:
    MLToHLInterpolateFunction(const char* n) :
        Function(n)
    {
        info = "Performs interpolation from model levels to height levels";
    }
    Value Execute(int arity, Value* arg) override;
    int ValidArguments(int arity, Value* arg) override;
};

int MLToHLInterpolateFunction::ValidArguments(int arity, Value* arg)
{
    if (arity >= 6) {
        bool sea = false;
        if (arg[4].GetType() == tstring) {
            const char* v;
            arg[4].GetValue(v);
            sea = (strcmp(v, "sea") == 0);
        }
        else {
            return false;
        }

        if (arg[0].GetType() != tgrib || arg[1].GetType() != tgrib) {
            return false;
        }

        if (!sea && arg[2].GetType() != tgrib) {
            return false;
        }

        if (arg[3].GetType() != tgrib && arg[3].GetType() != tlist) {
            return false;
        }

        if (arg[5].GetType() != tstring) {
            return false;
        }

        if (arity == 7) {
            if (arg[2].GetType() != tgrib && arg[6].GetType() != tnumber) {
                return false;
            }
        }

        return true;
    }

    return false;
}

Value MLToHLInterpolateFunction::Execute(int arity, Value* arg)
{
    fieldset* fs = nullptr;
    fieldset* zFs = nullptr;
    fieldset* zsFs = nullptr;
    fieldset* hFs = nullptr;
    fieldset* surfFs = nullptr;
    std::vector<double> hVec;
    double surfVal = 0.;

    arg[0].GetValue(fs);
    arg[1].GetValue(zFs);

    const char* chV;
    arg[4].GetValue(chV);
    bool aboveSea = (strcmp(chV, "sea") == 0);

    arg[5].GetValue(chV);
    metview::VerticalInterpolation::VerticalInterpolationMethod interMode =
        (strcmp(chV, "linear") == 0) ? metview::VerticalInterpolation::LinearInterpolation : metview::VerticalInterpolation::LogInterpolation;


    if (!aboveSea) {
        arg[2].GetValue(zsFs);
    }

    metview::MlToHlInter inter = metview::MlToHlInter(fs, zFs, zsFs, aboveSea, interMode);

    if (arg[3].GetType() == tgrib) {
        arg[3].GetValue(hFs);
        inter.setTargetVc(hFs);
    }
    else if (arg[3].GetType() == tlist) {
        CList* l = nullptr;
        arg[3].GetValue(l);
        for (int i = 0; i < l->Count(); i++) {
            double v;
            (*l)[i].GetValue(v);
            hVec.push_back(v);
        }
        inter.setTargetVc(hVec);
    }
    else {
        return Error("%s: invalid h argument!", Name());
    }

    if (arity == 7) {
        if (arg[6].GetType() == tgrib) {
            arg[6].GetValue(surfFs);
            inter.setSurfaceValues(surfFs);
        }
        else if (arg[6].GetType() == tnumber) {
            arg[6].GetValue(surfVal);
            inter.setSurfaceValues(surfVal);
        }
        else {
            return Error("%s: invalid fs_surf argument!", Name());
        }
    }

    try {
        return Value(inter.compute());
    }
    catch (MvException& e) {
        return Error("%s: %s", Name(), e.what());
    }
    return Value();
}

//=============================================================================

class PLToPLInterpolateFunction : public Function
{
public:
    PLToPLInterpolateFunction(const char* n) :
        Function(n)
    {
        info = "Performs interpolation from pressure levels to pressure levels";
    }
    Value Execute(int arity, Value* arg) override;
    int ValidArguments(int arity, Value* arg) override;
};

int PLToPLInterpolateFunction::ValidArguments(int arity, Value* arg)
{
    if (arity >= 2) {
        if (arity == 3) {
            if (arg[2].GetType() == tstring) {
                const char* name;
                arg[2].GetValue(name);
                if (strcmp(name, "linear") != 0 && strcmp(name, "log") != 0) {
                    return false;
                }
            }
            else {
                return false;
            }
        }
        if (arg[0].GetType() == tgrib &&
            (arg[1].GetType() == tlist || arg[1].GetType() == tvector)) {
            return true;
        }
    }
    return false;
}

Value PLToPLInterpolateFunction::Execute(int arity, Value* arg)
{
    fieldset* fs = nullptr;

    arg[0].GetValue(fs);

    // target pressure values
    std::vector<double> pres;
    if (arg[1].GetType() == tlist) {
        CList* l = nullptr;
        arg[1].GetValue(l);
        for (int i = 0; i < l->Count(); i++) {
            double v;
            (*l)[i].GetValue(v);
            pres.push_back(v);
        }
    }
    else if (arg[1].GetType() == tvector) {
        CVector* v = nullptr;
        arg[1].GetValue(v);
        for (int i = 0; i < v->Count(); i++) {
            pres.push_back((*v)[i]);
        }
    }

    auto interMode = metview::VerticalInterpolation::LinearInterpolation;
    if (arity == 3) {
        const char* chV;
        arg[2].GetValue(chV);
        if (chV) {
            if (strcmp(chV, "log") == 0) {
                interMode = VerticalInterpolation::LogInterpolation;
            }
            else if (strcmp(chV, "linear") != 0) {
                return Error("%s: invalid interpolation method=", Name(), chV);
            }
        }
        else {
            return Error("%s: invalid interpolation method", Name());
        }
    }

    try {
        auto inter = metview::PlToPlInter(fs, interMode);
        inter.setTargetVc(pres);
        return Value(inter.compute());
    }
    catch (MvException& e) {
        return Error("%s: %s", Name(), e.what());
    }
    return Value();
}

//=============================================================================

class VertIntFunction : public Function
{
public:
    VertIntFunction(const char* n) :
        Function(n)
    {
        info = "Performs vertical integration";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int VertIntFunction::ValidArguments(int arity, Value* arg)
{
    if (arity < 1)
        return 0;
    if (arity > 0)
        if (arg[0].GetType() != tgrib)
            return false;
    if (arity > 1)
        if (arg[1].GetType() != tgrib)
            return false;
    if (arity > 2)
        return false;

    return true;
}

Value VertIntFunction::Execute(int arity, Value* arg)
{
    fieldset* v;
    field* lnsp = 0;
    fortint datarep;

    if (arity == 1) {
        arg[0].GetValue(v);  //-- we need to find lnsp
        int n = -1;

        for (int i = 0; (i < v->count) && (n == -1); i++) {
            field* g = GetIndexedFieldWithAtLeastPackedMem(v, i);
            // gribsec1   *s1 = (gribsec1*)&g->ksec1[0];
            std::unique_ptr<MvGridBase> grd(MvGridFactory(g));
            // if(s1->parameter == 152) n = i;
            if (grd->getLong(parameterKey) == 152)
                n = i;
            release_field(g);
        }
        if (n == -1)
            return Error("vertint: LNSP (152) not found");

        lnsp = get_field(v, n, expand_mem);
    }
    else {
        fieldset* p;
        arg[0].GetValue(p);
        arg[1].GetValue(v);
        lnsp = get_field(p, 0, expand_mem);
    }
#if 0
    for( j=0;j<lnsp->sec4len;j++)
        if (! MISSING_VALUE(lnsp->rsec4[j]))
            lnsp->rsec4[j] = exp(lnsp->rsec4[j]); //-- lost if lnsp among other fields!
#endif
    MvField F_lnsp(lnsp);
    MvFieldExpander expa_lnsp(F_lnsp);

    datarep = F_lnsp.dataRepres();  // s2->data_rep;
    if (datarep != GRIB_LAT_LONG && datarep != GRIB_GAUSSIAN)
        return Error("vertint: Field is not lat/long nor Gaussian!");

    if (!F_lnsp.isModelLevel())  // GRIB_MODEL_LEVEL )
        return Error("vertint: Field is not model level");

    if ((int)(F_lnsp.parameter()) != 152)
        return Error("vertint: Field is not LNSP (152)");

    fieldset* z = copy_fieldset(v, 1, false);  //-- this releases lnsp memory (if same field)
    field* h = get_field(z, 0, expand_mem);

    set_field_state(lnsp, expand_mem);  //-- restore into memory (may have been released)
    set_field_state(h, expand_mem);

    assert(h->value_count);
    for (size_t j = 0; j < h->value_count; j++) {
        h->values[j] = 0;
        if (!MISSING_VALUE(lnsp->values[j]))
            lnsp->values[j] = exp(lnsp->values[j]);
    }

    for (int i = 0; i < v->count; i++) {
        field* g = get_field(v, i, expand_mem);
        if (g != lnsp) {
            MvField F_g(g);
            MvFieldExpander expa_lnsp(F_g);

            if (F_g.dataRepres() != datarep)
                return Error("vertint: Field has different representation than LNSP!");

            if (!F_g.isModelLevel())
                return Error("vertint: Field is not model level");

            if (g->value_count != lnsp->value_count)
                return Error("vertint: Field and LNSP are different");


            int level = (int)F_g.level();  // (int)s1->top_level;

            double C11, C12, C21, C22;
            F_g.vertCoordCoefs(level - 1, C11, C12);
            F_g.vertCoordCoefs(level, C21, C22);

            for (size_t j = 0; j < g->value_count; j++) {
                // We only calculate for this gridpoint if the input values
                // are valid and we have not already invalidated this gridpoint.

                if (!(MISSING_VALUE(g->values[j])) &&
                    !(MISSING_VALUE(lnsp->values[j])) &&
                    !(MISSING_VALUE(h->values[j]))) {
                    double pkpd = C21 + C22 * lnsp->values[j];
                    double pkmd = C11 + C12 * lnsp->values[j];

                    double dp = pkpd - pkmd;
                    h->values[j] += g->values[j] * dp;
                }
                else {
                    SetFieldElementToMissingValue(h, j);
                }
            }
            release_field(g);
        }
    }

    for (size_t j = 0; j < h->value_count; j++)
        if (!MISSING_VALUE(h->values[j]))
            h->values[j] /= MvSci::cEarthG;

    release_field(h);
    release_field(lnsp);

    return Value(z);
    return Value(z);
}
//=============================================================================

class UniVertIntFunction : public Function
{
public:
    UniVertIntFunction(const char* n) :
        Function(n)
    {
        info = "Universal vertical integration, also for sparse vertical data";
    }
    Value Execute(int arity, Value* arg) override;
    int ValidArguments(int arity, Value* arg) override;

protected:
    int lnspId_{152};
    int nrfs_{0};
    int top_{-1};
    int bottom_{-1};
};

int UniVertIntFunction::ValidArguments(int arity, Value* arg)
{
    if (arity < 1)
        return 0;

    if (arg[0].GetType() != tgrib)  //-- 1st param must be fieldset
        return false;

    CList* l;

    lnspId_ = 152;
    nrfs_ = 0;
    top_ = -1;
    bottom_ = -1;
    int valid = true;
    switch (arity) {
        case 1:
            nrfs_ = 1;  //-- single fieldset
            break;

        case 2:
            if (arg[1].GetType() == tgrib) {
                nrfs_ = 2;  //-- two fieldsets
            }
            else {
                if (arg[1].GetType() == tnumber) {
                    nrfs_ = 1;
                    arg[1].GetValue(lnspId_);  //-- single fieldset + lnsp code
                }
                else
                    valid = false;
            }
            break;

        case 3:
            if (arg[1].GetType() != tgrib)
                valid = false;

            nrfs_ = 2;

            if (arg[2].GetType() == tnumber) {
                arg[2].GetValue(lnspId_);  //-- two fieldsets + lnsp code
            }
            else if (arg[2].GetType() == tlist) {
                arg[2].GetValue(l);  //-- two fieldsets + [top,bottom]
                if (l->Count() != 2)
                    valid = false;
                else {
                    (*l)[0].GetValue(top_);
                    (*l)[1].GetValue(bottom_);
                }
            }
            else
                valid = false;
            break;

        default:
            valid = false;
    }

    return valid;
}

Value UniVertIntFunction::Execute(int /*arity*/, Value* arg)
{
    fieldset* dataFs;
    fieldset* lnspFs = 0;

    if (nrfs_ == 1)
        arg[0].GetValue(dataFs);  //-- single fieldset: lnsp + data together
    else {
        arg[0].GetValue(lnspFs);  //-- 1st param is supposed to contain lnsp
        arg[1].GetValue(dataFs);  //-- 2nd param is supposed to be the data
    }

    try {
        return Value(metview::verticalIntegral(dataFs, lnspFs, lnspId_, top_, bottom_));
    }
    catch (MvException& e) {
        return Error("%s: %s", Name(), e.what());
    }

    return Value();
}

//=============================================================================

class ThicknessFunction : public Function
{
    boolean pressure;

public:
    ThicknessFunction(const char* n, boolean p) :
        Function(n),
        pressure(p)
    {
        info = "Creates fields of pressure or thickness (input in lat/lon only).";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int ThicknessFunction::ValidArguments(int arity, Value* arg)
{
    CList* l;
    int i;

    if (arity < 1)
        return false;

    if (arity > 0)
        if (arg[0].GetType() != tgrib)
            return false;

    if (arity > 1) {
        switch (arg[1].GetType()) {
            case tnumber:
            case tgrib:
                break;

            case tlist:
                arg[1].GetValue(l);
                if (l->Count() < 1)
                    return false;
                for (i = 0; i < l->Count(); i++)
                    if ((*l)[i].GetType() != tnumber)
                        return false;
                break;

            default:
                return false;
        }
    }

    if (arity > 2)
        return false;

    return true;
}

Value ThicknessFunction::Execute(int arity, Value* arg)
{
    fieldset* v;
    int* levels = nullptr;
    int n = 0, l = 0, m = 0;
    int count = 0;


    arg[0].GetValue(v);

    if (arity == 2) {
        int i;
        switch (arg[1].GetType()) {
            case tnumber:
                count = 1;
                levels = new int[1];
                arg[1].GetValue(levels[0]);
                break;

            case tlist: {
                CList* l;  // this shadows int l above
                arg[1].GetValue(l);
                count = l->Count();
                levels = new int[count];
                for (i = 0; i < count; i++)
                    (*l)[i].GetValue(levels[i]);
                break;
            }

            case tgrib:
                fieldset* w;
                arg[1].GetValue(w);

                count = w->count;
                levels = new int[w->count];

                for (i = 0; i < w->count; i++) {
                    field* g = GetIndexedFieldWithAtLeastPackedMem(w, i);
                    auto* f = new MvField(g);
                    if (f->dataRepres() != GRIB_LAT_LONG)
                        ++l;
                    if (!f->isModelLevel())
                        ++m;
                    levels[i] = (int)f->level();
                    delete f;
                    release_field(g);
                }
                break;
            default:
                break;
        }
    }


    fieldset* z = new_fieldset(0);


    for (int i = 0; i < v->count; i++) {
        field* g = get_field(v, i, expand_mem);
        auto* F_g = new MvField(g);
        int levcount = F_g->vertCoordCoefPairCount();

        if ((int)F_g->parameter() == 152)
            ++n;
        if (F_g->dataRepres() != GRIB_LAT_LONG)
            ++l;
        if (!F_g->isModelLevel())
            ++m;

        if (arity == 1) {
            if (levels) {
                delete[] levels;
            }

            count = levcount - 1;
            levels = new int[count];
            for (int k = 0; k < count; k++)
                levels[k] = k + 1;
        }

        for (int k = 0; k < count; k++) {
            int level = levels[k];
            field* h = copy_field(g, false);

            MvField F_h(h);
            double C11, C12, C21, C22;
            F_h.vertCoordCoefs(level - 1, C11, C12);
            F_h.vertCoordCoefs(level, C21, C22);

            if (pressure)
                for (size_t j = 0; j < g->value_count; j++) {
                    if (!MISSING_VALUE(g->values[j])) {
                        double e = exp(g->values[j]);
                        double a = (C11 + C21) / 2.0;
                        double b = (C12 + C22) / 2.0;
                        h->values[j] = a + b * e;
                    }
                    else {
                        SetFieldElementToMissingValue(h, j);
                    }
                }
            else
                for (size_t j = 0; j < g->value_count; j++) {
                    if (!MISSING_VALUE(g->values[j])) {
                        double e = exp(g->values[j]);
                        double pkpd = C21 + C22 * e;
                        double pkmd = C11 + C12 * e;
                        double dpg = pkpd - pkmd;
                        h->values[j] = dpg;
                    }
                    else {
                        SetFieldElementToMissingValue(h, j);
                    }
                }

            grib_set_long(h->handle, parameterKey, 54);
            /*int err = */ grib_set_long(h->handle, "level", (long)level);

            add_field(z, h);
        }

        delete F_g;
        release_field(g);
    }

    if (levels) {
        delete[] levels;
    }

    Value r(z);

    if (n != v->count)
        return Error("pressure/thickness: Not all fields are LSNP");
    if (m)
        return Error("pressure/thickness: Not all fields are model level");
    if (l)
        return Error("pressure/thickness: Not all fields are lat/long");


    return r;
}

//=============================================================================

class UniThicknessAndPressureFunction : public Function
{
private:
    boolean needPressure_{true};
    int arity_{-1};
    int lnspId_{152};  //-- default ECMWF 'lnsp' code value, in table 2 version 128

public:
    UniThicknessAndPressureFunction(const char* n, boolean p) :
        Function(n),
        needPressure_(p)
    {
        info = "Creates fields of pressure or thickness (accepts several grid types)";
    }
    Value Execute(int arity, Value* arg) override;
    int ValidArguments(int arity, Value* arg) override;
};

int UniThicknessAndPressureFunction::ValidArguments(int arity_in, Value* arg)
{
    CList* l;
    int i;

    arity_ = arity_in;  //-- we may change the-number-of-parameters value!

    if (arity_ < 1)
        return false;

    if (arity_ > 0) {
        if (arg[0].GetType() != tgrib)
            return false;

        if (arity_ > 1)  //-- if 'lnsp' code given, it is the last param and a number
        {
            if (arg[arity_ - 1].GetType() == tnumber)  //-- 'arity_-1' = last index
            {
                arg[arity_ - 1].GetValue(lnspId_);
                --arity_;  //-- lnsp stored, ignore from now on
            }
        }
    }

    if (arity_ > 2)
        return false;

    if (arity_ == 2) {
        switch (arg[1].GetType()) {
            case tgrib:
                break;

            case tlist:
                arg[1].GetValue(l);
                if (l->Count() < 1)
                    return false;
                for (i = 0; i < l->Count(); i++)
                    if ((*l)[i].GetType() != tnumber)
                        return false;
                break;

            case tnumber:
                return false;  //-- cannot be 'lnsp' number any more

            default:
                return false;
        }
    }

    return true;
}

Value UniThicknessAndPressureFunction::Execute(int, Value* arg)
{
    fieldset* lnsp;
    arg[0].GetValue(lnsp);

    if (arity_ != 1 && arity_ != 2) {
        return Error("%s: invalid number of arguments specified!", Name());
    }

    try {
        assert(arity_ == 1 || arity_ == 2);
        if (arity_ == 1) {
            return Value(metview::pressureOnMl(lnsp, lnspId_, !needPressure_));
        }
        else if (arg[1].GetType() == tlist) {
            CList* l = nullptr;
            std::vector<int> levels;
            arg[1].GetValue(l);
            double levVal;
            for (int i = 0; i < l->Count(); i++) {
                (*l)[i].GetValue(levVal);
                levels.push_back(levVal);
            }
            return Value(metview::pressureOnMl(lnsp, lnspId_, !needPressure_, levels));
        }
        else if (arg[1].GetType() == tgrib) {
            fieldset* fs;
            arg[1].GetValue(fs);
            return Value(metview::pressureOnMl(lnsp, lnspId_, !needPressure_, fs));
        }
    }
    catch (MvException& e) {
        return Error("%s: %s", Name(), e.what());
    }

    return Value();
}

//=============================================================================

/*******************************************************************************
 *
 * Class        : DataInfoFunction : Function
 *
 * Description  : Macro function that returns information about the missing
 *                values in a fieldset.
 *
 ******************************************************************************/


class DataInfoFunction : public Function
{
public:
    DataInfoFunction(const char* n) :
        Function(n)
    {
        info = "Returns information on missing values in fieldsets";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};


int DataInfoFunction::ValidArguments(int arity, Value* arg)
{
    /* We accept only a fieldset as a single argument */

    if ((arity == 1) && arg[0].GetType() == tgrib)
        return true;
    else
        return false;
}


Value DataInfoFunction::Execute(int /*arity*/, Value* arg)
{
    fieldset* fs;
    field* f;
    request* r;
    CList* list;
    int nNumPresent;
    int nNumMissing;
    double dProportionPresent;
    double dProportionMissing;
    int nNumFields;
    int i;
    size_t j;


    arg[0].GetValue(fs);


    // note the number of fields and create a list to hold the result

    nNumFields = fs->count;

    list = new CList(nNumFields);


    // loop through the fields, noting the statistics for each one

    for (i = 0; i < nNumFields; i++) {
        f = get_field(fs, i, expand_mem);
        r = empty_request(nullptr);

        nNumPresent = 0;
        nNumMissing = 0;
        dProportionPresent = 0.0;
        dProportionMissing = 0.0;

        // If field has missing values, then we need to check them all

        if (FIELD_HAS_MISSING_VALS(f)) {
            for (j = 0; j < f->value_count; j++) {
                if (MISSING_VALUE(f->values[j]))
                    nNumMissing++;
                else
                    nNumPresent++;
            }

            if (f->value_count != 0) {
                dProportionPresent = (double)nNumPresent / f->value_count;
                dProportionMissing = (double)nNumMissing / f->value_count;
            }
        }

        else {
            // field has no missing values - we can take a shortcut

            nNumPresent = f->value_count;
            nNumMissing = 0;
            dProportionPresent = 1.0;
            dProportionMissing = 0.0;
        }

        release_field(f);


        // store that field's statistics definintion in a list

        set_value(r, "index", "%d", i + Context::BaseIndex());
        set_value(r, "number_present", "%d", nNumPresent);
        set_value(r, "number_missing", "%d", nNumMissing);
        set_value(r, "proportion_present", "%g", dProportionPresent);
        set_value(r, "proportion_missing", "%g", dProportionMissing);

        (*list)[i] = Value(r);
    }


    return Value(list);
}


//=============================================================================

class GribIndexesFunction : public Function
{
public:
    GribIndexesFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Returns a list of indexes into GRIB files used by a fieldset";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value GribIndexesFunction::Execute(int /*arity*/, Value* arg)
{
    fieldset* fs;
    arg[0].GetValue(fs);  // get the fieldset variable

    // the result will be a list of lists - for each field, a list of [path,offset,length]
    auto* l = new CList(fs->count);  // initialise the resulting list - 1 element per field

    for (int i = 0; i < fs->count; i++) {
        auto* lsub = new CList(3);
        (*lsub)[0] = fs->fields[i]->file->fname;
        (*lsub)[1] = fs->fields[i]->offset;

        // it seems that when we create a temporary GRIB file, the length is reported as zero, so
        // we will have to ask ecCodes for it
        long len = fs->fields[i]->length;
        if (len == 0) {
            AtLeastPackedMemExpander fx(fs->fields[i]);
            len = MvGridBase::getLong(fs->fields[i], "totalLength");
        }
        (*lsub)[2] = len;
        (*l)[i] = lsub;
    }

    return Value(l);
}

//=============================================================================


class GribMatrixFunction : public Function
{
public:
    GribMatrixFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Converts a fieldset to a matrx";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value GribMatrixFunction::Execute(int, Value* arg)
{
    fieldset* v;
    CList* l = nullptr;
    CMatrix* m = nullptr;
    int fields_processed = 0;

    arg[0].GetValue(v);

    if (v->count > 1)
        l = new CList(v->count);


    for (int i = 0; i < v->count; i++) {
        field* g = get_field(v, i, expand_mem);
        // gribsec2_ll *s2 = (gribsec2_ll*) &g->ksec2[0];
        std::unique_ptr<MvGridBase> grd(MvGridFactory(g));
        std::string gridType = grd->gridType();

        if ((gridType != cLatLonGrid) && (gridType != cSatelliteImage)) {
            marslog(LOG_WARN,
                    "Warning: matrix() function only works on regular grids. Field %d (indexes start at 1) has grid of type %s and will not be processed.",
                    i + 1, gridType.c_str());
            continue;
        }

        // int x = (int)s2->points_meridian;
        // int y = (int)s2->points_parallel;
        int x = (int)grd->getLong("numberOfPointsAlongAParallel");
        int y = (int)grd->getLong("numberOfPointsAlongAMeridian");

        m = new CMatrix(y, x);

        for (int ix = 0; ix < x; ix++)
            for (int iy = 0; iy < y; iy++)
                (*m)(iy, ix) = g->values[ix + iy * x];

        if (v->count > 1 && l)
            (*l)[i] = Value(m);

        release_field(g);

        fields_processed++;
    }

    if (fields_processed > 0) {
        if (v->count > 1)
            return Value(l);

        return Value(m);
    }
    else {
        return Value();  // nil
    }
}

//=============================================================================

/*
    Grib2To1Function
    Takes two GRIB fieldsets as input and computes a single fieldset
    as a result. Current examples are speed and direction.
*/
class Grib2To1Function : public Function
{
public:
    Grib2To1Function(const char* n, eG2To1 t) :
        Function(n, 2, tgrib, tgrib),
        type_(t)
    {
        if (type_ == G2TO1_DIRECTION)
            info = "Computes meteorological wind direction using U and V wind components";
        else if (type_ == G2TO1_SPEED)
            info = "Computes meteorological wind speed using U and V wind components";
    }
    Value Execute(int arity, Value* arg) override;

private:
    eG2To1 type_;
};

Value Grib2To1Function::Execute(int /*arity*/, Value* arg)
{
    fieldset* u = nullptr;
    arg[0].GetValue(u);
    fieldset* v = nullptr;
    arg[1].GetValue(v);

    assert(u);
    assert(v);

    if (u->count != v->count) {
        return Error("%s: U and V: different number of fields! %d != %d", Name(), u->count, v->count);
    }

    // create resulting fieldset with empty fields
    fieldset* result = new_fieldset(u->count);

    int baseIndex = Context::BaseIndex();

    for (int i = 0; i < u->count; i++) {
        field* fu = get_field(u, i, expand_mem);
        field* fv = get_field(v, i, expand_mem);
        long uParamId = 131;
        {
            // TODO: only the spectral fields should be omitted here! Speed and dir computations otherwise are
            // completely indenpendent of the grid type
            std::unique_ptr<MvGridBase> gu(MvGridFactory(fu));
            if (gu->gridType() == "NA")
                return Error("%s: U[field=%d]: cannot process this type of field!", Name(), i + baseIndex);

            std::unique_ptr<MvGridBase> gv(MvGridFactory(fv));
            if (gv->gridType() == "NA")
                return Error("%s: V[field=%d]: cannot process this type of field!", Name(), i + baseIndex);

            if (!gu->isEqual(gv.get()))
                return Error("%s: U and V [field=%d]: different grids!", Name(), i + baseIndex);

            uParamId = gu->getLong("paramId", false, true);
        }

        assert(fu->shape == expand_mem);
        assert(fv->shape == expand_mem);
        field* result_i = copy_field(fu, false);
        set_field(result, result_i, i);
        assert(result_i->shape == expand_mem);

        {
            switch (type_) {
                case G2TO1_SPEED: {
                    for (size_t j = 0; j < fu->value_count; ++j) {
                        double v1 = fu->values[j];
                        double v2 = fv->values[j];

                        if (!MISSING_VALUE(v1) && !MISSING_VALUE(v2)) {
                            double s = sqrt(v1 * v1 + v2 * v2);
                            result_i->values[j] = s;
                        }
                        else {
                            SetFieldElementToMissingValue(result_i, j);
                        }
                    }
                    break;
                }
                case G2TO1_DIRECTION:
                default: {
                    const double cEpsilon = 1E-5;
                    const double c270inRad = MvSci::degToRad(270.);

                    for (size_t j = 0; j < fu->value_count; ++j) {
                        double v1 = fu->values[j];
                        double v2 = fv->values[j];

                        if (!MISSING_VALUE(v1) && !MISSING_VALUE(v2)) {
                            double d = 0;

                            if (fabs(v1) < cEpsilon)  //-- v only: North or South
                            {
                                if (v2 > 0) {
                                    d = M_PI;  //-- from South
                                }
                                else {
                                    d = 0;  //-- from North
                                }
                            }
                            else {
                                d = atan(v2 / fabs(v1));  //-- ok for Westerly winds
                                if (v1 < 0) {
                                    d = M_PI - d;  //-- fix for Easterly winds
                                }

                                d = c270inRad - d;  //-- rotate 0 from North & reverse direction
                            }
                            result_i->values[j] = MvSci::radToDeg(d);
                        }
                        else {
                            SetFieldElementToMissingValue(result_i, j);
                        }
                    }
                    break;
                }
            }

            if (type_ == G2TO1_DIRECTION) {
                MvGridBase::setLong(result_i, parameterKey, 3031);
            }
            else {
                // U paramId -> wind speed paramId
                static std::map<long, long> uToSpeed = {
                    {131, 10},         // atmospheric wind
                    {165, 207},        // 10m wind
                    {228246, 228249},  // 100m wind
                    {228239, 228241}   // 200m wind
                };
                long speedParamId = 10;
                auto it = uToSpeed.find(uParamId);
                if (it != uToSpeed.end()) {
                    speedParamId = it->second;
                }
                MvGridBase::setLong(result_i, parameterKey, speedParamId);
            }
        }

        release_field(fu);
        release_field(fv);
        save_fieldset(result);
    }

    return Value(result);
}

//=============================================================================

class MeanEwFunction : public Function
{
public:
    MeanEwFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Generates a fieldset out of East-West means";
    }
    Value Execute(int arity, Value* arg) override;
};


Value MeanEwFunction::Execute(int, Value* arg)
{
    fieldset* v = nullptr;
    arg[0].GetValue(v);
    assert(v);

    // create the resulting fieldset with empty fields
    fieldset* result = new_fieldset(v->count);

    for (int i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->hasLocationInfo())
            return Error("%s: unimplemented grid type: %s", Name(), grd->gridType().c_str());

        if (grd->gridType() != cLatLonGrid &&
            grd->gridType() != cMercatorGrid &&
            grd->gridType() != cGaussianGrid &&
            grd->gridType() != cGaussianReducedGrid)
            return Error("%s: unsuitable grid type: %s", Name(), grd->gridType().c_str());

        assert(grd->fieldPtr()->shape == expand_mem);
        auto fRes = copy_field(v->fields[i], false);
        assert(fRes->shape == expand_mem);
        {
            // When it is destroyed the shape of fRes will not be reset
            std::unique_ptr<MvGridBase> newGrd(MvGridFactory(fRes, false));
            double currentLatRow = grd->lat_y();
            bool cont = true;

            while (cont) {
                double sum = 0;
                int cnt = 0;

                //-- compute mean of one lat row --//
                while (cont && (grd->lat_y() == currentLatRow)) {
                    if (grd->hasValue()) {
                        //-- missing values are ignored, hopefully this is ok!
                        double val = grd->value();
                        sum += val;
                        cnt++;
                    }
                    cont = grd->advance();
                }

                double meanVal = cnt ? sum / cnt : grd->missingValue();

                //-- in output, fill the whole row with the mean value --//
                bool contNew = true;
                while (contNew && (newGrd->lat_y() == currentLatRow)) {
                    newGrd->value(meanVal);
                    contNew = newGrd->advance();
                }

                currentLatRow = grd->lat_y();  //-- next latitude row
            }
        }

        assert(fRes->shape == expand_mem);
        set_field(result, fRes, i);
        assert(fRes->shape == expand_mem);
        save_fieldset(result);
        assert(fRes->shape == packed_file);
    }

    return Value(result);
}
//=============================================================================

class LatLonAverageFunction : public Function
{
public:
    LatLonAverageFunction(const char* n, bool isEW) :
        Function(n, 1, tgrib),
        isEW_(isEW)
    {
        if (isEW_)
            info = "Returns the zonal averages as a list (or list of lists) of numbers.";
        else
            info = "Returns the meridional averages as a list (or list of lists) of numbers.";
    }
    virtual int ValidArguments(int arity, Value* arg);
    virtual Value Execute(int arity, Value* arg);

private:
    double area_[4] = {0., 0., 0., 0.};
    double grid_{1.};
    bool isEW_{false};
};

int LatLonAverageFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 3)
        return false;

    if (arg[0].GetType() != tgrib)  //-- 1. argument: fieldset
        return false;

    if (arg[1].GetType() != tlist)  //-- 2. argument: list of 4 numbers
        return false;

    if (arg[2].GetType() != tnumber)  //-- 3. argument: number
        return false;

    arg[2].GetValue(grid_);

    return true;
}

Value LatLonAverageFunction::Execute(int, Value* arg)
{
    //-- check input parameter values
    if (grid_ <= 0)
        return Error("average_xx: grid interval negative or zero");

    CList* l;
    arg[1].GetValue(l);  //-- list of area values

    if (l->Count() != 4)
        return Error("average_xx: area list must contain 4 values!");

    for (int i = 0; i < l->Count(); i++) {
        if ((*l)[i].GetType() == tnumber)
            (*l)[i].GetValue(area_[i]);
        else
            return Error("average_xx: area list must contain numbers!");
    }

    Value lst1;       //-- single: will store a list of numbers
    CList* lst2 = 0;  //-- double: will store a list of lists of numbers

    fieldset* fs;
    arg[0].GetValue(fs);

    if (fs->count > 1)
        lst2 = new CList(fs->count);  //-- list size: will store one list per each field

    for (int fi = 0; fi < fs->count; fi++)  //-- loop over fields in fieldset
    {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(fs->fields[fi]));
        if (!grd->hasLocationInfo())
            return Error("average_xx: unimplemented grid type: %s", grd->gridType().c_str());

        std::vector<double> vals;
        try {
            vals = grd->averageCalc(isEW_, area_[0], area_[1], area_[2], area_[3], grid_);
        }
        catch (MvException& e) {
            return Error("average_xx: %s", e.what());
        }

        int listSize = vals.size();
        auto* z = new CVector(listSize);

        for (int iv = 0; iv < listSize; ++iv) {
            z->setIndexedValue(iv, vals[iv]);
        }

        if (fs->count > 1)
            (*lst2)[fi] = Value(z);
        else
            lst1 = Value(z);
    }


    if (fs->count > 1)
        return Value(lst2);

    return lst1;
}

//=============================================================================

class GribFunction : public Function
{
    func* F_;

public:
    GribFunction(const char* n, func* f) :
        Function(n)
    {
        F_ = f;
        info = f->info;
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int GribFunction::ValidArguments(int arity, Value* arg)
{
    int g = 0;
    int n = 0;
    int o = 0;

    if (F_->arity > -1 && F_->arity != arity)
        return false;

    for (int i = 0; i < arity; i++)
        switch (arg[i].GetType()) {
            case tgrib:
                g++;
                break;
            case tnumber:
                n++;
                break;
            default:
                o++;
                break;
        }

    return (g > 0) && (o == 0);
}

Value GribFunction::Execute(int arity, Value* arg)
{
    funcproc f = F_->addr;
    mathproc d = F_->proc;
    Value p;

    for (int i = 0; i < arity; i++)
        switch (arg[i].GetType()) {
            case tgrib:
                fieldset* s;
                arg[i].GetValue(s);
                if (s == 0)
                    return Error("GribFunction: cannot get fieldset");
                push_named_fieldset((char*)"field", s);
                break;

            case tnumber:
                double d;
                arg[i].GetValue(d);
                push_named_scalar((char*)"scalar", d);
                break;
        }

    Math.name = (char*)Name();
    Math.arity = arity;

    feclearexcept(FE_ALL_EXCEPT);

    f(&Math, d);
    variable* v = pop();

    if (!v)
        return Error("GribFunction: error computing fields");

    if (v->scalar) {
        if (v->val != mars.grib_missing_value)
            p = Value(v->val);
        else
            p = Value();  // nil
    }
    else {
        /* v->fs->refcnt++; */
        p = Value(v->fs);
    }

    // check for division by zero
    int i;
    i = fetestexcept(FE_DIVBYZERO);
    if (i & FE_DIVBYZERO) {
        return Error("Division by zero - consider using the bitmap function to replace zeros with missing values");
    }

    return p;
}

//=============================================================================

class GribSetBitsFunction : public Function
{
public:
    GribSetBitsFunction(const char* n) :
        Function(n, 1, tnumber)
    {
        info = "Sets GRIB packing bit width";
    }

    virtual Value Execute(int arity, Value* arg);
};

Value GribSetBitsFunction::Execute(int, Value* arg)
{
    int rv = mars.accuracy;

    int iv;
    arg[0].GetValue(iv);
    mars.accuracy = iv;

    return Value(rv);
}

//=============================================================================
//-----------------------------------------------------   to_float  (Mar06/vk)

class GribIntToFloatFunction : public Function
{
public:
    GribIntToFloatFunction(const char* n) :
        Function(n)
    {
        info = "Converts int GRIB to float GRIB";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);

private:
    int marsAccuracy{-1};
};

int GribIntToFloatFunction::ValidArguments(int arity, Value* arg)
{
    if (arity < 1 || arity > 2)
        return false;

    if (arg[0].GetType() != tgrib)  // && arg[0].GetType() != timage)
        return false;

    if (arity == 2 && arg[1].GetType() != tnumber)
        return false;

    if (arity == 2)  //-- second param: bits per value
        arg[1].GetValue(marsAccuracy);
    else
        marsAccuracy = -1;

    return true;
}

Value GribIntToFloatFunction::Execute(int /*arity*/, Value* arg)
{
    fieldset* v;
    arg[0].GetValue(v);  //-- get parameters...

    int prevAcc = mars.accuracy;
    if (marsAccuracy > 1)  //-- second param: bits per value
    {
        mars.accuracy = marsAccuracy;
        marslog(LOG_INFO, "float: bits-per-value requested to be %d", marsAccuracy);
    }

    fieldset* z = copy_fieldset(v, v->count, false);

    for (int i = 0; i < v->count; ++i)  //-- for all fields in a fieldset
    {
        field* g = get_field(v, i, expand_mem);
        std::unique_ptr<MvGridBase> grd(MvGridFactory(g, false));
        if (!grd->isValid())
            return Error("float: unimplemented grid type: %s", grd->gridType().c_str());

        field* h = get_field(z, i, expand_mem);
        std::unique_ptr<MvGridBase> grdOut(MvGridFactory(h));
        if (!grdOut->isValid())
            return Error("float: unimplemented grid type: %s", grdOut->gridType().c_str());

        if (grd->getLong("integerPointValues"))  //-- if integer values
        {
            grdOut->setLong("integerPointValues", 0);
            grdOut->setDouble("missingValue", (double)grd->getDouble("missingValue"));
        }

        for (size_t j = 0; j < h->value_count; ++j)  //-- copy all values
            grdOut->valueAt(j, (double)(grd->valueAt(j)));

        release_field(g);
    }

    save_fieldset(z);

    mars.accuracy = prevAcc;

    //-- do we need to release 'z' ???
    return Value(z);
}

//=============================================================================
//-----------------------------------------------------   to_int  (Jan07/fi)

class GribFloatToIntFunction : public Function
{
public:
    GribFloatToIntFunction(const char* n) :
        Function(n)
    {
        info = "Converts float GRIB to int GRIB";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);

private:
    int marsAccuracy{-1};
};

int GribFloatToIntFunction::ValidArguments(int arity, Value* arg)
{
    if (arity < 1 || arity > 2)
        return false;

    if (arg[0].GetType() != tgrib)  // && arg[0].GetType() != timage)
        return false;

    if (arity == 2 && arg[1].GetType() != tnumber)
        return false;

    if (arity == 2)  //-- second param: bits per value
        arg[1].GetValue(marsAccuracy);
    else
        marsAccuracy = -1;

    return true;
}

Value GribFloatToIntFunction::Execute(int /*arity*/, Value* arg)
{
    fieldset* v;
    arg[0].GetValue(v);  //-- get parameters...

    int prevAcc = mars.accuracy;
    if (marsAccuracy > 1)  //-- second param: bits per value
    {
        mars.accuracy = marsAccuracy;
        marslog(LOG_INFO, "integer: bits-per-value requested to be %d", marsAccuracy);
    }

    fieldset* z = copy_fieldset(v, v->count, false);

    for (int i = 0; i < v->count; ++i)  //-- for all fields in a fieldset
    {
        field* g = get_field(v, i, expand_mem);  //-- input field
        std::unique_ptr<MvGridBase> grd(MvGridFactory(g, false));
        if (!grd->isValid())
            return Error("integer: unimplemented grid type: %s", grd->gridType().c_str());

        field* h = get_field(z, i, expand_mem);  //-- output field
        std::unique_ptr<MvGridBase> grdOut(MvGridFactory(h));
        if (!grdOut->isValid())
            return Error("integer: unimplemented grid type: %s", grdOut->gridType().c_str());

        if (!grd->getLong("integerPointValues"))  //-- if float values
        {
            grdOut->setLong("integerPointValues", 1);   //-- change to integer values
            grdOut->setLong("missingValue", LONG_MAX);  //-- set missing value indicator
        }

        for (size_t j = 0; j < h->value_count; j++) {
            if (MISSING_VALUE(grd->valueAt(j)))
                grdOut->valueAt(j, (double)LONG_MAX);
            else
                grdOut->valueAt(j, (double)((long)(grd->valueAt(j))));
        }

        release_field(g);
    }

    save_fieldset(z);

    mars.accuracy = prevAcc;

    //-- do we need to release 'z' ???
    return Value(z);
}

//=============================================================================

class GradientFunction : public Function
{
public:
    GradientFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Computes horizontal gradient of fields";
    }
    Value Execute(int arity, Value* arg) override;
};

Value GradientFunction::Execute(int, Value* arg)
{
    fieldset* v = nullptr;
    arg[0].GetValue(v);
    if (!v || v->count == 0) {
        return Value();
    }
    assert(v);

    // create the resulting fieldset with empty fields
    fieldset* result = new_fieldset(2 * v->count);

    int baseIndex = Context::BaseIndex();

    for (int i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd || !grd->hasLocationInfo())
            return Error("gradient: [field %d] unimplemented or spectral data - unable to extract location data", i + baseIndex);

        if (!grd->isRegularLatLongGrid())
            return Error("gradient: [field %d] - unsupported grid, implemented only for regular lat-lon grid", i + baseIndex);

        assert(v->fields[i]->shape == expand_mem);

        // df/dx
        field* fResX = copy_field(v->fields[i], false);
        assert(fResX->shape == expand_mem);
        {
            // will not change expanded state in the desctructor
            std::unique_ptr<MvGridBase> grdX(MvGridFactory(fResX, false));
            grd->firstDerivativeX(grdX.get());
            grd->init();
        }
        assert(fResX->shape == expand_mem);
        set_field(result, fResX, 2 * i);
        save_fieldset(result);

        // df/dy
        field* fResY = copy_field(v->fields[i], false);
        assert(fResY->shape == expand_mem);
        {
            // will not change expanded state in the desctructor
            std::unique_ptr<MvGridBase> grdY(MvGridFactory(fResY, false));
            grd->firstDerivativeY(grdY.get());
        }
        assert(fResY->shape == expand_mem);
        set_field(result, fResY, 2 * i + 1);
        save_fieldset(result);
    }

    return Value(result);
}

//=============================================================================

class DerivativeFunction : public Function
{
public:
    DerivativeFunction(const char* n, int type) :
        Function(n, 1, tgrib),
        type_(type)
    {
        static std::vector<std::string> infoVec =
            {"Computes the first West-East derivative of fields",
             "Computes the first South-North derivative of fields",
             "Computes the second West-East derivative of fields",
             "Computes the second South-North derivative of fields"};

        if (type_ >= 0 && type_ < 4) {
            info = strcache(infoVec[type_].c_str());
        }
        assert(type_ >= 0 && type_ < 4);
    }
    Value Execute(int arity, Value* arg) override;

protected:
    int type_{-1};
};

Value DerivativeFunction::Execute(int, Value* arg)
{
    fieldset* v = nullptr;
    arg[0].GetValue(v);
    if (!v || v->count == 0) {
        return Value();
    }
    assert(v);

    // create the resulting fieldset with empty fields
    fieldset* result = new_fieldset(v->count);

    static std::vector<MvGridBase::DerivativeMethod> derivativeMethods = {
        &MvGridBase::firstDerivativeX, &MvGridBase::firstDerivativeY,
        &MvGridBase::secondDerivativeX, &MvGridBase::secondDerivativeY};

    MvGridBase::DerivativeMethod dMethod;
    if (type_ >= 0 && type_ < 4) {
        dMethod = derivativeMethods[type_];
    }
    else {
        return Error("%s: invalid function type=%d is specified", Name(), type_);
    }

    int baseIndex = Context::BaseIndex();

    for (int i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd || !grd->hasLocationInfo())
            return Error("%s: [field %d] unimplemented or spectral data - unable to extract location data", Name(), i + baseIndex);

        if (!grd->isRegularLatLongGrid())
            return Error("%s: [field %d] unsupported grid - implemented only for regular lat-lon grid", Name(), i + baseIndex);

        field* fRes = copy_field(v->fields[i], false);
        assert(fRes->shape == expand_mem);
        {
            // will not change expanded state in the desctructor
            std::unique_ptr<MvGridBase> grdZ(MvGridFactory(fRes, false));
            (grd.get()->*dMethod)(grdZ.get());
        }
        assert(fRes->shape == expand_mem);
        set_field(result, fRes, i);
        save_fieldset(result);
    }

    return Value(result);
}

//=============================================================================

class GridCellAreaFunction : public Function
{
public:
    GridCellAreaFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Computes grid cell area for each gridpoints in a field";
    }
    Value Execute(int arity, Value* arg) override;
};

Value GridCellAreaFunction::Execute(int, Value* arg)
{
    fieldset* v = nullptr;
    arg[0].GetValue(v);
    if (!v || v->count == 0) {
        return Value();
    }
    assert(v);

    // create the resulting fieldset with empty fields
    fieldset* result = new_fieldset(v->count);

    int baseIndex = Context::BaseIndex();

    for (int i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd || !grd->hasLocationInfo())
            return Error("%s: [field %d] unimplemented or spectral data - unable to extract location data", Name(), i + baseIndex);

        if (!grd->isGridCellAreaSupported())
            return Error("%s: [field %d] unsupported grid for grid cell area computations", Name(), i + baseIndex);

        assert(v->fields[i]->shape == expand_mem);

        field* fRes = copy_field(v->fields[i], false);
        assert(fRes->shape == expand_mem);
        {
            // will not change expanded state in the desctructor
            std::unique_ptr<MvGridBase> grdZ(MvGridFactory(fRes, false));
            grd->gridCellArea(grdZ.get());
        }
        assert(fRes->shape == expand_mem);
        set_field(result, fRes, i);
        save_fieldset(result);
    }

    return Value(result);
}


//=============================================================================

class CovarianceFunction : public Function
{
public:
    CovarianceFunction(const char* n);
    Value Execute(int arity, Value* arg) override;
    int ValidArguments(int arity, Value* arg) override;

private:
    enum Type
    {
        NoType,
        CorrType,
        CovarType,
        VarType,
        StdevType,
        RmsType,
        DeprecType
    };
    Type type_{NoType};
    static std::map<std::string, Type> types_;

    double computeVar(field* f, const MvGeoBox& area);
    double computeRms(field* f, const MvGeoBox& area);
    double computeCovar(field*, field*, const MvGeoBox&);
    double computeCorr(field*, field*, const MvGeoBox&);
};

std::map<std::string, CovarianceFunction::Type> CovarianceFunction::types_ =
    {{"corr_a", CorrType}, {"covar_a", CovarType}, {"var_a", VarType}, {"stdev_a", StdevType}, {"rms_a", RmsType}};

CovarianceFunction::CovarianceFunction(const char* n) :
    Function(n)
{
    auto nStr = std::string(Name());
    auto it = types_.find(nStr);
    if (it != types_.end()) {
        type_ = it->second;
    }
    else {
        throw MvException("CovarianceFunction: invalid function name=" + nStr + " specified!");
    }

    switch (type_) {
        case CorrType:
            info = "Computes the area-weighted correlation for each field in a fieldset";
            break;
        case CovarType:
            info = "Computes the area-weighted covariance for each field in a fieldset";
            break;
        case VarType:
            info = "Computes the area-weighted variance for each field in a fieldset";
            break;
        case StdevType:
            info = "Computes the area-weighted standard deviation for each field in a fieldset";
            break;
        case RmsType:
            info = "Computes the area-weighted root mean square for each field in a fieldset";
            break;
        default:
            break;
    }
}

int CovarianceFunction::ValidArguments(int arity, Value* arg)
{
    if (arity < 1)
        return false;

    if (arg[0].GetType() != tgrib)  //-- 1st always fieldset
        return false;

    if (type_ == CovarType || type_ == CorrType) {
        if (arity < 2 || arity > 3) {
            return false;
        }

        if (arg[1].GetType() != tgrib)  //-- 2nd is a fieldset
            return false;

        if (arity == 3) {
            if (arg[2].GetType() != tlist)  //-- 3rd is an optional area list
                return false;
        }
    }
    else {
        if (arity > 2)
            return false;

        if (arity == 2) {
            if (arg[1].GetType() != tlist)  //-- 2nd is an optional area list
                return false;
        }
    }

    return true;
}


Value CovarianceFunction::Execute(int arity, Value* arg)
{
    if (type_ == NoType) {
        return Error("CovarianceFunction: invalid function name=%s", Name());
    }

    double geo[4];
    double& n = geo[0];
    double& w = geo[1];
    double& s = geo[2];
    double& e = geo[3];

    n = 90;
    s = -90;  //-- default area limits = full globe
    w = 0;
    e = 360;

    bool corrOrCovar = (type_ == CovarType || type_ == CorrType);

    fieldset* v1 = nullptr;
    arg[0].GetValue(v1);  //-- 1st fieldset always

    fieldset* v2 = nullptr;
    if (corrOrCovar) {
        arg[1].GetValue(v2);  //-- 2nd fieldset only for covar_a and corr_a
    }

    if ((corrOrCovar && arity == 3) || (!corrOrCovar && arity == 2)) {
        CList* ls;
        arg[arity - 1].GetValue(ls);  //-- get area limits list if given
        for (int i = 0; i < 4; i++)
            (*ls)[i].GetValue(geo[i]);
    }

    MvGeoBox geoArea(n, w, s, e);  //-- define area limits

    if (corrOrCovar && (v1->count != v2->count)) {
        return Error("%s: different number of fields in input fieldsets", Name());
    }

    CList* lis = nullptr;
    if (v1->count > 1) {
        lis = new CList(v1->count);  //-- several values => return a list
    }

    double val = 0;
    for (int i = 0; i < v1->count; i++)  //-- loop all fields in fieldset(s)
    {
        try {
            switch (type_) {
                case VarType:
                    val = computeVar(v1->fields[i], geoArea);
                    break;
                case StdevType:
                    val = std::sqrt(computeVar(v1->fields[i], geoArea));
                    break;
                case RmsType:
                    val = computeRms(v1->fields[i], geoArea);
                    break;
                case CovarType:
                    val = computeCovar(v1->fields[i], v2->fields[i], geoArea);
                    break;
                case CorrType:
                    val = computeCorr(v1->fields[i], v2->fields[i], geoArea);
                    break;
                default:
                    break;
            }
        }
        catch (MvException& e) {
            return Error("%s: %s", Name(), e.what());
        }

        if (v1->count > 1) {
            (*lis)[i] = Value(val);  //-- several values => add this value into list
        }
    }

    if (v1->count > 1) {
        return Value(lis);  //-- return a list
    }

    return Value(val);  //-- return a single value
}

double CovarianceFunction::computeVar(field* f, const MvGeoBox& area)
{
    std::unique_ptr<MvGridBase> grd(MvGridFactory(f));
    if (!grd->hasLocationInfo()) {
        throw MvException("Unimplemented or spectral data - unable to extract location data");
    }

    double wght = 0;
    double sum11 = 0;
    double sum1 = 0;

    for (int j = 0; j < grd->length(); ++j) {
        if (grd->hasValue() && area.isInside(grd->lat_y(), grd->lon_x())) {
            double w = grd->weight();
            double v = grd->value();
            double wv = w * v;

            sum1 += wv;
            sum11 += wv * v;
            wght += w;
        }
        grd->advance();
    }

    if (!wght)
        throw MvException("No points to compute");

    return sum11 / wght - (sum1 * sum1) / (wght * wght);
}

double CovarianceFunction::computeRms(field* f, const MvGeoBox& area)
{
    std::unique_ptr<MvGridBase> grd(MvGridFactory(f));
    if (!grd->hasLocationInfo()) {
        throw MvException("Unimplemented or spectral data - unable to extract location data");
    }

    double wght = 0;
    double sum11 = 0;

    for (int j = 0; j < grd->length(); ++j) {
        if (grd->hasValue() && area.isInside(grd->lat_y(), grd->lon_x())) {
            double w = grd->weight();
            double v = grd->value();

            sum11 += w * v * v;
            wght += w;
        }
        grd->advance();
    }

    if (!wght)
        throw MvException("No points to compute");

    return std::sqrt(sum11 / wght);
}
double CovarianceFunction::computeCovar(field* f1, field* f2, const MvGeoBox& area)
{
    std::unique_ptr<MvGridBase> grd1(MvGridFactory(f1));
    if (!grd1->hasLocationInfo())
        throw MvException("Unimplemented or spectral data - unable to extract location data");

    std::unique_ptr<MvGridBase> grd2(MvGridFactory(f2));
    if (!grd2->hasLocationInfo())
        throw MvException("Unimplemented or spectral data - unable to extract location data");

    if (!grd1->isEqual(grd2.get()))
        throw MvException("fields are in different grids!");

    double wght = 0;
    double sum1 = 0;
    double sum2 = 0;
    double sum12 = 0;

    for (int j = 0; j < grd1->length(); ++j) {
        int pointOk1 = grd1->hasValue() && area.isInside(grd1->lat_y(), grd1->lon_x());
        int pointOk2 = grd2->hasValue() && area.isInside(grd2->lat_y(), grd2->lon_x());

        if (pointOk1 && pointOk2) {
            double w = grd1->weight();
            double v1 = grd1->value();
            double v2 = grd2->value();
            double wv1 = w * v1;
            sum1 += wv1;
            sum2 += w * v2;
            sum12 += wv1 * v2;
            wght += w;
        }

        grd1->advance();
        grd2->advance();
    }

    if (!wght)
        throw MvException("No points to compute");

    return (sum12 / wght) - (sum1 / wght) * (sum2 / wght);
}

double CovarianceFunction::computeCorr(field* f1, field* f2, const MvGeoBox& area)
{
    std::unique_ptr<MvGridBase> grd1(MvGridFactory(f1));
    if (!grd1->hasLocationInfo())
        throw MvException("Unimplemented or spectral data - unable to extract location data");

    std::unique_ptr<MvGridBase> grd2(MvGridFactory(f2));
    if (!grd2->hasLocationInfo())
        throw MvException("Unimplemented or spectral data - unable to extract location data");

    if (!grd1->isEqual(grd2.get()))
        throw MvException("fields are in different grids!");

    double wght = 0;
    double sum1 = 0;
    double sum2 = 0;
    double sum12 = 0;
    double sum11 = 0;
    double sum22 = 0;

    for (int j = 0; j < grd1->length(); ++j) {
        int pointOk1 = grd1->hasValue() && area.isInside(grd1->lat_y(), grd1->lon_x());
        int pointOk2 = grd2->hasValue() && area.isInside(grd2->lat_y(), grd2->lon_x());

        if (pointOk1 && pointOk2) {
            double w = grd1->weight();
            double v1 = grd1->value();
            double v2 = grd2->value();
            double wv1 = w * v1;
            double wv2 = w * v2;

            sum1 += wv1;
            sum2 += wv2;
            sum12 += wv1 * v2;
            sum11 += wv1 * v1;
            sum22 += wv2 * v2;
            wght += w;
        }

        grd1->advance();
        grd2->advance();
    }

    if (!wght)
        throw MvException("No points to compute");

    double var1 = sum11 / wght - (sum1 * sum1) / (wght * wght);
    double var2 = sum22 / wght - (sum2 * sum2) / (wght * wght);
    return ((sum12 / wght) - (sum1 / wght) * (sum2 / wght)) / std::sqrt(var1 * var2);
}

//=============================================================================

CGrib::CGrib(fieldset* v, bool from_filter) :
    InPool(tgrib),
    cube(0),
    from_filter_(from_filter)
{
    path_ = "";

    static int done = 0;
    if (!done) {
        // check_precision(); //-- was needed previously for Fortran REAL interface
        done = 1;
    }

    save_fieldset(v);
    fs = v;
}

CGrib::CGrib(request* r) :
    InPool(tgrib, r),
    cube(0)
{
    from_filter_ = (const char*)get_value(r, "FIELDSET_FROM_FILTER", 0) ? true : false;

    fs = request_to_fieldset(r);
    path_ = MakeAbsolutePath((const char*)get_value(r, "PATH", 0), mdirname(Script::MacroMainPath()));

    // Make sure all path are temporary
    while (r) {
        if (get_value(r, "PATH", 0)) {
            const char* p = get_value(r, "TEMPORARY", 0);
            if (!p)
                return;
            if (atoi(p) == 0)
                return;
        }
        r = r->next;
    }

    // We will handle the unlinking of temp file ourselves
    if (!IsIcon()) {
        IsIcon(true);  // InPool won't touch the data
        SetFileTempFlag(true);
    }
}

CGrib::CGrib(const char* fname, bool from_filter) :
    InPool(tgrib),
    cube(0),
    from_filter_(from_filter)
{
    path_ = "";
    fs = read_fieldset(FullPathName(fname).c_str(), 0);
}

void CGrib::DestroyContent()
{
    // Check if we have more that one ref

    if (fs->refcnt > 1)
        isIcon = true;

    for (int i = 0; i < fs->count; i++) {
        field* g = fs->fields[i];
        if (g->refcnt > 1)
            isIcon = true;
        if (g->file && g->file->refcnt > 1)
            isIcon = true;
    }

    free_fieldset(fs);
    if (cube)
        free_hypercube(cube);
}

CGrib::~CGrib()
{
    DestroyContent();
}


void CGrib::ToRequest(request*& s)
{
    static request* r = 0;

    free_all_requests(r);

    r = fieldset_to_request(fs);

    if (from_filter_)
        set_value_int(r, "FIELDSET_FROM_FILTER", 1);

    // Initialise hidden parameters
    if (!get_value(r, "_CLASS", 0))
        set_value(r, "_CLASS", "GRIB");
    if (!get_value(r, "_NAME", 0))
        set_value(r, "_NAME", "%s", GetName());
    if (!get_value(r, "_PATH", 0)) {
        std::string fullPath = MakeAbsolutePath((const char*)get_value(r, "PATH", 0),
                                                mdirname(Script::MacroMainPath()));
        set_value(r, "_PATH", fullPath.c_str());
    }

    // fieldset_to_request() sets the 'temp' member of the gribfile for each
    // field to 'false', meaning that even if this is supposed to be a temporary
    // file, it would not be deleted when it's finished with. We correct that here,
    // because there is other code in place to ensure that temporary files are
    // deleted at the right time.

    // however... if the original fieldset contains more than one
    // GRIB files (e.g. if it's the result of a merge), then
    // fieldset_to_request will write it as a combined temporary
    // GRIB file and set the temporary flag to 'true'. This has
    // dangerous implications because it could then result in a
    // 'permanent' GRIB file being deleted later on because it is
    // marked as temporary. We check for this condition by seeing
    // if the PATH in the resulting request matches the PATH
    // of our original fieldset.

    if (fs->count > 0) {
        char* oldPath = fs->fields[0]->file->fname;
        if (oldPath) {
            const char* newPath = get_value(r, "PATH", 0);

            // if old and new paths are the same, then reset the flag;
            // in the case where they are not the same, then fieldset_to_request
            // has created a temporary file with its own flag, and it will
            // actually be this that is passed on, not our original.

            if (newPath) {
                if (strcmp(oldPath, newPath))  // path has been altered?
                {
                    // yes - that means that this fieldset is the result of
                    // a merge, and we now have (courtesy of fieldset_to_request)
                    // a new temporary file which contains the actual
                    // merged fieldset. We will now free the 'old' fieldset
                    // and replace it with this new, merged one. This is for two
                    // reasons:  1) to ensure that the new temporary file is
                    // deleted (otherwise no CGrib will point to it, and it will
                    // not be cleaned up) and 2) so that we don't have to write out
                    // a new copy of this temporary merged file every time we pass the
                    // original merged fieldset to another module.

                    DestroyContent();
                    fs = request_to_fieldset(r);
                }


                const char* ctemp = get_value(r, "TEMPORARY", 0);
                int itemp = atoi(ctemp);
                if (itemp) {
                    IsIcon(true);  // InPool won't touch the data
                    SetFileTempFlag(true);
                }
            }
        }
    }


    // Temp fix : Some one want the path, so don't delete it
    // Attach();
    s = r;
}


Content* CGrib::Clone(void)
{
    auto* g = new CGrib(merge_fieldsets(fs, 0));
    return g;
}


// sets the flags necessary to ensure that the physical file behind this
// class is not deleted (if perm=true)

void CGrib::SetFileTempFlag(boolean temp)
{
    for (int i = 0; i < fs->count; i++) {
        field* g = fs->fields[i];
        g->file->temp = temp;
    }
}


//=============================================================================

class FrequenciesFunction : public Function
{
public:
    FrequenciesFunction(const char* n) :
        Function(n)
    {
        info = "Computes frequencies of a field";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int FrequenciesFunction::ValidArguments(int arity, Value* arg)
{
    int i;
    CList* l;

    // Initial checks
    if (arity < 2)
        return false;
    if (arg[0].GetType() != tgrib)  // fieldset
        return false;
    if (arg[1].GetType() != tlist)  // intervals
        return false;

    // Check area values
    switch (arity) {
        case 3:
            arg[2].GetValue(l);
            if (l->Count() != 4)
                return false;
            for (i = 0; i < 4; i++)
                if ((*l)[i].GetType() != tnumber)
                    return false;

            return true;

        case 2:
            return true;

        default:
            return false;
    }
    return true;
}


Value FrequenciesFunction::Execute(int arity, Value* arg)
{
    fieldset* v;
    int i, j, k;
    Value lst1;       // single: will store a list of numbers
    CList* lst2 = 0;  // double: will store a list of lists of numbers

    // Get fieldset
    arg[0].GetValue(v);

    // Get list of intervals
    CList* l;
    arg[1].GetValue(l);
    int nint = l->Count();
    std::vector<double> vint(nint);
    for (i = 0; i < nint; i++)
        (*l)[i].GetValue(vint[i]);

    // Get area
    double d[4];
    double& n = d[0];
    double& w = d[1];
    double& s = d[2];
    double& e = d[3];
    if (arity == 2)  // default area
    {
        n = 90;
        s = -90;
        w = 0;
        e = 360;
    }
    else if (arity == 3)  // area given as a list
    {
        arg[2].GetValue(l);
        for (i = 0; i < 4; i++)
            (*l)[i].GetValue(d[i]);
    }
    else
        getNumberArgsAsArrayOfNumbers(arg, 1, 4, d);  // area given as 4 numbers
    // note: looks like ValidArguments prevents this case

    while (w > e)
        w -= 360.0;
    MvGeoBox geoArea(n, w, s, e);

    // Variables initialization
    // List size: will store one list per each field
    if (v->count > 1)
        lst2 = new CList(v->count);

    // Frequencies counter: size = number of intervals plus 1
    std::vector<long> vfreq(nint + 1, 0L);

    // Loop fieldset
    double v1;
    bool found;
    for (i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->hasLocationInfo())
            return Error("frequencies: unimplemented or spectral data - unable to extract location data");

        // loop gridpoints
        for (j = 0; j < grd->length(); ++j) {
            // Check if it is a valid point
            if (grd->hasValue() && geoArea.isInside(grd->lat_y(), grd->lon_x())) {
                // Compute frequency
                v1 = grd->value();
                found = false;
                for (k = 0; k < nint; k++) {
                    if (v1 < vint[k]) {
                        vfreq[k]++;
                        found = true;
                        break;
                    }
                }
                if (!found)
                    vfreq[nint]++;
            }
            grd->advance();
        }

        // Save result
        auto* z = new CList(nint + 1);
        for (k = 0; k < nint + 1; k++)
            (*z)[k] = vfreq[k];

        if (v->count > 1)
            (*lst2)[i] = Value(z);
        else
            lst1 = Value(z);
    }

    if (v->count > 1)
        return Value(lst2);

    return lst1;
}

//=============================================================================
// Fill missing values along the horizontal line.
// Implemented only for regular latlong grid format.
// For each latitude line, analyses each point from left to right. If a point is
// a missing value then replaces it by either the previous point, if the next
// point is a missing value, or by the next point.

class FillMVEWFunction : public Function
{
public:
    FillMVEWFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Fills missing values along the horizontal line";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value FillMVEWFunction::Execute(int, Value* arg)
{
    double v1;
    fieldset* v;

    arg[0].GetValue(v);
    fieldset* z = copy_fieldset(v, v->count, false);

    for (int i = 0; i < v->count; i++) {
        std::unique_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->isRegularLatLongGrid())
            return Error("fill_missing_values_ew: implemented only for regular latlong format");

        std::unique_ptr<MvGridBase> newGrd(MvGridFactory(z->fields[i]));

        // Copy first point
        v1 = grd->value();
        newGrd->value(v1);
        grd->advance();
        newGrd->advance();

        // Loop from the second to the one before the last point
        for (int j = 1; j < grd->length() - 1; j++) {
            if (grd->hasValue()) {
                v1 = grd->value();
                newGrd->value(v1);
                grd->advance();
            }
            else {
                grd->advance();
                if (grd->hasValue())
                    newGrd->value(grd->value());
                else
                    newGrd->value(v1);
            }

            newGrd->advance();
        }

        // Compute the last point
        if (grd->hasValue())
            newGrd->value(grd->value());
        else
            newGrd->value(v1);
    }

    return Value(z);
}

//=============================================================================

class MeanSumFunction : public Function
{
public:
    MeanSumFunction(const char* n, bool a) :
        Function(n, 1, tgrib),
        doAvg_(a)
    {
        info = (doAvg_) ? "Returns the mean of the values in a Fieldset in each gridpoint" : "Returns the sum of the values in a Fieldset in each gridpoint";
    }
    int ValidArguments(int arity, Value* arg) override;
    Value Execute(int arity, Value* arg) override;

private:
    bool doAvg_{true};
    bool handleMissing_{false};
    int adjustedArity_{0};
    std::string incorrectOption_;
};

int MeanSumFunction::ValidArguments(int arity, Value* arg)
{
    metview::checkStringOption("missing", arity, arg, handleMissing_, incorrectOption_);
    adjustedArity_ = arity;

    if (arity == 1) {
        return (arg[0].GetType() == tgrib);
    }

    return false;
}

Value MeanSumFunction::Execute(int /*arity*/, Value* arg)
{
    if (!incorrectOption_.empty()) {
        return Error("%s: if supplied, the option parameter must be 'missing'; it is '%s'",
                     Name(), incorrectOption_.c_str());
    }

    fieldset* v{nullptr};
    arg[0].GetValue(v);

    if (v->count <= 0) {
        return Error("%s: empty input fieldset", Name());
    }

    // the resulting field
    field* resF{nullptr};

    // "missing" mode: the computation uses the non-missing values in a gridpoint
    if (handleMissing_) {
        std::vector<int> validNum;
        for (int i = 0; i < v->count; i++) {
            field* g = get_field(v, i, expand_mem);
            if (!MISSING_FIELD(g)) {
                if (!resF) {
                    resF = copy_field(g, true);
                    validNum.reserve(resF->value_count);
                    // init resulting values and valid num
                    for (std::size_t j = 0; j < resF->value_count; j++) {
                        if (!MISSING_VALUE(resF->values[j])) {
                            validNum[j] = 1;
                        }
                        else {
                            resF->values[j] = 0.0;
                            validNum[j] = 0;
                        }
                    }
                    // at this point we do not have bitmap in the resulting field
                    resF->bitmap = false;
                }
                else {
                    assert(resF);
                    if (g->value_count != resF->value_count) {
                        release_field(g);
                        release_field(resF);
                        return Error("%s: not all fields have the same number of values!", Name());
                        // has missing value in input
                    }
                    else if (g->missing_vals) {
                        for (std::size_t j = 0; j < g->value_count; j++) {
                            if (!MISSING_VALUE(g->values[j])) {
                                resF->values[j] += g->values[j];
                                validNum[j]++;
                            }
                        }
                    }
                    else {
                        for (std::size_t j = 0; j < g->value_count; j++) {
                            resF->values[j] += g->values[j];
                            validNum[j]++;
                        }
                    }
                }
            }
            release_field(g);
        }

        if (!resF) {
            return Error("%s: no valid fields found!", Name());
        }

        if (doAvg_) {
            for (std::size_t j = 0; j < resF->value_count; j++) {
                if (validNum[j] > 0) {
                    resF->values[j] /= validNum[j];
                }
                else {
                    SetFieldElementToMissingValue(resF, j);
                }
            }
        }
        else {
            for (std::size_t j = 0; j < resF->value_count; j++) {
                if (validNum[j] == 0) {
                    SetFieldElementToMissingValue(resF, j);
                }
            }
        }
        // default mode: if there is even one missing value in a gridpoint the result will be missing
        // value in that gridpoint
    }
    else {
        int missingFieldNum = 0;
        for (int i = 0; i < v->count; i++) {
            field* g = get_field(v, i, expand_mem);
            if (!MISSING_FIELD(g)) {
                if (!resF) {
                    resF = copy_field(g, true);
                }
                else {
                    assert(resF);
                    if (g->value_count != resF->value_count) {
                        release_field(g);
                        release_field(resF);
                        return Error("%s: not all fields have the same number of values!", Name());
                        // has missing value in either res or input
                    }
                    else if (g->missing_vals || resF->missing_vals) {
                        for (std::size_t j = 0; j < g->value_count; j++) {
                            if (!MISSING_VALUE(resF->values[j])) {
                                if (!MISSING_VALUE(g->values[j])) {
                                    resF->values[j] += g->values[j];
                                }
                                else {
                                    SetFieldElementToMissingValue(resF, j);
                                }
                            }
                        }
                    }
                    else {
                        for (std::size_t j = 0; j < g->value_count; j++) {
                            resF->values[j] += g->values[j];
                        }
                    }
                }
            }
            else {
                missingFieldNum++;
            }
            release_field(g);
        }

        if (!resF || missingFieldNum >= v->count) {
            return Error("%s: no valid fields found!", Name());
        }

        if (doAvg_) {
            for (std::size_t j = 0; j < resF->value_count; j++) {
                if (!MISSING_VALUE(resF->values[j])) {
                    resF->values[j] /= (v->count - missingFieldNum);
                }
            }
        }
    }

    assert(resF);
    fieldset* resFs = new_fieldset(1);
    set_field(resFs, resF, 0);
    return Value(resFs);
}

//=============================================================================

static void install(Context* c)
{
    func* f = mars_functions();

    c->AddGlobal(new Variable("grib_missing_value", Value(mars.grib_missing_value)));

    // note: it is important that our own MergeGribFunction is added before the MARS
    // built-in functions are added, so that ours is chosen first.

    c->AddFunction(new DumpGribFunction("dumpgrib"));
    c->AddFunction(new SortGribFunction("sort"));
    c->AddFunction(new IntegrateFunction("integrate"));
    c->AddFunction(new InterpolateFunction("interpolate"));
    c->AddFunction(new NearestGridpointFunction("nearest_gridpoint", false));
    c->AddFunction(new NearestGridpointFunction("nearest_gridpoint_info", true));
    c->AddFunction(new SurroundingPointsFunction("surrounding_points_indexes"));
    c->AddFunction(new AccumulateFunction("accumulate", false));
    c->AddFunction(new AccumulateFunction("average", true));
    c->AddFunction(new LatLonAverageFunction("average_ew", true));
    c->AddFunction(new LatLonAverageFunction("average_ns", false));
    c->AddFunction(new CosLatFunction("coslat"));
    c->AddFunction(new SinLatFunction("sinlat"));
    c->AddFunction(new TanLatFunction("tanlat"));
    c->AddFunction(new SolarZenithAngleFunction("solar_zenith_angle"));
    c->AddFunction(new AbsVortFunction("absolute_vorticity"));
    c->AddFunction(new GridDistanceFunction("distance"));
    c->AddFunction(new GridBearingFunction("bearing"));
    c->AddFunction(new MaskFunction("mask"));
    c->AddFunction(new RMaskFunction("rmask"));
    c->AddFunction(new PolyMaskFunction("poly_mask"));
    c->AddFunction(new GenerateFunction("generate"));
    c->AddFunction(new SubGribFunction("[]"));
    c->AddFunction(new MergeGribFunction("&"));
    c->AddFunction(new MergeGribFunction("merge"));
    c->AddFunction(new GribMatrixFunction("matrix"));
    c->AddFunction(new Grib2To1Function("direction", G2TO1_DIRECTION));
    c->AddFunction(new Grib2To1Function("speed", G2TO1_SPEED));
    c->AddFunction(new MeanEwFunction("mean_ew"));
    c->AddFunction(new GradientFunction("_cpp_gradient"));
    c->AddFunction(new DerivativeFunction("_cpp_first_derivative_x", 0));
    c->AddFunction(new DerivativeFunction("_cpp_first_derivative_y", 1));
    c->AddFunction(new DerivativeFunction("_cpp_second_derivative_x", 2));
    c->AddFunction(new DerivativeFunction("_cpp_second_derivative_y", 3));
    c->AddFunction(new GridCellAreaFunction("grid_cell_area"));

    c->AddFunction(new CovarianceFunction("corr_a"));
    c->AddFunction(new CovarianceFunction("covar_a"));
    c->AddFunction(new CovarianceFunction("var_a"));
    c->AddFunction(new CovarianceFunction("stdev_a"));
    c->AddFunction(new CovarianceFunction("rms_a"));

    c->AddFunction(new GeoOnMlFunction("mvl_geopotential_on_ml"));
    c->AddFunction(new MLToPLInterpolateFunction("mvl_ml2hPa"));
    c->AddFunction(new MLToHLInterpolateFunction("ml_to_hl"));
    c->AddFunction(new PLToPLInterpolateFunction("pl_to_pl"));
    c->AddFunction(new VertIntFunction("vertint"));
    c->AddFunction(new UniVertIntFunction("univertint"));
    c->AddFunction(new ThicknessFunction("thickness", false));
    c->AddFunction(new ThicknessFunction("pressure", true));
    c->AddFunction(new UniThicknessAndPressureFunction("unithickness", false));
    c->AddFunction(new UniThicknessAndPressureFunction("unipressure", true));
    c->AddFunction(new GribMinMaxFunction("min", true));
    c->AddFunction(new GribMinMaxFunction("max", false));
    c->AddFunction(new MinMaxAreaFunction("minvalue", true));
    c->AddFunction(new MinMaxAreaFunction("maxvalue", false));

    c->AddFunction(new LookupFunction("lookup"));
    c->AddFunction(new LookupFunction2("lookup"));
    c->AddFunction(new FindIndexesFunction("indexes"));

    c->AddFunction(new FindFunction("find"));
    c->AddFunction(new GFindFunction("gfind"));

    c->AddFunction(new GridValsFunction("values", false));             // new version
    c->AddFunction(new GridValsFunction("gridvals", true, "values"));  // deprecated version

    c->AddFunction(new GridLatLonsFunction("latitudes", GLL_LATS, false));             // new version
    c->AddFunction(new GridLatLonsFunction("gridlats", GLL_LATS, true, "latitudes"));  // deprecated version

    c->AddFunction(new GridLatLonsFunction("longitudes", GLL_LONS, false));             // new version
    c->AddFunction(new GridLatLonsFunction("gridlons", GLL_LONS, true, "longitudes"));  // deprecated version

    c->AddFunction(new SetGridValsFunction("set_values", false));                 // new version
    c->AddFunction(new SetGridValsFunction("set_gridvals", true, "set_values"));  // deprecated version

    c->AddFunction(new BoundingBoxFunction("bounding_box"));

    c->AddFunction(new GribDateFunction("base_date", GDT_BASE));
    c->AddFunction(new GribDateFunction("valid_date", GDT_VALID));

#if 0
	c->AddFunction(new SpectrumFunction("spectrum"));
#endif
    c->AddFunction(new DataInfoFunction("datainfo"));
    c->AddFunction(new GribIndexesFunction("grib_indexes"));

    for (int i = 0; f[i].name; i++)
        c->AddFunction(new GribFunction(f[i].name, &f[i]));

    c->AddFunction(new GribSetBitsFunction("gribsetbits"));

    c->AddFunction(new DistributionFunction("distribution"));
    c->AddFunction(new CubeFunction("cube"));

    c->AddFunction(new GribIntToFloatFunction("float"));
    c->AddFunction(new GribFloatToIntFunction("integer"));
    c->AddFunction(new FrequenciesFunction("frequencies"));

    c->AddFunction(new GribHeaderFunctionR("grib_get_long", GRIB_LONG));
    c->AddFunction(new GribHeaderFunctionR("grib_get_double", GRIB_DOUBLE));
    c->AddFunction(new GribHeaderFunctionR("grib_get_string", GRIB_STRING));

    c->AddFunction(new GribHeaderFunctionR("grib_get_long_array", GRIB_LONG_ARRAY));
    c->AddFunction(new GribHeaderFunctionR("grib_get_double_array", GRIB_DOUBLE_ARRAY));
    c->AddFunction(new GribHeaderFunctionRGeneric("grib_get"));

    c->AddFunction(new GribHeaderFunctionW("grib_set_long", GRIB_LONG));
    c->AddFunction(new GribHeaderFunctionW("grib_set_double", GRIB_DOUBLE));
    c->AddFunction(new GribHeaderFunctionW("grib_set_string", GRIB_STRING));
    c->AddFunction(new GribHeaderFunctionW("grib_set_long_array", GRIB_LONG_ARRAY));
    c->AddFunction(new GribHeaderFunctionWGeneric("grib_set"));

    c->AddFunction(new FillMVEWFunction("fill_missing_values_ew"));

    // we push these to the front to use them instead of the functions in the mars-client
    c->AddFunctionToFront(new MeanSumFunction("mean", true));
    c->AddFunctionToFront(new MeanSumFunction("sum", false));
}

static Linkage linkage(install);
