/***************************** 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 "Metview.h"
#include "MvPath.hpp"
#include "MvDate.h"
#include "MvMiscellaneous.h"
#include "Tokenizer.h"

#include <iostream>
#include <stdexcept>
#include <algorithm>

#define FLEXTRA_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()
{
    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)
{
    // std::cout << "PROG callback: " << std::endl;
    MvRequest in(r);
    if (auto* b = static_cast<MvMacroCallerService*>(obj))
        b->progress(in);
}

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

class Met3DPrep : public MvMacroCallerService
{
public:
    Met3DPrep();
    void serve(MvRequest&, MvRequest&);

protected:
    void reply(const MvRequest&);
    void progress(const MvRequest&);

    std::string id_;
    std::string paramPrefix_;
    std::string outReqType_;
    std::map<std::string, std::string> retModeIds_;
    std::map<std::string, std::string> modeIds_;
    std::map<std::string, std::string> levelTypeIds_;
    std::map<std::string, std::string> prodIds_;
    std::map<std::string, std::string> onOffIds_;
    bool hasReply_;
    MvRequest replyReq_;
};

Met3DPrep::Met3DPrep() :
    MvMacroCallerService("MET3D_PREPARE"),
    id_("MET3D_PREPARE"),
    hasReply_(false)
{
    modeIds_["FC"] = "fc";
    modeIds_["AN"] = "an";

    levelTypeIds_["PL"] = "pl";
    levelTypeIds_["ML"] = "ml";

    prodIds_["HIRES"] = "hires";
    prodIds_["ENS"] = "ens";

    onOffIds_["ON"] = "1";
    onOffIds_["OFF"] = "0";
}

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

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

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

    std::string str;
    std::string prepMacro;
    char* mvbin = getenv("METVIEW_BIN");
    if (mvbin == 0) {
        setError(1, "Met3dPrepare-> No METVIEW_BIN env variable is defined. Cannot locate mv_met3d_prep.mv macro!");
        return;
    }
    else {
        prepMacro = std::string(mvbin) + "/mv_met3d_prep.mv";
    }

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

    // retrieve mode
    std::string retMode;
    FLEXTRA_CHK(in.getValueId("RETRIEVE_GROUP_BY_TIME", retMode, onOffIds_));
    param.push_back(retMode);

    // perparation mode
    std::string prepMode;
    FLEXTRA_CHK(in.getValueId("MODE", prepMode, modeIds_));
    param.push_back(prepMode);

    // Product
    std::string prod;
    if (prepMode != "an")
        FLEXTRA_CHK(in.getValueId("PRODUCT", prod, prodIds_));
    else
        prod = "x";

    param.push_back(prod);

    // ENS member
    if (prod == "ens") {
        std::vector<std::string> ensNumbers;
        FLEXTRA_CHK(in.getValue("NUMBER", ensNumbers));
        param.push_back(metview::merge(ensNumbers, "/"));
    }
    else {
        param.push_back("-1");
    }

    // level type
    std::string levType;
    FLEXTRA_CHK(in.getValueId("LEVTYPE", levType, levelTypeIds_));
    param.push_back(levType);

    // pressure levels
    if (levType == "pl") {
        std::vector<std::string> plLevels;
        FLEXTRA_CHK(in.getValue("PL_LEVELIST", plLevels));
        param.push_back(metview::merge(plLevels, "/"));
    }

    // model levels
    else if (levType == "ml") {
        std::vector<std::string> mlLevels;
        FLEXTRA_CHK(in.getValue("ML_LEVELIST", mlLevels));
        param.push_back(metview::merge(mlLevels, "/"));
    }

    // surf params
    std::vector<std::string> surfParams;
    FLEXTRA_CHK(in.getValue("PARAMS_2D", surfParams));
    param.push_back(metview::merge(surfParams, "/"));

    // upper params
    std::vector<std::string> upperParams;
    FLEXTRA_CHK(in.getValue("PARAMS_3D", upperParams));
    param.push_back(metview::merge(upperParams, "/"));


#if 0
	//Add reuse(check) input status
    FLEXTRA_CHK(in.getValueId(paramName("REUSE_INPUT"),str,onOffIds_));
    param.push_back(str);

	//Add outpath
    std::string outPath;
    FLEXTRA_CHK(in.getPath(paramName("OUTPUT_PATH"),outPath,false));
	param.push_back(outPath);
#endif


    // Add area
    std::vector<std::string> area;
    FLEXTRA_CHK(in.getValue("AREA", area));
    param.push_back(metview::merge(area, "/"));

    // Add grid resolution
    std::vector<std::string> grid;
    FLEXTRA_CHK(in.getValue("GRID", grid));
    param.push_back(metview::merge(grid, "/"));

#if 0
	//Add top level
    std::string topL;
    FLEXTRA_CHK(in.getValue(paramName("TOP_LEVEL"),topL));
	param.push_back(topL);
	
    std::string topUnits;
    FLEXTRA_CHK(in.getValue(paramName("TOP_LEVEL_UNITS"),topUnits));
	param.push_back(topUnits);
#endif

    std::vector<std::string> numXy;
    std::string errStr;
    if (!metview::checkGrid(area, grid, numXy, errStr)) {
        setError(1, "Met3DPrepare-> Inconsistency between AREA and GRID: %s", errStr.c_str());
        return;
    }

    if (prepMode == "fc") {
#if 0
        //Add mars expver
        std::string marsExpver;
        FLEXTRA_CHK(in.getValue(paramName("FC_MARS_EXPVER"),marsExpver));
		param.push_back(marsExpver);
#endif
        FLEXTRA_CHK(in.getValue("DATE", str));
        std::cout << "date=" << str << std::endl;

        param.push_back(str);
#if 0
        MvDate md(str.c_str());
		if(md < etaDotDate)
		{
            marslog(LOG_EROR,"Cannot prepare data for the date specified in FLEXTRA_DATE: %s",str.c_str());
			marslog(LOG_EROR,"Etadot is not avaliable in MARS for this date! It has only been archived since 4 June 2008.");	
			setError(13);
			return;
		}
#endif

        FLEXTRA_CHK(in.getValue("TIME", str));
        param.push_back(str);

        std::vector<std::string> steps;
        FLEXTRA_CHK(in.getValue("STEP", steps));
        param.push_back(metview::merge(steps, "/"));
    }

    else {
#if 0
        //Add mars expver
		string marsExpver;
        FLEXTRA_CHK(in.getValue(paramName("FC_MARS_EXPVER"),marsExpver));
		param.push_back(marsExpver);	
	  
		//Add mars expver
		string marsAnExpver;
        FLEXTRA_CHK(in.getValue(paramName("AN_MARS_EXPVER"),marsAnExpver));
		param.push_back(marsAnExpver);
#endif

        FLEXTRA_CHK(in.getDate("ANALYSIS_START_DATE", str));
        param.push_back(str);
        MvDate mdStart(str.c_str());

        FLEXTRA_CHK(in.getValue("ANALYSIS_START_TIME", str));
        param.push_back(str);

        FLEXTRA_CHK(in.getDate("ANALYSIS_END_DATE", str));
        param.push_back(str);
        MvDate mdEnd(str.c_str());

        FLEXTRA_CHK(in.getValue("ANALYSIS_END_TIME", str));
        param.push_back(str);

        FLEXTRA_CHK(in.getValue("ANALYSIS_STEP", str));
        param.push_back(str);

        if (mdEnd < mdStart) {
            setError(1, "Met3DPrepare-> Inconsistency in period definition! ANALYSIS_END_DATE precedes ANALYSIS_START_DATE!");
            return;
        }
    }

    // The output file
    char* outPath = marstmp();
    param.insert(param.begin(), std::string(outPath));  // This should be the first argument!

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

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

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

    // const char *cdir=getenv("PWD");
    // marslog(LOG_INFO,"PWD=%s",cdir);

    // Run macro
    sendProgress("Met3DPrepare-> Execute macro: %s", prepMacro.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 = "Met3DPrepare-> Failed to run MET3D input preparation! Macro failed!";
            if (const char* cc = replyReq_("MESSAGE")) {
                err += "Message: ";
                err += cc;
            }
            setError(1, err.c_str());
            return;
        }
        else if (strcmp(v, "NUMBER") == 0) {
            if (const char* numCh = replyReq_("VALUE")) {
                if (strcmp(numCh, "0") != 0) {
                    std::string err = "Met3DPrepare-> Failed to run MET3D input preparation. Macro exited with code ";
                    err += numCh;
                    setError(1, err.c_str());
                    return;
                }
            }
        }
    }
    else {
        setError(1, "Met3DPrepare-> Failed to run MET3D input preparation!");
        return;
    }

#if 0
    //MvRequest reply = MvApplication::waitService("macro",req,error);

	//Call myself to process the macro reply request
	//if(!error && reply)
    if(!error)
    {
		//const char* myname = reply("_REPLY");
		//MvApplication::callService(myname,reply,0);
	}
	else
	{
	  	marslog(LOG_EROR,"Failed to run FLEXTRA input preparation! Macro failed!");
		setError(13);
		return;
	}
#endif
    // reply.print();

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

    std::cout << "--------------FlextraPrepare::serve() out--------------" << std::endl;
    out.print();
}


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

    Met3DPrep m3prep;

    theApp.run();
}
