/***************************** 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 <float.h>
#include <string.h>
#include "macro.h"
#include "arith.h"
#include "cbufr.h"
#include "MvFieldSet.h"
#include "inc_stl.h"
#include "MvPath.hpp"

// should include MvFieldSet.h, but it requires other header files...
//double interpolateValue( field* Grib, double lat, double lon );


// To do : filter(geo,area)
// Rms = sqrt(geo*geo/2)
// Mean
// distance(ab) = R*acos(sin(latA)*sin(latB)+cos(latA)*cos(latB)*cos(lonA-LonB))

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


// used in function GeoGetFieldFunction
enum eGeoptFieldType
{
    GPT_VALUE =  0,
    GPT_VALUE2,
    GPT_LAT,
    GPT_LON,
    GPT_DATE,
	GPT_TIME,
	GPT_LEVEL
};


CBufr::CBufr(request *s) : InPool(tbufr,s)
{
	r = clone_all_requests(s);
}

CBufr::~CBufr()
{
	mars.debug = true;
	print_all_requests(r);
	mars.debug = false;

	const char *t = get_value(r,"TEMPORARY",0);
	const char *p = get_value(r,"PATH",0);
	if(t && p)
	{
		if(atoi(t)) {
			unlink(p);
		}
	}
	free_all_requests(r);
}

const char *CBufr::GetFileName()
{
	return get_value(r,"PATH",0);
}

void CBufr::ToRequest(request* &s)
{
   // Initialise hidden parameters
   if ( !get_value(r,"_CLASS",0) )
      set_value(r,"_CLASS","BUFR");

   const char* path = get_value(r,"PATH",0);
   if ( !get_value(r,"_NAME",0) )
   {
      if ( path )
      {
         const char* name = mbasename(path);
         set_value(r,"_NAME",name);
      }
      else
         set_value(r,"_NAME","bufr_data");
   }

   if ( !get_value(r,"_PATH",0) )
   {
      if ( path )
      {
         const char *path1 = mdirname(path);
         set_value(r,"_PATH",path1);
      }
      else
         set_value(r,"_PATH",".");
   }

   // Copy request
   s = r;
}

int CBufr::Write(FILE* f)
{
	return CopyFile(get_value(r,"PATH",0),f);
}

CBufr::CBufr(const char  *p,int temp) : InPool(tbufr)
{
	r = empty_request("BUFR");
	//set_value(r,"PATH","%s",p);
	set_value(r,"PATH","%s",FullPathName(p).c_str() );
	set_value(r,"TEMPORARY","%d",temp);
}

//===========================================================================
class MergeBufrFunction : public Function {
public:
	MergeBufrFunction (const char *n) : Function(n,2,tbufr,tbufr) { };
	virtual Value Execute(int arity,Value *arg);
};

Value MergeBufrFunction::Execute(int,Value *arg)
{
	CBufr *a;
	CBufr *b;

	arg[0].GetValue(a);
	arg[1].GetValue(b);

	char *tmp = marstmp();

	char buf[2048];
	sprintf(buf,"cat %s %s > %s",a->GetFileName(),
			b->GetFileName(),tmp);
	system(buf);

	return Value(new CBufr(tmp,1));

}

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


const char *CGeopts::GetFileName()
{
	return get_value(r,"PATH",0);
}


void CGeopts::load(void)
{
	if( gpts.count() > 0 )
	  return;

	if( r )
	  {
	    const char* path = get_value(r,"PATH",0);
	    gpts.load( path );
	  }
	//else
	//  is probably a geopoints structure in memory, or with zero count!
}

void CGeopts::unload(void)
{
//	if( gpts.count() > 0 || (r == NULL))
//	  {
	    if(r == NULL)
	      {
		char* path = marstmp();
		gpts.write( path );

		r = empty_request("GEOPOINTS");
		set_value(r,"TEMPORARY","1");
		set_value(r,"PATH","%s",path);
	      }

	    gpts.unload();
//	  }
}

int CGeopts::Write(FILE* f)
{
	unload();
	return CopyFile(get_value(r,"PATH",0),f);
}

CGeopts::CGeopts(request *s) : InPool(tgeopts,s)
{
	r = clone_all_requests(s);
}

CGeopts::CGeopts(const char  *p,int temp) : InPool(tgeopts)
{
	r = empty_request("GEOPOINTS");
	//set_value(r,"PATH","%s",p);
	set_value(r,"PATH","%s",FullPathName(p).c_str());
	set_value(r,"TEMPORARY","%d",temp);
}

CGeopts::CGeopts(long count) : InPool(tgeopts)
{
	r = NULL;
	gpts.newReservedSize (count);
}


CGeopts::CGeopts(CGeopts  *p) : InPool(tgeopts)
{
	r = NULL;
	p->load();
	gpts = p->gpts;
}

CGeopts::CGeopts(MvGeoPoints &gptsIn) : InPool(tgeopts)
{
    r = NULL;
    gpts = gptsIn;  // makes a copy, we should make this more efficient
}


CGeopts::CGeopts( CGeopts* p, fieldset* v, int n, bool nearest)  : InPool(tgeopts)
{
	r = NULL;
	p->load();
	gpts.newReservedSize( p->gpts.count() );
	gpts.format( p->gpts.format() );

	field* g = get_field(v,n,expand_mem);
   
   // It needs to be dynamically allocated because it needs to
   // be destroyed before releasing the field (field* g). This is
   // because the destructor of MvField will try to restore the
   // previous memory status of the field, which is expand memory.
	MvField* fld = new MvField( g );

	//-- get date/time/level metadata from the GRIB field
	MvDate base( fld->yyyymmddFoh() );  //-- base (analysis) date
	double step = fld->stepFoh();       //-- forecast step
	MvDate valid = base + step;        //-- valid date
	long dat = valid.YyyyMmDd();
	long tim = valid.Hour()*100 + valid.Minute();
	double level = fld->level();

	n = 0;
	for( int i=0; i < p->gpts.count(); i++ )
	{
		double x;
		if( nearest )
		{
			x = fld->nearestGridpoint( (*p)[i].lon_x(), (*p)[i].lat_y() );
		}
		else
		{
			x = fld->interpolateAt( (*p)[i].lon_x(), (*p)[i].lat_y() );
		}

		MvGeoP1 pt = p->gpts[ i ];

		if(x != DBL_MAX && x != mars.grib_missing_value)
		{
			pt.value( x );
		}
		else
		{
			// x will be DBL_MAX if there is no valid corresponding grid value.
			pt.set_value_missing();
		}

		pt.date( dat );
		pt.time( tim );
		pt.height( level );

		gpts[ n ] = pt;
		n++;
	}

	delete fld;
   fld = 0;
	release_field(g);
}


//CGeopts::CGeopts(fieldset *v,int n) : InPool(tgeopts)
CGeopts::CGeopts(fieldset*, int ) : InPool(tgeopts)
{
#if 0
	r = NULL;
	field *g = get_field(v,n,expand_mem);

	count  = g->sec4len;
	pts    = new geopt[count];

	float *lats = new float[count];
	float *lons = new float[count];

	fortint len    = g->length;
	fortint incnt  = count;
	fortint outcnt = count;
	fortint ret    = 0;

	getfld_(g->buffer,&len,lats,lons,&incnt,&outcnt,&ret);

	for(int i=0;i<count;i++)
	{
		pts[i].latitude  = lats[i];
		pts[i].longitude = lons[i];
		pts[i].value     = g->rsec4[i];
		pts[i].height    = 0;
		pts[i].date      = 0;
		pts[i].time      = 0;
	}

	release_field(g);

	delete[] lats;
	delete[] lons;
#endif
}

CGeopts::~CGeopts()
{
	if(r)
	{
		const char *t = get_value(r,"TEMPORARY",0);
		const char *p = get_value(r,"PATH",0);
		if(t && p)
		{
			if(atoi(t)) {
				unlink(p);
			}
		}
	}
	//if(count) delete[] pts;
	free_all_requests(r);
}

void CGeopts::ToRequest(request* &s)
{
   unload();

   // Initialise hidden parameters
   if ( !get_value(r,"_CLASS",0) )
      set_value(r,"_CLASS","GEOPOINTS");

   const char* path = get_value(r,"PATH",0);
   if ( !get_value(r,"_NAME",0) )
   {
      if ( path )
      {
         const char* name = mbasename(path);
         set_value(r,"_NAME",name);
      }
      else
         set_value(r,"_NAME","geopoints_data");
   }

   if ( !get_value(r,"_PATH",0) )
   {
      if ( path )
      {
         const char *path1 = mdirname(path);
         set_value(r,"_PATH",path1);
      }
      else
         set_value(r,"_PATH",".");
   }

   s = r;
}


// NOTE: this function could be put into the MvGeoPoints class if needed
bool const CGeopts::doesMetadataMatch(const request *r)
{
    // for each parameter in the request, check that it exists in the metadata
    // and its value matches

    MvGeoPoints::metadata_t &md = metadata();
    parameter *p = r->params;
    while (p)
    {
        const char *key = p->name;
        value *v = p->values;

        if (key)
        {
            // loop through all the given values for this key - succeed if any match
            bool doesThisKeyMatch = false;
            MvGeoPoints::metadata_t::iterator it = md.find(key);

            if (it == md.end()) // key not found
                return false;

            while (v)
            {
                MvVariant val(v->name);
                if (it->second == val)  // key found and matches
                {
                    doesThisKeyMatch = true;
                    break;
                }
    
                v = v->next;
            }

            if (!doesThisKeyMatch)
                return false;
        }

        p = p->next;
    }

    return true; // if we got to here, then everything matched
}



//===========================================================================
class CountGeoFunction : public Function {
public:
	CountGeoFunction (const char *n) : Function(n,1,tgeopts) { };
	virtual Value Execute(int arity,Value *arg);
};

Value CountGeoFunction::Execute(int,Value *arg)
{
	CGeopts *g;
	arg[0].GetValue(g);
	g->load();
	return Value(g->Count());
}

//===========================================================================
class MaxGeoFunction : public Function {
	int usemax;
public:
	MaxGeoFunction(const char *n,int m) : Function(n,1,tgeopts),usemax(m) { };
	virtual Value Execute(int arity,Value *arg);
};

Value MaxGeoFunction::Execute(int,Value *arg)
{
	long i;
	long nFirstValid;
	double x;
	CGeopts *g;
	arg[0].GetValue(g);
	g->load();
	if(g->Count() == 0) return Value();


	// Get the index of the first valid point. If -1 is returned,
	// then there are no valid points.

	nFirstValid = g->indexOfFirstValidPoint();

	if (nFirstValid == -1)
	{
		return Value();
	}


	// store the first valid value, then replace it with each successive
	// greater or smaller value

	x = (*g)[nFirstValid].value();

	if (usemax)
	{
		for (i = nFirstValid + 1; i <g->Count(); i++)
 		{
			if (!(*g)[i].value_missing())	// only consider non-missing values
				if ((*g)[i].value() > x)
					x = (*g)[i].value();
		}
	}
	else
	{
		for (i = nFirstValid + 1; i<g->Count(); i++)
		{
			if (!(*g)[i].value_missing())	// only consider non-missing values
				if ((*g)[i].value() < x )
					x = (*g)[i].value();
		}
	}

	return Value(x);
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
class FilterBoxFunction : public Function {
public:
	FilterBoxFunction (const char *n) : Function(n) { };
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};
Value FilterBoxFunction::Execute(int arity,Value *arg)
{
	CGeopts *g;
	arg[0].GetValue(g);

	double d[4];  //-- get area and normalize it with MvGeoBox
	if(arity == 2)
    {
            CList *l;
            arg[1].GetValue(l);
            for(int i = 0;i<4;i++)
                (*l)[i].GetValue(d[i]);
    }
	else
	{
	    for(int i = 0;i<4;i++)
        arg[i+1].GetValue(d[i]);
	}
	MvGeoBox myArea( d[0], d[1], d[2], d[3] );

	g->load();
	CGeopts *x = new CGeopts(g);

	int n = 0;
	for(int i=0; i<g->Count(); i++ )
	{
		if( myArea.isInside( (*g)[i].lat_y(), (*g)[i].lon_x() ) )
		{
			(*x)[n++] = (*g)[i];
	}
	}

	x->SetSize(n);

	g->unload();
	x->unload();

	return Value(x);
}
int FilterBoxFunction::ValidArguments(int arity,Value *arg)
{
    int i;
    CList *l;

    if(arity<1) return false;
    if(arg[0].GetType() != tgeopts)   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;

                default:
                    return false;
            }

            /* break; */

        case 1:
            break;

        default:
            return false;

    }
    return true;
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
class FilterLevelFunction : public Function {
public:
	FilterLevelFunction (const char *n) : Function(n) { };
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};
Value FilterLevelFunction::Execute(int arity,Value *arg)
{
	CGeopts *g;
	arg[0].GetValue(g);

	int    i;
	float  f[2];      //-- geopt.height is float
	float& l1 = f[0];
	float& l2 = f[1];

	if(arity == 2)
	  {
		if(arg[1].GetType() == tnumber)
		  {
		    double dd;
		    arg[1].GetValue(dd);
		    l2 = l1 = (float)dd;
		  }
		else
		  {
		    CList *l;
		    arg[1].GetValue(l);
		    double dd;
		    for(i = 0;i<2;i++)
		      {
			(*l)[i].GetValue(dd);
			f[i] = (float)dd;
		      }
		  }
	  }
	else
	  {
	    for(i = 0;i<2;i++)
	      {
		double dd;
		arg[i+1].GetValue(dd);
		f[i] = dd;
	      }
	  }

	g->load();

	CGeopts *x = new CGeopts(g);

	int n = 0;

	for(i=0;i<g->Count();i++)
	  {
	    if( l1 <= (*g)[i].height() && (*g)[i].height() <= l2 )
	      {
		(*x)[n++] = (*g)[i];
	      }
	  }

	x->SetSize(n);

	g->unload();
	x->unload();

	return Value(x);
}
int FilterLevelFunction::ValidArguments(int arity,Value *arg)
{
    int i;
    CList *l;

    if(arity<1) return false;
    if(arg[0].GetType() != tgeopts)   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;
				case tnumber:
                    return true;

                default:
                    return false;
            }

            /* break; */

        case 1:
            break;

        default:
            return false;

    }
    return true;
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
class FilterDateFunction : public Function {
public:
	FilterDateFunction (const char *n) : Function(n) { };
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};
Value FilterDateFunction::Execute(int arity,Value *arg)
{
	CGeopts *g;
	arg[0].GetValue(g);

   	Date d[4];

    Date& d1 = d[0];
    Date& d2 = d[1];

	int	   i;

	if(arity == 2)
    {
		if(arg[1].GetType() == tdate)
		{
			arg[1].GetValue(d1);
			d2 = d1;
		}
		else
		{
            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]);
    }

	g->load();

	CGeopts *x = new CGeopts(g);

	int n = 0;

	for(i=0;i<g->Count();i++)  {
		Date date((*g)[i].date(), (*g)[i].time());
		if( d1 <= date && date <= d2 )
			(*x)[n++] = (*g)[i];
	}

	x->SetSize(n);

	g->unload();
	x->unload();

	return Value(x);
}

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

    if(arity<1)
       return false;

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

    switch(arity)
    {
        case 3:
            for(i = 1;i<3;i++)
               if(arg[i].GetType() != tdate)
                  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() != tdate)
                           return false;

                    return true;

                case tdate:
                    return true;

                default:
                    return false;
            }

            /* break; */

        case 1:
            break;

        default:
            return false;

    }
    return true;
}


//-------------------------------------------------------------------
//-------------------------------------------------------------------


/*******************************************************************************
 *
 * Class        : RemoveMissingValuesFunction : Function
 *
 * Description  : Macro function that creates a new set of geopoints based on
 *                an input set of geopoints but with all the missing values
 *                removed. Both value and value2 are considered.
 *
 ******************************************************************************/


class RemoveMissingValuesFunction : public Function {
public:
	RemoveMissingValuesFunction (const char *n) : Function(n,1,tgeopts)
	{  info = "Copies a set of geopoints, removing missing values"; };
	virtual Value Execute(int arity,Value *arg);
};


Value RemoveMissingValuesFunction::Execute(int ,Value *arg)
{
	CGeopts *g;
	arg[0].GetValue(g);
	int n = 0;
	int i;


	// load the input geopoints and create a new set based on those

	g->load();

	CGeopts *x = new CGeopts(g);


	// for each input geopoint, only copy it over if it is not missing

	for (i = 0; i < g->Count(); i++)
	{
		if ( !(*g)[i].any_missing() )
		{
			(*x)[n++] = (*g)[i];
		}
	}

	x->SetSize(n);


	// unload the data and return the result

	g->unload();
	x->unload();

	return Value (x);
}



//-------------------------------------------------------------------
//-------------------------------------------------------------------

/*******************************************************************************
 *
 * Function      : GeoIntBits
 *
 * Description   : For each geopoint, converts the value into an integer and then returns
 *                 the value of a given bit or bits, starting from the least
 *                 significant. For example, intbits (4, 1) = 0,
 *                 intbits (4, 2) = 0, intbits (4, 3) = 1, intbits (4, 4) = 0,
 *                 ... The user can also specify the number of bits to extract,
 *                 for example intbits (3, 1, 2): this extracts the first
 *                 2 bits from the number 3, returning the result of 3.
 *                 As another example, intbits (12, 3, 2) = 3.
 *                 This function was originally written to help with ODB
 *                 access.
 *
 ******************************************************************************/

class GeoIntBits : public Function {

public:
	GeoIntBits (const char *n) : Function (n)
	{info = "Returns ranges of bits in a geopoints variable";}
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

int GeoIntBits::ValidArguments(int arity,Value *arg)
{
	if ( arity != 2 && arity != 3)      // only accept 2- and 3-argument calls
		return false;

	if ( arg[0].GetType() != tgeopts )  // first argument must be a geopoints
		return false;

	if ( arg[1].GetType() != tnumber )  // second argument must be a number
		return false;

	if ( (arity == 3) && (arg[2].GetType() != tnumber) )
		return false;                     // argument 3, if supplied, must be a number

    return true;
}

Value GeoIntBits::Execute(int arity, Value *arg)
{
	int    value, bitindex;
	//int    integervalue;
	int    bitmask = 0;
	int    bits;
	int    i;
	int    num_bits = 1;
	int    max_bits_in_long;
	CGeopts *geo, *geonew;
	arg[0].GetValue(geo);
	arg[1].GetValue(bitindex);


	// how many bits can we hold in a 'long'?

	max_bits_in_long = sizeof (long) * 8;


	// get the number of bits we want to retrieve

	if (arity == 3)
	{
		arg[2].GetValue(num_bits);
	}


	// sensible numbers of bits only, please

	if (num_bits < 1)
	{
		return Error("The number of bits must be between 1 and %d inclusive.", max_bits_in_long);
	}


	// we cannot handle bit indexes outside of a sensible range

	if (bitindex < 1 || (bitindex + num_bits - 1) > max_bits_in_long)
	{
		return Error("The bit indexes must be between 1 and %d inclusive.", max_bits_in_long);
	}



	// load the input geopoints and create a new set based on those

	geo->load();

	geonew = new CGeopts(geo);



	// compute the bitmask we will use to isolate the desired bit(s)

	for (i = 0; i < num_bits; i++)
	{
		bitmask = bitmask | (int) pow (2.0, (bitindex - 1 + i));
	}


	// isolate the desired bits using the bitmask

	for (i = 0; i < geo->Count(); i++)
	{
		value = (int) (*geo)[i].value();  // take input value as an integer
		bits  = value & bitmask;    // mask off the bits we want

		// shift the result down. For instance, if the 5th bit is set, we
		// just want the result to be 1 when checking for that bit. If
		// we are checking bits 10 and 11, then we want a result between
		// 0 and 3 inclusive. So what we want is the position-independent
		// value of the bit(s).

		(*geonew)[i].value( bits >> (bitindex - 1) );
	}


	// unload the data and return the result

	geo->unload();
	geonew->unload();

	return Value (geonew);
}




//-------------------------------------------------------------------
//-------------------------------------------------------------------

class GeoGetFieldFunction : public Function {
	eGeoptFieldType fieldtype;
	bool deprecated;
	const char *fieldName;
	const char *newName;
	char expandedInfo[100];
public:
	GeoGetFieldFunction (const char *n, eGeoptFieldType field, bool d, const char *fieldn, const char *nn = NULL) : Function(n,1,tgeopts), fieldtype(field), deprecated(d), fieldName(fieldn), newName(nn)
	{
		sprintf (expandedInfo, "Returns a list/vector of %ss from the given geopoints.", fieldName);
		info = expandedInfo;//"Returns a list of heights from the given geopoints.";
    }
	virtual Value Execute(int arity,Value *arg);
};

Value GeoGetFieldFunction::Execute(int,Value *arg)
{
    DeprecatedMessage (deprecated, "geopoints", newName);

	CGeopts *g;
	arg[0].GetValue(g);

	g->load();

	int i;

	switch (fieldtype)  // which column do we wish to extract?
	{
		case GPT_LEVEL:
		{
			CVector *v = new CVector(g->Count());

			for(i=0;i<g->Count();i++)
				v->setIndexedValue(i, (*g)[i].height());

			return Value(v);
			break;
		}

		case GPT_LAT:
		{
			CVector *v = new CVector(g->Count());

			for(i=0;i<g->Count();i++)
				v->setIndexedValue(i, (*g)[i].lat_y());

			return Value(v);
			break;
		}

		case GPT_LON:
		{
			CVector *v = new CVector(g->Count());

			for(i=0;i<g->Count();i++)
				v->setIndexedValue(i, (*g)[i].lon_x());

			return Value(v);
			break;
		}

		case GPT_DATE:
		{
			CList *v = new CList(g->Count());

			for(i=0;i<g->Count();i++)  {
				Date d((*g)[i].date(), (*g)[i].time() );
				(*v)[i] = Value(d);
			}

			return Value(v);
			break;
		}


		case GPT_VALUE:
		{
    		// strings are put into a list
    		if( g->Format() == eGeoString )
    		{
        		CList *v = new CList(g->Count());
        		for(i=0;i<g->Count();i++)
        		{
            		MvGeoP1 gp = (*g)[i];
            		(*v)[i] = gp.strValue().c_str();
        		}
 	    		return Value(v);
    		}
    		// numbers are put into a vector
    		else
    		{
        		CVector *v = new CVector(g->Count());
        		for(i=0;i<g->Count();i++)
        		{
            		MvGeoP1 gp = (*g)[i];
            		double value = gp.value();

            		if (value != GEOPOINTS_MISSING_VALUE)
            			v->setIndexedValue(i, value);
            		else
            			v->setIndexedValueToMissing(i);
        		}
 	    		return Value(v);
    		}
			break;
		}

		case GPT_VALUE2:
		{
    		CVector *v = new CVector(g->Count());

    		for(i=0;i<g->Count();i++)
    		{
        		MvGeoP1 gp = (*g)[i];
        		double value = gp.direc();

        		if (value != GEOPOINTS_MISSING_VALUE)
        			v->setIndexedValue(i, value);
        		else
        			v->setIndexedValueToMissing(i);
    		}

    		return Value(v);
		}

		default:
		{
			// should never get here
			return Error("Cannot extract this field  (%d) from geopoints", fieldtype);
		}
	}
}


//===========================================================================
class GeoFilterFunction : public Function {
public:
	GeoFilterFunction (const char *n) : Function(n,2,tgeopts,tgeopts) { };
	virtual Value Execute(int arity,Value *arg);
};

Value GeoFilterFunction::Execute(int,Value *arg)
{
	CGeopts *g;
	CGeopts *h;
	arg[0].GetValue(g);
	arg[1].GetValue(h);

	g->load();
	h->load();
	if(g->Count() != h->Count())
		return Error("The two geopoints have different sizes");

	CGeopts *x = new CGeopts(g);

	int n = 0;

	for(int i=0;i<g->Count();i++)
		if((*h)[i].value() )
			(*x)[n++] = (*g)[i];

	x->SetSize(n);

	g->unload();
	h->unload();
	x->unload();

	return Value(x);
}

//===========================================================================
class GeoVectorFunction : public Function {
public:
	GeoVectorFunction (const char *n) : Function(n,2,tgeopts,tgeopts) { };
	virtual Value Execute(int arity,Value *arg);
	virtual eGeoFormat vectorType() = 0;
};

class GeoPolarVectorFunction : public GeoVectorFunction {
public:
	GeoPolarVectorFunction (const char *n) : GeoVectorFunction(n)
	{  info = "Combines two 1-parameter geopoints variables into polar vector style"; };
	virtual eGeoFormat vectorType(){ return eGeoVectorPolar; }
};

class GeoXYVectorFunction : public GeoVectorFunction {
public:
	GeoXYVectorFunction (const char *n) : GeoVectorFunction(n)
	{ info = "Combines two 1-parameter geopoints variables into u/v vector style"; };
	virtual eGeoFormat vectorType(){ return eGeoVectorXY; }
};

Value GeoVectorFunction::Execute(int,Value *arg)
{
	CGeopts *g;
	CGeopts *h;
	arg[0].GetValue(g);
	arg[1].GetValue(h);

	g->load();
	h->load();
	if(g->Count() != h->Count())
		return Error("The two geopoints have different sizes");

	CGeopts* cgpts = new CGeopts( g );

	for( int p=0; p<g->Count(); ++p )
	  {
	    MvGeoP1 gp1 = (*g)[ p ];
	    MvGeoP1 gp2 = (*h)[ p ];

	    gp1.direc( gp2.value() );

	    (*cgpts)[ p ] = gp1;
	  }

	cgpts->SetFormat( vectorType() );

	g->unload();
	h->unload();
	cgpts->unload();

	return Value(cgpts);
}

//===========================================================================
//                                                      this one interpolates
class GeoFromGribFunction1 : public Function {
public:
	GeoFromGribFunction1 (const char *n) : Function(n,2,tgrib,tgeopts) { };
	virtual Value Execute(int arity,Value *arg);
};

Value GeoFromGribFunction1::Execute(int,Value *arg)
{
	fieldset *v;
	CGeopts  *g;

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

	return Value(new CGeopts(g,v,0));
}

//===========================================================================
//                                          this one looks for nearest points
class GeoFromGribFunction2 : public Function {
public:
	GeoFromGribFunction2 (const char *n) : Function(n,2,tgrib,tgeopts) { };
	virtual Value Execute(int arity,Value *arg);
};

Value GeoFromGribFunction2::Execute(int,Value *arg)
{
	fieldset *v;
	CGeopts  *g;

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

	return Value(new CGeopts(g,v,0,true));
}

#if 0

//===========================================================================
//                                               use grib-to-geo app instead!
class GeoFromGribFunction3 : public Function {
public:
	GeoFromGribFunction3(char *n) : Function(n,1,tgrib) { };
	virtual Value Execute(int arity,Value *arg);
};

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

	arg[0].GetValue(v);

	return Value(new CGeopts(v,0));
}
#endif

//===========================================================================
//===========================================================================
class SubGeoFunction : public Function {
public:
	SubGeoFunction (const char *n) : Function(n,2,tgeopts,tnumber) { };
	virtual Value Execute(int arity,Value *arg);
};

Value SubGeoFunction::Execute(int,Value *arg)
{
	CGeopts *p;
	long n;

	arg[0].GetValue(p);
	arg[1].GetValue(n);

	p->load();

	request *r = empty_request(NULL);

	if (n < 0 || n > p->Count()-1)
		return Error("Geopoints index is %ld, but should be from 0 to %ld", n, p->Count()-1);


	MvGeoP1& g = (*p)[n];

	set_value(r,"latitude",		"%g",	g.lat_y() );
	set_value(r,"longitude",	"%g",	g.lon_x() );
	set_value(r,"height",		"%g",	g.height() );
	set_value(r,"date",			"%ld",	g.date() );
	set_value(r,"time",			"%ld",	g.time() );
	set_value(r,"value",		"%g",	g.value() );
	set_value(r,"value_missing","%d",	g.value_missing() ? 1 : 0);

	//-- valid only for geovectors
	set_value(r,"value2",		"%g",	g.direc() );
	set_value(r,"value2_missing","%g",	g.direc_missing()  ? 1 : 0);

	return Value(r);
}

//===========================================================================
// These are single-argument functions such as sin, cos, abs...

class GeoUnOp : public Function {

	uniproc F_;

public:
	GeoUnOp(const char *n,uniproc f) : Function(n,1,tgeopts) { F_ = f; }
	virtual Value Execute(int arity,Value *arg);
};

Value GeoUnOp::Execute(int,Value *arg)
{
	CGeopts *g;	// original  geopoints
	CGeopts *p;	// resultant geopoints

	arg[0].GetValue(g);
	g->load();

	p = new CGeopts(g);

	for(int i = 0; i< g->Count();i++)
	{
		if ((*g)[i].value_missing())		// if the input value is missing
			(*p)[i].set_value_missing();	// set the output value to missing
		else					// otherwise, compute with (first) value
			(*p)[i].value( F_((*g)[i].value() ) );

		if( g->Format() == eGeoVectorXY )	// if XY vectors then operate second value too
			(*p)[i].value2( F_((*g)[i].value2() ) );
	}


	return Value(p);
}

//===========================================================================
// Grib + Geopoint

class GribGeoBinOp : public Function {

	binproc F_;

public:
	GribGeoBinOp(const char *n,binproc f) : Function(n) { F_ = f; }
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

Value GribGeoBinOp::Execute(int,Value *arg)
{
	fieldset *v;
	CGeopts  *g;

	if(arg[0].GetType() == tgrib)
	{
		arg[0].GetValue(v);
		arg[1].GetValue(g);

		CGeopts *p = new CGeopts(g,v,0);


		// for each point,
		//   if either input value is missing
		//     set the output value to missing
		//   otherwise, compute

		for (int i = 0; i< g->Count();i++)
			if ((*g)[i].value_missing() || (*p)[i].value_missing())
				(*p)[i].set_value_missing();
			else
				(*p)[i].value( F_((*p)[i].value(), (*g)[i].value() ) );

		return Value(p);
	}
	else
	{
		arg[0].GetValue(g);
		arg[1].GetValue(v);

		CGeopts *p = new CGeopts(g,v,0);


		// for each point,
		//   if either input value is missing
		//     set the output value to missing
		//   otherwise, compute

		for (int i = 0; i< g->Count();i++)
			if ((*g)[i].value_missing() || (*p)[i].value_missing())
				(*p)[i].set_value_missing();
			else
				(*p)[i].value( F_((*g)[i].value(),(*p)[i].value() ) );

		return Value(p);
	}
}

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

//===========================================================================
// Number + Geopoint

class NumGeoBinOp : public Function {

	binproc F_;

public:
	NumGeoBinOp(const char *n,binproc f) : Function(n) { F_ = f; }
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

Value NumGeoBinOp::Execute(int,Value *arg)
{
	if(arg[0].GetType() == tnumber)
	{
		double  d;
		CGeopts *g;

		arg[0].GetValue(d);
		arg[1].GetValue(g);

		CGeopts *p = new CGeopts(g);

		// for each point,
		//   if the geopoint value is missing
		//     set the output value to missing
		//   otherwise, compute
		//   if XY geovector then compute also with the second value

		for(int i = 0; i< g->Count();i++)
		{
			if ((*g)[i].value_missing())
				(*p)[i].set_value_missing();
			else
				(*p)[i].value( F_( d, (*g)[i].value() ) );

			if( g->Format() == eGeoVectorXY )
				(*p)[i].value2( F_( d, (*g)[i].value2() ) );
		}

		return Value(p);
	}
	else
	{
		double  d;
		CGeopts *g;

		arg[0].GetValue(g);
		arg[1].GetValue(d);

		CGeopts *p = new CGeopts(g);


		// for each point,
		//   if the geopoint value is missing
		//     set the output value to missing
		//   otherwise, compute

		for(int i = 0; i< g->Count();i++)
			if ((*g)[i].value_missing())
				(*p)[i].set_value_missing();
			else
				(*p)[i].value( F_((*g)[i].value(), d ) );

		return Value(p);
	}
}

int NumGeoBinOp::ValidArguments(int arity,Value *arg)
{
	if(arity != 2) return false;
	if(arg[0].GetType() == tnumber && arg[1].GetType() == tgeopts)
		return true;
	if(arg[1].GetType() == tnumber && arg[0].GetType() == tgeopts)
		return true;
	return false;
}

//===========================================================================
// Geopoint + Geopoint

class GeoGeoBinOp : public Function {

	binproc F_;

public:
	GeoGeoBinOp(const char *n,binproc f) : Function(n,2,tgeopts,tgeopts) { F_ = f; }
	virtual Value Execute(int arity,Value *arg);
};

Value GeoGeoBinOp::Execute(int,Value *arg)
{
	CGeopts *g;
	CGeopts *h;

	arg[0].GetValue(g);
	arg[1].GetValue(h);

	g->load();
	h->load();

	if(g->Count() != h->Count())
		return Error("geopoints are not identical");

	CGeopts *p = new CGeopts(g);

	for(int i = 0;i<g->Count();i++)
	{
#if 0
		if(h->Lat(i) != g->Lat(i)
		|| h->Lon(i) != h->Lon(i))
			// Not yet, || h->Height() != g->Height())
			return Error("geopoints are not identical");
#endif

		if ((*g)[i].value_missing() || (*h)[i].value_missing())
			(*p)[i].set_value_missing();
		else
			(*p)[i].value( F_( (*g)[i].value(), (*h)[i].value() ) );

		if( g->Format() == eGeoVectorXY )
			(*p)[i].value2( F_( (*g)[i].value2(), (*h)[i].value2() ) );
	}

	return Value(p);
}

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

/* Commented out by Iain Russell, 17/12/2004.
   This class/function appears to not be called by anything, so it has been
   commented out to avoid confusion.

class GeoMulOp : public Function {

	typedef double (*binproc)(double,double);
	binproc F_;

public:
	GeoMulOp(char *n,binproc f) : Function(n) { F_ = f; }
	virtual Value Execute(int arity,Value *arg);
	virtual int  ValidArguments(int arity,Value *arg);
};

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

Value GeoMulOp::Execute(int arity,Value *arg)
{
	double d;
	arg[0].GetValue(d);

	for(int i = 1 ; i < arity; i++)
	{
		double x;
		arg[i].GetValue(x);
		d = F_(d,x);
	}

	return Value(d);
}
 */



class MeanGeoFunction : public Function {
	int compute_mean;
public:
	MeanGeoFunction(const char *n,int m) : Function(n,1,tgeopts),compute_mean(m)
	{ info="Returns the sum or mean of the values in a geopoints variable"; };
	virtual Value Execute(int arity,Value *arg);
};

Value MeanGeoFunction::Execute(int,Value *arg)
{
	CGeopts *g;
	double dSum = 0.0;
	int nNumValid = 0;

	arg[0].GetValue(g);

	g->load();

	for (int i = 0;i<g->Count();i++)
	{
		if (!((*g)[i].value_missing()))
		{
			dSum += (*g)[i].value();
			nNumValid++;
		}
	}

	g->unload();


	if (nNumValid > 0)
	{
		if (compute_mean)
			return Value(dSum / nNumValid);
		else
			return Value(dSum);
	}
	else
		return Value();
}



//-------------------------------------------------------------------
class GeoDistanceFunction : public Function {
public:
	GeoDistanceFunction (const char *n) : Function(n) { };
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};
Value GeoDistanceFunction::Execute(int arity,Value *arg)
{
  CGeopts *g;
  arg[0].GetValue(g);

  double d[2];
  double lat, lon;
  int	   i;

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

  MvLocation centre( d[0], d[1] );
  MvLocation point;

  g->load();

  CGeopts *x = new CGeopts(g);

  int n = 0;

  for( i=0; i<g->Count(); i++ )
    {
      lat = (*g)[i].lat_y();
      lon = (*g)[i].lon_x();
      point.set( lat, lon );
      double dist = point.distanceInMeters( centre );
      (*x)[n++].value( dist );
    }

  x->SetSize(n);

  g->unload();
  x->unload();

  return Value(x);
}

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

    if( arity < 2 )
      return false;

    if( arg[0].GetType() != tgeopts )
      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;
            }

        default:
            return false;

    }
    return true;
}



//-------------------------------------------------------------------

/*******************************************************************************
 *
 * Class        : GeoMergeFunction : Function
 *
 * Description  : Macro function that merges two geopoints variables. The second
 *                set is simply appended to the first.
 *
 ******************************************************************************/


class GeoMergeFunction : public Function {
public:
    GeoMergeFunction (const char *n) : Function(n, 2, tgeopts, tgeopts)
    { info = "Merges 2 sets of geopoints"; };
    virtual Value Execute(int arity,Value *arg);
};

Value GeoMergeFunction::Execute(int,Value *arg)
{
	CGeopts *g1;
	CGeopts *g2;
	CGeopts *gmerged;
	int n, i;

	// load up our input geopoints

	arg[0].GetValue(g1);
	arg[1].GetValue(g2);

	g1->load();
	g2->load();


	// we can only merge two geopoints if they are in the same format

	if(g1->Format() != g2->Format())
		return Error ("The two geopoints have different formats");

	// create a new set of geopoints large enough to hold both sets

	gmerged = new CGeopts(g1->Count() + g2->Count());
	gmerged->SetFormat( g1->Format() );


	// copy the first set, followed by the second set

	n = 0;

	for (i = 0; i < g1->Count(); i++)
		(*gmerged)[n++] = (*g1)[i];

	for (i = 0; i < g2->Count(); i++)
		(*gmerged)[n++] = (*g2)[i];


	// unload the data and return the result

	g1->unload();
	g2->unload();
	gmerged->unload();

	return Value(gmerged);
}

//-------------------------------------------------------------------

/*******************************************************************************
 *
 * Class        : GeoCreateFunction : Function
 *
 * Description  : Macro function that creates a new geopoints variable
 *
 ******************************************************************************/


class GeoCreateFunction : public Function {
    bool type_specified;
public:
	GeoCreateFunction (const char *n) : Function(n)
    { info = "Creates a new set of geopoints"; };
	virtual Value Execute(int arity,Value *arg);
    virtual int ValidArguments(int arity,Value *arg);
};

int GeoCreateFunction::ValidArguments(int arity,Value *arg)
{
	if ( arity != 1 && arity != 2)      // only accept 1- and 2-argument calls
		return false;

	if ( arg[0].GetType() != tnumber )  // first argument must be a number
		return false;

	if ( (arity == 2) && (arg[1].GetType() != tstring) )
		return false;                     // argument 2, if supplied, must be a string

    return true;
}



Value GeoCreateFunction::Execute(int arity,Value *arg)
{
    CGeopts *gnew;
    int n;
    const char *type = "";


    arg[0].GetValue(n);

    if (arity == 2) arg[1].GetValue(type);


    // create a new set of geopoints

    gnew = new CGeopts(n);


    // set the format according to the supplied string

    if (type[0] != '\0')
    {
             if ( strcmp ( type, "polar_vector") == 0 ) gnew->SetFormat (eGeoVectorPolar);
        else if ( strcmp ( type, "xy_vector")    == 0 ) gnew->SetFormat (eGeoVectorXY);
        else if ( strcmp ( type, "xyv")          == 0 ) gnew->SetFormat (eGeoXYV);
        else return Error("create_geo: format %s not recognised", type);
    }

    return Value(gnew);
}


//===========================================================================
class GeoTypeFunction : public Function {
public:
	GeoTypeFunction (const char *n) : Function(n,1,tgeopts)
    { info = "Returns the subtype of a geopoints variable"; };
	virtual Value Execute(int arity,Value *arg);
};

Value GeoTypeFunction::Execute(int,Value *arg)
{
  CGeopts* g;
  arg[0].GetValue(g);

  g->load();

  switch (g->Format())
  {
    case eGeoTraditional: return Value("standard");
    case eGeoXYV:         return Value("xyv");
    case eGeoVectorXY:    return Value("xy_vector");
    case eGeoVectorPolar: return Value("polar_vector");
    case eGeoString:      return Value("standard_string");
    default:              return Value("unknown");   
  }
}


//===========================================================================
class GeoSortFunction : public Function {
public:
	GeoSortFunction (const char *n) : Function(n,1,tgeopts)
	{ info = "Sorts the geopoints North to South and West to East"; };
	virtual Value Execute(int arity,Value *arg);
};

Value GeoSortFunction::Execute(int,Value *arg)
{
  CGeopts* g;
  arg[0].GetValue(g);

  g->load();
  g->sort();

  CGeopts* x = new CGeopts(g);

  int n = 0;
  for( int i=0; i < g->Count() ; ++i )
    {
      (*x)[n++] = (*g)[i];
    }

  g->unload();
  x->unload();

  return Value(x);
}

//===========================================================================
class GeoSubsampleFunction : public Function {
public:
	GeoSubsampleFunction (const char *n) : Function(n,2,tgeopts,tgeopts)
	{ info = "Filters from the first geopoints variable points that exist in the second"; };
	virtual Value Execute(int arity,Value *arg);
};

Value GeoSubsampleFunction::Execute(int,Value *arg)
{
    CGeopts* g1;
    arg[0].GetValue(g1);                 //-- geopoints to filter

    CGeopts* g2;
    arg[1].GetValue(g2);                 //-- geopoints to define subsample

    g1->load();
    g2->load();

    g1->sort();
    g2->sort();

    CGeopts* x = new CGeopts(g2);

    int count_g1 = g1->Count();
    int count_g2 = g2->Count();
    int ix  = 0;
    int ig1 = 0;
    for( int ig2=0; ig2 < count_g2 ; ++ig2 )
    {
        while( ig1 < count_g1 && (*g1)[ig1].latLonHeightBefore((*g2)[ig2]) )
            ++ig1;

        if( ig1 < count_g1 && (*g1)[ig1].sameLocation( (*g2)[ig2] ) )
        {
            (*x)[ix] = (*g1)[ig1++];      //-- g2 location exists in g1
        }
        else
        {
            (*x)[ix] = (*g2)[ig2];        //-- g2 location missing in g1
            (*x)[ix].set_value_missing();
            (*x)[ix].set_direc_missing();
        }

        ++ix;
    }

    g1->unload();
    g2->unload();
    x->unload();

    return Value(x);
}

//===========================================================================
// Maybe this could/should be a functionality in visualisation...
//
class GeoOffsetFunction : public Function
{
	double dlat, dlon;
public:
	GeoOffsetFunction (const char *n) : Function(n)
	{ info = "Offsets the locations of geopoints"; };
	virtual int ValidArguments(int arity,Value *arg);
	virtual Value Execute(int arity,Value *arg);
};

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

  if( arity < 2 )
     return false;

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

  switch( arity )
  {
  case 3:                              //-- lat/lon given as two numbers
     if( arg[1].GetType() != tnumber )
         return false;
     if( arg[2].GetType() != tnumber )
         return false;

     arg[1].GetValue( dlat );
     arg[2].GetValue( dlon );

     break;

  case 2:                              //-- lat/lon given as a list of 2 numbers
     if( arg[1].GetType() == tlist )
     {
        CList* lst;
        arg[1].GetValue(lst);
        if( lst->Count() != 2 )
            return false;

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

        (*lst)[0].GetValue( dlat );
        (*lst)[1].GetValue( dlon );
     }
     else
        return false;

     break;

  default:
        return false;
  }

  return true;
}

Value GeoOffsetFunction::Execute( int, Value* arg )
{
  CGeopts* g;
  arg[0].GetValue( g );
  g->load();

  CGeopts* x = new CGeopts( g );

  g->unload();

  x->offset( dlat, dlon );

  x->unload();

  return Value( x );
}

//===========================================================================
class GeoRemoveDuplicatesFunction : public Function {
public:
	GeoRemoveDuplicatesFunction (const char *n) : Function(n,1,tgeopts)
	{ info = "Removes geopoint duplicates"; };
	virtual Value Execute(int arity,Value *arg);
};

Value GeoRemoveDuplicatesFunction::Execute(int,Value *arg)
{
  CGeopts* g;
  arg[0].GetValue(g);

  g->load();
  g->removeDuplicates();

  CGeopts* x = new CGeopts(g);

  int n = 0;
  for( int i=0; i < g->Count() ; ++i )
    {
      (*x)[n++] = (*g)[i];
    }

  g->unload();
  x->unload();

  return Value(x);
}

//===========================================================================
class GeoSetFunction : public Function
{
	eGeoptFieldType  col;
	bool isList;
    bool isVector;
	bool deprecated;
	const char *fieldName;
	const char *newName;
	eGeoptFieldType fieldtype;
	char expandedInfo[100];
public:
	GeoSetFunction(const char* n, eGeoptFieldType c, bool d, const char *fieldn, const char *nn = NULL) : Function(n), col(c), deprecated(d), fieldName(fieldn), newName(nn)
	{
		sprintf (expandedInfo, "Sets the %s column in the geopoints variable.", fieldName);
		info = expandedInfo;
    }
	virtual int ValidArguments(int arity,Value *arg);
	virtual Value Execute(int arity,Value *arg);
};

int GeoSetFunction::ValidArguments(int arity,Value *arg)
{
    if( arity != 2 )                   // only accept 2-argument calls
        return false;

    if( arg[0].GetType() != tgeopts )  // first argument must be a geopoints
        return false;


    isList   = false;
    isVector = false;

    if( arg[1].GetType() == tnumber )  // second argument can be a number, a list or a vector
    {
        return true;
    }

    if( arg[1].GetType() == tlist )    // second argument can be a number, a list or a vector
    {
        isList = true;
        return true;
    }

    if( arg[1].GetType() == tvector )  // second argument can be a number, a list or a vector
    {
        isVector = true;
        return true;
    }

  return false;
}

Value GeoSetFunction::Execute(int,Value *arg)
{
  DeprecatedMessage (deprecated, "geopoints", newName);

  CGeopts* g;
  arg[0].GetValue(g);                //-- get geopoints

  CList*   lst;
  CVector* vector;
  int inputCount;
  double val = GEOPOINTS_MISSING_VALUE; //-- used if list contains non-numbers
  if( isList )
  {
    arg[1].GetValue(lst);
    inputCount = lst->Count();
  }
  
  else if ( isVector )
  {
    arg[1].GetValue(vector);
    inputCount = vector->Count();
  }
  else
  {
    arg[1].GetValue(val);
  }

  g->load();
  CGeopts* x = new CGeopts(g);       //-- create a copy of input geopoints

  int cnt = g->Count();              //-- count is the count of geopoints
                                     //-- unless a list/vector and list/vector is shorter
  if( (isList || isVector) && inputCount < cnt)
    cnt = inputCount;


  for( int i=0; i < cnt ; ++i )
  {
    double cur = val;                //-- if list then cur is initialised as missing_value
    if( isList && (*lst)[i].GetType() == tnumber )
    {
      (*lst)[i].GetValue( cur );     //-- if list and current list element is number
    }
    else if( isVector )
    {
      cur = (*vector)[i];     //-- if vector

      if (cur == VECTOR_MISSING_VALUE)  //-- convert missing value indicators
        cur = GEOPOINTS_MISSING_VALUE;
    }

    switch( col )
    {
      case GPT_LAT:
        ((*x)[i]).lat_y( cur );
        break;

      case GPT_LON:
        ((*x)[i]).lon_x( cur );
        break;

      case GPT_LEVEL:
        ((*x)[i]).height( cur );
        break;

      case GPT_DATE:
        ((*x)[i]).date( (long)cur );
        break;

      case GPT_TIME:
        ((*x)[i]).time( (long)cur );
        break;

      case GPT_VALUE:
        ((*x)[i]).value( cur );
        break;

      case GPT_VALUE2:
        ((*x)[i]).value2( cur );
        break;
    }
  }

  g->unload();
  x->unload();

  return Value(x);
}


//===========================================================================
class GeoMetadataFunction : public Function {

public:
    GeoMetadataFunction (const char *n) : Function(n, 1, tgeopts)
    {info = "Returns a metadata definition from the given geopoints.";};
    virtual Value Execute(int arity,Value *arg);
};

Value GeoMetadataFunction::Execute(int,Value *arg)
{
    request  *r = empty_request(NULL);

    CGeopts *g;
    arg[0].GetValue(g);
    g->load();

    MvGeoPoints::metadata_t &md = g->metadata();
    if (md.size() > 0)
    {
        for (MvGeoPoints::metadata_t::iterator it=md.begin(); it!=md.end(); ++it)
            set_value(r,(it->first).c_str(),  "%s", (it->second).toString().c_str());

        g->unload();
        return Value(r);
    }
    else
    {
        g->unload();
        return Value();
    }
}

//===========================================================================
class GeoSetMetadataFunction : public Function {

public:
    GeoSetMetadataFunction (const char *n) : Function(n, 2, tgeopts, trequest)
    {info = "Sets a geopoints metadata from a definition";};
    virtual Value Execute(int arity,Value *arg);
};

Value GeoSetMetadataFunction::Execute(int,Value *arg)
{
    request  *r = empty_request(NULL);
    CGeopts *g;
    arg[0].GetValue(g);
    arg[1].GetValue(r);
    g->load();
    CGeopts *newg = new CGeopts(g);  // create a clone of the input geopoints

    MvGeoPoints::metadata_t &md = newg->metadata();
    md.clear();

    // loop through the request/definition and add each element to the metadata map
    parameter *p = r->params;
    while (p)
    {
		const char *key = p->name;
        value *v = p->values;
        md[key] = MvVariant(v->name);
		p = p->next;
	}

    g->unload();
    newg->unload();
    return Value(newg);
}

//===========================================================================
class GeoDbFunction : public Function {

public:
	GeoDbFunction (const char *n) : Function(n)
	{info = "Returns a string of the database from the given geopoints.";};
	virtual int ValidArguments(int arity,Value *arg);
	virtual Value Execute(int arity,Value *arg);
};

int GeoDbFunction::ValidArguments(int arity,Value *arg)
{
  if( arity != 2 && arity != 3 )                   // only accept 2-argument calls
    return false;

  if( arg[0].GetType() != tgeopts )  // first argument must be a geopoints
    return false;

  if( arg[1].GetType() == tstring )  // second argument must be a string
  {
      	if(arity == 3)
	{
		const char *cval;
		arg[1].GetValue(cval);
		string db_item=cval;

		if(arg[2].GetType() == tstring &&
                  (db_item == "column" || db_item == "alias"))
		{
			return true;
		}
		return false;
	}

	return true;
  }

  return false;
}

Value GeoDbFunction::Execute(int,Value *arg)
{
	CGeopts *g;
	arg[0].GetValue(g);

	g->load();

	const char *cval;
	string db_item;
	arg[1].GetValue(cval);

	db_item=cval;

	if(db_item == "name")
	{
		return Value(g->dbSystem().c_str());
	}
	else if(db_item == "column")
	{
		const char *col;
		arg[2].GetValue(col);

		return Value(g->dbColumn(col).c_str());;
	}
	else if(db_item == "alias")
	{
		const char *col;
		arg[2].GetValue(col);

		return Value(g->dbColumnAlias(col).c_str());
	}
	else if(db_item == "path")
	{
		return Value(g->dbPath().c_str());
	}
	else if(db_item == "query")
	{
		int n=g->dbQuery().size();

		CList *v = new CList(n);

		for(unsigned int i=0; i < g->dbQuery().size(); i++)
		{
			(*v)[i]= g->dbQuery().at(i).c_str();
		}

		return Value(v);
	}

	return Value(" ");
}


//-------------------------------------------------------------------
//===========================================================================

static void install(Context *c)
{
	c->AddGlobal  (new Variable("geo_missing_value", Value(GEOPOINTS_MISSING_VALUE)));

	c->AddFunction(new MergeBufrFunction("&"));
	c->AddFunction(new MergeBufrFunction("merge"));

	c->AddFunction(new CountGeoFunction("count"));
	c->AddFunction(new SubGeoFunction("[]"));

	c->AddFunction(new GeoMergeFunction("&"));
	c->AddFunction(new GeoMergeFunction("merge"));

	int i;

	// Binary op

	for(i=0;BinOps[i].symb;i++)
		c->AddFunction(new GeoGeoBinOp(BinOps[i].symb,BinOps[i].proc ));

	for(i=0;BinOps[i].symb;i++)
		c->AddFunction(new NumGeoBinOp(BinOps[i].symb,BinOps[i].proc ));

	for(i=0;BinOps[i].symb;i++)
		c->AddFunction(new GribGeoBinOp(BinOps[i].symb,BinOps[i].proc ));

	// Mult op as Binary op

	for(i=0;MulOps[i].symb;i++)
		c->AddFunction(new GeoGeoBinOp(MulOps[i].symb,MulOps[i].proc ));

	for(i=0;MulOps[i].symb;i++)
		c->AddFunction(new NumGeoBinOp(MulOps[i].symb,MulOps[i].proc ));

	for(i=0;MulOps[i].symb;i++)
		c->AddFunction(new GribGeoBinOp(MulOps[i].symb,MulOps[i].proc ));

	// Unary op

	for(i=0;UniOps[i].symb;i++)
		c->AddFunction(new GeoUnOp(UniOps[i].symb,UniOps[i].proc ));

	c->AddFunction(new GeoFromGribFunction1("interpolate"));
	c->AddFunction(new GeoFromGribFunction2("nearest_gridpoint"));
//	c->AddFunction(new GeoFromGribFunction3("geopoints"));
	c->AddFunction(new GeoFilterFunction("filter"));
	c->AddFunction(new GeoSortFunction("geosort"));
	c->AddFunction(new GeoSubsampleFunction("subsample"));
	c->AddFunction(new GeoPolarVectorFunction("polar_vector"));
	c->AddFunction(new GeoXYVectorFunction("xy_vector"));
	c->AddFunction(new MaxGeoFunction("maxvalue",1));
	c->AddFunction(new MaxGeoFunction("minvalue",0));
	c->AddFunction(new MeanGeoFunction("mean",1));
	c->AddFunction(new MeanGeoFunction("sum",0));


    // 'new' versions of geopoints column extraction functions
	//                                       fn name      field    deprecated  name-of-field
	c->AddFunction(new GeoGetFieldFunction ("dates",      GPT_DATE,   false, "date"));
	c->AddFunction(new GeoGetFieldFunction ("levels",     GPT_LEVEL,  false, "height"));
	c->AddFunction(new GeoGetFieldFunction ("latitudes",  GPT_LAT,    false, "latitude"));
	c->AddFunction(new GeoGetFieldFunction ("longitudes", GPT_LON,    false, "longitude"));
	c->AddFunction(new GeoGetFieldFunction ("values",     GPT_VALUE,  false, "value"));
	c->AddFunction(new GeoGetFieldFunction ("value2s",    GPT_VALUE2, false, "2nd value"));

	// deprecated versions of the above
	//                                      fn name       field  deprecated  name-of-field, name-of-new-version
	c->AddFunction(new GeoGetFieldFunction ("date",      GPT_DATE,   true, "date",         "dates"));
	c->AddFunction(new GeoGetFieldFunction ("level",     GPT_LEVEL,  true, "height",       "levels"));
	c->AddFunction(new GeoGetFieldFunction ("latitude",  GPT_LAT,    true, "latitude",     "latitudes"));
	c->AddFunction(new GeoGetFieldFunction ("longitude", GPT_LON,    true, "longitude",    "longitudes"));
	c->AddFunction(new GeoGetFieldFunction ("value",     GPT_VALUE,  true, "value",        "values"));
	c->AddFunction(new GeoGetFieldFunction ("value2",    GPT_VALUE2, true, "2nd value",    "value2s"));


    // 'new' versions of geopoints column extraction functions
	//                                     fn name      field  deprecated  name-of-field
	c->AddFunction(new GeoSetFunction("set_latitudes", GPT_LAT,    false, "latitude"));
	c->AddFunction(new GeoSetFunction("set_longitudes",GPT_LON,    false, "longitude"));
	c->AddFunction(new GeoSetFunction("set_levels",    GPT_LEVEL,  false, "level"));
	c->AddFunction(new GeoSetFunction("set_dates",     GPT_DATE,   false, "date"));
	c->AddFunction(new GeoSetFunction("set_times",     GPT_TIME,   false, "time"));
	c->AddFunction(new GeoSetFunction("set_values",    GPT_VALUE,  false, "value"));
	c->AddFunction(new GeoSetFunction("set_value2s",   GPT_VALUE2, false, "value2"));

	// deprecated versions of the above
	//                                    fn name       field  deprecated  name-of-field, name-of-new-version
	c->AddFunction(new GeoSetFunction("set_latitude",  GPT_LAT,    true, "latitude",  "set_latitudes"));
	c->AddFunction(new GeoSetFunction("set_longitude", GPT_LON,    true, "longitude", "set_longitudes"));
	c->AddFunction(new GeoSetFunction("set_level",     GPT_LEVEL,  true, "level",     "set_levels"));
	c->AddFunction(new GeoSetFunction("set_date",      GPT_DATE,   true, "date",      "set_dates"));
	c->AddFunction(new GeoSetFunction("set_time",      GPT_TIME,   true, "time",      "set_times"));
	c->AddFunction(new GeoSetFunction("set_value",     GPT_VALUE,  true, "value",     "set_values"));
	c->AddFunction(new GeoSetFunction("set_value2",    GPT_VALUE2, true, "value2",    "set_value2s"));


	c->AddFunction(new GeoDistanceFunction("distance"));
	c->AddFunction(new GeoCreateFunction("create_geo"));
	c->AddFunction(new GeoTypeFunction("dtype"));

	c->AddFunction(new FilterBoxFunction("filter"));
	c->AddFunction(new FilterLevelFunction("filter"));
	c->AddFunction(new FilterDateFunction("filter"));
	c->AddFunction(new GeoOffsetFunction("offset"));
	c->AddFunction(new GeoRemoveDuplicatesFunction("remove_duplicates"));
	c->AddFunction(new RemoveMissingValuesFunction("remove_missing_values"));
	c->AddFunction(new GeoIntBits("intbits"));

	c->AddFunction(new GeoMetadataFunction("metadata"));
	c->AddFunction(new GeoSetMetadataFunction("set_metadata"));
	c->AddFunction(new GeoDbFunction("db_info"));
}

static Linkage linkage(install);
