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

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

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

		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 = "Generate 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 = "Generate masks"; }
	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 = "Generate rmask"; }
	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 = "Compute distances 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 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 = "Read 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 = "Read 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 = "Write 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 = "Write 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 = "Dump 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 = "Sort 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 = "Generate a field of cos(latitude)"; }
	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 = "Generate a field of sin(latitude)"; }
	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 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)
	{info = "Returns the grid point latitudes/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)
	{info = "Returns a single or list of base/valid dates for the 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)
		{ info = "Sum/Average the values of the grid points"; };
	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 = "Integrate a field"; };
	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)
    { info = "Min/max 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 = "Interpolate a field"; };
	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)
		{ info = "Returns the indexes of the four surrounding grid points"; };
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

int SurroundingPointsFunction::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;
}


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

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

        std::vector<MvGridPoint> surroundingPoints;
        a = grd->interpolatePoint( lat, lon, &surroundingPoints);
        size_t npoints = surroundingPoints.size();


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

        if (a == DBL_MAX || npoints == 0)
            returnVal = Value(); // nil
        else
        {
            // for the benefit of sorting by distance from the target point, we will
            // first replace the value with the distance from the target point
            MvLocation targetLoc(lat, lon);
            for (size_t p = 0; p < npoints; p++)
            {
                surroundingPoints[p].value_ = targetLoc.distanceInMeters(surroundingPoints[p].loc_);
            }

            std::sort(surroundingPoints.begin(), surroundingPoints.end(), ComparePointsByValue);

            CVector *vec4 = new CVector(4);
            vec4->setIndexedValue(0, surroundingPoints[0].index_ + 1);  // C-to-Macro indexing
            vec4->setIndexedValue(1, surroundingPoints[1].index_ + 1);  // C-to-Macro indexing
            vec4->setIndexedValue(2, surroundingPoints[2].index_ + 1);  // C-to-Macro indexing
            vec4->setIndexedValue(3, surroundingPoints[3].index_ + 1);  // C-to-Macro indexing
            returnVal = Value(vec4);
        }


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

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

    return returnVal;
}


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

class NearestGridpointFunction : public Function {
	bool loc_info;
public:
	NearestGridpointFunction(const char *n,bool loc_info) : Function(n), loc_info(loc_info)
		{ info = "Interpolate (get nearest grid point of) 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 < 1 )
	  return false;
	if( arg[0].GetType() != tgrib )
	  return false;

	switch(arity)
	{
		case 3:
			if (arg[1].GetType() == tnumber && arg[2].GetType() == tnumber)
				return true;
			if (arg[1].GetType() == tvector && arg[2].GetType() == tvector && !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

    Value returnVal, returnLocVal;
	vtype input_type = tnumber;

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

            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_+1);  // +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 = "Find 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 = "Find values in field into 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++)
	  {
	    auto_ptr<MvGridBase> grd( MvGridFactory( v->fields[i] ) );
	    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)
		      {
			fprintf(f,"%g\t%g\t%g\t%ld\t%ld\t%g\n",
					grd->lat_y(),
					grd->lon_x(),
					0.0, // level
					0L, // date
					0L, // time
					grd->value() );
		      }
		  }

		grd->advance();
	      }
	  }

	fclose(f);

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

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

class VertIntFunction : public Function {
public:
	VertIntFunction (const char *n) : Function(n)
	{ info = "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)
	{
		CList *l;
		int i;
		switch(arg[1].GetType())
		{
			case tnumber:
				count = 1;
				levels = new int[1];
				arg[1].GetValue(levels[0]);
				break;

			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);
					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)
	{
		CList *l;
		int i;
		switch(arg[1].GetType())
		{
			case tnumber:
				count = 1;
				levels = new int[1];
				arg[1].GetValue(levels[0]);
				break;

			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 = 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 = "Create 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 + 1);
		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 = "Convert 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 = "Compute direction using U and V"; }
  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 = "Generate 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)
	{info = "Returns meridional or zonal 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 = "Convert 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 = "Convert 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 CovarianceFunction : public Function {
	int type_;
public:
	CovarianceFunction(const char* n, int t) : Function(n), type_(t)
		{ info = "Covariance/variance/std deviation/correlation for field(s)"; };
	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 = "Compute 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 = "Fill 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 GridDistanceFunction("distance"));
	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 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);
