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

/*

B.Raoult
ECMWF Oct-93

*/

#include <iostream>

#include "Metview.h"
#include "MvPath.hpp"
#include "MvMiscellaneous.h"

#include <stdlib.h>

// static Cached METVIEW_ICON ("METVIEW_ICON");

class Retrieve : public MvService
{
public:
    Retrieve(const char* c);
    void serve(MvRequest&, MvRequest&);
    virtual void marsServe(MvRequest&, MvRequest&);

private:
    bool forcePrintLog_;
};


class Read : public Retrieve
{
public:
    Read(const char* c) :
        Retrieve(c) {}
    virtual void marsServe(MvRequest&, MvRequest&);
};

class Stage : public Retrieve
{
public:
    Stage(const char* c) :
        Retrieve(c) {}
};

Retrieve::Retrieve(const char* c) :
    MvService(c), forcePrintLog_(false)
{
    //-- if flag -mlog was set in startup script then
    //-- activate more log output from Mars processes
    //-- also user env var METVIEW_MARS_LOG=1

    if (metview::isForceMarsLogSet())
        forcePrintLog_ = true;

    const char* mlog = getenv("MV_MLOG");
    if ((mlog && strcmp(mlog, "yes") == 0) || forcePrintLog_) {
        mars.info = true;
        mars.warning = true;
        // putenv("MV_DEBUG_PRINT=1");
        // sendProgress("Retrieve-> Spotted -mlog, set all kind of flags...");
    }
}

void Retrieve::marsServe(MvRequest& in, MvRequest& out)
{
    Cached name = (const char*)in("_NAME");
    Cached icon_class = (const char*)in("_CLASS");

    Cached mode = getenv("METVIEW_MODE");
    Cached appl = "metview";

    if (mode)
        appl = appl + "-" + mode;

    in("_APPL") = appl;


    //-- make the verb MARS understandable

    if (strcmp(in.getVerb(), "READ_MIR") == 0)
        in.setVerb("READ");
    else if (strcmp(in.getVerb(), "RETRIEVE_MIR") == 0)
        in.setVerb("RETRIEVE");
    else if (strcmp(in.getVerb(), "READ_EMOS") == 0)
        in.setVerb("READ");
    else if (strcmp(in.getVerb(), "RETRIEVE_EMOS") == 0)
        in.setVerb("RETRIEVE");
    else if (strcmp(in.getVerb(), "READ_TIGGE") == 0)
        in.setVerb("READ");
    else if (strcmp(in.getVerb(), "RETRIEVE_TIGGE") == 0)
        in.setVerb("RETRIEVE");
    else if (strcmp(in.getVerb(), "READ_ODB") == 0)
        in.setVerb("READ");
    else if (strcmp(in.getVerb(), "RETRIEVE_ODB") == 0)
        in.setVerb("RETRIEVE");


    if (is_bufr(in)) {
        in("TARGET") = marstmp();

        err e = handle_request(in, this);
        if (e != 0) {
            setError(1, "Retrieve-> Error code: %d", e);
            return;
        }

        MvRequest r = "BUFR";
        r("PATH") = in("TARGET");
        r("TEMPORARY") = 1;

        out = r;
    }
    else if (is_odb(in)) {
        // Check double quotes in FILTER
        // if we don't have double quotes around the FILTER expression
        // then it won't work (actually, any characters will do - ODB
        // removes the first and last characters from the string!
        const char* filter = in("FILTER");
        if (filter) {
            std::string fstr(filter);
            if (fstr.size() > 0) {
                if (fstr.substr(0, 1) != "\"")
                    fstr.insert(0, "\"");

                int n = fstr.size();
                if (fstr.substr(n - 1, 1) != "\"")
                    fstr.append("\"");

                in("FILTER") = fstr.c_str();
            }
        }


        in("TARGET") = marstmp();

        err e = handle_request(in, this);
        if (e != 0) {
            setError(1, "Retrieve-> Error code: %d", e);
            return;
        }

        // If size of the target file is 0
        // then there is no data retrieved
        if (!FileHasValidSize(in("TARGET"))) {
            request* field = nullptr;
            out = field;
        }
        else {
            MvRequest r = "ODB_DB";
            r("PATH") = in("TARGET");
            r("TEMPORARY") = 1;

            out = r;
        }
    }
    else if (image(in) || simulated_image(in)) {
        in("TARGET") = marstmp();

        err e = handle_request(in, this);
        if (e != 0) {
            setError(1, "Retrieve-> Error code: %d", e);
            return;
        }

        // If size of the target file is 0
        // then there is no data retrieved
        if (!FileHasValidSize(in("TARGET"))) {
            request* field = nullptr;
            out = field;
        }
        else {
            //		MvRequest r = "IMAGE";
            MvRequest r = "GRIB";  // we don't want to distinguish between IMAGEs and other GRIBs now
            r("PATH") = in("TARGET");
            r("TEMPORARY") = 1;
            out = r;
        }
    }
    else {
        err e = handle_request(in, this);
        if (e != 0) {
            setError(1, "Retrieve-> Error code: %d", e);
            return;
        }

        variable* v = find_variable((const char*)in("FIELDSET"));
        if (v == nullptr) {
            setError(1, "Retrieve-> Parameter FIELDSET not found");
            return;
        }

        if (v->scalar) {
            MvRequest r = "NUMBER";
            r("VALUE") = v->val;

            out = r;
        }
        else {
            out = fieldset_to_request(v->fs);
        }
    }

    return;
}

void Read::marsServe(MvRequest& in, MvRequest& out)
{
    const char* data = in("DATA");
    const char* path = in("SOURCE");

    if (data && path)  //-- do not allow two data sources
    {
        setError(1, "READ-> two input files: both Source and Data have values!");
        return;
    }
    if ((!data) && (!path))  //-- one data source required
    {
        setError(1, "READ-> no input file: both Source and Data are empty!");
        return;
    }

    MvRequest grib;
    if (data) {
        in.getValue(grib, "DATA");

        if ((const char*)grib("FIELDSET_FROM_FILTER") &&
            (int)grib("FIELDSET_FROM_FILTER") == 1) {
            fieldset* fs = request_to_fieldset(grib);
            fieldset* z = copy_fieldset(fs, fs->count, true);
            save_fieldset(z);
            grib = fieldset_to_request(z);
            grib("FIELDSET_FROM_FILTER") = 1;
        }

        path = grib("PATH");
        in("SOURCE") = path;
        in.unsetParam("DATA");
    }

    int temp = 0;
    char* name = guess_class(path);

    FILE* f = fopen(path, "r");
    if (!f) {
        setError(1, "READ-> Unable to open file: %s", path);
        return;
    }
    fclose(f);

    if (strcmp(name, "GRIB") == 0) {
        Retrieve::marsServe(in, out);

        // Check whether the request should be sorted.
        const char* field_order = in("ORDER");
        if (field_order && strcmp(field_order, "1") == 0) {
            marslog(LOG_DBUG, "============== Before sorting:");
            out.print();

            MvFieldSet fs(out);
            MvFieldSetIterator ii(fs);
            MvField ff;
            MvRequest one_field;

            out.unsetParam("OFFSET");
            out.unsetParam("LENGTH");

            ii.sort("PARAM");
            ii.sort("LEVELIST");
            ii.sort("LEVTYPE");
            ii.sort("NUMBER");
            ii.sort("_DIAGITER");
            ii.sort("STEP");
            ii.sort("TIME");
            ii.sort("DATE");
            while ((ff = ii())) {
                one_field = ff.getRequest();
                out.addValue("OFFSET", ff.getOffset());
                out.addValue("LENGTH", ff.getLength());
            }

            marslog(LOG_DBUG, "============== After sorting:");
        }
        else {
            marslog(LOG_INFO, "READ: No sorting done");
        }

        // Set parameters to indicate:
        // a) this is a result of a filter command
        // b) if a temporary file was created
        out("FIELDSET_FROM_FILTER") = 1;
        if (grib && (const char*)grib("FIELDSET_FROM_FILTER") &&
            (int)grib("FIELDSET_FROM_FILTER") == 1) {
            const char* outpath = out("PATH");
            if (out.isEmpty() || (outpath && strcmp(outpath, path))) {
                // 1) if a new file was created from the filtering (as can happen if
                //    GRID or AREA are specified), then the resulting object will store
                //    a path to the new file, and the temporary file that we created at
                //    the top of this function will be orphaned. But in this case we know
                //    that that first temporary file can be deleted, because the result of
                //    this operation no longer depends on it.
                // 2) if the result is empty, then we need to delete the temporary
                //    file that we created here, because this is the only place
                //    that knows about it
                unlink(path);
            }
            out("TEMPORARY") = 1;
        }
    }
    else {
        out = MvRequest(name);
        out("PATH") = path;
        out("TEMPORARY") = temp;
    }
}

void Retrieve::serve(MvRequest& in, MvRequest& out)
{
    // Make sure the request is clean...

    std::cout << "--> Retrieve::serve in -->" << std::endl;
    std::cout << "MARS HOME:           " << mars.mars_home << std::endl;
    std::cout << "MARS LANGUAGE FILE:  " << mars.langfile << std::endl;
    std::cout << "MARS RULES FILE:     " << mars.testfile << std::endl;
    std::cout << std::endl;
    in.print(forcePrintLog_);

    reset_language(mars_language());
    expand_flags(EXPAND_MARS);

    MvRequest clean(
        expand_all_requests(
            mars_language(),
            mars_rules(),
            //			in));
            trim_all_requests(mars_language(), in)));

    // Make sure we have a fieldset
    clean("FIELDSET") = iconName();

    // Do the real job there
    marsServe(clean, out);

    std::cout << "--> Retrieve::serve out -->" << std::endl;

    out.print();
}

extern "C" {
err pproc_print_version();
};
int main(int argc, char** argv)
{
    // from the environment, check whether we're in 'standard', 'MIR' or 'TIGGE' mode

    char* marsmode = getenv("MV_MARS_MODE");
    const char* servicename = "mars";

    if (marsmode && strcmp(marsmode, "MIR") == 0) {
        servicename = "marsMIR";
    }
    else if (marsmode && strcmp(marsmode, "EMOS") == 0) {
        servicename = "marsEMOS";
    }
    else if (marsmode && strcmp(marsmode, "TIGGE") == 0) {
        servicename = "Mars_TIGGE";
    }
    else if (marsmode && strcmp(marsmode, "odb") == 0) {
        servicename = "marsOdb";
    }

    MvApplication theApp(argc, argv, servicename);

    mars.appl = strcache(servicename);

    Retrieve r("RETRIEVE");
    Retrieve r2("RETRIEVE_MIR");
    Retrieve r3("RETRIEVE_EMOS");
    Retrieve rt("RETRIEVE_TIGGE");
    Retrieve ro("RETRIEVE_ODB");
    Read x("READ");
    Read x2("READ_MIR");
    Read x3("READ_EMOS");
    Read xt("READ_TIGGE");
    Read xo("READ_ODB");
    Stage y("STAGE");

    pproc_print_version();
    theApp.run();
}
