/***************************** 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 <ctype.h>
#include <string.h>
#include <vector>
#include "macro.h"
#include "script.h"
#include "value.h"

#include "MvPath.hpp"
#include "MvRequest.h"
#include "MvApplication.h"

using std::vector;

static Cache LangCache;
static Cache TestCache;
static Cache ObjsCache;

static const char* M_UPLOT = "uPlotManager";


static const char *find_service(const char *verb,const char *mode)
{
	char *state = strcache("state");
	request *u = mars.setup;
	const char *service = 0;

	while(u && !service)
	{
		if(u->name == state)
		{
			int n = 0;
			const char *x ;

			while((x = get_value(u,"class",n++)) || (x == 0 && n==1))
			{
				if(x == verb || x == 0)
				{
					int m = 0;
					const char *y;
					while( (y = get_value(u,"action",m++)) ||
						  ( y == 0 && m==1))
						if((y == 0) || (strcmp(y,"prepare") == 0))
						{
							const char *z = get_value(u,"context",0);

							if(z == 0 || strcmp(z,"macro") == 0)
							{
								if(mode)
								{
									const char *w; int k = 0;
									while( (w = get_value(u,"mode",k++)) )
										if(w == mode)
											service = get_value(u,"service",0);
								}
								else {
									if(get_value(u,"mode",0) == 0)
										service = get_value(u,"service",0);
								}
							}
						}

				}
			}
		}
		u = u->next;
	}
	return service;
}

int CRequest::Write(FILE *f)
{
	// If the first request is one of a certain set of types (e.g. PSFILE)
	// and it contains a PATH parameter, then we save (i.e. copy) that file
	// to the destination file.
	// Otherwise we just do what we always did, which is to save all the
	// requests into a text file.

	if (!strcmp (r->name, "PSFILE") ||
//	    !strcmp (r->name, "GRIB")   ||
	    !strcmp (r->name, "PNG")    ||
	    !strcmp (r->name, "JPEG")   ||
	    !strcmp (r->name, "GIF")    ||
	    !strcmp (r->name, "PDF")    ||
	    !strcmp (r->name, "SVG"))
	{
		const char *path = get_value(r,"PATH",0); // Get the PATH from request.

		if (path != NULL)
		{
			return CopyFile(path,f);
		}
	}


	save_all_requests(f,r);
	return ferror(f);
}

void CRequest::Dump2(void)
{
	Perl(r);
}

void CRequest::Print(void)
{
	request   *s = r;

	while(s)
	{

		int pflg = 0;
		parameter *p = s->params;

		if(*s->name != '_') cout << s->name;
		cout << '(';
		while(p)
		{
			if(*p->name != '_')
			{
				if(pflg) cout << ',';
				cout << p->name << ":";
				pflg++;

				value *v = p->values;
				if(v && v->next) cout << '[';
				value *w = v;
				int vflg = 0;

				while(w)
				{
					if(vflg) cout << ',';
					cout <<  w->name ;
					vflg++;
					w = w->next;
				}

				if(v && v->next) cout << ']';
			}
			p = p->next;
		}
		cout << ')';
		s = s->next;
	}
}

static void SetValue(request *r,const char *param,Value &val)
{

	double sd;
	const char  *sv;
	Date  d;
	CList *v;
	Value *x;
	request *u,*p,*q;
	int j;
	char buf[80];

	switch(val.GetType())
	{
		case tnumber:
			val.GetValue(sd);
			set_value(r,param,"%.12g",sd);
			break;

		case tstring:
			val.GetValue(sv);
#if 0
			if(*sv != '/')
				set_list(r,param,sv);
			else
#endif
				set_value(r,param,"%s",sv);
			break;

		case tdate:
			val.GetValue(d);
			d.Format("yyyy-mm-dd HH:MM:SS",buf);
			set_value(r,param,"%s",buf);
			break;

		case tlist:
			u = p = q = 0;

			val.GetValue(v);
			x = v->Values();

			unset_value(r,param);

			for(j=0;j<v->Count();j++)
				switch(x[j].GetType())
				{
					case tnumber:
						x[j].GetValue(sd);
						add_value(r,param,"%.12g",sd);
						break;

					case tstring:
						x[j].GetValue(sv);
						add_value(r,param,"%s",sv);
						break;

					case tdate:
						x[j].GetValue(d);
						d.Format("yyyy-mm-dd HH:MM:SS",buf);
						add_value(r,param,"%s",buf);
						break;

					default:
						x[j].GetValue(p);
						if( (p = clone_all_requests(p)) )
						{
							if(u == 0) u = p; else q->next = p;
							while(p) { q = p; p = p->next; };
						}
						break;
				}

			if(u) {
				set_subrequest(r,param,u);
				free_all_requests(u);
			}

			break;


		case tnil:
			unset_value(r,param);
			break;

		default:
			val.GetValue(u);
			set_subrequest(r,param,u);
			break;

		}
}

//=============================================================================
class PrintRequestFunction : public Function {
public:
	PrintRequestFunction(const char *n) : Function(n,1,trequest|tgrib|timage|tgeopts)
	{ info = "Prints a definition";};
	virtual Value Execute(int ,Value *arg)
	{
		request *r;
		arg[0].GetValue(r);
		print_all_requests(r);
		return Value(0.0);
	}
};

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

class SimpleRequestFunction : public Function {

	typedef struct { CList *v;int n; } lk;

	request *def;

protected:
	request *GetRequest(int arity ,Value *arg);
	void     QuickDirtyFix( request* r );
	const char *verb;
	const char *service;
	vector<Content *> attachedContent; // to build up a list of content which will need to be destroyed after an async call - see comments for attachedContent in value.h.

static void LookUpCnt(char*, void *d);
static void LookUpLst(char*n,void *d);

public:
	SimpleRequestFunction(request *r,const char *n);
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
	Value Language(const char *,const char *);

	request *Expand(request *,long flags = 0);
	request *GetLanguage(void);
	rule    *GetRules(void);
};

void SimpleRequestFunction::LookUpCnt(char*p, void *d)
{
	int *n = (int*)d;
	if(isalnum(*p)) (*n)++;
}

void SimpleRequestFunction::LookUpLst(char*n,void *d)
{
	lk *l = (lk*)d;
	if(isalnum(*n)) (*(l->v))[l->n++]=Value(n);
}

SimpleRequestFunction::SimpleRequestFunction(request *r,const char *n):
	Function(n)
{
	def     = r;
	verb    = get_value(r,"class",  0);
	info    = strcache(get_value(r,"info",0));
	service = find_service(verb,0);

	if(verb == 0) verb = get_value(r,"_VERB",0);
	if(verb == 0) verb = "_";
	if(info == 0) info = "Metview object";
}

request *SimpleRequestFunction::GetLanguage(void)
{
	const char *q;
	request *lang = 0;

	if( (q = get_value(def,"definition_file",0)) )
	{
		lang = (request *)LangCache.Find(q);
		if(lang == 0)
			LangCache.Set(q,lang = read_language_file(q));
	}
	return lang;
}

rule *SimpleRequestFunction::GetRules(void)
{
	const char *p;
	rule *test = 0;
	if( (p = get_value(def,"rules_file",0)) )
	{
		test = (rule*)TestCache.Find(p);
		if(test == 0)
			TestCache.Set(p,test = read_check_file(p));
	}
	return test;
}


Value SimpleRequestFunction::Language(const char *v,const char *p)
{

	request *lang = GetLanguage();

	if(lang == 0) return Value(); // Return nil

	int n = 0;
	loopuk_language(lang,v,p,LookUpCnt,&n);

	lk k;

	k.v = new CList(n);
	k.n = 0;

	loopuk_language(lang,v,p,LookUpLst,&k);

	return Value(k.v);


}
//=============================================================================


int SimpleRequestFunction::ValidArguments(int arity,Value *arg)
{
	if(arity == 0) return true;
	int i =0;

	while(i < arity)
	{
		switch(arg[i].GetType())
		{
			case tstring:
				if(i == arity-1) return false;
				i++;
				/* we should check the type of arguments there */
				break;

			case trequest:
				break;

			case tlist:
				CList *v;
				arg[i].GetValue(v);

				if(!ValidArguments(v->Count(),v->Values()))
					return false;

				break;

			case tnil:
				break;

			default:
				return false;
				//break;
		}
		i++;
	}

	return true;
}

request *SimpleRequestFunction::GetRequest(int arity ,Value *arg)
{
	request *r = empty_request(verb);
	const char *q;
	CList *v;


	// Build request from arguments

	int i =0;
	while(i < arity)
	{
		switch(arg[i].GetType())
		{
			case tstring:
			{
				// e.g. data: gpts
				// see comments for attachedContent in value.h. for explaination of why we store these
				// in fact, we only need to store certain variable types here (e.g. odb, geopoints), but
				// that might make the code less generic. Another option could be to have a virtual
				// function in Value which says which variable types have underlying data files; but
				// perhaps the overhead of another entry in the vtable would almost outweigh the savings?

				arg[i].GetValue(q);
				SetValue(r,q,arg[++i]);
				Content *c = arg[i].GetContent();
				attachedContent.push_back(c);  // we will need to detach these later - see comments for attachedContent in value.h.
				c->Attach(); // attach because the data will be passed to another module, so we shouldn't allow it to be deleted yet
				break;
			}

			case tnil:
				break;

			case trequest:
				request *u;
				arg[i].GetValue(u);
				reqcpy(r,u);
				break;

			case tlist:
				arg[i].GetValue(v);
				u = GetRequest(v->Count(),v->Values());
				reqcpy(r,u);
				free_all_requests(u);
				break;
		}
		i++;
	}

	return Expand(r);

}


void SimpleRequestFunction::QuickDirtyFix( request* r )
{
  //-- 010912/vk:
  //--   Old macros created by PlotMod contained
  //--   an undocumented, hidden, now obsolete(!)
  //--   parameter PRINT_SELECTION => remove if found!
  //-- 010920/vk:
  //--   warn about PATH parameter (to be removed!!!???)
  if( r && ( strcmp( r->name, "DEVICE_DRIVER" ) == 0 ) )
    {
      const char* p = get_value( r, "PRINT_SELECTION", 0 );
      if( p )
	{
	  marslog( LOG_INFO, "PRINT_SELECTION ignored in macro output(...)" );
	  unset_value( r, "PRINT_SELECTION" );
	}

      p = get_value( r, "PATH", 0 );
      if( p )
	{
	  marslog( LOG_INFO, "PATH obsolete, use FILE_NAME in output(...)" );
	  unset_value( r, "PATH" );
	}
    }
}

request *SimpleRequestFunction::Expand(request *r,long flags)
{
	// Expand it

	const char    *p     = 0;
	request *lang  = GetLanguage();
	rule    *test  = GetRules();
	long     flag  = EXPAND_MARS;

	if(lang == 0 && (verb == 0 || *verb == '_') )
	{
		const char *c = get_value(r,"_CLASS",0);
		if(c) {
			strfree(r->name);
			r->name = strcache(c);
		}
		return r;
	}

	// Get flags from config file

	if( (p = get_value(def,"expand",0)) )
		flag = atol(p);

	if(flags) flag &= flags; // Used as mask

	if(lang)
	{
		QuickDirtyFix( r ); //-- Device driver
		long f = expand_flags(flag);
		reset_language(lang);
		request *z = expand_all_requests(lang,test,r);
		if (z)
		{
			unset_value(z, "_UNSET");  // for efficiency - we don't need this param
		}
		else
		{
			marslog(LOG_EROR, "There was a problem parsing the definition finishing on line %d. Please see the above message(s) to see which parameter(s) could be at fault.", Context::CurrentLine());
            //print_all_requests(r);
		}

		free_all_requests(r);
		expand_flags(f);
		r = z;
	}

	set_value(r,"_NAME",Context::UniqueName() );
	if(verb) set_value(r,"_CLASS",verb);

	return r;

}

Value SimpleRequestFunction::Execute(int arity ,Value *arg)
{
	Value v;
	request *r = GetRequest(arity,arg);
	request *s = 0;

	if (!r)
	{
		return Error("Definition was incorrect - please see the preceding message.");
	}



	if(service) // Passes the device to modules, needed for VisMod...
	{
	   if( strcmp( service, M_UPLOT ) == 0 )
	   {
		Script::Output.GetValue(s);
		set_subrequest(r,"_DEVICE_DRIVER",s);  //-- directly for uPlot
	   }
#if 0 //Remove this code later. We decided not to use a translator anymore.
	   else if( strcmp( service, "VisModTrans" ) == 0 )
	   {
//		Script::Driver.GetValue(s);
		Script::Output.GetValue(s);
		if ( s )
		{
			set_subrequest(r,"_DRIVER",s);
			service = M_UPLOT; //-- directly to uPlot
		}
		else
		{
			Script::Output.GetValue(s);
			if ( s )
				set_subrequest(r,"_DEVICE_DRIVER",s);  //-- directly for PlotMod
			else
			{
				Script::Device.GetValue(s);
				if ( s )
					set_subrequest(r,"_DEVICE",s);  //-- for old VisMod, via translator
				else
				{
					// There is no "device" definition, so plot on screen.
					// Because PlotMod and uPlot share the same
					// PLOT_SUPERPAGE command and in order to avoid
					// uPlot having to call the translator all the time,
					// the command below is needed (not nice,
					// but it would require several changes elsewhere)
					if ( strcmp(r->name,"PLOT_SUPERPAGE") == 0 )
					{
						// Get preferences
						MvRequest myPref = MvApplication::getPreferences();

						// Get preferred plot module name
						const char* myPlotModule = myPref( "DEFAULT_PLOT_MODULE" );

						// Set output function
						if( myPlotModule && ( strcmp( myPlotModule, M_UPLOT ) == 0 ) )
							service = M_UPLOT;
					}
				}
			}

            		// Ok, we know it's VisModTrans, but check if the
            		// plotter() function has been set.
            		service = PlotterFunction::Plotter();
		}
	   }
	   else if( strcmp( service, "PlotMod" ) == 0 )  //D Remove later
	   {
		Script::Output.GetValue(s);
		set_subrequest(r,"_DEVICE_DRIVER",s);  //-- directrly for new PlotMod
	   }
#endif

      set_value(r,"_MACRO","%s",Script::MacroPath());
      string fullPath = MakeAbsolutePath((const char*)get_value(r,"PATH",0),mdirname(Script::MacroMainPath()));
      set_value(r,"_PATH",fullPath.c_str());
	}

	v = service ? Value(service,r,attachedContent) : Value(r);

	free_all_requests(r);

	attachedContent.clear();

	return v;
}

//=============================================================================
// Note : the metview function need the GUI

Value Context::Metview(const char *name)
{
	request *r = empty_request("RESOLVE");
	set_value(r,"NAME","%s",name);
	request *s = empty_request("MACRO");
	set_value(r,"_MODE", "MACRO");
	set_value(r,"_CALLED_FROM_MACRO", "0");

	//Get desktop app name
	char *desktop=getenv("MV_DESKTOP_NAME");
	if(desktop == 0)  
	{	
		cout << "Error: MV_DESKTOP_NAME is not defined! Macro exits!" << endl;
	}
	
	string desktopName(desktop);	
	Value v(desktopName.c_str(),r);
	free_all_requests(s);
	free_all_requests(r);
	return v;
}

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


Value MetviewRequestFunction::Execute(int,Value *arg)
{
	const char *p;
	Value v;
	arg[0].GetValue(p);
	return Context::Metview(p);
}

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

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

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

Value ValuesFunction::Execute(int arity,Value *arg)
{
	const char *p,*q = 0;

	arg[0].GetValue(p);
	if(arity == 2) arg[1].GetValue(q);

	SimpleRequestFunction *f = (SimpleRequestFunction*)ObjsCache.Find(p);

	if(f) return f->Language(p,q);

	return Value();

}

//=============================================================================
char*   PlotterFunction::plotter   = strcache(NULL);
boolean PlotterFunction::setbyuser = false;

//-- list for plotters given by numbers (starting from 0)
static const char *plotters[] = {
	"PlotMod",      //-- 0
	"VisModTrans",  //-- 1
	M_UPLOT,        //-- 2
};

//-- list of pairs for plotter aliases
//-- for each "pair" the first item is the alias, the second one the corresponding plotter
static const char *aliases[] = {
	"magics",	"PlotMod",
	"magics6",	"PlotMod",
	"magics++",	M_UPLOT,
	"mag++",	M_UPLOT,
};

PlotterFunction::PlotterFunction(const char *n) : Function(n)
{
		 setbyuser = false;
		 plotter = 0;
}

void PlotterFunction::Init()
{
	if (plotter)
	{
		strfree(plotter);
		plotter = 0;
	}
	setbyuser = false;
}

int PlotterFunction::ValidArguments(int arity,Value *arg)
{
	return (arity == 0) || ( arity == 1 &&
		(arg[0].GetType() == tstring || arg[0].GetType() == tnumber));
}

Value PlotterFunction::Execute(int arity,Value *arg)
{

	char* prev = strcache(plotter);

	if(arity == 1)
	{

		if(arg[0].GetType() == tstring )
		{
			const char    *p;
			arg[0].GetValue(p);
			boolean found = false;

			//-- is it a valid plotter name?
			for( int i=0; i < (signed)NUMBER(plotters); ++i )
			{
			   if( strcmp( p, plotters[i] ) == 0 )
			   {
			      strfree(plotter);
			      plotter = strcache(p);
			      found = true;
			   }
			}

			if( ! found )
			{
			   //-- not a plotter name, then is it an alias name?
			   for( int i=0; i < (signed)NUMBER(aliases); )
			   {
			      if( strcmp( p, aliases[i] ) == 0 )
			      {
			         strfree(plotter);
			         plotter = strcache(aliases[i+1]);
			         found = true;
			      }
			      i = i + 2;
			   }
			}
			if( ! found )
			   return Error("Unknown plotter alias %s", p );
		}

		if(arg[0].GetType() == tnumber)
		{
			int n;
			arg[0].GetValue(n);

			if(n < 1 || n > (signed)NUMBER(plotters))
			   return Error("Invalid plotter id %d, should be between 1 and %d",
				n, NUMBER(plotters));

			strfree(plotter);
			plotter = strcache(plotters[n-1]);
		}
	}
    
    setbyuser = true;

	Value v(prev);
	strfree(prev);
	return v;
}

char* PlotterFunction::Plotter()
{
	if(plotter == 0)
		plotter = strcache(M_UPLOT);   //default value

	return plotter;
}

void PlotterFunction::SetPlotter( const char* p )
{
    // only set the plotter if the user has not specified which one to use
    
    if (!setbyuser)
    {
	    strfree(plotter);
	    plotter = strcache(p);
    }
}

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

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

Value RequestGetElemFunction::Execute(int,Value *arg)
{
	const char    *p;
	request *r,*s = 0;
	CList   *l;

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


	if ((s = get_subrequest(r,p,0)) )
	{
		Value v;
		v.SetContent(s);          // This will force expension of "DATA",..
		v.GetContent()->Attach(); // Subrequests are parameters,
								  // they must be saved ....
		free_all_requests(s);
		return v;
	}

	// If the request has a language file
	// first expand the parameter name and get the defaults

	SimpleRequestFunction *f = (SimpleRequestFunction*)ObjsCache.Find(r->name);
	if(f != 0)
	{
		s = empty_request(r->name);
		s = f->Expand(s,~EXPAND_NO_DEFAULT & ~EXPAND_NO_OFF);

		// Make sure the parameter name is right

		parameter *q = closest_parameter(
			closest_verb(f->GetLanguage(),r->name),
			p);

		if(q) p = q->name;

	}


	int n = count_values(r,p);

	if(n == 0 && s != 0)
	{
		// Use language
		r = s;
		n = count_values(r,p);
	}

	if(n == 0) return Value(); // return nil
	if(n >  1) l = new CList(n);

	Date d;

	parameter *par = find_parameter(r,p);
	if(par == NULL) return Value();
	value *vi = par->values;

	for(int i = 0; i <n ;i++)
	{
		const char *q = vi->name;
		Value v;

		if(strcmp(p,"DATE")==0 && is_number(q))
		{
			d =  Date(atof(q));
			v =  Value(d);
		}
		else if(isdate(q))
			v =  Value(Date(q));
		else if(is_number(q))
			v =  Value(atof(q));
		else
			v =  Value(q);

		if(n == 1)
		{
			free_all_requests(s);
			return v;
		}
		(*l)[i] = v;

		vi = vi->next;
	}

	free_all_requests(s);
	return Value(l);

}
//=============================================================================

class ComplexRequestFunction : public Function{
	char *service;
	virtual request *Header(void) { return 0; };
	virtual char *Service()   { return service; }
public:
	ComplexRequestFunction(const char *n,const char *s) : Function(n)
		{ service = strcache(s); };
	virtual int ValidArguments(int arity,Value *arg);
	virtual Value Execute(int arity,Value *arg);
};


int ComplexRequestFunction::ValidArguments(int arity,Value*)
{
	if(arity == 0) return false;
	return true;

#if 0
	if(arity == 1 && arg[0].GetType() == tlist)
	{

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

		if(!ValidArguments(v->Count(),v->Values()))
				return false;

		return true;
	}

	for(int i = 0; i< arity; i++)
	{
		vtype t = arg[i].GetType();
		if(t == tlist)
		{
			CList *v;
			arg[0].GetValue(v);
			if(!ValidArguments(v->Count(),v->Values()))
				return false;

		}
		else if( (t & (trequest|tgrib|timage|tbufr|tgeopts)) !=0) return false;
	}

	return true;
#endif
}

// This function used to handle requests such as EDIT, NEWPAGE and PLOT.
// First, it was updated to handle the new uPlot plot command. Then, these changes
// were too specific and two new function were created:
// PlotFunction::ExecuteComplexRequestFunction (to handle plot command)
// PageFunction::Execute (to handle newpage command)
Value ComplexRequestFunction::Execute(int arity ,Value *arg)
{
    request *p = 0, *q = 0;

    if(arity == 1 && arg[0].GetType() == tlist)
    {
        CList *v;
        arg[0].GetValue(v);
        return Execute(v->Count(),v->Values());
    }

    q = p = Header();
    set_value(q,"_NAME",Context::UniqueName());

    // Loop over parameters
    for(int i=0;i<arity;i++)
    {
        request *u;
        arg[i].GetValue(u);
        if( (u = clone_all_requests(u)) )
        {
            if(get_value(u,"_CLASS",0) == 0)
                set_value(u,"_CLASS",u->name);

            set_value(u,"_NAME", Context::UniqueName() );

            if(p == 0) p = u; else q->next = u;
            while(u) { q = u; u = u->next; };
        }
    }

    set_value(p,"_MACRO","%s",Script::MacroPath());
    string fullPath = MakeAbsolutePath((const char*)get_value(p,"PATH",0),mdirname(Script::MacroMainPath()));
    set_value(p,"_PATH",fullPath.c_str());

    Value v(Service(),p);
    free_all_requests(p);
    v.Sync(); // Force sync

    return v;
}

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

class PlotFunction : public ComplexRequestFunction{
	virtual request *Header(void);
	virtual Value Execute(int arity,Value *arg);
	Value ExecuteComplexRequestFunction(int arity,Value *arg);
	virtual char* Service() { return PlotterFunction::Plotter(); }
public:
	PlotFunction(const char *n) : ComplexRequestFunction(n,"VisModTrans") {};
};

Value PlotFunction::Execute(int arity ,Value *arg)
{
	if(arity == 1 && arg[0].GetType() == tlist)
		return ComplexRequestFunction::Execute(arity,arg);


	if(arity < 1)
		return Value();

	request *r;
	arg[0].GetValue(r);

	// Check for a 'mode' in the first argument

	request *mode = get_subrequest(r,"_MODE",0);

	if(mode && r->name && strcmp(r->name,"DROP") == 0)
	{
		for(int i = 1; i< arity; i++)
		{
			request *u;
			arg[i].GetValue(u);
			const char *service = find_service(u->name,mode->name);
			if(service)
			{
				// Send it to the proper place
				request *t = clone_all_requests(u);
				set_subrequest(t,"_MODE",mode);
				Value v(service,t);
				v.Sync(); // Force sync
				arg[i] = v; // I don't know it this is safe
				free_all_requests(t);
			}

		}
		free_all_requests(mode);
	}

	return ExecuteComplexRequestFunction(arity,arg);

}

Value PlotFunction::ExecuteComplexRequestFunction(int arity ,Value *arg)
{
	request *p = 0, *q = 0;

	if(arity == 1 && arg[0].GetType() == tlist)
	{
		CList *v;
		arg[0].GetValue(v);
		return Execute(v->Count(),v->Values());
	}

	// Loop over parameters
	MvRequest preq;
	for(int i=0;i<arity;i++)
	{
		request *u;
		arg[i].GetValue(u);
		Content *c = arg[i].GetContent();
		c->Attach();  // arguments will be popped off the stack and destroyed after use, so preserve them here

		if( (u = clone_all_requests(u)) )
		{
			// Save driver requests
			const char* cval = (const char*)get_value(u,"CLASS",0);
			if( cval && strcmp(cval,"DRIVER") == 0 )
			{
				preq = preq + u;
				continue;
			}

			if(p == 0) p = u; else q->next = u;
			while(u) { q = u; u = u->next; };
		}
	}

   // Add hidden parameters
   set_value(p,"_MACRO","%s",Script::MacroPath());
   set_value(p,"_NAME",Context::UniqueName());
   string fullPath = MakeAbsolutePath((const char*)get_value(p,"PATH",0),mdirname(Script::MacroMainPath()));
   set_value(p,"_PATH",fullPath.c_str());

	// Define output requests according to the following rules/priorities:
	// 1. Drivers defined in the "plot" command have top priority (overwrite "setoutput")
	// 2. Drivers defined in the "setoutput" command have second priority
	// 3. If neither 1 or 2 is defined then output goes to "screen"
    if ( preq )       	   //drivers defined in the plot command
	{
		// Set output function
		PlotterFunction::SetPlotter("uPlotBatch");

        MvRequest req("PRINTER_MANAGER");
        req.setValue("OUTPUT_DEVICES",preq);
        Script::Output = Value(req);
    }

	// Concatenate all requests
	if ( Script::PlotReq )
		Script::PlotReq = Script::PlotReq + p;
	else
		Script::PlotReq = p;

	return Value((request*)0);
}

// Get the current device

request *PlotFunction::Header(void)
{
	request *r = 0;

	Script::Output.GetValue(r);

	if( !r )
		Script::Device.GetValue(r);

	r =  clone_all_requests(r);
	return r;
}

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

class PageFunction : public ComplexRequestFunction{
	virtual request *Header(void);
    virtual Value Execute(int arity,Value *arg);
	virtual char* Service() { return PlotterFunction::Plotter(); }
public:
	PageFunction(const char *n) : ComplexRequestFunction(n,"VisModTrans") {};
};

Value PageFunction::Execute(int ,Value*)
{
    // Get header info
    MvRequest hreq = Header();
    if ( hreq )
    {
        // Concatenate all requests
        if ( Script::PlotReq )
            Script::PlotReq = Script::PlotReq + hreq;
       else
            Script::PlotReq = hreq;
    }

     return Value((request*)0);
}

request *PageFunction::Header(void)
{
	return empty_request("NEWPAGE");
}

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

class DialogFunction : public ComplexRequestFunction{
	virtual request *Header(void);
public:
	DialogFunction(const char *n,const char *s) : ComplexRequestFunction(n,s) {};
};


request *DialogFunction::Header(void)
{
	return empty_request("EDIT");
}

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

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

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

Value ServiceFunction::Execute(int arity ,Value *arg)
{
	request *s = 0;
	request *t = 0;

	const char *serv;
	arg[0].GetValue(serv);

	for(int i = 1; i< arity;i++)
	{
		request *r;
		arg[i].GetValue(r);
		r = clone_all_requests(r);
		if(t) t->next = r; else s = r;
		while(r)
		{
			t = r;
			r = r->next;
		}
	}

	Value v(serv,s);

	free_all_requests(s);

	return v;

}

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

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

Value RequestFunction::Execute(int ,Value *arg)
{

	const char *p;
	request *r;

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

	request *s = empty_request(p);
	reqcpy(s,r);

	Value v(s);

	free_all_requests(s);

	return v;

}

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

Value VerbFunction::Execute(int,Value *arg)
{
	request *r = 0;

	arg[0].GetValue(r);
	if(r == 0)
		return "";
	return Value(r->name);

}

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

Value KeyFunction::Execute(int,Value *arg)
{
	request *r = 0;

	arg[0].GetValue(r);
	if (r == 0)
		return"";

	parameter* p = r->params;
	int count = 0;
	while (p)
	{
		count++;
		p = p->next;
	}
	CList *l = new CList(count);
	p = r->params;
	int n = 0;
	while (p)
	{
		(*l)[n++] = p->name;
		p = p->next;
	}

	return Value(l);

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

Value OrderFunction::Execute(int,Value*)
{
	int count = mars_order_count();
	CList *l = new CList(count);
	char **order = mars_order();
	for (int i = 0; i < count; i++)
		(*l)[i] = order[i];

	return Value(l);

}
//=========================================================================


void CRequest::SetSubValue(Value& v,int arity,Value *arg)
{
	if(!Check(1,v,arity,arg,tany,1,tnumber|tstring))
		return;

	const char    *p;

	arg[0].GetValue(p);

	SetValue(r,p,v);

	// If needed, expand request
	SimpleRequestFunction *f = (SimpleRequestFunction*)ObjsCache.Find(r->name);

	if(f)
	{
		request *s = f->Expand(clone_all_requests(r));
		if(s) free_all_requests(r);
		r = s;
	}

}

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

class PlotSuperpageFunction : public Function {
public:
	PlotSuperpageFunction(const char *n) : Function(n,2,tstring,trequest) {};
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
	request *GetRequest(int arity ,Value *arg);
};

int PlotSuperpageFunction::ValidArguments(int arity,Value*)
{
	return (arity < 2 ? false : true);
}

Value PlotSuperpageFunction::Execute(int arity ,Value *arg)
{
	// Get full request
	MvRequest r = GetRequest(arity,arg);

	// Expand request to make sure that all parameters
	// are defined in capital letters
	// Also note that we need to expand properly so that the rules
	// are properly applied so that 'portrait', 'a3', etc are used.
	// This means we should NOT use EXPAND_NO_DEFAULT.
	r = r.ExpandRequest("PlotSuperPageDef","PlotSuperPageRules",0);

	// Remove PAGES request
	MvRequest superpage = r;
	superpage.unsetParam("PAGES");

	// Get pages requests
	MvRequest pages = r.getSubrequest("PAGES");

	// Build list of requests (superpage+page[i])
	int npages = count_requests(pages);
	CList* lreq = new CList(npages);
	for ( int i = 0; i < npages; i++ )
	{
	    MvRequest page = pages.justOneRequest();
	    MvRequest oneReq = superpage;
	    oneReq("PAGES") = page;
	    (*lreq)[i] = Value(oneReq);
	    pages.advance();
	}

	return Value(lreq);
}

request *PlotSuperpageFunction::GetRequest(int arity ,Value *arg)
{
	request *r = empty_request("PLOT_SUPERPAGE");
	const char *q;
	CList *v;

	// Build request from arguments
	int i =0;
	while(i < arity)
	{
		switch(arg[i].GetType())
		{
			case tstring:
				arg[i].GetValue(q);
				SetValue(r,q,arg[++i]);
				break;

			case tnil:
				break;

			case trequest:
				request *u;
				arg[i].GetValue(u);
				reqcpy(r,u);
				break;

			case tlist:
				arg[i].GetValue(v);
				u = GetRequest(v->Count(),v->Values());
				reqcpy(r,u);
				free_all_requests(u);
				break;
		}
		i++;
	}
	
	return r;
}

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

static void install(Context *c)
{
	request *r = mars.setup;

	c->AddFunction(new MetviewRequestFunction("metview"));
	c->AddFunction(new SimpleRequestFunction(0,"definition"));

	char *object = strcache("object");

	while(r)
	{
		if(r->name == object)
		{
			const char *n = get_value(r,"macro",0);
			if ( n && strcmp(n,"plot_superpage") == 0 )
			{
			      r = r->next;
			      continue;
			}
			
			const char *m = get_value(r,"class",0);
			if(n) {
				Function *f = new SimpleRequestFunction(r,n);

				ObjsCache.Set(n,f);
				if(m) ObjsCache.Set(m,f);

				c->AddFunction(f);
			}
		}
		r = r->next;
	}

	strfree(object);

	//Get desktop name
	char *desktop=getenv("MV_DESKTOP_NAME");
	if(desktop == 0)  
	{	
		cout << "Error: MV_DESKTOP_NAME is not defined! GeoTool exits!" << endl;
		return;
	}
	
	string desktopName(desktop);
	
	
	c->AddFunction(new PlotFunction("plot"));
	c->AddFunction(new PageFunction("newpage"));
	c->AddFunction(new DialogFunction("dialog",desktopName.c_str()));
	c->AddFunction(new RequestGetElemFunction("[]"));
	c->AddFunction(new ValuesFunction("values"));
	c->AddFunction(new PrintRequestFunction("printdef"));
	c->AddFunction(new ServiceFunction("service"));
	c->AddFunction(new RequestFunction("request"));
	c->AddFunction(new PlotterFunction("plotter"));
	c->AddFunction(new VerbFunction("verb"));
	c->AddFunction(new VerbFunction("class"));
	c->AddFunction(new KeyFunction("keywords"));
	c->AddFunction(new OrderFunction("marsorder"));
	c->AddFunction(new PlotSuperpageFunction("plot_superpage"));
}

static Linkage linkage(install);
