/***************************** 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 "macro.h"
#include "cbufr.h"
#include "inc_stl.h"
#include "script.h"

#include "MvException.h"
#include "MvFieldSet.h"
#include "MvGrid.h"
#include "MvPath.hpp"

#define NO_MVGRID 0


// used in function GribHeaderFunctionR
enum eGribHeaderType
{
    GRIB_LONG = 0,
    GRIB_DOUBLE,
    GRIB_STRING,
    GRIB_DOUBLE_ARRAY,
    GRIB_LONG_ARRAY
};


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


double CovarFunc(field*, field*, const MvGeoBox&);

static math Math = {
    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, NULL);
        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()
{
    cout << "\n";
    if (fs->count > 1)
        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 == NULL
        //		-- 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, NULL);
        release_field(g);
        Perl(r);
        cout << "\n";
        free_all_requests(r);
    }

    cout << "\n";
    if (fs->count > 1)
        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)
{
    cout << '<' << fs->count << " field";
    if (fs->count > 1)
        cout << 's';
    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 NULL;
    }

    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" */


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

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

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

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

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_grib_api_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);

        auto_ptr<MvGridBase> grd(MvGridFactory(g));
        if (!grd->hasLocationInfo())
            return Error("GenerateFunction: unimplemented or spectral data - unable to extract location data");
        auto_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 Function
{
public:
    MaskFunction(const char* n) :
        Function(n) { info = "Generates masks for fieldsets"; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

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

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

Value MaskFunction::Execute(int arity, Value* arg)
{
    fieldset* v;
    int i;

    double d[4];

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

    arg[0].GetValue(v);

    if (arity == 2) {
        CList* l;
        arg[1].GetValue(l);
        for (i = 0; i < 4; i++)
            (*l)[i].GetValue(d[i]);
    }
    else
        for (i = 0; i < 4; i++)
            arg[i + 1].GetValue(d[i]);

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

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


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

        auto_ptr<MvGridBase> newGrd(MvGridFactory(z->fields[i]));

        for (int j = 0; j < grd->length(); ++j) {
            newGrd->value(geoArea.isInside(grd->lat_y(), grd->lon_x()));
            grd->advance();
            newGrd->advance();
        }

        //F This is causing problems to Emoslib, if the fieldset
        //F is greater than 10. Vesa is checking ...
        //F		if( ((i+1) % 10) == 0)
        //F		  save_fieldset(z);
    }

    //-- do we need to release 'z' ???

    return Value(z);
}

//=============================================================================
//----------------------   rmask - "round mask" alias "radius mask" (020805/vk)

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

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

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

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

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

    if (arity == 2) {
        CList* l;  //-- extract 3 numeric values from a list
        arg[1].GetValue(l);
        for (i = 0; i < 3; i++)
            (*l)[i].GetValue(d[i]);
    }
    else
        for (i = 0; i < 3; i++)  //-- extract 3 numeric values
            arg[i + 1].GetValue(d[i]);

    MvLocation center(d[0], d[1]);  //-- mask center, lat/lon
    double rad_m = d[2];            //-- mask radius, metres
    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
    {
        auto_ptr<MvGridBase> newGrd(MvGridFactory(z->fields[i]));

        if (!newGrd->hasLocationInfo())
            return Error("rmask: 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(center);
            newGrd->value(distance_m > rad_m ? 0.0 : 1.0);
        } while (newGrd->advance());
    }

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

//=============================================================================
//-----------------------------------------------------   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) {
        CList* l;  //-- extract 2 numeric values from a list
        arg[1].GetValue(l);
        for (i = 0; i < 2; i++)
            (*l)[i].GetValue(d[i]);
    }
    else
        for (i = 0; i < 2; i++)  //-- extract 3 numeric values
            arg[i + 1].GetValue(d[i]);

    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
    {
        auto_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 Function
{
public:
    GridBearingFunction(const char* n) :
        Function(n) { info = "Computes the bearings for all the gridpoints with respect to a reference point"; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

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

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

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

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

    if (arity == 2) {
        CList* l;  //-- extract 2 numeric values from a list
        arg[1].GetValue(l);
        for (int i = 0; i < 2; i++)
            (*l)[i].GetValue(d[i]);
    }
    else
        for (int i = 0; i < 2; i++)  //-- extract 3 numeric values
            arg[i + 1].GetValue(d[i]);

    double latRef = d[0];
    double lonRef = d[1];

    if (lonRef > 180.)
        lonRef -= 360.;

    const double cDEG2RAD = M_PI / 180.0;
    const double cRAD2DEG = 1. / cDEG2RAD;
    double cosLatRef      = cos(latRef * cDEG2RAD);
    double sinLatRef      = sin(latRef * cDEG2RAD);

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

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

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

        do {
            double bearing = 0.;
            double lon     = newGrd->lon_x();
            double lat     = newGrd->lat_y();

            if (lon > 180.)
                lon -= 360.;

            double d_lon = (lon - lonRef) * cDEG2RAD;

            //the bearing is constant on the same latitude
            if (fabs(lat - latRef) < 1E-9) {
                //same location
                if (fabs(lon - lonRef) < 1E-9) {
                    newGrd->setValueToMissing();
                }
                else {
                    if (d_lon <= 0.)
                        bearing = 270.;
                    else
                        bearing = 90.;

                    newGrd->value(bearing);
                }
            }
            else {
                lat *= cDEG2RAD;

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

                bearing *= cRAD2DEG;
                newGrd->value(bearing);
            }
        } while (newGrd->advance());
    }

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


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

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_grib_api_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);

	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");

	}

	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];
		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);

    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");
    }

    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];
        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;
        err = 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 );
        err = 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
        cerr << ">>> DistributionFunction::Execute() - unable to get 'numberOfBitsContainingEachPackedValue'"
             << 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";
    }
    virtual Value Execute(int arity, Value* arg);
};


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

Value GetGribHeaderValue(MvField* mvfield, const char* key, eGribHeaderType type)
{
    Value value;


    try {
        switch (type) {
            case GRIB_LONG: {
                long n = mvfield->getGribKeyValueLong(key, true);
                value  = n;
                break;
            }

            case GRIB_DOUBLE: {
                double r = mvfield->getGribKeyValueDouble(key, true);
                value    = r;
                break;
            }

            case GRIB_STRING: {
                string str = mvfield->getGribKeyValueString(key, true);
                value      = str.c_str();
                break;
            }

            case GRIB_LONG_ARRAY: {
                long* lvals   = NULL;
                long num_vals = mvfield->getGribKeyArrayLong(key, &lvals, true);

                if ((lvals != NULL) && (num_vals > 0)) {
                    CVector* 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 = NULL;
                long num_vals = mvfield->getGribKeyArrayDouble(key, &dvals, true);

                if ((dvals != NULL) && (num_vals > 0)) {
                    CVector* 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;
    const char* key;

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


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

    for (int i = 0; i < fs->count; i++) {
        field* h         = GetIndexedFieldWithAtLeastPackedMem(fs, i);
        MvField* mvfield = new MvField(h);
        Value value      = GetGribHeaderValue(mvfield, key, type);


        (*l)[i] = value;


        // note: mvfield must be created with 'new' and not on the stack. This is
        // because if it is on the stack, it will be destroyed after release_field()
        // is called, and in its destructor it reverts the field to its previous
        // shape, which is packed_mem, i.e. it re-reads and allocates the field!
        // In the new version of the code, we delete the mvfield first which causes
        // no change in its shape (packed_mem->packed_mem) and then release it, which
        // really does get rid of it.

        delete mvfield;
        release_field(h);
    }

    if (l->Count() > 1)
        return Value(l);
    else
        return 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";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};


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("grib_get: 3rd parameter should be 'field' or 'key'; %s is not valid.", groupingString);
    }


    CList* fieldValues = NULL;
    CList* keyValues   = NULL;

    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++) {
        field* h         = GetIndexedFieldWithAtLeastPackedMem(fs, i);
        MvField* mvfield = new MvField(h);

        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

            if (typeString[0] != '\0') {
                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
                    return Error("grib_get: type specifier must be one of 'l', 'd', 's', 'da', 'la'. It is '%s'", typeString);
            }


            Value value = GetGribHeaderValue(mvfield, 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;


        // note: mvfield must be created with 'new' and not on the stack. This is
        // because if it is on the stack, it will be destroyed after release_field()
        // is called, and in its destructor it reverts the field to its previous
        // shape, which is packed_mem, i.e. it re-reads and allocates the field!
        // In the new version of the code, we delete the mvfield first which causes
        // no change in its shape (packed_mem->packed_mem) and then release it, which
        // really does get rid of it.

        delete mvfield;
        release_field(h);

    }  // for each field


    if (grouping == GROUP_BY_FIELD)
        return fieldValues;
    else
        return keyValues;


    //    if (fieldValues->Count() > 1)
    //        return Value(fieldValues);
    //    else
    //        return Value((*fieldValues)[0]);
}


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

/*******************************************************************************
 *
 * 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";
    }

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


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

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

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


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


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


    // for each field, apply the changes

    for (i = 0; i < v->count; i++) {
        field* h = GetIndexedFieldWithAtLeastPackedMem(z, i);
        MvField mvfield(h);

        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);
                    mvfield.setGribKeyValueLong(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);
                    mvfield.setGribKeyValueDouble(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);
                    string valuestring = string(value);
                    mvfield.setGribKeyValueString(key, valuestring);
                }
                break;
            }

            default:

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

        //        mars.accuracy = h->ksec4[1];  // do we need this line?
        //        save_fieldset(z);  // was already commented out in the original putksec function
        //        release_field(h);  // was already commented out in the original putksec function
    }


    Value x(z);

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

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


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

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

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


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


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


    // for each field, apply the changes

    for (i = 0; i < v->count; i++) {
        field* h = GetIndexedFieldWithAtLeastPackedMem(z, i);
        MvField mvfield(h);


        // for each key/value pair, set them in the field

        for (int j = 0; j < l->Count(); j += 2) {
            const char* key;
            vtype valueType;

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

            switch (valueType) {
                case tstring: {
                    const char* value;
                    (*l)[j + 1].GetValue(value);
                    string valuestring = string(value);
                    mvfield.setGribKeyValueString(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
                        mvfield.setGribKeyValueLong(key, numAsInt);
                    }
                    else {
                        // set as double
                        mvfield.setGribKeyValueDouble(key, value);
                    }
                    break;
                }

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


    Value x(z);

    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;

    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, NULL);
        release_field(g);

        if (v->count > 1)
            (*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
{
    typedef request* reqp;
    typedef field* grbp;
    typedef char* chrp;

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);
    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, *l2;

                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[] = {
    "DATE",
    "TIME",
    "STEP",
    "NUMBER",
    "LEVELIST",
    "PARAM",
};


Value SortGribFunction::Execute(int arity, Value* arg)
{
    fieldset *v, *w;

    arg[0].GetValue(v);
    w = copy_fieldset(v, v->count, true);

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

    int i;
    for (i = 0; i < w->count; i++) {
        g[i] = get_field(w, i, packed_mem);
        n[i] = i;
        r[i] = empty_request(0);
        //grib_to_request(r[i],g[i]->buffer,g[i]->length);
        /*int e = */ handle_to_request(r[i], g[i]->handle, NULL);
        release_field(g[i]);
    }

    char** names;
    char** order;
    int count;

    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;
                arg[2].GetValue(p);
                order = up(count, p);
            }
            else
                order = extract(arg[2], count);
            break;
    }

    i = count;
    while (--i >= 0)
        sort(names[i], r, n, v->count, order[i]);

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

    for (i = 0; i < w->count; i++)
        w->fields[i] = g[n[i]];

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

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

    return Value(w);
}


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

class CosLatFunction : public Function
{
public:
    CosLatFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Generates a field with the cosine of the gridpoint latitudes";
    }
    virtual Value Execute(int arity, Value* arg);
};

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

    arg[0].GetValue(v);
    fieldset* z = copy_fieldset(v, v->count, false);


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

        auto_ptr<MvGridBase> newGrd(MvGridFactory(z->fields[i]));

        for (int j = 0; j < grd->length(); ++j) {
            newGrd->value(cos(grd->lat_y() * M_PI / 180.0));
            grd->advance();
            newGrd->advance();
        }

        //F This is causing problems to Emoslib, if the fieldset
        //F is greater than 10. Vesa is checking ...
        //F		if( ((i+1) % 10) == 0)
        //F		  save_fieldset(z);
    }

    return Value(z);
}

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

class SinLatFunction : public Function
{
public:
    SinLatFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Generates a field with the sine of the gridpoint latitudes";
    }
    virtual Value Execute(int arity, Value* arg);
};

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

    arg[0].GetValue(v);
    fieldset* z = copy_fieldset(v, v->count, false);


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

        auto_ptr<MvGridBase> newGrd(MvGridFactory(z->fields[i]));

        for (int j = 0; j < grd->length(); ++j) {
            newGrd->value(sin(grd->lat_y() * M_PI / 180.0));
            grd->advance();
            newGrd->advance();
        }

        //F This is causing problems to Emoslib, if the fieldset
        //F is greater than 10. Vesa is checking ...
        //F		if( ((i+1) % 10) == 0)
        //F		  save_fieldset(z);
    }

    return Value(z);
}

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

class TanLatFunction : public Function
{
public:
    TanLatFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Generates a field with the tangent of the gridpoint latitudes";
    }
    virtual Value Execute(int arity, Value* arg);
};

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

    arg[0].GetValue(v);
    fieldset* z = copy_fieldset(v, v->count, false);

    double eps = 0.0000001;

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

        auto_ptr<MvGridBase> newGrd(MvGridFactory(z->fields[i]));

        for (int j = 0; j < grd->length(); ++j) {
            //we set missing value near the poles
            if (90. - fabs(grd->lat_y()) < eps) {
                newGrd->setValueToMissing();
            }
            else {
                newGrd->value(tan(grd->lat_y() * M_PI / 180.0));
            }
            grd->advance();
            newGrd->advance();
        }
    }

    return Value(z);
}

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

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

public:
    GridValsFunction(const char* n, bool d, const char* nn = NULL) :
        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 = 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...
    {
        field* g   = get_field(fs, i, expand_mem);
        CVector* 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)[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 = NULL) :
        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...
    {
        auto_ptr<MvGridBase> grid(MvGridFactory(fs->fields[i]));
        if (!grid->hasLocationInfo())
            return Error("gridlats/gridlons: unimplemented or spectral data - unable to extract location data");

        CVector* 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;
    const char* newName;
    bool option_given;  // did the user supply a string option parameter?
public:
    SetGridValsFunction(const char* n, bool d, const char* nn = NULL) :
        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

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

    for (int i = 0; i < fs->count; i++) {
        field* h            = GetIndexedFieldWithAtLeastPackedMem(fs, i);
        MvField* 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
        return Value((*l)[0]);
}

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

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;
    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)[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();
    double* 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;

    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");
            }
            field* k = get_field(w, n, expand_mem);
            if (j >= k->value_count) {
                return Error("lookup: fields mismatch");
            }
            h->values[j] = k->values[j];
            release_field(k);
        }

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

    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
{
public:
    FindIndexesFunction(const char* n) :
        Function(n, 2, tgrib, tvector)
    {
        info = "Builds an output fieldset containing each gridpoint's indexed position in the given vector";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value FindIndexesFunction::Execute(int, Value* arg)
{
    fieldset* fs;
    CVector* v;
    int i;

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

    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;
            int 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) {
                        if (vindex == 0)  // only at the first value in the vector?
                        {
                            result = 0;
                        }
                        else {
                            // find the closest point - the current or the previous one

                            double d1 = d - v->getIndexedValue(vindex - 1);
                            double d2 = dvector - d;

                            if (d1 < d2)
                                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];

    arg[0].GetValue(v);

    if (arity == 1) {
        n = 90;
        s = -90;
        w = 0;
        e = 360;
    }
    else if (arity == 2) {
        if (arg[1].GetType() == tgrib) {
            arg[1].GetValue(z);
        }
        else {
            CList* l;
            arg[1].GetValue(l);
            for (i = 0; i < 4; i++)
                (*l)[i].GetValue(d[i]);
        }
    }
    else
        for (i = 0; i < 4; i++)
            arg[i + 1].GetValue(d[i]);

    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);

    double val;
    Value returnVal;

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

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

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

        val         = 0;
        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) {
            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;

        //cout << " integrated over " << pcnt << " points" << 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++) {
        auto_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:
            for (i = 1; i < 3; i++)
                if (arg[i].GetType() != tnumber)
                    return false;
            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];
    double a;
    Value returnVal;

    double& lat = d[0];
    double& lon = d[1];

    arg[0].GetValue(v);

    if (arity == 2) {
        CList* l;
        arg[1].GetValue(l);
        for (i = 0; i < 2; i++)
            (*l)[i].GetValue(d[i]);
    }
    else
        for (i = 0; i < 2; i++)
            arg[i + 1].GetValue(d[i]);

    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
        auto_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));

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

        a = grd->interpolatePoint(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;
        }
#endif
    }

    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;       // if vector of coordinates
    CVector* vlon;       // 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
        CList* l;
        arg[1].GetValue(l);
        for (i = 0; i < 2; i++)
            (*l)[i].GetValue(d[i]);
    }
    else {
        input_type = arg[1].GetType();

        if (input_type == tnumber)  // get 2 coordinates as numbers
            for (i = 0; i < 2; i++)
                arg[i + 1].GetValue(d[i]);

        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("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);

        auto_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);
                CVector* 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(NULL);

    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) {
        CList* l;
        arg[1].GetValue(l);
        for (i = 0; i < 2; i++)
            (*l)[i].GetValue(d[i]);

    } else {
        input_type = arg[1].GetType();

        if (input_type == tnumber)  // get 2 coordinates as numbers
            for (i = 0; i < 2; i++)
                arg[i + 1].GetValue(d[i]);

        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());

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

    if (arity == 2) {
        n = 90;
        s = -90;
        w = 0;
        e = 360;
    }
    else if (arity == 3) {
        if (arg[2].GetType() == tgrib) {
            arg[2].GetValue(z);
        }
        else {
            CList* l;
            arg[2].GetValue(l);
            for (i = 0; i < 4; i++)
                (*l)[i].GetValue(d[i]);
        }
    }
    else
        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++) {
        auto_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->hasLocationInfo())
            return Error("find: unimplemented or spectral data - unable to extract location data");

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

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

        CList** 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) {
                    CList* z = new CList(2);
                    (*z)[0]  = grd->lat_y();
                    (*z)[1]  = grd->lon_x();
                    q[cnt++] = z;
                }
            }

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


        CList* 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 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];
            auto_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] /= 9.80665;

    release_field(h);
    release_field(lnsp);

    return Value(z);

#if 0
	fieldset *v;
	field    *lnsp = 0;
	fortint   datarep;

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

		for(int i = 0; (i < v->count) && (n == -1) ;i++)
		{
			field *g  = get_field(v,i,expand_mem);
			gribsec1   *s1 = (gribsec1*)&g->ksec1[0];
			if(s1->parameter == 152) n = i;
			release_field(g);
		}
		if(n == -1)
			 return Error("vertint: LNSP 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);
	}
	int j;
#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
	gribsec1    *s1 = (gribsec1*)    &lnsp->ksec1[0];
	gribsec2_ll *s2 = (gribsec2_ll*) &lnsp->ksec2[0];

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

	if(s1->level_type != GRIB_MODEL_LEVEL)
		return Error("vertint: Field is not model level");

	if(s1->parameter != 152)
		return Error("vertint: Field is not LNSP");

	fortfloat *aa = &lnsp->rsec2[10];
	fortfloat *bb = &lnsp->rsec2[10 + s2->vertical/2];

	//cout << "LNSP level = " << s1->top_level << ", C1 & C2: " << aa[0] << ", " << bb[0] << endl;

	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)

	for(j=0;j<h->sec4len;j++)
	  {
	    h->rsec4[j] = 0;
	    if (! MISSING_VALUE(lnsp->rsec4[j]))
	      lnsp->rsec4[j] = exp(lnsp->rsec4[j]);
	  }

	for(int i = 0; i < v->count ;i++)
	{
		field *g = get_field(v,i,expand_mem);
		if(g != lnsp)
		{
			gribsec1    *s1 = (gribsec1*)    &g->ksec1[0];
			gribsec2_ll *s2 = (gribsec2_ll*) &g->ksec2[0];

			if( s2->data_rep != datarep )
				return Error("vertint: Field has different representation than LNSP!");

			if(s1->level_type != GRIB_MODEL_LEVEL)
				return Error("vertint: Field is not model level");

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


			int level = (int)s1->top_level;
			//cout << "Level = " << level << ", C1 & C2: " << aa[level] << ", " << bb[level] << endl;

			for(int j=0;j<g->sec4len;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->rsec4[j]))    &&
				    !(MISSING_VALUE (lnsp->rsec4[j])) &&
					!(MISSING_VALUE (h->rsec4[j])))
				{
					double pkpd = aa[level]   + bb[level]   * lnsp->rsec4[j];
					double pkmd = aa[level-1] + bb[level-1] * lnsp->rsec4[j];
					double dp = pkpd - pkmd;
					h->rsec4[j] += g->rsec4[j] * dp;
				}
				else
				{
					SetFieldElementToMissingValue (h, j);
				}
			}
			release_field(g);
		}
	}

	for(j=0;j<h->sec4len;j++)
		if (!MISSING_VALUE(h->rsec4[j]))
			h->rsec4[j] /= 9.80665;

	release_field(h);
	release_field(lnsp);

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

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

protected:
    int lnsp_;
    int nrfs_;
    bool limitedRange;
    int top_;
    int bottom_;
};

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

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

    limitedRange = false;
    top_         = 0;
    bottom_      = -1;
    CList* l;

    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(lnsp_);  //-- 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(lnsp_);  //-- 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_);
                    limitedRange = true;
                }
            }
            else
                valid = false;
            break;

        default:
            valid = false;
    }

    return valid;
}

const char* UniVertIntFunction::fs_name(fieldset* fs)
{
    return fs->fields[0]->file->fname;
}

Value UniVertIntFunction::Execute(int /*arity*/, Value* arg)
{
    const double cSuspicious = 20000.0;  //-- suspicious pressure layer > 200 hPa

    fieldset* fs_data;
    fieldset* fs_lnsp = 0;

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

    field* f_lnsp_orig;
    int n_lnsp = -1;

    if (nrfs_ == 1)  //-- single fieldset - we need to find lnsp
    {
        for (int i = 0; (i < fs_data->count) && (n_lnsp == -1); ++i) {
            f_lnsp_orig = GetIndexedFieldWithAtLeastPackedMem(fs_data, i);
            auto_ptr<MvGridBase> grd(MvGridFactory(f_lnsp_orig));
            if (grd->getLong(parameterKey) == lnsp_) {
                n_lnsp = i;  //-- lnsp found - stop the loop
            }
            else
                release_field(f_lnsp_orig);
        }

        if (n_lnsp == -1)  //-- did we find lnsp?
            return Error("univertint: LNSP not found");
    }
    else  //-- two fieldsets - lnsp & data
    {
        f_lnsp_orig = get_field(fs_lnsp, 0, expand_mem);
    }

    field* f_lnsp = copy_field(f_lnsp_orig, true);  //-- make copy, not to modify original field

    fieldset* z = copy_fieldset(fs_data, 1, false);  //-- create output field...
    field* h    = get_field(z, 0, expand_mem);
    for (size_t jj = 0; jj < h->value_count; ++jj)  //-- ...with all values zeroed
    {
        h->values[jj] = 0;
    }

    MvField F_sp(f_lnsp);
    MvFieldExpander expa_lnsp(F_sp);  //-- input is lnsp...

    for (int j = 0; j < F_sp.countValues(); ++j)  //-- ...but we need sp: lnsp -> sp
        if (!MISSING_VALUE(F_sp[j]))
            F_sp[j] = exp(F_sp[j]);


    //-- confirm lnsp/sp data is ok
    if ((int)F_sp.parameter() != lnsp_)
        return Error("univertint: Field is not LNSP");

    int datarep = F_sp.dataRepres();
    if (datarep != GRIB_LAT_LONG && datarep != GRIB_GAUSSIAN)
        return Error("univertint: Data is not lat/long nor Gaussian!");

    if (!F_sp.isModelLevel())
        return Error("univertint: Data is not model level");


    int upmost = top_ ? top_ - 1 : 0;                  //-- top_ != 0 if limited range
    double prevC1, prevC2;                             //-- for computing pressure level
    if (!F_sp.vertCoordCoefs(upmost, prevC1, prevC2))  //-- for top level
    {
        prevC1 = prevC2 = 0;
        marslog(LOG_INFO, "univertint: Top level coefficients set to zeros");
    }


    for (int fi = 0; fi < fs_data->count; ++fi)  //-- loop all (vertical) data...
    {
        if (fi != n_lnsp)  //-- ...but skip if lnsp field
        {
            field* g = get_field(fs_data, fi, expand_mem);

            MvField F(g);
            if (F.dataRepres() != datarep)
                return Error("univertint: Data has different representation than LNSP!");

            if (!F.isModelLevel())
                return Error("univertint: Data is not model level");

            if (F.countValues() != F_sp.countValues())
                return Error("univertint: Data and LNSP are different");

            int currLev = (int)F.level();
            if (limitedRange && (currLev < top_ || currLev > bottom_)) {
                continue;  //-- limited range and level not inside the range!
            }

            double currC1, currC2;
            F.vertCoordCoefs(currC1, currC2);

            bool errMsgIssued = false;

            for (int gp = 0; gp < F.countValues(); ++gp) {
                if (MISSING_VALUE(F[gp]) || MISSING_VALUE(F_sp[gp]) || MISSING_VALUE(h->values[gp])) {
                    SetFieldElementToMissingValue(h, gp);
                }
                else {
                    double pkpd = currC1 + currC2 * F_sp[gp];
                    double pkmd = prevC1 + prevC2 * F_sp[gp];
                    double dp   = pkpd - pkmd;
                    if ((dp < 0 || dp > cSuspicious) && !errMsgIssued) {
                        if (dp < 0)
                            Error("univertint: Data produces negative layer %d hPa (%d-%d)",
                                  (int)(dp / 100), (int)(pkpd / 100), (int)(pkmd / 100));
                        else
                            marslog(LOG_WARN,
                                    "univertint: suspiciously thick layer %d hPa (%d-%d)",
                                    (int)(dp / 100), (int)(pkpd / 100), (int)(pkmd / 100));

                        errMsgIssued = true;
                    }
                    h->values[gp] += F[gp] * dp;
                }
            }  // end for values

            prevC1 = currC1;
            prevC2 = currC2;

            release_field(g);
        }  // end if not lnsp
    }      // end for fields

    for (size_t ij = 0; ij < h->value_count; ++ij)
        if (!MISSING_VALUE(h->values[ij]))
            h->values[ij] /= 9.80665;

    release_field(h);
    release_field(f_lnsp);

    return Value(z);
}

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

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 = 0;
    int n = 0, l = 0, m = 0;
    int count;


    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);
                    MvField* 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;
        }
    }


    fieldset* z = new_fieldset(0);


    for (int i = 0; i < v->count; i++) {
        field* g     = get_field(v, i, expand_mem);
        MvField* 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) {
            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);
                    }
                }


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

            add_field(z, h);
        }

        delete F_g;
        release_field(g);
    }

    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;

#if 0
	fieldset *v;
	int *levels = 0;
	int n = 0, l = 0, m = 0;
	int count;


	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;
				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 = get_field(w,i,expand_mem);
					gribsec1   *s1  = (gribsec1*)&g->ksec1[0];
					gribsec2_ll *s2 = (gribsec2_ll*) &g->ksec2[0];
					if(s2->data_rep   != GRIB_LAT_LONG)    l++;
					if(s1->level_type != GRIB_MODEL_LEVEL) m++;
					levels[i] = (int)s1->top_level;
					release_field(g);
				}
				break;
		}
	}


	fieldset *z = new_fieldset(0);


	for(int i = 0; i < v->count ;i++)
	{
		field *g        = get_field(v,i,expand_mem);
		gribsec1   *s1  = (gribsec1*)&g->ksec1[0];
		gribsec2_ll *s2 = (gribsec2_ll*) &g->ksec2[0];
		int    levcount = (int)s2->vertical/2;

		fortfloat *aa   = &g->rsec2[10];
		fortfloat *bb   = &g->rsec2[10 + levcount];

		if(s1->parameter  == 152)              n++;
		if(s2->data_rep   != GRIB_LAT_LONG)    l++;
		if(s1->level_type != GRIB_MODEL_LEVEL) m++;

		if(arity == 1)
		{
			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];
			int j;

			field *h = copy_field(g,false);

			if(pressure)
				for(j=0;j<g->sec4len;j++)
				{
					if (!MISSING_VALUE(g->rsec4[j]))
					{
						double e = exp(g->rsec4[j]);
						double a = (aa[level] + aa[level-1])/2.0;
						double b = (bb[level] + bb[level-1])/2.0;
						h->rsec4[j] = a + b * e;
					}
					else
					{
						SetFieldElementToMissingValue (h, j);
					}
				}
			else
				for(j=0;j<g->sec4len;j++)
				{
					if (!MISSING_VALUE(g->rsec4[j]))
					{
						double e = exp(g->rsec4[j]);
						double pkpd = aa[level]   + bb[level]   * e;
						double pkmd = aa[level-1] + bb[level-1] * e;
						double dpg  = pkpd - pkmd;
						h->rsec4[j] = dpg;
					}
					else
					{
						SetFieldElementToMissingValue (h, j);
					}
				}


			gribsec1   *s1  = (gribsec1*)&h->ksec1[0];
			s1->top_level = level;

			add_field(z,h);
		}

		release_field(g);
	}

	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;
#endif
}

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

class UniThicknessAndPressureFunction : public Function
{
    boolean pressure;
    int arity_;
    int lnsp_;

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

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

    arity_ = arity_in;  //-- we may change the-number-of-parameters value!
    lnsp_  = 152;       //-- default ECMWF 'lnsp' code value, in table 2 version 128

    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(lnsp_);
                --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* v;
    int* levels = 0;
    int count;


    arg[0].GetValue(v);

    if (arity_ == 2) {
        CList* l;
        int i;
        switch (arg[1].GetType()) {
            case tlist:
                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_ptr<MvField> fld(new MvField(g));
                    levels[i] = (int)fld->level();
                    release_field(g);
                }
                break;
        }
    }


    fieldset* z = new_fieldset(0);


    for (int i = 0; i < v->count; i++) {
        auto_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->isValid())
            return Error("unipressure/unithickness: cannot process this type of grid!");
        long param = grd->getLong(parameterKey);
        if (param != lnsp_)
            return Error("unipressure/unithickness: lnsp [%d] required, found %d", lnsp_, param);

        int levcount = (int)grd->vertCoordCoefPairCount();

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

        field* g = get_field(v, i, expand_mem);

        for (int k = 0; k < count; k++) {
            int level = levels[k];
            if (level < 1 || level >= levcount)
                return Error("unipressure/unithickness: non-existing level %d!", level);

            grd->init();

            field* h = copy_field(g, false);
            auto_ptr<MvGridBase> grdOut(MvGridFactory(h));

            double a1, b1, a0, b0;
            if (!grd->vertCoordCoefs(a1, b1, level))
                return Error("unipressure/unithickness: no coefficients for level %d!", level);
            if (!grd->vertCoordCoefs(a0, b0, level - 1))
                return Error("unipressure/unithickness: no coefficients for level %d!", level - 1);

            if (pressure) {
                double a = (a1 + a0) / 2.0;
                double b = (b1 + b0) / 2.0;

                for (int j = 0; j < grd->length(); j++) {
                    if (grd->hasValue()) {
                        double e = exp(grd->value());
                        grdOut->value(a + b * e);
                    }
                    else {
                        grdOut->value(grd->missingValue());
                    }
                    grd->advance();
                    grdOut->advance();
                }
            }
            else {
                for (int j = 0; j < grd->length(); j++) {
                    if (grd->hasValue()) {
                        double e    = exp(grd->value());
                        double pkpd = a1 + b1 * e;
                        double pkmd = a0 + b0 * e;
                        double dpg  = pkpd - pkmd;
                        grdOut->value(dpg);
                    }
                    else {
                        grdOut->value(grd->missingValue());
                    }
                    grd->advance();
                    grdOut->advance();
                }
            }

            //-- PARAMETER CODE VALUE NEEDS TO BE CHANGED TOO!!!
#if 0
		//-- change parameter code number for the new computed fields
		gribsec1* s1  = (gribsec1*)&h->ksec1[0];
		s1->top_level = level;
		if( pressure )
		{                       //-- change parameter to 'pressure':
		   s1->parameter =  54; //-- 'pressure' is parameter 54 ...
		   s1->version   = 128; //-- ... in GRIB-Table-2 version 128
		}
		else
		{
		   //-- is there a param code for thickness? (i have not found one)
		   s1->parameter =  54; //-- use 'pressure' again...
		   s1->version   = 128;
		}
#else
            //-- Q&D: this may/will not work for GRIB2:
            grdOut->setLong(parameterKey, 54);
            grdOut->setLong("level", (long)level);
#endif
            add_field(z, h);
        }

        release_field(g);
    }

    delete[] levels;

    Value r(z);

    return r;
}


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

/*******************************************************************************
 *
 * 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(NULL);

        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 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;
    CMatrix* m;
    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];
        auto_ptr<MvGridBase> grd(MvGridFactory(g));
        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)[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
    }
}

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

class GribDirectionFunction : public Function
{
public:
    GribDirectionFunction(const char* n) :
        Function(n, 2, tgrib, tgrib) { info = "Computes meteorological wind direction using U and V wind components"; }
    virtual Value Execute(int arity, Value* arg);
};

Value GribDirectionFunction::Execute(int /*arity*/, Value* arg)
{
    //-- code copied from function 'xy2sd' in MagicsCalls.cc --//

    const double cRad2Deg  = 180 / M_PI;
    const double cEpsilon  = 0.00001;
    const double c270inRad = 270 / cRad2Deg;

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

    if (u->count != v->count)
        return Error("direction: U and V: different number of fields!");

    fieldset* dir = copy_fieldset(u, u->count, false);

    for (int i = 0; i < u->count; i++) {
        auto_ptr<MvGridBase> gu(MvGridFactory(u->fields[i]));
        if (gu->gridType() == "NA")
            return Error("direction: U: cannot process this type of field!");

        auto_ptr<MvGridBase> gv(MvGridFactory(v->fields[i]));
        if (gv->gridType() == "NA")
            return Error("direction: V: cannot process this type of field!");

        if (!gu->isEqual(gv.get()))
            return Error("direction: U and V: different grids!");

        auto_ptr<MvGridBase> gdir(MvGridFactory(dir->fields[i]));

        for (int j = 0; j < gu->length(); ++j) {
            double u = gu->value();
            double v = gv->value();

            //double s = sqrt( u*u + v*v ); //this would be for speed
            double d = 0;

            if (fabs(u) < cEpsilon)  //-- v only: North or South
            {
                if (v > 0)
                    d = M_PI;  //-- from South
                else
                    d = 0;  //-- from North
            }
            else {
                d = atan(v / fabs(u));  //-- ok for Westerly winds
                if (u < 0) {
                    d = M_PI - d;  //-- fix for Easterly winds
                }

                d = c270inRad - d;  //-- rotate 0 from North & reverse direction
            }

            d *= cRad2Deg;  //-- rad-to-degrees
            gdir->value(d);

            gu->advance();  //-- next grid point
            gv->advance();
            gdir->advance();
        }

        //dir->fields[i]->ksec1[0] =  2;        //-- GRIB Table 2 version: version 2 (WMO)
        //dir->fields[i]->ksec1[5] = 31;        //-- Wind direction in Table 2 version 2
        //gdir->setLong( "gribTablesVersionNo", 2 );  // not necessary
        gdir->setLong(parameterKey, 31);
    }

    return Value(dir);
}

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

class MeanEwFunction : public Function
{
public:
    MeanEwFunction(const char* n) :
        Function(n, 1, tgrib)
    {
        info = "Generates a fieldset out of East-West means";
    }
    virtual Value Execute(int arity, Value* arg);
};


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

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

    for (int i = 0; i < v->count; i++) {
        auto_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));

        if (!grd->hasLocationInfo())
            return Error("mean_ew: unimplemented grid type: %s", grd->gridType().c_str());

        if (grd->gridType() != cLatLonGrid &&
            grd->gridType() != cMercatorGrid &&
            grd->gridType() != cGaussianGrid &&
            grd->gridType() != cGaussianReducedGrid)
            return Error("mean_ew: unsuitable grid type: %s", grd->gridType().c_str());

        auto_ptr<MvGridBase> newGrd(MvGridFactory(z->fields[i]));

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

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

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];
    double grid_;
    bool isEW_;
};

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
    {
        auto_ptr<MvGridBase> grd(MvGridFactory(fs->fields[fi]));
        if (!grd->hasLocationInfo())
            return Error("average_xx: unimplemented grid type: %s", grd->gridType().c_str());

        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();
        CVector* 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("field", s);
                break;

            case tnumber:
                double d;
                arg[i].GetValue(d);
                push_named_scalar("scalar", d);
                break;
        }

    Math.name  = (char*)Name();
    Math.arity = arity;

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

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

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);
        auto_ptr<MvGridBase> grd(MvGridFactory(g));
        if (!grd->isValid())
            return Error("float: unimplemented grid type: %s", grd->gridType().c_str());

        field* h = get_field(z, i, expand_mem);
        auto_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;
};

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
        auto_ptr<MvGridBase> grd(MvGridFactory(g));
        if (!grd->isValid())
            return Error("integer: unimplemented grid type: %s", grd->gridType().c_str());

        field* h = get_field(z, i, expand_mem);  //-- output field
        auto_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";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value GradientFunction::Execute(int, Value* arg)
{
    fieldset* v = 0;
    arg[0].GetValue(v);
    assert(v);
    fieldset* z = new_fieldset(2*v->count);
    for (int i = 0; i < v->count; i++) {
        field *gx = get_field(v, i, expand_mem);
        field *g = copy_field(gx, false);
        set_field(z, g, 2*i);
        release_field(gx);

        gx = get_field(v, i, expand_mem);
        g = copy_field(gx, false);
        set_field(z, g, 2*i+1);
        release_field(gx);
    }

    for (int i = 0; i < v->count; i++) {

        auto_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->hasLocationInfo())
            return Error("gradient: [field %d] unimplemented or spectral data - unable to extract location data", i);

        if (!grd->isRegularLatLongGrid())
            return Error("gradient: [field %d] - unsupported grid, implemented only for regular lat-lon grid", i);

        auto_ptr<MvGridBase> grdX(MvGridFactory(z->fields[2*i]));
        auto_ptr<MvGridBase> grdY(MvGridFactory(z->fields[2*i+1]));
        grd->firstDerivativeX(grdX.get());  //df/dx
        grd->init();                        //reset the iterator
        grd->firstDerivativeY(grdY.get());  //df/dy
    }

    return Value(z);
}

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

class DerivativeFunction : public Function
{
public:
    DerivativeFunction(const char* n, int type) :
        Function(n, 1, tgrib),
        type_(type)
    {
        static std::vector<std::string> infoVec =
            {"Computes first West-East derivative of fields",
             "Computes first South-North derivative of fields",
             "Computes second West-East derivative of fields",
             "Computes second South-North derivative of fields"};

        if (type_ >= 0 && type_ < 4) {
            info = strcache(infoVec[type_].c_str());
        }
    }
    Value Execute(int arity, Value* arg);
    int ValidArguments(int arity, Value* arg);

protected:
    int type_;
};

int DerivativeFunction::ValidArguments(int arity, Value* arg)
{
    return true;
}

Value DerivativeFunction::Execute(int, Value* arg)
{
    fieldset* v = 0;
    arg[0].GetValue(v);
    assert(v);
    fieldset* z = copy_fieldset(v, v->count, false);

    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", type_);

    int baseIndex = Context::BaseIndex();

    for (int i = 0; i < v->count; i++) {
        auto_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!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);

        auto_ptr<MvGridBase> grdZ(MvGridFactory(z->fields[i]));
        (grd.get()->*dMethod)(grdZ.get());
    }

    return Value(z);
}

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

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);
    int ValidArguments(int arity, Value* arg);
};

int GridCellAreaFunction::ValidArguments(int arity, Value* arg)
{
    return true;
}

Value GridCellAreaFunction::Execute(int, Value* arg)
{
    fieldset* v = 0;
    arg[0].GetValue(v);
    assert(v);
    fieldset* z = copy_fieldset(v, v->count, false);

    int baseIndex = Context::BaseIndex();

    for (int i = 0; i < v->count; i++) {
        auto_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!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);

        auto_ptr<MvGridBase> grdZ(MvGridFactory(z->fields[i]));
        grd->gridCellArea(grdZ.get());
    }

    return Value(z);
}


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

class CovarianceFunction : public Function
{
    int type_;

public:
    CovarianceFunction(const char* n, int t) :
        Function(n),
        type_(t)
    {       
        if (type_ == 0)
            info = "Computes the area-weighted correlation for each field in a fieldset";
        else if(type_ == 1)
            info = "Computes the area-weighted covariance for each field in a fieldset";
        else if(type_ == 2)
            info = "Computes the area-weighted variance for each field in a fieldset";
        else if(type_ == 3)
            info = "Computes the area-weighted standard deviation for each field in a fieldset";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

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

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

    if (type_ < 2)  //-- type_ 0 = corr_a, 1 = covar_a --//
    {
        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  //-- type_ 2 = var_a, 3 = stdev_a --//
    {
        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)
{
    string funk[5] = {"corr_a", "covar_a", "var_a", "stdev_a", "UPDATE REQUIRED!"};

    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;

    fieldset* v1 = 0;
    arg[0].GetValue(v1);  //-- 1st fieldset always

    fieldset* v2 = 0;
    if (type_ < 2)
        arg[1].GetValue(v2);  //-- 2nd fieldset only for covar_a and corr_a

    if ((type_ < 2 && arity == 3) || (type_ > 1 && 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 (type_ < 2 && (v1->count != v2->count))
        return Error("%s: different number of fields in input fieldsets", funk[type_].c_str());

    CList* lis;
    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  //-- do the computations
        {
            if (type_ < 2)
                val = CovarFunc(v1->fields[i], v2->fields[i], geoArea);  //-- covar_a (corr_a)
            else
                val = CovarFunc(v1->fields[i], v1->fields[i], geoArea);  //-- var_a (stdev_a)

            if (type_ == 0) {
                double std1 = sqrt(CovarFunc(v1->fields[i], v1->fields[i], geoArea));
                double std2 = sqrt(CovarFunc(v2->fields[i], v2->fields[i], geoArea));

                val = val / (std1 * std2);  //-- corr_a
            }
            else if (type_ == 3) {
                val = sqrt(val);  //-- final stdev_a
            }
        }
        catch (MvException& e)  //-- catch possible computing problems
        {
            return Error("%s: %s", funk[type_].c_str(), 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 CovarFunc(field* f1, field* f2, const MvGeoBox& area)
{
    auto_ptr<MvGridBase> grd1(MvGridFactory(f1));
    if (!grd1->hasLocationInfo())
        throw MvException("Unimplemented or spectral data - unable to extract location data");

    auto_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 w1 = grd1->weight();
            double v1 = grd1->value();
            double v2 = grd2->value();

            sum1 += w1 * v1;
            sum2 += w1 * v2;
            sum12 += w1 * v1 * v2;
            wght += w1;
        }

        grd1->advance();
        grd2->advance();
    }

    if (!wght)
        throw MvException("No points to compute");

    return (sum12 / wght) - (sum1 / wght) * (sum2 / wght);
}
//=============================================================================

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)) {
        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)
{
    CGrib* 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();
    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
        for (i = 0; i < 4; i++)  //area given as 4 numbers
            arg[i + 1].GetValue(d[i]);

    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
    vector<long> vfreq(nint + 1, 0L);

    // Loop fieldset
    double v1;
    bool found;
    for (i = 0; i < v->count; i++) {
        auto_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
        CList* 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++) {
        auto_ptr<MvGridBase> grd(MvGridFactory(v->fields[i]));
        if (!grd->isRegularLatLongGrid())
            return Error("fill_missing_values_ew: implemented only for regular latlong format");

        auto_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);
}

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

static void install(Context* c)
{
    func* f = mars_functions();

    c->AddGlobal(new Variable("grib_missing_value", Value(mars.grib_missing_value)));

    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 GridDistanceFunction("distance"));
    c->AddFunction(new GridBearingFunction("bearing"));
    c->AddFunction(new MaskFunction("mask"));
    c->AddFunction(new RMaskFunction("rmask"));
    c->AddFunction(new GenerateFunction("generate"));
    c->AddFunction(new SubGribFunction("[]"));
    c->AddFunction(new GribMatrixFunction("matrix"));
    c->AddFunction(new GribDirectionFunction("direction"));
    c->AddFunction(new MeanEwFunction("mean_ew"));
    c->AddFunction(new GradientFunction("gradient"));
    c->AddFunction(new DerivativeFunction("first_derivative_x", 0));
    c->AddFunction(new DerivativeFunction("first_derivative_y", 1));
    c->AddFunction(new DerivativeFunction("second_derivative_x", 2));
    c->AddFunction(new DerivativeFunction("second_derivative_y", 3));
    c->AddFunction(new GridCellAreaFunction("grid_cell_area"));

    c->AddFunction(new CovarianceFunction("corr_a", 0));
    c->AddFunction(new CovarianceFunction("covar_a", 1));
    c->AddFunction(new CovarianceFunction("var_a", 2));
    c->AddFunction(new CovarianceFunction("stdev_a", 3));

    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 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"));

    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 GribHeaderFunctionWGeneric("grib_set"));

    c->AddFunction(new FillMVEWFunction("fill_missing_values_ew"));
}

static Linkage linkage(install);
