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


#include <Metview.h>
#include <MvPath.hpp>
#include <MvScanFileType.h>
#include "MvMiscellaneous.h"

MvRequest::MvRequest(const char* verb)
{
    _overwrite = 1;
    _free_req_on_destruct = 0;
    CurrentRequest = FirstRequest = empty_request(verb);
}

MvRequest::MvRequest(request* r, boolean clone, boolean free_req)
{
    _overwrite = 1;
    _free_req_on_destruct = free_req;
    CurrentRequest = FirstRequest = clone ? clone_all_requests(r) : r;
}

MvRequest::MvRequest(const MvRequest& r)

{
    _overwrite = 1;
    _free_req_on_destruct = 0;
    CurrentRequest = FirstRequest = clone_all_requests(r.FirstRequest);

    // Make sure currents match
    request* s = r.FirstRequest;
    while (s && s != r.CurrentRequest) {
        s = s->next;
        CurrentRequest = CurrentRequest->next;
    }
}

MvRequest::~MvRequest()
{
    if (_free_req_on_destruct)
        free_all_requests(FirstRequest);
}

void MvRequest::clean()
{
    free_all_requests(FirstRequest);
    FirstRequest = CurrentRequest = 0;
    CurrentValue = 0;
}

MvRequest& MvRequest::operator=(const MvRequest& r)
{
    free_all_requests(FirstRequest);
    CurrentRequest = FirstRequest = clone_all_requests(r.FirstRequest);

    // Make sure currents match
    request* s = r.FirstRequest;
    while (s && s != r.CurrentRequest) {
        s = s->next;
        CurrentRequest = CurrentRequest->next;
    }
    return *this;
}

// MvRequest& MvRequest::operator=(const request *r)
//{
//	free_all_requests(FirstRequest);
//	CurrentRequest = FirstRequest = clone_all_requests(r);
//	return *this;
// }

MvRequest MvRequest::operator+(const MvRequest& r) const
{
    if (CurrentRequest == nullptr)
        return r;

    request* a = clone_all_requests(CurrentRequest);
    request* b = clone_all_requests(r.CurrentRequest);

    request *q = nullptr, *p = a;

    while (p) {
        q = p;
        p = p->next;
    }

    if (q) {
        q->next = b;
    }
    return MvRequest(a, false);
}

void MvRequest::operator+=(const MvRequest& r)
{
    request* a = nullptr;
    request* b = nullptr;

    if (CurrentRequest) {
        a = CurrentRequest;
    }

    if (r.CurrentRequest) {
        b = clone_all_requests(r.CurrentRequest);
    }

    request *q = nullptr, *p = a;

    if (a) {
        while (p)  // find the end of request a
        {
            q = p;
            p = p->next;
        }

        q->next = b;
    }
    else {
        CurrentRequest = FirstRequest = b;
    }
}


void MvRequest::copyFromCurrent(const MvRequest& r)
{
    _overwrite = 1;
    CurrentRequest = FirstRequest = clone_all_requests(r.CurrentRequest);
}

void MvRequest::copyFromCurrentTo(const MvRequest& r, const char* verb)
{
    _overwrite = 1;
    CurrentRequest = FirstRequest = copyFromCurrentTo_Recursive(r.CurrentRequest, verb);
}

request* MvRequest::copyFromCurrentTo_Recursive(const request* r, const char* verb)
{
    if (r) {
        request* q = clone_one_request(r);
        if (strcmp(request_verb(r), verb))  // if not the verb we're looking for
        {
            q->next = copyFromCurrentTo_Recursive(r->next, verb);
        }
        return q;
    }
    return 0;
}


void MvRequest::setVerb(const char* v)
{
    if (FirstRequest) {
        strfree(CurrentRequest->name);
        CurrentRequest->name = strcache(v);
    }
    else
        FirstRequest = CurrentRequest = empty_request(v);
}

int MvRequest::countValues(const char* p) const
{
    return count_values(CurrentRequest, p);
}

void MvRequest::print(bool force) const
{
    if (force || mvDebugPrint())  //-- to control prints that go to log files
    {
        print_all_requests(CurrentRequest);
        fflush(stdout);  //-- may be mixed with C++ io
    }
}

const char* MvRequest::getVerb() const
{
    return CurrentRequest ? CurrentRequest->name : nullptr;
}

void MvRequest::unsetParam(const char* pname)
{
    unset_value(CurrentRequest, pname);
}

void MvRequest::read(const char* fileNamePath, bool expand, bool hidden)
{
    // boolean is defined in mars.h as int
    boolean debugOriVal = mars.debug;
    if (hidden && !mars.debug) {
        debugOriVal = mars.debug;
        mars.debug = 1;
    }

    // Read request using MARS library
    // The problem here is that this function does not expand any
    // subrequests. This expansion will be done manually, if second
    // parameter is true.
    free_all_requests(FirstRequest);
    CurrentRequest = FirstRequest = read_request_file(fileNamePath);

    if (!expand) {
        if (mars.debug != debugOriVal) {
            mars.debug = debugOriVal;
        }
        return;
    }

    // Get object request. This will help to find out if a parameter
    // is a subrequest, which in this case will need to be expanded.
    request* obj = this->findRequestObject();
    if (obj == nullptr) {
        if (mars.debug != debugOriVal) {
            mars.debug = debugOriVal;
        }
        return;
    }

    // Expand subrequests
    std::string path = mdirname(fileNamePath);
    this->importSubObjects(path, obj);

    if (mars.debug != debugOriVal) {
        mars.debug = debugOriVal;
    }
}

void MvRequest::save(const char* file, bool hidden) const
{
    FILE* fp = fopen(file, "w");
    if (fp) {
        if (hidden && !mars.debug) {
            // boolean is defined in mars.h as int
            boolean oriDebugVal = mars.debug;
            mars.debug = 1;
            save(fp);
            mars.debug = oriDebugVal;
        }
        else {
            save(fp);
        }
        fclose(fp);
    }
}

void MvRequest::save(FILE* file) const
{
    save_all_requests(file, FirstRequest);
}


void MvAccess::operator=(const MvAccess& a)
{
    if (this == &a)
        marslog(LOG_WARN,
                "MvAccess::operator= cannot work on the same"
                " object (%s/%d)",
                LastGet, LastIndex);

    set_value(
        Request->CurrentRequest, LastGet, "%s",
        get_value(a.Request->CurrentRequest, a.LastGet, a.LastIndex));
    LastGet = nullptr;
    LastIndex = 0;
}

MvAccess::operator const char*() const
{
    return get_value(Request->CurrentRequest, LastGet, LastIndex);
}

MvConstAccess::operator const char*() const
{
    return get_value(Request->CurrentRequest, LastGet, LastIndex);
}

MvAccess::operator double() const
{
    const char* p = get_value(Request->CurrentRequest, LastGet, LastIndex);
    return p ? atof(p) : 0;
}

MvConstAccess::operator double() const
{
    const char* p = get_value(Request->CurrentRequest, LastGet, LastIndex);
    return p ? atof(p) : 0;
}

MvAccess::operator int() const
{
    const char* p = get_value(Request->CurrentRequest, LastGet, LastIndex);
    return p ? atoi(p) : 0;
}

MvConstAccess::operator int() const
{
    const char* p = get_value(Request->CurrentRequest, LastGet, LastIndex);
    return p ? atoi(p) : 0;
}

MvAccess::operator MvDate() const
{
    long save = expand_flags(0);  // So it is working OK
    const char* p = get_value(Request->CurrentRequest, LastGet, LastIndex);
    expand_flags(save);
    return MvDate(p);
}

MvConstAccess::operator MvDate() const
{
    long save = expand_flags(0);  // So it is working OK
    const char* p = get_value(Request->CurrentRequest, LastGet, LastIndex);
    expand_flags(save);
    return MvDate(p);
}

MvAccess::operator long() const
{
    const char* p = get_value(Request->CurrentRequest, LastGet, LastIndex);

    /* backward compatiblity */
    long julian, second;
    boolean dum;
    if (p && parsedate(p, &julian, &second, &dum))
        return mars_julian_to_date(julian, false);

    return p ? atol(p) : 0;
}
MvConstAccess::operator long() const
{
    const char* p = get_value(Request->CurrentRequest, LastGet, LastIndex);

    /* backward compatiblity */
    long julian, second;
    boolean dum;
    if (p && parsedate(p, &julian, &second, &dum))
        return mars_julian_to_date(julian, false);

    return p ? atol(p) : 0;
}


MvAccess::operator request*() const
{
    return get_subrequest(Request->CurrentRequest, LastGet, LastIndex);
}

void MvAccess::operator=(const char* val)
{
    if (LastGet)
        set_value(Request->CurrentRequest, LastGet, "%s", val);
    LastGet = nullptr;
    LastIndex = 0;
}
void MvAccess::operator=(double val)
{
    if (LastGet)
        set_value(Request->CurrentRequest, LastGet, "%.12g", val);
    LastGet = nullptr;
    LastIndex = 0;
}
void MvAccess::operator=(int val)
{
    if (LastGet)
        set_value(Request->CurrentRequest, LastGet, "%d", val);
    LastGet = nullptr;
    LastIndex = 0;
}
void MvAccess::operator=(const request* val)
{
    if (LastGet)
        set_subrequest(Request->CurrentRequest, LastGet, val);
    LastGet = nullptr;
    LastIndex = 0;
}
void MvAccess::operator=(long val)
{
    if (LastGet)
        set_value(Request->CurrentRequest, LastGet, "%ld", val);
    LastGet = nullptr;
    LastIndex = 0;
}

void MvAccess::operator=(const MvDate& val)
{
    char buf[80];
    val.Format("yyyy-mm-dd HH:MM:SS", buf);
    if (LastGet)
        set_value(Request->CurrentRequest,
                  LastGet, "%s", buf);
    LastGet = nullptr;
    LastIndex = 0;
}

void MvAccess::operator+=(const request* val)
{
    add_subrequest(Request->CurrentRequest, LastGet, val);
}

void MvAccess::operator+=(const MvDate& val)
{
    char buf[80];
    val.Format("yyyy-mm-dd HH:MM:SS", buf);
    if (LastGet)
        add_value(Request->CurrentRequest,
                  LastGet, "%s", buf);
    LastGet = nullptr;
    LastIndex = 0;
}

void MvAccess::operator+=(const char* val)
{
    if (LastGet)
        add_value(Request->CurrentRequest, LastGet, "%s", val);
    LastGet = nullptr;
    LastIndex = 0;
}

void MvAccess::operator+=(const MvAccess& a)
{
    if (this == &a)
        marslog(LOG_WARN,
                "MvAccess::operator+= cannot work on the same"
                " object (%s/%d)",
                LastGet, LastIndex);

    add_value(
        Request->CurrentRequest, LastGet, "%s",
        get_value(a.Request->CurrentRequest, a.LastGet, a.LastIndex));
    LastGet = nullptr;
    LastIndex = 0;
}

void MvRequest::getValue(int& v, const char* p, int i) const
{
    const char* t = get_value(CurrentRequest, p, i);
    v = (t) ? atoi(get_value(CurrentRequest, p, i)) : 0;
}

void MvRequest::getValue(double& v, const char* p, int i) const
{
    const char* t = get_value(CurrentRequest, p, i);
    v = (t) ? atof(get_value(CurrentRequest, p, i)) : 0;
}

void MvRequest::getValue(const char*& v, const char* p, int i) const
{
    v = get_value(CurrentRequest, p, i);
}

void MvRequest::getValue(Cached& v, const char* p, int i) const
{
    v = get_value(CurrentRequest, p, i);
}

void MvRequest::getValue(void*& v, const char* p, int i) const
{
    sscanf(get_value(CurrentRequest, p, i), "%p", &v);
}

void MvRequest::getValue(MvRequest& v, const char* name, int i) const
{
    v = get_subrequest(CurrentRequest, name, i);
}

MvRequest MvRequest::getSubrequest(const char* name, int i) const
{
    return MvRequest(get_subrequest(CurrentRequest, name, i), false);
}


int MvRequest::iterInit(const char* param)
{
    parameter* p = find_parameter(CurrentRequest, param);
    if (p == 0) {
        CurrentCount = 0;
        CurrentValue = 0;
    }
    else {
        CurrentCount = count_values(CurrentRequest, param);
        CurrentValue = p->values;
    }

    return CurrentCount;  //-- 'iterInit' returns parameter count
}

bool MvRequest::iterGetNextValue(const char*& val)
{
    bool myStat = false;
    if (CurrentValue && CurrentValue->name) {
        val = CurrentValue->name;
        myStat = true;
    }
    else
        val = 0;

    if (CurrentValue)
        CurrentValue = CurrentValue->next;

    return myStat;
}

bool MvRequest::iterGetNextValue(double& val)
{
    bool myStat = false;
    if (CurrentValue && CurrentValue->name) {
        val = atof(CurrentValue->name);
        myStat = true;
    }
    else
        val = 0;

    if (CurrentValue)
        CurrentValue = CurrentValue->next;

    return myStat;
}

void MvRequest::setValue(const char* p, double v)
{
    set_value(CurrentRequest, p, "%.12g", v);
}

void MvRequest::setValue(const char* p, const void* v)
{
    set_value(CurrentRequest, p, "%p", v);
}

void MvRequest::setValue(const char* p, int v)
{
    set_value(CurrentRequest, p, "%d", v);
}

void MvRequest::setValue(const char* p, const char* v)
{
    set_value(CurrentRequest, p, "%s", v);
}

void MvRequest::setValue(const char* p, long v)
{
    set_value(CurrentRequest, p, "%ld", v);
}

void MvRequest::setValue(const char* p, const request* r)
{
    set_subrequest(CurrentRequest, p, r);
}

void MvRequest::setValue(const char* p, const MvRequest& v)
{
    set_subrequest(CurrentRequest, p, v);
}

void MvRequest::addValue(const char* p, double v)
{
    add_value(CurrentRequest, p, "%.12g", v);
}

void MvRequest::addValue(const char* p, const void* v)
{
    add_value(CurrentRequest, p, "%p", v);
}

void MvRequest::addValue(const char* p, int v)
{
    add_value(CurrentRequest, p, "%d", v);
}

void MvRequest::addValue(const char* p, const char* v)
{
    if (v)
        add_value(CurrentRequest, p, "%s", v);
}

void MvRequest::addValue(const char* p, long v)
{
    add_value(CurrentRequest, p, "%ld", v);
}

void MvRequest::addValue(const char* p, const MvRequest& v)
{
    MvRequest sub;
    getValue(sub, p, 0);
    setValue(p, sub + v);
}

MvRequest& MvRequest::rewind()
{
    CurrentRequest = FirstRequest;
    return *this;
}

MvRequest& MvRequest::advance()
{
    if (CurrentRequest)
        CurrentRequest = CurrentRequest->next;
    return *this;
}

void MvRequest::advanceToEnd()
{
    while (CurrentRequest)
        advance();
}

MvRequest& MvRequest::advanceTo(std::string& verb)
{
    while (CurrentRequest) {
        if (getVerb() == verb)
            break;

        advance();
    }
    return *this;
}

MvRequest& MvRequest::advanceAfter(std::string& verb)
{
    while (CurrentRequest) {
        if (getVerb() == verb) {
            advance();
            break;
        }

        advance();
    }
    return *this;
}

MvRequest MvRequest::justOneRequest() const
{
    return MvRequest(clone_one_request(CurrentRequest), false);
}

#if 0
//-- following commented-out functions are now (May 2008) obsolete
//
// Set value to list of requests, if request name matches
//

void set_value_to_requests(int overwrite, request* first_reqst, const char* reqname, const char* parname, const char* parformat, ...)
{
	char parvalue[1024];
	va_list ap;
	va_start(ap,parformat);
	vsprintf(parvalue, parformat, ap);

	request* reqst;
	for (reqst = first_reqst; reqst; reqst = reqst->next)
	{
		if (!strcmp(reqst->name, reqname))
		{
			if (overwrite || !get_value(reqst, parname, 0))
				set_value(reqst, parname, parvalue);
		}
	}
}

void add_value_to_requests(request* first_reqst, const char* reqname, const char* parname, const char* parformat, ...)
{
	char parvalue[1024];
	va_list ap;
	va_start(ap,parformat);
	vsprintf(parvalue, parformat, ap);

	request* reqst;
	for (reqst = first_reqst; reqst; reqst = reqst->next)
	{
		if (!strcmp(reqst->name, reqname))
			add_value(reqst, parname, parvalue);
	}
}

void MvRequest :: setValueToAll(const char* reqname, const char* parname, const char* value)
{
	set_value_to_requests(_overwrite, FirstRequest, reqname, parname, value);
}


void MvRequest :: setValueToAll(const char* reqname, const char* parname, int ivalue)
{
	char value[20];
	sprintf(value, "%d", ivalue);
	set_value_to_requests(_overwrite, FirstRequest, reqname, parname, value);
}

void MvRequest :: setValueToAll(const char* reqname, const char* parname, double fvalue)
{
	char value[20];
	sprintf(value, "%.12g", fvalue);
	set_value_to_requests(_overwrite, FirstRequest, reqname, parname, value);
}

void MvRequest :: addValueToAll(const char* reqname, const char* parname, const char* value)
{
	add_value_to_requests(FirstRequest, reqname, parname, value);
}

void MvRequest :: addValueToAll(const char* reqname, const char* parname, int ivalue)
{
	char value[20];
	sprintf(value, "%d", ivalue);
	add_value_to_requests(FirstRequest, reqname, parname, value);
}

void MvRequest :: addValueToAll(const char* reqname, const char* parname, double fvalue)
{
	char value[20];
	sprintf(value, "%.12g", fvalue);
	add_value_to_requests(FirstRequest, reqname, parname, value);
}
#endif

int MvRequest::countParameters(bool includeHidden) const
{
    if (!CurrentRequest)
        return 0;
    int n = 0;
    parameter* p = CurrentRequest->params;
    parameter* lastp = p;
    while (p) {
        n++;
        lastp = p;
        p = p->next;
    }
    // here, 'hidden' means that it starts with a double underscore, as in "__strings"
    if (!includeHidden) {
        if (lastp &&
            lastp->name &&
            strlen(lastp->name) > 1 &&
            lastp->name[0] == '_' &&
            lastp->name[1] == '_')
            n--;
    }
    return n;
}

const char* MvRequest::getParameter(int i) const
{
    if (!CurrentRequest)
        return nullptr;

    int n = 0;
    parameter* p = CurrentRequest->params;
    while (p) {
        if (n++ == i)
            return p->name;
        p = p->next;
    }
    return nullptr;
}

bool MvRequest::iterInitParam()
{
    CurrentParam = CurrentRequest->params;

    return (CurrentParam ? true : false);
}

bool MvRequest::iterGetNextParamInterface(MvRequest& req)
{
    if (!CurrentParam)  // end of request
        return false;

    // clone the Interface request
    request* r = CurrentParam->interface;
    if (r) {
        req = clone_all_requests(CurrentParam->interface);
    }
    else  // if there is no Interface, creates a default one
    {
        req.clean();
        req.setVerb("Interface");
        req("interface") = "any";
    }

    CurrentParam = CurrentParam->next;
    return true;
}

#if 0
//-- following commented-out function is now (May 2008) obsolete
const char* MvRequest :: enquire(const char* name, const char* classe, const char* keyw)
{
	const char* classe_value;
	const char* cmd = nullptr;
	request* reqst = FirstRequest;

	while (reqst)
	{
		if (!strcmp(name, reqst->name))
		{
			classe_value = get_value(reqst,"class",0);
			if (classe_value && !strcmp(classe_value, classe))
				cmd = get_value(reqst,keyw,0);
		}
		reqst = reqst->next;
	}

	return cmd;
}
#endif

const char* MvRequest ::enquire(const char* name, const char* keyw, int posit)
{
    const char* value = nullptr;
    request* reqst = FirstRequest;

    while (reqst && !value) {
        if (!strcmp(name, reqst->name))
            value = get_value(reqst, keyw, posit);
        reqst = reqst->next;
    }
    return value;
}

// Apply the application override parameters to the current request

void MvRequest ::appOverrides(MvRequest& r)
{
    int i = -1;
    const char *parname, *parvalue;

    while ((parname = r("_APPLICATION_OVERRIDES", ++i)))  //-- xtra pranthesis to avoid warning
    {
        // reset parameter "parname"
        unset_value(CurrentRequest, parname);

        // copy parameter values from "r";
        int j = -1;
        while ((parvalue = r(parname, ++j)))  //-- xtra pranthesis to avoid warning
            add_value(CurrentRequest, parname, parvalue);

        // copy overrides
        add_value(CurrentRequest, "_APPLICATION_OVERRIDES", parname);
    }
}

void MvRequest ::merge(const MvRequest& r)
{
    update(r);  //-- calls a member function (below)
}

void MvRequest ::mars_merge(const MvRequest& r)
{
    reqcpy(CurrentRequest, r.CurrentRequest);  //-- calls a Mars function
}

void MvRequest::update(const MvRequest& r)
{
    request* a = CurrentRequest;
    const request* b = r.CurrentRequest;

    if (!a || !b)
        return;

    parameter* p = b->params;
    while (p) {
        // Parameter not found in the target request
        if (find_parameter(a, p->name) == nullptr) {
            p = p->next;
            continue;
        }

        boolean b = false;
        value* v = p->values;
        while (v) {
            update_value(a, p->name, v->name, b);
            b = true;
            v = v->next;
        }

        if (p->subrequest)
            set_subrequest(a, p->name, p->subrequest);

        /* For marsgen */
        {
            parameter* q = find_parameter(a, p->name);
            if (q) {
                free_all_values(q->default_values);
                q->default_values = clone_all_values(p->default_values);
            }
        }

        p = p->next;
    }
}

// FI 20201103: does it need to check for subrequests and default_values?
MvRequest MvRequest::convertLetterCase(bool upper)
{
    if (CurrentRequest == nullptr)
        return MvRequest(nullptr, false);

    std::string spar;
    request* a = clone_all_requests(FirstRequest);
    request* b = a;
    while (b) {
        // Convert verb
        spar = b->name;
        if (upper)
            std::transform(spar.begin(), spar.end(), spar.begin(), ::toupper);
        else
            std::transform(spar.begin(), spar.end(), spar.begin(), ::tolower);

        strfree(b->name);
        b->name = strcache(spar.c_str());

        // Convert parameter names
        parameter* p = b->params;
        MvRequest reqAux;
        while (p) {
            spar = p->name;
            if (upper)
                std::transform(spar.begin(), spar.end(), spar.begin(), ::toupper);
            else
                std::transform(spar.begin(), spar.end(), spar.begin(), ::tolower);

            // Update parameter name
            strfree(p->name);
            p->name = strcache(spar.c_str());

            p = p->next;
        }

        b = b->next;
    }

    return MvRequest(a, false);
}

void MvRequest::update_value(request* r, const char* parname, const char* valname, boolean append)
{
    parameter* p;
    value* v;

    if (!r)
        return;


    if ((p = find_parameter(r, parname)) == nullptr)
        return;

    if (append) {
        value *a = p->values, *b = nullptr;
        while (a) {
            b = a;
            //			c = b;
            a = a->next;
        }
        v = new_value(strcache(valname));
        if (b)
            b->next = v;
        else
            p->values = v;

        p->count = 0;
    }
    else {
        if (p->values) {
            free_all_values(p->values->next);
            p->values->next = nullptr;
            p->count = 0;
            if (EQ(p->values->name, valname))
                return;
            else {
                strfree(p->values->name);
                p->values->name = strcache(valname);
            }
        }
        else {
            v = new_value(strcache(valname));
            p->values = v;
            p->count = 0;
        }
    }
}

MvRequest MvRequest::ExpandRequest(const char* defName, const char* rulesName, long expandFlag)
{
    // Create the full path
    std::string sdef = MakeSystemEtcPath(defName);
    std::string srules = MakeSystemEtcPath(rulesName);

    MvLanguage langMetview(sdef.c_str(), srules.c_str(), expandFlag);

    return langMetview.expandOne(CurrentRequest);
}

bool MvRequest::getPath(const std::string& par, std::string& resPath, bool canBeEmpty) const
{
    const char* inPath = (*this)(par.c_str());

    if (inPath && strlen(inPath) > 0) {
        resPath = std::string(inPath);

        if (resPath[0] == ' ') {
            std::string errTxt = "Leading whitespace in parameter: " + std::string(par) + "!";
            marslog(LOG_EROR, "%s", errTxt.c_str());
            return false;
        }

        // Handle relative paths
        else if (resPath[0] != '/') {
            // If _MACRO and _PATH is defined it means this was run from a macro
            //

            if (const char* fromMacro = (*this)("_MACRO")) {
                if (const char* fromMacroPathCh = (*this)("_PATH")) {
                    std::string fromMacroPath(fromMacroPathCh);
                    if (fromMacroPath.size() > 0 && fromMacroPath[0] == '/') {
                        resPath = fromMacroPath + "/" + resPath;
                        return true;
                    }
                    else {
                        std::string errTxt = "Could not resolve path for parameter=" + std::string(par) +
                                             " because both _MACRO=" + fromMacro + "and _PATH=" + fromMacroPath +
                                             " but _PATH does not seem to be an absolute path!";
                        marslog(LOG_EROR, "%s", errTxt.c_str());
                        return false;
                    }
                }
            }

            // We try to use the path of the icon the request belongs to
            const char* callerIcon = (*this)("_NAME");
            if (callerIcon) {
                std::string callerDir(callerIcon);

                // special case: if callerDir is a path with "Process@" in it, then it
                // very probably means that this was run from a macro - in this case, we can't
                // use the returned path (because it's invalid). If we are here the logic behind
                // the path resolving technique is wrong, because the path should have been resolved
                // in the if block above!
                // As a fallback solution we suppose that the path is relative to where the macro is run
                // from; so in this case we would simplyremove the path.

                if (callerDir.find("Process@") != std::string::npos) {
                    // yes, it's called from Macro - files are relative to current dir
                    callerDir = ".";
                }
                else {
                    char* mvudir = getenv("METVIEW_USER_DIRECTORY");
                    if (mvudir) {
                        callerDir = std::string(mvudir) + "/" + callerDir;
                    }
                    std::string::size_type pos = callerDir.find_last_of("/");
                    if (pos != std::string::npos) {
                        callerDir = callerDir.substr(0, pos);
                    }
                }

                resPath = callerDir + "/" + resPath;
            }
        }
    }

    if (!inPath || (resPath.empty() && canBeEmpty == false)) {
        std::string errTxt = "No value is defined for parameter: " + std::string(par) + "!";
        marslog(LOG_EROR, "%s", errTxt.c_str());
        return false;
    }

    return true;
}

bool MvRequest::getPathAndReplace(const std::string& parName, std::string& resPath,
                                  const std::string& replaceFrom, const std::string& replaceTo, bool canBeEmpty)
{
    std::string str;
    if (getValue(parName, str, true)) {
        if (str.empty() || str == replaceFrom) {
            resPath = replaceTo;
            return true;
        }
    }

    return getPath(parName, resPath, canBeEmpty);
}

bool MvRequest::getPath(const char* iconPar, const char* textPar, std::string& resPath, bool canBeEmpty, std::string& errTxt)
{
    errTxt.clear();

    MvRequest dataR = (*this)(iconPar);
    const char* dataPath = dataR("PATH");

    // First try to get the path from the icon object
    if (dataPath) {
        resPath = std::string(dataPath);
    }
    // If there is no icon we use the text parameter to get the path
    else {
        if (textPar) {
            std::string s(textPar);
            if (!getPath(s, resPath, true)) {
                return false;
            }
        }
        else {
            return false;
        }
    }

    if (!canBeEmpty && resPath.empty()) {
        errTxt = "No value found for paramaters: " + std::string(iconPar) + " " + std::string(textPar);
        return false;
    }

    return true;
}


// MvRequest::getBaseDate()
// returns zero if neither HDATE nor DATE are present in the request
int MvRequest::getBaseDate()
{
    int date = (*this)("HDATE");  // try HDATE first (will return 0 if not there)

    if (date == 0)
        date = (*this)("DATE");  // no HDATE? try DATE (will return 0 if not there)

    return date;
}

bool MvRequest::getDate(const std::string& par, std::string& value, bool canBeEmpty)
{
    bool retVal = false;
    std::string str;
    if ((retVal = getValue(par, str, canBeEmpty)) == true) {
        if ((retVal = MvDate::parseYYYYMMDD(str, value)) == false) {
            marslog(LOG_EROR, "Invalid date value (%s) used for parameter: %s", str.c_str(), par.c_str());
        }
    }
    return retVal;
}

bool MvRequest::getDate(const std::string& par, const std::string& str, std::string& value)
{
    bool retVal = false;
    if ((retVal = MvDate::parseYYYYMMDD(str, value)) == false) {
        marslog(LOG_EROR, "Invalid date value (%s) used for parameter: %s", str.c_str(), par.c_str());
    }
    return retVal;
}

bool MvRequest::getTime(const std::string& par, std::string& value, bool canBeEmpty)
{
    bool retVal = false;
    std::string str;
    if ((retVal = getValue(par, str, canBeEmpty)) == true) {
        retVal = MvRequest::getTime(par, str, value);
    }
    return retVal;
}

bool MvRequest::getTime(const std::string& par, const std::string& str, std::string& value)
{
    bool retVal = false;
    if ((retVal = MvDate::timeToHHMMSS(str, value)) == false) {
        std::string errTxt = "Time value=" + str + "for param=" + par +
                             " does not match the expected format: hh[:mm[:ss]]";
        marslog(LOG_EROR, "%s", errTxt.c_str());
        return false;
    }
    return retVal;
}

bool MvRequest::getTimeLenInSec(const std::string& par, std::string& value, bool canBeEmpty)
{
    bool retVal = false;
    std::string str;
    if ((retVal = getValue(par, str, canBeEmpty)) == true) {
        retVal = MvRequest::getTimeLenInSec(par, str, value);
    }
    return retVal;
}

bool MvRequest::getTimeLenInSec(const std::string& par, const std::string& str, std::string& value)
{
    bool retVal = false;
    if ((retVal = MvDate::timeToLenInSec(str, value)) == false) {
        std::string errTxt = "Time value=" + str + "for param=" + par +
                             " does not match the expected format: hhh[:mm[:ss]]";
        marslog(LOG_EROR, "%s", errTxt.c_str());
        return false;
    }
    return retVal;
}

bool MvRequest::getValueId(const std::string& par, std::string& value, const std::map<std::string, std::string>& idMap, bool canBeEmpty)
{
    value.clear();

    const char* cval = (*this)(par.c_str());

    if (cval) {
        value = std::string(cval);
        if (idMap.empty() == false) {
            auto it = idMap.find(value);
            if (it != idMap.end())
                value = it->second;
            else
                value.clear();
        }
    }
    else if (!canBeEmpty) {
        std::string errTxt = "Parameter not found: " + par;
        marslog(LOG_EROR, "%s", errTxt.c_str());
        return false;
    }

    if (!canBeEmpty && value.empty()) {
        std::string errTxt = "No value found for paramater: " + std::string(par);
        marslog(LOG_EROR, "%s", errTxt.c_str());
        return false;
    }

    return true;
}


bool MvRequest::getValue(const std::string& par, std::string& value, bool canBeEmpty) const
{
    value.clear();

    const char* cval = (*this)(par.c_str());

    if (cval) {
        value = std::string(cval);
    }
    else if (!canBeEmpty) {
        std::string errTxt = "Parameter not found: " + std::string(par);
        marslog(LOG_EROR, "%s", errTxt.c_str());
        return false;
    }

    if (!canBeEmpty && value.empty()) {
        std::string errTxt = "No value found for parameter: " + std::string(par);
        marslog(LOG_EROR, "%s", errTxt.c_str());
        return false;
    }

    return true;
}

bool MvRequest::getValue(const std::string& par, std::vector<std::string>& value, bool canBeEmpty) const
{
    value.clear();
    int cnt = countValues(par.c_str());

    if (cnt == 0) {
        if (!canBeEmpty) {
            std::string errTxt = "No value found for parameter: " + par;
            marslog(LOG_EROR, "%s", errTxt.c_str());
            return false;
        }
    }
    else if (cnt == 1) {
        if (const char* cval = (*this)(par.c_str())) {
            value.push_back(std::string(cval));
        }
    }
    else {
        std::string val;
        for (int i = 0; i < cnt; i++) {
            if (const char* cval = (*this)(par.c_str(), i)) {
                value.push_back(std::string(cval));
            }
        }
    }

    return true;
}

// Algorithm:
// - find a subrequest ERROR and get the error message from parameter MESSAGE
// - only the first message is retrieved (if any)
bool MvRequest::getError(std::string& err)
{
    if (!CurrentRequest)
        return false;

    MvRequest req;
    parameter* p = CurrentRequest->params;
    while (p) {
        if ((req = getSubrequest(p->name))) {
            if (std::string(req.getVerb()) == std::string("ERROR")) {
                if ((const char*)req("MESSAGE")) {
                    // build error message
                    err = "ERROR-> ";
                    if ((const char*)req("_SERVICE"))
                        err = err + (const char*)req("_SERVICE") + ": ";
                    err += (const char*)req("MESSAGE");
                    return true;
                }
            }
        }
        p = p->next;
    }

    return false;
}

//===================================================================
// MvAccess

void MvAccess::operator+=(double val)
{
    if (LastGet)
        add_value(Request->CurrentRequest, LastGet, "%.12g", val);
    LastGet = nullptr;
    LastIndex = 0;
}

MvAccess::operator Cached() const
{
    const char* p = *this;
    return Cached(p);
}

MvAccess::operator MvRequest() const
{
    return MvRequest(get_subrequest(Request->CurrentRequest, LastGet, LastIndex), false);
}
MvConstAccess::operator Cached() const
{
    const char* p = *this;
    return Cached(p);
}

MvConstAccess::operator MvRequest() const
{
    return MvRequest(get_subrequest(Request->CurrentRequest, LastGet, LastIndex), false);
}


MvValue::MvValue(double d)
{
    CurrentRequest = FirstRequest = empty_request("NUMBER");
    set_value(CurrentRequest, "VALUE", "%.12g", d);
}

MvValue::MvValue(const char* s)
{
    CurrentRequest = FirstRequest = empty_request("STRING");
    set_value(CurrentRequest, "VALUE", "%s", s);
}

MvGrib::MvGrib(const char* s)
{
    CurrentRequest = FirstRequest = empty_request("GRIB");
    set_value(CurrentRequest, "PATH", "%s", s);
}

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


void MvRequest::importSubObjects(std::string& path, request* obj)
{
    // Get the definition filename
    const char* defFile = get_value(obj, "definition_file", 0);
    if (!defFile)
        return;

    // Read the definition file
    request* rdef = read_language_file(defFile);
    if (!rdef)
        return;

    // This request may be structured as a set of requests.
    // Loop each request and expand all parameters that are subrequests.
    this->rewind();
    while (CurrentRequest) {
        // Find the correspondent definition request
        request* rl = rdef;
        while (rl && (rl->name != this->getVerb()))
            rl = rl->next;

        // Skip this request if there is no related definition request
        if (!rl)
            continue;

        // Check each parameter to find out if it needs to be expanded
        parameter* p = rl->params;
        MvRequest reqAux;
        while (p) {
            // Search for parameters that have an icon interface
            const char* cicon = get_value(p->interface, "interface", 0);
            if (cicon && (strcmp(cicon, "icon") == 0)) {
                // Build all subrequests and add to this parameter
                const char* cclass = get_value(p->interface, "class", 0);
                const char* q;
                for (int i = 0; i < this->countValues(p->name); i++) {
                    // Build subrequest
                    this->getValue(q, p->name, i);
                    std::string fullPath = (path[path.size() - 1] == '/') ? path + q : path + "/" + q;
                    if (!IsBinaryOrMissingFile(fullPath.c_str())) {
                        // Read request from file and add parameter _CLASS.
                        // Do not add parameter _NAME because this request
                        // will be built by loading the contents of a file.
                        // Adding parameter _NAME will make further routines to
                        // read the file again.
                        reqAux.read(fullPath.c_str(), true);
                        reqAux("_CLASS") = reqAux.getVerb();
                    }
                    else {
                        // Build request manually
                        reqAux.setVerb(cclass);

                        // Add hidden parameters
                        reqAux("_CLASS") = cclass;

                        // Name without the Metview user directory path
                        std::string user_dir = GetUserDirectory();
                        std::size_t found = fullPath.find(user_dir);
                        if (found != std::string::npos)
                            reqAux("_NAME") = fullPath.substr(found + user_dir.size()).c_str();
                        else
                            reqAux("_NAME") = fullPath.c_str();
                    }

                    // Add subrequest to this parameter
                    if (i == 0)  // clean parameter values once
                        this->unsetParam(p->name);

                    this->addValue(p->name, reqAux);
                }
            }

            p = p->next;
        }

        CurrentRequest = CurrentRequest->next;
    }

    this->rewind();
}

request* MvRequest::findRequestObject()
{
    const char* className = this->getVerb();
    if (className == nullptr) {
        return nullptr;
    }

    const char* c;
    request* u = mars.setup;
    while (u) {
        if (strcmp(u->name, "object") == 0 &&
            (c = get_value(u, "class", 0)) &&
            c == className)
            return u;

        u = u->next;
    }

    return nullptr;
}

bool MvRequest::checkParameters(MvRequest& req, std::vector<std::string>& params)
{
    const char* par;

    for (auto& param : params) {
        par = param.c_str();
        if (!checkOneParameter(req, par))
            return false;
    }
    return true;
}

bool MvRequest::checkParameters(MvRequest& req, std::vector<std::string>& params, std::string& parOut)
{
    const char* par;

    for (auto& param : params) {
        par = param.c_str();
        if (!checkOneParameter(req, par)) {
            parOut = par;
            return false;
        }
    }
    return true;
}

bool MvRequest::checkOneParameter(MvRequest& req, const std::string& param)
{
    const char *v1, *v2;

    const char* par = param.c_str();
    int cnt1 = this->iterInit(par);
    int cnt2 = req.iterInit(par);
    if (cnt1 && (cnt1 == cnt2)) {
        for (int j = 0; j < cnt1; j++) {
            this->iterGetNextValue(v1);
            req.iterGetNextValue(v2);
            if (v1 != v2)
                return false;
        }
    }

    return true;
}

// Replace all the "." values of param "_PATH" to "./".
void MvRequest::replaceDotInPath(MvRequest& in)
{
    MvRequest r;
    while (in) {
        MvRequest rCurrent = in.justOneRequest();
        if (const char* ch = in("_PATH")) {
            //            marslog(LOG_INFO,"replaceDotInPath: found _PATH=%s", ch);
            if (strcmp(ch, ".") == 0) {
                marslog(LOG_INFO, "replaceDotInPath: replace _PATH");
                rCurrent("_PATH") = "./";
            }
        }
        int n = in.countParameters();
        for (int i = 0; i < n; i++) {
            const char* pch = in.getParameter(i);
            MvRequest sr = in.getSubrequest(pch);
            if (sr) {
                //                marslog(LOG_INFO, "replaceDotInPath: check subrequest=%s", pch);
                replaceDotInPath(sr);
                rCurrent(pch) = sr;
            }
        }
        //        marslog(LOG_INFO, "current request=");
        //        in.justOneRequest().print();
        r = r + rCurrent;
        in.advance();
    }
    in.rewind();
    in = r;
}

std::string MvRequest::toJson() const
{
    auto v = request2json(CurrentRequest);
    json_print(v);
    auto ch = json_get_string(v);
    if (ch) {
        return std::string(ch);
    }
    return {};
}

bool MvRequest::paramsEqual(MvRequest& req1, MvRequest& req2,
                            const std::string& name,
                            const std::string& label1, const std::string& label2,
                            std::string& msg)
{
    msg.clear();
    int num1 = req1.countValues(name.c_str());
    int num2 = req2.countValues(name.c_str());
    std::string inLabel1 = (!label1.empty()) ? ("in " + label1) : " in first request";
    std::string inLabel2 = (!label2.empty()) ? ("in " + label2) : " in second request";

    if (num1 == 0) {
        msg = "No parameter " + name + " found " + inLabel1;
        return false;
    }
    if (num2 == 0) {
        msg = "No parameter " + name + " found " + inLabel2;
        return false;
    }
    if (num1 != num2) {
        msg = name + " parameter has different number of items: " +
              std::to_string(num1) + " (" + inLabel1 + ") and " +
              std::to_string(num2) + " (" + inLabel2 + ")";
        return false;
    }

    if (num1 == 1) {
        std::string val1, val2;
        req1.getValue(name, val1, true);
        req2.getValue(name, val2, true);
        if (val1 != val2) {
            msg = name + " parameter has two different values: " +
                  val1 + " (" + inLabel1 + ") and " + val2 + " (" + inLabel2 + ")";
            return false;
        }
    }
    else {
        std::vector<std::string> vec1, vec2;
        req1.getValue(name, vec1, true);
        req2.getValue(name, vec2, true);
        std::string val1 = metview::merge(vec1, "/");
        std::string val2 = metview::merge(vec2, "/");
        if (val1 != val2) {
            msg = name + " parameter has two different values: " +
                  val1 + " (" + inLabel1 + ") and " + val2 + " (" + inLabel2 + ")";
            return false;
        }
    }

    return true;
}

bool MvRequest::paramEqualToVerb(MvRequest& req1, MvRequest& req2,
                                 const std::string& name,
                                 const std::string& label1, const std::string& label2,
                                 std::string& msg)
{
    msg.clear();
    std::string inLabel1 = (!label1.empty()) ? ("in " + label1) : " in first request";
    std::string inLabel2 = (!label2.empty()) ? ("in " + label2) : " in second request";
    std::string val1, val2;

    if (const char* ch = (const char*)req1(name.c_str())) {
        val1 = std::string(ch);
    }
    else {
        msg = "No parameter " + name + " found " + inLabel1;
        return false;
    }
    std::cout << "val1=" << val1 << std::endl;
    if (const char* ch = req2.getVerb()) {
        val2 = std::string(ch);
    }
    else {
        msg = "No verb found " + inLabel2;
        return false;
    }

    if (val1 != val2) {
        msg = name + " parameter " + inLabel1 + " does not match verb " + inLabel2 +
              ": " + val1 + " != " + val2;
        return false;
    }
    return true;
}
