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

#include <stdlib.h>
#include <time.h>
#include <MvRequest.h>
#include <MvVersionInfo.h>
#include <MvScanFileType.h>
#include <MvMiscellaneous.h>

#if defined(METVIEW_ODB)
#include <odc/api/odc.h>
#endif

#if defined(HAVE_PPROC_MIR)
#include "mir/api/mir_version.h"
#endif

#include "macro.h"
#include "script.h"
#include "cbufr.h"


#define QUOTE_PP(P) #P
#define QUOTE_PPP(P) QUOTE_PP(P)


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

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

Value NilFunction::Execute(int, Value*)
{
    return Value();
}

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

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

int NilAppendFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;
    if (arg[0].GetType() == tnil || arg[1].GetType() == tnil)
        return true;
    return false;
}

Value NilAppendFunction::Execute(int, Value* arg)
{
    return arg[0].GetType() == tnil ? arg[1] : arg[0];
}

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

class NilCompareFunction : public Function
{
    int equal;

public:
    NilCompareFunction(const char* n, int e) :
        Function(n) { equal = e; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int NilCompareFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;
    if (arg[0].GetType() == tnil || arg[1].GetType() == tnil)
        return true;
    return false;
}

Value NilCompareFunction::Execute(int, Value* arg)
{
    int cmp;
    if (arg[0].GetType() == tnil && arg[1].GetType() == tnil)
        cmp = 1;
    else
        cmp = 0;
    return Value(cmp == equal);
}


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

Value NilCountFunction::Execute(int, Value* /*arg*/)
{
    int zero = 0;
    return Value(zero);
}


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

class TypeFunction : public Function
{
public:
    TypeFunction(const char* n) :
        Function(n, 1, tany)
    {
        info = "Returns the type of an expression";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value TypeFunction::Execute(int, Value* arg)
{
    const char* p;

    arg[0].GetType(&p);

    Value v = Value(p);

    return v;
}

//=============================================================================
class CmpFallback : public Function
{
    boolean equal;

public:
    CmpFallback(const char* n, boolean eq) :
        Function(n) { equal = eq; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

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

Value CmpFallback::Execute(int, Value*)
{
    int d = equal ? 0 : 1;
    return Value(d);
}
//=============================================================================
class SubFallback : public Function
{
public:
    SubFallback(const char* n) :
        Function(n) {}
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int SubFallback::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;
    if (arg[1].GetType() != tnumber)
        return false;
    int n;
    arg[1].GetValue(n);
    return n == 1;
}

Value SubFallback::Execute(int, Value* arg)
{
    return arg[0];
}
//=============================================================================
class CountFallback : public Function
{
public:
    CountFallback(const char* n) :
        Function(n, 1, tany) {}
    virtual Value Execute(int arity, Value* arg);
};

Value CountFallback::Execute(int, Value*)
{
    return Value(1);
}
//=============================================================================
class RandomFunction : public Function
{
public:
    RandomFunction(const char* n) :
        Function(n, 0) { info = "Generat random"; }
    virtual Value Execute(int arity, Value* arg);
};

Value RandomFunction::Execute(int, Value*)
{
    static int first = 1;

    if (first) {
        srand48(getpid() * time(0));
        first = 0;
    }

    return Value(drand48());
}

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

class PrintFunction : public Function
{
public:
    PrintFunction(const char* n) :
        Function(n) { info = "Prints all its arguments"; }
    virtual Value Execute(int arity, Value* arg);
};

class FailFunction : public PrintFunction
{
public:
    FailFunction(const char* n) :
        PrintFunction(n)
    {
        info = "Prints all its arguments and stop in error";
    }
    virtual Value Execute(int arity, Value* arg);
};

class StopFunction : public PrintFunction
{
public:
    StopFunction(const char* n) :
        PrintFunction(n)
    {
        info = "Prints all its arguments and stop the macro";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value PrintFunction::Execute(int arity, Value* arg)
{
    if (Context::Trace() >= 2)
        return Value(0.0);

    for (int i = 0; i < arity; i++) {
        arg[i].Sync();  // This will force sync
        arg[i].Print();
    }
    std::cout << '\n'
              << std::flush;
    return Value(0.0);
}

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

Value ImportFunction::Execute(int, Value* arg)
{
    const char* p;
    Context* c = Context::Instruction->Owner();
    arg[0].GetValue(p);
    return c->ImportVariable(p);
}

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

Value ExportFunction::Execute(int, Value* arg)
{
    const char* p;
    Context* c = Context::Instruction->Owner();
    arg[0].GetValue(p);
    return c->ExportVariable(p);
}

class DescribeFunction : public Function
{
    bool actionSpecified;

public:
    DescribeFunction(const char* n) :
        Function(n)
    {
        info = "Describes available function";
    }
    virtual int ValidArguments(int arity, Value* arg);
    virtual Value Execute(int arity, Value* arg);
};

int DescribeFunction::ValidArguments(int arity, Value* arg)
{
    actionSpecified = false;

    if (arity == 1 && arg[0].GetType() == tstring)
        return true;

    if (arity == 2 && arg[0].GetType() == tstring && arg[1].GetType() == tstring) {
        actionSpecified = true;
        return true;
    }

    return false;
}


Value DescribeFunction::Execute(int, Value* arg)
{
    const char* p;
    Context* c      = Context::Instruction->Owner();
    int num_matches = 0;

    arg[0].GetValue(p);

    if (actionSpecified)  // action can currently only be 'as_value', meaning return the description(s) as a value
    {
        const char* second_arg;
        arg[1].GetValue(second_arg);
        if (strcmp(second_arg, "as_value"))
            return Error("The second argument, if supplied, must be 'as_value'.");
    }

    CList* outList;
    if (actionSpecified)
        outList = new CList(1);  // 1 to start with because we don't know how big it wikll grow to


    List* l = (List*)c->Dictionaries()->Head();
    while (l) {
        auto* n = (Function*)l->Head();
        while (n) {
            if (p == n->Name()) {
                num_matches++;
                if (actionSpecified)
                    if (num_matches > 1)
                        outList->Add(n->ToString());
                    else
                        (*outList)[0] = n->ToString();
                else
                    n->Print();
            }
            n = (Function*)n->Next();
        }
        l = (List*)l->Next();
    }

    while (c) {
        Node* n = c->FirstFunction();
        while (n) {
            if (p == n->Name()) {
                num_matches++;
                if (actionSpecified)
                    if (num_matches > 1)
                        outList->Add(n->ToString());
                    else
                        (*outList)[0] = n->ToString();
                else
                    n->Print();
            }
            n = n->Next();
        }
        c = c->Owner();
    }

    if (actionSpecified)
        return outList;
    else
        return Value(0.0);
}

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

class StoreFunction : public Function
{
public:
    StoreFunction(const char* n) :
        Function(n, 2, tstring, tany)
    {
        info = "Saves the data in a cache under the given name. args: (string, any)";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value StoreFunction::Execute(int, Value* arg)
{
    request* r;
    const char* name;
    arg[0].GetValue(name);
    arg[1].GetValue(r);

    ASync::Store(name, r);


    // for some file types, we need to manually set some flags to indicate
    // that the underlying files should not be deleted

    if (arg[1].GetType() == tgrib) {
        CGrib* c;
        arg[1].GetValue(c);
        c->IsIcon(true);  // so that InPool won't touch the data
        c->SetFileTempFlag(false);
    }
    else if (arg[1].GetType() == tbufr) {
        CBufr* c;
        arg[1].GetValue(c);
        c->IsIcon(true);  // so that InPool won't touch the data
    }

    set_value(r, "TEMPORARY", "%d", 0);  // store/fetch should have permanent files

    return Value();
}

class FetchFunction : public Function
{
public:
    FetchFunction(const char* n) :
        Function(n, 1, tstring)
    {
        info = "Returns the item stored in the named cache (or nil). args: (string)";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value FetchFunction::Execute(int, Value* arg)
{
    const char* name;
    arg[0].GetValue(name);
    request* r = ASync::Fetch(name);
    set_value(r, "TEMPORARY", "%d", 0);  // store/fetch should have permanent files
    Value v;
    v.SetContent(r);


    // for some file types, we need to manually set some flags to indicate
    // that the underlying files should not be deleted

    Content* content = v.GetContent();

    if (content->GetType(nullptr) == tgrib) {
        auto* c = (CGrib*)content;
        c->IsIcon(true);  // so that InPool won't touch the data
        c->SetFileTempFlag(false);
    }
    else if (content->GetType(nullptr) == tbufr) {
        auto* c = (CBufr*)content;
        c->IsIcon(true);  // so that InPool won't touch the data
    }

    return v;
}

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

class NameFunction : public Function
{
public:
    NameFunction(const char* n) :
        Function(n, 0)
    {
        info = "Returns the path/name of the macro being executed. args: none";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value NameFunction::Execute(int, Value*)
{
    return Value(Script::MacroPath());
}

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

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

Value InlineFunction::Execute(int, Value* arg)
{
    const char *t, *p;
    FILE* f;
    long len;
    void* buffer = 0;
    char* q      = 0;

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

    switch (Value::NameType(t)) {
        case tstring:
            f = fopen(p, "r");
            if (!f) {
                marslog(LOG_EROR | LOG_PERR, "Cannot open %s", p);
                return Error("Inline: internal error");
            }

            // Should check more here,,,
            fseek(f, 0, SEEK_END);
            len    = ftell(f);
            buffer = MALLOC(int(len + 1));
            q      = (char*)buffer;
            rewind(f);
            fread(buffer, 1, (unsigned int)len, f);
            fclose(f);
            q[len] = 0;
            break;

        case tnone:
        case tany:
            return Error("Invalid type name '%s'", t);
            /* break; */

        default:
            return Error("Inline data not supported for type %s", t);
            /* break; */
    }

    Value v = q;
    FREE(buffer);
    return v;
}

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

class ArgsFunction : public Function
{
public:
    ArgsFunction(const char* n) :
        Function(n, 0)
    {
        info = "Returns the list of the calling arguments of the current function";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value ArgsFunction::Execute(int, Value*)
{
    Context* c = Context::Instruction->Owner();
    int n      = c->Argc();
    Value* v   = c->Argv();

    auto* l = new CList(n);

    for (int i = 0; i < n; i++)
        (*l)[i] = v[i];

    return Value(l);
}


class DictionaryFunction : public Function
{
public:
    DictionaryFunction(const char* n) :
        Function(n)
    {
        info = "Returns the list of all the documented functions";
    }
    virtual Value Execute(int arity, Value* arg);
};

static int cmp(const void* a, const void* b)
{
    char** x = (char**)a;
    char** y = (char**)b;
    return strcmp(*x, *y);
}

Value DictionaryFunction::Execute(int arity, Value*)
{
    using charp = const char*;
    Context* c  = Context::Instruction->Owner();
    int count   = 0;
    int all     = arity > 0;

    List* l = (List*)c->Dictionaries()->Head();
    while (l) {
        auto* n = (Function*)l->Head();
        while (n) {
            if (all || n->Info())
                count++;
            n = (Function*)n->Next();
        }
        l = (List*)l->Next();
    }


    while (c) {
        Function* n = c->FirstFunction();
        while (n) {
            if (all || n->Info())
                count++;
            n = (Function*)n->Next();
        }
        c = c->Owner();
    }

    auto* names = new charp[count];

    count = 0;
    l     = (List*)c->Dictionaries()->Head();
    while (l) {
        auto* n = (Function*)l->Head();
        while (n) {
            if (all || n->Info())
                names[count++] = n->Name();
            n = (Function*)n->Next();
        }
        l = (List*)l->Next();
    }

    c = Context::Instruction->Owner();
    while (c) {
        Function* n = c->FirstFunction();
        while (n) {
            if (all || n->Info())
                names[count++] = n->Name();
            n = (Function*)n->Next();
        }
        c = c->Owner();
    }

    qsort(names, count, sizeof(charp), cmp);
    int m = 0;
    int i;
    for (i = 1; i < count; i++)
        if (names[m] != names[i])
            names[++m] = names[i];
    count = m + 1;

    auto* v = new CList(count);
    for (i = 0; i < count; i++)
        (*v)[i] = Value(names[i]);
    delete[] names;


    // add the names of the mvl functions
    char line[1024];
    std::string mlvFuncsFile(metview::mvlMacroDirFile("mvl_funcs.txt"));
    FILE* fp = fopen(mlvFuncsFile.c_str(), "rt");

    while (fgets(line, sizeof(line), fp)) {
        line[strlen(line) - 1] = '\0';
        v->Add(Value(line));
    }
    fclose(fp);

    return Value(v);
}

Value FailFunction::Execute(int arity, Value* arg)
{
    Context::Stop();
    PrintFunction::Execute(arity, arg);
    return Error("Macro failed");
}

Value StopFunction::Execute(int arity, Value* arg)
{
    Context::Stop();
    PrintFunction::Execute(arity, arg);
    return Value(0.0);
}


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

class MagMLFunction : public Function
{
public:
    MagMLFunction(const char* n) :
        Function(n, 1, tstring)
    { /*"Executes MagML code";*/
    }

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

Value MagMLFunction::Execute(int, Value* arg)
{
    const char* magml_file;
    arg[0].GetValue(magml_file);
    MvRequest out;
    Value v;

    // create a new request to send to the MagML module

    MvRequest r;
    r.setVerb("MAGML");
    r("PATH")   = magml_file;
    r("_MODE")  = "MACRO";
    r("PATH")   = magml_file;
    r("_MACRO") = Script::MacroPath();
    r("_PATH")  = Script::MacroMainPath();


    // call the MagML service directly with this request

    v = Value("MagML", r);

    return v;
}

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

class ExamineFunction : public Function
{
public:
    ExamineFunction(const char* n) :
        Function(n)
    {
        info = "Actives an interactive data examiner window on the given data";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int ExamineFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 1)
        return false;

    vtype vt = arg[0].GetType();
    if (vt == tgrib || vt == tbufr || vt == tnetcdf || vt == todb || vt == tgeopts)
        return true;
    else
        return false;
}

Value ExamineFunction::Execute(int, Value* arg)
{
    MvRequest out;
    Value v;
    const char* verb    = nullptr;
    const char* service = nullptr;

#ifdef ENABLE_UI

    // check the data type and lauch the correct examiner
    if (arg[0].GetType() == tgrib) {
        verb    = "GRIB";
        service = "UiAppManager";
    }
    else if (arg[0].GetType() == tbufr) {
        verb    = "BUFR";
        service = "UiAppManager";
    }
    else if (arg[0].GetType() == tnetcdf) {
        verb    = "NETCDF";
        service = "UiAppManager";
    }
    else if (arg[0].GetType() == todb) {
        verb    = "ODB_DB";
        service = "UiAppManager";
    }

    else if (arg[0].GetType() == tgeopts) {
        verb    = "GEOPOINTS";
        service = "UiAppManager";
    }
    else {
        // should never actually get here if the guards in ValidArguments are correct
        marslog(LOG_WARN, "examine: cannot examine this data type");
        return Value();
    }

    // get a request from the Value so that we can extract its path
    request* datareq;
    arg[0].GetValue(datareq);
    request* finalreq = datareq;

    // GRIB has the added complication that it may not have a file on disk,
    // so in this case we need to write it first
    if (arg[0].GetType() == tgrib) {
        const char* fff = get_value(datareq, "FIELDSET_FROM_FILTER", 0);
        if (fff && !strcmp(fff, "1")) {
            fieldset* fs = request_to_fieldset(datareq);
            fieldset* z  = copy_fieldset(fs, fs->count, true);
            save_fieldset(z);
            finalreq = fieldset_to_request(z);
        }
    }


    // construct the request to launch the examiner service
    MvRequest r;
    r.setVerb(verb);
    const char* path = get_value(finalreq, "PATH", 0);
    r("PATH")        = path;
    r("_ACTION")     = "examine";
    v                = Value(service, r);

#else
    // warning, but do not fail, as this could be part of an interactive Jupyter session
    std::string message("examine: this Metview installation has been built without user interface");
    marslog(LOG_WARN, message.c_str());
    if (Context::IsPython()) {
        std::string message2 = message + "; consider using methods ls() and describe() if using a Fieldset";
        v                    = Value(message2.c_str());
    }
    else
        v = Value(message.c_str());
#endif
    return v;
}

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

class EditFunction : public Function
{
public:
    EditFunction(const char* n) :
        Function(n)
    {
        info = "Activates an interactive editor window on the given file";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

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

Value EditFunction::Execute(int, Value* arg)
{
    MvRequest out;
    Value v;
    const char* service = "";
    std::string spath;
    MvRequest r;

#ifdef ENABLE_UI

    // if the argument is a string, assume it's a path to the file to be edited
    if (arg[0].GetType() == tstring) {
        const char* path = nullptr;
        arg[0].GetValue(path);
        spath = std::string(path);
    }
    // otherwise, if it's data, extract its path from its request
    else {
        request* datareq;
        arg[0].GetValue(datareq);
        const char* path = get_value(datareq, "PATH", 0);
        spath            = std::string(path);
    }

    std::string kind = ScanFileType(spath.c_str());


    if (kind == "GEOPOINTS" || kind == "GEOPOINTSET" || kind == "TABLE" || kind == "LLMATRIX" || kind == "NOTE" || kind == "MACRO") {
        service = "macroedit";
        r.setVerb("MACROEDIT");
        r("LANGUAGE") = kind.c_str();
        r("_CLASS")   = kind.c_str();
    }
    else if (kind == "SCM_INPUT_DATA") {
        service = "ScmDataEditor";
        r.setVerb("SCM_INPUT_DATA");
    }
    else {
        return Error("Cannot call the interactive editor for type %s", kind.c_str());
    }


    // construct the request to launch the editor service
    r("PATH") = spath.c_str();
    v         = Value(service, r);

#else
    // warning, but do not fail, as this could be part of an interactive Jupyter session
    marslog(LOG_WARN, "edit: this Metview installation has been built without user interface");
#endif
    return v;
}

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

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

Value MemoryFunction::Execute(int, Value*)
{
    memory_info();
    return Value();
}

class PurgeMemFunction : public Function
{
public:
    PurgeMemFunction(const char* n) :
        Function(n, 0)
    {
        info = "Releases unused memory";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value PurgeMemFunction::Execute(int, Value*)
{
    // The mars-client purge_mem function releases the first unused block it finds
    // and then returns 1. If there are no unused blocks, it returns 0. Therefore
    // we must call it in a loop to free all unused memory.
    while (purge_mem()) {
    }
    return Value();
}

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

class CallFunction : public Function
{
public:
    CallFunction(const char* n) :
        Function(n)
    {
        info = "Calls the named function with the supplied arguments";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value*) { return arity > 0; }
};

Value CallFunction::Execute(int arity, Value* argv)
{
    const char* name;
    argv[0].GetValue(name);

    for (int i = 1; i < arity; i++)
        Owner()->Push(argv[i]);

    Owner()->CallFunction(name, arity - 1);
    return Owner()->Pop();
}

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

class CallArgsFunction : public Function
{
public:
    CallArgsFunction(const char* n) :
        Function(n, 2, tstring, tlist)
    {
        info = "Calls the named function with the given list of arguments";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value CallArgsFunction::Execute(int /*arity*/, Value* argv)
{
    const char* name;
    argv[0].GetValue(name);

    CList* l;
    argv[1].GetValue(l);

    for (int i = 0; i < l->Count(); i++)
        Owner()->Push((*l)[i]);

    Owner()->CallFunction(name, l->Count());
    return Owner()->Pop();
}


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


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

class HelloWorldFunction : public Function
{
public:
    HelloWorldFunction(const char* n) :
        Function(n, 0)
    {
        info = "Says Hello";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int /*arity*/, Value*) { return true; }
};

Value HelloWorldFunction::Execute(int arity, Value* argv)
{
    std::cout << "HelloWorldFunction" << std::endl;
    char* pr = strcache("print");

    for (int i = 0; i < arity; i++) {
        Owner()->Push(argv[i]);
        Owner()->CallFunction(pr, 1);
    }

    return Value();
}

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


/*******************************************************************************
 *
 * Class        : MetviewVersionFunction : Function
 *
 * Description  : Macro function that returns version information about Metview
 *
 ******************************************************************************/

extern "C" {

#ifdef FORTRAN_NO_UNDERSCORE
#define EMOSNUM emosnum
#else
#define EMOSNUM emosnum_
#endif

fortint EMOSNUM(fortint* KONOFF);
}


class MetviewVersionFunction : public Function
{
public:
    MetviewVersionFunction(const char* n) :
        Function(n, 0)
    {
        info = "Returns version information about Metview";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value MetviewVersionFunction::Execute(int /*arity*/, Value* /*arg*/)
{
    request* r;
    long grib_api_version;
    long mars_version;
    MvVersionInfo mvInfo;  // the constructor populates the class with information

    // the return value will be a request whose elements we will populate below
    r = empty_request(nullptr);


    if (mvInfo.infoFound()) {
        set_value(r, "metview_version", "%d", mvInfo.version());
        set_value(r, "metview_major", "%d", mvInfo.majorVersion());
        set_value(r, "metview_minor", "%d", mvInfo.minorVersion());
        set_value(r, "metview_revision", "%d", mvInfo.revision());
    }

    else {
        char msg[1024];
        strncpy(msg, mvInfo.errorMessage().c_str(), 1023);
        marslog(LOG_EROR, msg);
    }

    const char* mdir = getenv("METVIEW_DIR");
    if (mdir) {
        set_value(r, "metview_dir", "%s", mdir);
    }


    // extra code to take into account the possibility of
    // using either GRIB_API or ecCodes for GRIB handling
    std::string grib_package = QUOTE_PPP(GRIB_HANDLING_PACKAGE);
    for (char& i : grib_package)
        i = tolower(i);
    grib_package += "_version";

    grib_api_version = grib_get_api_version();
    set_value(r, grib_package.c_str(), "%d", grib_api_version);

    mars_version = marsversion();
    set_value(r, "mars_version", "%d", mars_version);


    const char* cinterp = getenv("MARS_PPROC_BACKEND");
    if (cinterp) {
        std::string interp(cinterp);
        for (char& i : interp)
            i = tolower(i);
        set_value(r, "default_interp", "%s", interp.c_str());
    }


#if defined(HAVE_PPROC_EMOS)
    fortint emos_version, konoff = 1;
    emos_version = emosnum_(&konoff);
    set_value(r, "emos_version", "%d", emos_version);
#endif

#if defined(HAVE_PPROC_MIR)
    unsigned int this_mir_version = mir_version_int();
    set_value(r, "mir_version", "%d", this_mir_version);
#endif


#if defined(METVIEW_ODB)
    const char* odc_ver;
    odc_version(&odc_ver);
    set_value(r, "odc_version", "%s", odc_ver);
#endif

    return Value(r);
}


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

class WaitModeFunction : public Function
{
public:
    WaitModeFunction(const char* n) :
        Function(n, 1, tnumber)
    {
        info = "Causes Macro to always wait(1)/not wait(0) for functions to complete before continuing.";
    }
    virtual Value Execute(int, Value*);
};

Value WaitModeFunction::Execute(int, Value* arg)
{
    int n = Context::WaitMode();
    int m;

    arg[0].GetValue(m);
    Context::WaitMode(m);

    return Value(n);
}


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

class FeatureAvailabilityFunction : public Function
{
public:
    FeatureAvailabilityFunction(const char* n) :
        Function(n, 1, tstring)
    {
        info = "Returns 1 if the given feature is available, otherwise 0. Feature names: 'odb'";
    }
    virtual Value Execute(int, Value*);
};

Value FeatureAvailabilityFunction::Execute(int, Value* arg)
{
    const char* s;
    arg[0].GetValue(s);
    std::string featureName(s);

    if (featureName == "odb") {
#ifdef METVIEW_ODB
        return Value(1);
#else
        return Value(0.0);  // 0.0 to avoid ambiguity with nullptr
#endif
    }

    else if (featureName == "plotting") {
#ifdef ENABLE_PLOTTING
        return Value(1);
#else
        return Value(0.0);  // 0.0 to avoid ambiguity with nullptr
#endif
    }
    else if (featureName == "stations_db") {
#ifdef ENABLE_STATIONS_DB
        return Value(1);
#else
        return Value(0.0);  // 0.0 to avoid ambiguity with nullptr
#endif
    }
    else
        Error("is_feature_available: feature name %s not recognised", s);

    return Value();  // should not get to here in fact
}


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

static void install(Context* c)
{
    c->AddFunction(new PrintFunction("print"));
    c->AddFunction(new FailFunction("fail"));
    c->AddFunction(new StopFunction("stop"));
    c->AddFunction(new ImportFunction("import"));
    c->AddFunction(new ExportFunction("export"));
    c->AddFunction(new TypeFunction("type"));
    c->AddFunction(new DescribeFunction("describe"));
    c->AddFunction(new DictionaryFunction("dictionary"));
    c->AddFunction(new ArgsFunction("arguments"));
    c->AddFunction(new NilFunction("nil"));
    c->AddFunction(new NilAppendFunction("&"));
    c->AddFunction(new NilAppendFunction("merge"));
    c->AddFunction(new NilCompareFunction("=", 1));
    c->AddFunction(new NilCompareFunction("<>", 0));
    c->AddFunction(new NilCountFunction("count"));
    c->AddFunction(new NameFunction("name"));
    c->AddFunction(new StoreFunction("store"));
    c->AddFunction(new FetchFunction("fetch"));
    c->AddFunction(new InlineFunction("inline"));
    c->AddFunction(new RandomFunction("random"));
    c->AddFunction(new WaitModeFunction("waitmode"));

    c->AddFunction(new MagMLFunction("magml"));

    c->AddFunction(new ExamineFunction("examine"));
    c->AddFunction(new EditFunction("edit"));

    c->AddFunction(new CallFunction("call"));
    c->AddFunction(new CallArgsFunction("callargs"));

    c->AddFunction(new MemoryFunction("memory_info"));
    c->AddFunction(new PurgeMemFunction("purge_mem"));
    c->AddFunction(new MetviewVersionFunction("version_info"));
    c->AddFunction(new FeatureAvailabilityFunction("is_feature_available"));


    c->AddFunction(new HelloWorldFunction("hello"));


    c->AddFallback(new CountFallback("count"));
    c->AddFallback(new SubFallback("[]"));
    c->AddFallback(new CmpFallback("=", true));
    c->AddFallback(new CmpFallback("<>", false));
}

static Linkage Link(install);
