/***************************** 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 <iostream>
#include <set>
#include <vector>

#include "macro.h"
#include "script.h"
#include "value.h"

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

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 != nullptr) {
            return CopyFile(path, f);
        }
    }


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

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

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

    while (s) {
        int pflg     = 0;
        parameter* p = s->params;

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

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

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

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

void SetValue(request* r, const char* param, Value& val, bool attachData)
{
    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;

                    case tnil:
                        add_value(r, param, "%.12g", DBL_MAX);
                        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);

            // this is a data-type value, so we should record that we have a new
            // reference to it
            if (attachData)
                val.GetContent()->Attach();
            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
{
    struct lk
    {
        CList* v;
        int n;
    };

    request* def;

protected:
    request* GetRequest(int arity, Value* arg);
    void QuickDirtyFix(request* r);
    const char* verb;
    const char* service;
    std::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();
    rule* GetRules();
};

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()
{
    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()
{
    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
    static std::set<int> needToAttachTypes  =
        {tgrib, tbufr, tvector, tgeopts, tnetcdf, todb, ttable, tgptset};

    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?
                // THOUGHT: we could consider not setting attachData=false and instead remove the
                // c->Attach() that comes 3 lines later. It's not clear if this is foolproof though.

                arg[i].GetValue(q);
                Value val(arg[++i]);
                SetValue(r, q, val, false);  // attachData=false because we will attach 3 lines later
                // Attaching the value caused numeric, string etc. values to be created
                // and stored unnecessary, resulting in a significant slowdown in macros
                // heavily using definitions. So, now it is only done for the "complex"
                // datatypes. See METV-3244.
                if (needToAttachTypes.find(val.GetType()) != needToAttachTypes.end()) {
                    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());
        std::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) {
        std::cout << "Error: MV_DESKTOP_NAME is not defined! Macro exits!" << std::endl;
    }

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

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

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

    return Value();
}

//=============================================================================
char* PlotterFunction::plotter     = strcache(nullptr);
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 (auto& i : plotters) {
                if (strcmp(p, 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);
    }
}

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

// Get the value of a parameter name from a request structure
// The input request can be originated from different sources, including a
// Macro definition structure or a Macro read_request function.
// The search procedure to find the parameter name in the request follows this
// sequence:
// 1) use the original parameter name
// 2) convert parameter name to capital letters (request defined as a
//    definition structure in Macro is given in capital letters)
// 3) make sure the parameter name is right. If the name is not found in the
//    related definition file then guess a new name
// 4) expand the request and get the default value
// 5) return nil

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 = nullptr;

    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",..
        // The line below was commented out to solve the performance issues in METV-3244
        // The bottom line is: Attach() should not be called because v is already a copy.
        //v.GetContent()->Attach();  // Subrequests are parameters,
                                   // they must be saved ....
        free_all_requests(s);
        return v;
    }

    // First, search the parameter name in the request
    int n = count_values(r, p);
    if (n == 0) {
        // Second, search the input parameter name in capital letters
        std::string sp = p;
        for (char& i : sp)
            i = toupper(i);

        p = strcache(sp.c_str());
        n = count_values(r, p);
        if (n == 0) {
            // Third, make sure the parameter name is correct
            // Check if request has a language file
            auto* f = (SimpleRequestFunction*)ObjsCache.Find(r->name);
            if (f == 0)
                return Value();  //return nil

            // Get the closest parameter name
            parameter* q = closest_parameter(
                closest_verb(f->GetLanguage(), r->name), p);
            if (q)
                p = q->name;

            n = count_values(r, p);
            if (n == 0) {
                // Fourth, expand the request and get the default value
                s = empty_request(r->name);
                s = f->Expand(s, ~EXPAND_NO_DEFAULT & ~EXPAND_NO_OFF);

                // Use language
                r = s;
                n = count_values(r, p);
                if (n == 0)
                    return Value();
            }
        }
    }

    if (n > 1)
        l = new CList(n);

    parameter* par = find_parameter(r, p);
    if (par == nullptr) {
        if (l) {
            delete l;
        }
        return Value();
    }

    value* vi = par->values;
    // if the hidden item "__strings" exists, then it will tell us
    // if this parameter should be returned as a string, regardless of
    // the usual interpretation of parameter values

    parameter* stringpar = find_parameter(r, "__strings");
    if (stringpar) {
        value* svi = stringpar->values;
        //const char* svn = par->name;
        if (!strcmp(svi->name, p))
            return Value(vi->name);
    }


    Date d;
    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;
    bool argsRequired;
    virtual request* Header() { return 0; };
    virtual char* Service() { return service; }

protected:
    std::string plot_command_;

public:
    ComplexRequestFunction(const char* n, const char* s, bool argsReq = true) :
        Function(n),
        argsRequired(argsReq)
    {
        service       = strcache(s);
        plot_command_ = n;
    }
    virtual int ValidArguments(int arity, Value* arg);
    virtual Value Execute(int arity, Value* arg);
};


int ComplexRequestFunction::ValidArguments(int arity, Value*)
{
    if (argsRequired && (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());
    std::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();
    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());
    std::string fullPath = MakeAbsolutePath((const char*)get_value(p, "PATH", 0), mdirname(Script::MacroMainPath()));
    set_value(p, "_PATH", fullPath.c_str());
    set_value(p, "_MACRO_PLOT_COMMAND", plot_command_.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()
{
    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();
    virtual Value Execute(int arity, Value* arg);
    virtual char* Service() { return PlotterFunction::Plotter(); }

public:
    PageFunction(const char* n) :
        ComplexRequestFunction(n, "VisModTrans", false)  // no args required
    {
        info = "Forces a new page on PostScript output";
    }
};

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

    return Value(r);
}

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

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

class DialogFunction : public ComplexRequestFunction
{
    virtual request* Header();

public:
    DialogFunction(const char* n, const char* s) :
        ComplexRequestFunction(n, s) {}
};


request* DialogFunction::Header()
{
    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;
    }
    auto* 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();
    auto* 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
    auto* 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)
    {
        info = "Returns a super_page for plotting";
    }
    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);
    auto* 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) {
        std::cout << "Error: MV_DESKTOP_NAME is not defined! GeoTool exits!" << std::endl;
        return;
    }

    std::string desktopName(desktop);


    c->AddFunction(new PlotFunction("plot"));
    c->AddFunction(new PlotFunction("metzoom"));
    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);
