/***************************** LICENSE START ***********************************

 Copyright 2018 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 "Metview.h"
#include "MvPath.hpp"
#include "MvDate.h"
#include "MvMiscellaneous.h"
#include "Tokenizer.h"
#include "Path.h"

#include <assert.h>
#include <iostream>
#include <stdexcept>
#include <algorithm>

#define ECCHARTS_CHK(str) ({if(!(str)) {setError(13); return;} })


class MvMacroCallerService : public MvService
{
public:
    MvMacroCallerService(const char* a);

    static void progressCb(svcid* id, request* r, void* obj);
    static void replyCb(svcid* id, request* r, void* obj);

protected:
    void registerCallbacks();

    virtual void reply(const MvRequest&) = 0;
    virtual void progress(const MvRequest&) = 0;
};


MvMacroCallerService::MvMacroCallerService(const char* a) :
    MvService(a)
{
}

void MvMacroCallerService::registerCallbacks()
{
    // marslog(LOG_INFO, "Eccharts-> registerCallbacks");

    add_progress_callback(MvApplication::getService(), 0,
                          progressCb, (void*)this);

    add_reply_callback(MvApplication::getService(), 0,
                       replyCb, (void*)this);
}

void MvMacroCallerService::progressCb(svcid* /*id*/, request* r, void* obj)
{
    // marslog(LOG_INFO, "Eccharts-> PROG callback:");
    MvRequest in(r);
    if (const char* chv = in.getVerb())
        if (strcmp(chv, "REGISTER_MACRO_PID") == 0)
            return;

    if (auto* b = static_cast<MvMacroCallerService*>(obj))
        b->progress(in);
}

void MvMacroCallerService::replyCb(svcid* /*id*/, request* r, void* obj)
{
    // marslog(LOG_INFO, "Eccharts-> replyCb");
    // print_all_requests(r);
    MvRequest in(r);
    if (auto* b = static_cast<MvMacroCallerService*>(obj))
        b->reply(in);
}

class Eccharts : public MvMacroCallerService
{
public:
    Eccharts(const char* req);
    void serve(MvRequest&, MvRequest&);

protected:
    void reply(const MvRequest&);
    void progress(const MvRequest&);
    void generateData(const MvRequest& in, MvRequest& out, const std::string& macroPath, const std::string& reqPath, bool failOnError);

    std::string id_;
    std::string paramPrefix_;
    std::string outReqType_;
    bool hasReply_;
    MvRequest replyReq_;
    std::map<std::string, std::string> yesNoIds_;
    std::map<std::string, std::string> titleIds_;
};

Eccharts::Eccharts(const char* req) :
    MvMacroCallerService(req),
    id_(req),
    hasReply_(false)
{
    yesNoIds_["YES"] = "1";
    yesNoIds_["NO"] = "0";
    titleIds_["DEFAULT"] = "0";
    titleIds_["STYLE_1"] = "1";
}

void Eccharts::progress(const MvRequest& in)
{
    if (const char* c = in("PROGRESS")) {
        sendProgress("Eccharts-> progress: %s", c);
    }
}

void Eccharts::reply(const MvRequest& in)
{
    hasReply_ = true;
    replyReq_ = in;
}

void Eccharts::serve(MvRequest& inReq, MvRequest& out)
{
    std::cout << "------------Eccharts::serve() in------------" << std::endl;
    inReq.print();

    std::string macroPath;
    std::string reqPath;
    std::string prepPy;
    char* mvbin = getenv("METVIEW_BIN");
    if (mvbin == 0) {
        setError(1, "Eccharts-> No METVIEW_BIN env variable is defined. Cannot locate script mv_eccharts.py!");
        return;
    }
    else {
        prepPy = std::string(mvbin) + "/mv_eccharts.py";
    }

    // Get the verb. We accept two verbs:
    //  -ECCHARTS: it is sent when we call the action on a non-cached icon
    //  -GRIB: it sent when the result (basically a GRIB) is cached and we call the
    //         "export" action on the icon! In this case to correctly perform the action we need
    //         the original ECCHARTS request and we store int the "_ECHHARTS" parameter of the
    //         cached GRIB request!!
    std::string verb;
    if (const char* chverb = inReq.getVerb())
        verb = std::string(chverb);
    else {
        setError(1, "Eccharts-> Verb is not defined!");
        return;
    }

    // We will work  with a local copy oif the incoming request!
    MvRequest in = inReq;

    // Determine the user action
    const char* actionC = (const char*)in("_ACTION");
    std::string action = (actionC) ? std::string(actionC) : "execute";

    // When the incoming request is GRIB we need to recover the original
    // request from the _ECCHARTS parameter
    if (verb == "GRIB") {
        if (action != "export_macro" && action != "export_python")
            return;

        in = inReq("_ECCHARTS");
    }

    // Get params
    std::vector<std::string> param;

    // Mode
    std::string mode = "data";
    if (action == "export_macro") {
        mode = "macro";
    }
    else if (action == "export_python") {
        mode = "python";
    }
    assert(mode == "data" || mode == "macro" || mode == "python");
    param.push_back(mode);

    // Layer definition json file
    std::string layersFile = metview::ecchartsDirFile("layers.json");
    param.push_back(layersFile);

    // mars definition json file
    std::string marsFile = metview::ecchartsDirFile("layers_mars.json");
    param.push_back(marsFile);

    // layer name
    std::string layer;
    ECCHARTS_CHK(in.getValue("LAYER", layer));

    if (mode == "data") {
        // Macro file to be generated
        char* macroPathCh = marstmp();
        macroPath = std::string(macroPathCh);
        param.push_back(macroPath);

        // Request file to be generated
        char* reqPathCh = marstmp();
        reqPath = std::string(reqPathCh);
        in.print();
        param.push_back(reqPath);
    }
    else {
        std::string suffix;
        if (mode == "macro")
            suffix = ".mv";
        else if (mode == "python")
            suffix = ".py";

        in("MACRO_OUTPUT_PATH") = std::string("eccharts_" + layer + suffix).c_str();
        if (!in.getPath("MACRO_OUTPUT_PATH", macroPath, false)) {
            setError(1, "Eccharts-> MACRO_OUTPUT_PATH error");
            return;
        }

        Path p(macroPath);
        macroPath = p.directory().add(p.uniqueNameInDir()).str();
        param.push_back(macroPath);
        param.push_back("_dummy_path_");
    }

    // add layer name to params
    param.push_back(layer);

    // layer style - for contouring
    std::string style;
    ECCHARTS_CHK(in.getValue("STYLE", style, true));
    param.push_back(style);

    // need custom title
    std::string needTitle;
    ECCHARTS_CHK(in.getValueId("TITLE", needTitle, titleIds_));
    param.push_back(needTitle);

    // MARS params follow from here

    // date
    std::string dateStr;
    ECCHARTS_CHK(in.getValue("DATE", dateStr));
    param.push_back("date=" + dateStr);

    // time
    std::string timeStr;
    ECCHARTS_CHK(in.getValue("TIME", timeStr));
    param.push_back("time=" + timeStr);

    // grid
    std::vector<std::string> grid;
    ECCHARTS_CHK(in.getValue("GRID", grid));
    param.push_back("grid=" + metview::toMacroList(grid));

    // quantile
    std::vector<std::string> quantile;
    ECCHARTS_CHK(in.getValue("QUANTILE", quantile, true));
    if (!quantile.empty()) {
        param.push_back("quantile=" + quantile[0]);
    }

    // quantile
    std::string interval;
    ECCHARTS_CHK(in.getValue("INTERVAL", interval, true));
    if (!interval.empty()) {
        param.push_back("interval=" + interval);
    }

    // step
    std::vector<std::string> step;
    ECCHARTS_CHK(in.getValue("STEP", step));
    param.push_back("step=" + metview::stepToMacro(step));

    // expver
    std::string expver;
    ECCHARTS_CHK(in.getValue("EXPVER", expver));
    param.push_back("expver=" + expver);

    // fail on error
    std::string str;
    ECCHARTS_CHK(in.getValueId("FAIL_ON_DATA_ERROR", str, yesNoIds_));
    bool failOnError = (str == "1") ? true : false;

    // Run python script to generate the macro and requests
    std::string cmd = "python3 -u " + prepPy;
    for (auto& i : param) {
        cmd += " \"" + i + "\"";
    }

    char* pyLogCh = marstmp();
    std::string pyLog(pyLogCh);
    cmd += " > \"" + pyLog + "\" 2>&1";

    sendProgress("Eccharts-> Execute command: %s", cmd.c_str());
    int ret = system(cmd.c_str());

    // metview::writeFileToLogErr(pyLog);

    // If the script failed read log file and
    // write it into LOG_EROR
    if (ret == -1 || WEXITSTATUS(ret) != 0) {
        setError(1, "Eccharts-> Command (%s) to generate macro failed. Exit code: %d", cmd.c_str(), WEXITSTATUS(ret));
        metview::writeFileToLogErr(pyLog);
        return;
    }

    if (mode == "data") {
        generateData(inReq, out, macroPath, reqPath, failOnError);
    }

    std::cout << "------------Eccharts::serve() out------------" << std::endl;

    out.print();
}

void Eccharts::generateData(const MvRequest& in, MvRequest& out, const std::string& macroPath, const std::string& reqPath, bool failOnError)
{
    //=========================================
    // Read requests needed to built the plot
    //=========================================

    // Read the requests
    MvRequest vReq;
    vReq.read(reqPath.c_str());

    //=========================================
    // Retrieve and process data
    //=========================================

    // Build request to be sent to Macro
    MvRequest req("MACRO");

    std::string processName = MakeProcessName("Eccharts");
    // MvRequest macroReq("MACRO");
    req("PATH") = macroPath.c_str();
    req("_CLASS") = "MACRO";
    req("_ACTION") = "execute";
    req("_REPLY") = processName.c_str();
    req("_EXTENDMESSAGE") = "1";

    std::vector<std::string> param;

    // The resulting grib file
    char* dataPath = marstmp();
    param.push_back(std::string(dataPath));

    // Define argument list for the macro!
    for (auto& it : param) {
        req.addValue("_ARGUMENTS", it.c_str());
    }

    // Run macro
    sendProgress("Eccharts-> Execute macro: %s", macroPath.c_str());

    svc* mysvc = MvApplication::getService();
    registerCallbacks();
    MvApplication::callService("macro", req, this);

    for (;;) {
        if (hasReply_)
            break;

        svc_connect(mysvc);
        if (process_service(mysvc))
            break; /* Timeout */
    }

    if (const char* v = replyReq_.getVerb()) {
        if (strcmp(v, "ERROR") == 0) {
            std::string err = "Eccharts-> Failed to run Eccharts data preparation. ";
            if (const char* cc = replyReq_("MESSAGE")) {
                err += "Message: ";
                err += cc;
            }

            if (failOnError)
                setError(1, err.c_str());
            else
                sendProgress(err.c_str());

            return;
        }
        else if (strcmp(v, "NUMBER") == 0) {
            if (const char* numCh = replyReq_("VALUE")) {
                if (strcmp(numCh, "0") != 0) {
                    std::string err = "Eccharts-> Failed to run Eccharts data preparation. Macro exited with code: ";
                    err += numCh;
                    if (failOnError)
                        setError(1, err.c_str());
                    else
                        sendProgress(err.c_str());

                    return;
                }
            }
        }
    }
    else {
        std::string err = "Eccharts-> Failed to run Eccharts data preparation!";
        if (failOnError)
            setError(1, err.c_str());
        else
            sendProgress(err.c_str());

        return;
    }

    out = MvRequest("GRIB");
    out("TEMPORARY") = 1;
    out("PATH") = dataPath;

    out = out + vReq;

    // Add the original request as a hidden parameter. We need it to export
    // to macro under certain circumstances.
    out("_ECCHARTS") = in;

    return;
}

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv, "Eccharts");

    Eccharts eccharts("ECCHARTS");
    Eccharts ecchartsExport("GRIB");

    theApp.run();
}
