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

 Copyright 2013 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 "Tokenizer.h"

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

#include <unistd.h>


#include "MvMiscelaneous.h"

using namespace std;

#define VAPOR_CHK(str) \
    if (!(str))        \
    return

class Base : public MvService
{
protected:
    Base(const char* a) :
        MvService(a){};
};

class VaporPrep : public Base
{
public:
    VaporPrep() :
        Base("VAPOR_PREPARE"){};
    void serve(MvRequest&, MvRequest&);

protected:
    bool getDate(const string& dd, string& res, const string& parName);
    bool getParamValue(string& resVal, MvRequest& in, const string& parMv, bool canBeEmpty = false);
    bool checkGeometry(const string& areaStr, const string& gridStr);
    bool vdfExists(MvRequest&, bool&);
    bool getInputFiles(std::vector<string>& resVal, MvRequest& in, const string& parMv);
    void vapor_visualise(MvRequest&, MvRequest&);
    void vapor_execute(MvRequest&, MvRequest&);
};


bool VaporPrep::getInputFiles(std::vector<string>& resVal, MvRequest& in, const string& parMv)
{
    MvRequest r = in(parMv.c_str());

    do {
        MvRequest rs = r.justOneRequest();
        if (const char* c = rs("PATH")) {
            resVal.push_back(string(c));
        }

    } while (r.advance());

    bool retVal = (resVal.size() > 0) ? true : false;

    if (!retVal)
        setError(1, "VaporPrepare-> No input files specified for parameter: %s", parMv.c_str());

    return retVal;
}

bool VaporPrep::getDate(const string& dd, string& res, const string& parName)
{
    res = dd;

    if (dd.size() < 8) {
        istringstream iss(dd);
        double d;
        iss >> d;
        char buf[9];
        MvDate md(mars_julian_to_date(today() + d, 1));
        md.Format("yyyymmdd", buf);
        res = string(buf);
    }

    bool retVal = (res.size() == 8) ? true : false;

    if (!retVal)
        setError(1, "VaporPrepare-> Invalid date format used for parameter: %s", parName.c_str());

    return retVal;
}

bool VaporPrep::getParamValue(string& resVal, MvRequest& in, const string& parMv, bool canBeEmpty)
{
    int cnt = in.countValues(parMv.c_str());

    if (cnt == 1) {
        const char* cval = in(parMv.c_str());
        if (cval)
            resVal = string(cval);
    }
    else {
        vector<string> vals;
        int toIndex = -1;
        int byIndex = -1;
        for (int i = 0; i < cnt; i++) {
            const char* cval = in(parMv.c_str(), i);
            if (cval) {
                vals.push_back(string(cval));
                if (strcmp(cval, "TO") == 0)
                    toIndex = static_cast<int>(vals.size()) - 1;
                else if (strcmp(cval, "BY") == 0)
                    byIndex = static_cast<int>(vals.size()) - 1;
            }
        }


        if (vals.size() == 0) {
            setError(1, "VaporPrepare-> No value found for parameter: %s", parMv.c_str());
            return false;
        }
        if (toIndex == 1 && byIndex == 3 && static_cast<int>(vals.size()) == 5) {
            int fromValue;
            istringstream issF(vals[0]);
            issF >> fromValue;

            int toValue;
            istringstream issT(vals[2]);
            issT >> toValue;

            int byValue;
            istringstream issB(vals[4]);
            issB >> byValue;

            if (fromValue >= 0 && fromValue < 500 &&
                toValue >= fromValue && toValue < 500 &&
                byValue > 0 && byValue <= 6)

            {
                int currentValue = fromValue;
                resVal           = vals[0];
                currentValue += byValue;
                while (currentValue <= toValue) {
                    stringstream sst;
                    sst << currentValue;
                    resVal.append("/" + sst.str());
                    currentValue += byValue;
                }
            }
        }
        else if (toIndex != -1 || byIndex != -1) {
            setError(1, "VaporPrepare-> Incorrect syntax used for parameter: %s", parMv.c_str());
            return false;
        }
        else {
            resVal = vals[0];
            for (unsigned int i = 1; i < vals.size(); i++) {
                resVal.append("/" + vals[i]);
            }
        }
    }

    /*if(isDate)
	{
		string s;
		if(getDate(resVal,s,parMv))
		{
			  resVal=s;
		}
		else
			resVal.clear();
	}*/


    if (!resVal.empty()) {
        return true;
    }
    else {
        if (canBeEmpty) {
            resVal = "_UNDEF_";
            return true;
        }
        else {
            setError(1, "VaporPrepare-> No value found for parameter: %s", parMv.c_str());
            return false;
        }
    }

    return false;
}

bool VaporPrep::checkGeometry(const string& areaStr, const string& gridStr)
{
    vector<string> vArea;
    Tokenizer parseA("/");
    parseA(areaStr, vArea);

    vector<string> vGrid;
    Tokenizer parseG("/");
    parseG(gridStr, vGrid);

    if (vArea.size() != 4 || vGrid.size() != 2)
        return false;

    vector<float> area;
    for (unsigned int i = 0; i < 4; i++) {
        istringstream iss(vArea[i]);
        float d;
        iss >> d;
        area.push_back(d);
    }

    vector<float> grid;
    for (unsigned int i = 0; i < 2; i++) {
        istringstream iss(vGrid[i]);
        float d;
        iss >> d;
        if (d <= 0.)
            return false;

        grid.push_back(d);
    }

    double trVal;

    float nx = (area[2] - area[0]) / grid[1];
    trVal    = trunc(nx);
    if (nx != trVal || static_cast<float>(static_cast<int>(trVal)) != trVal)
        return false;

    float ny = (area[3] - area[1]) / grid[0];
    trVal    = trunc(ny);
    if (ny != trVal || static_cast<float>(static_cast<int>(trVal)) != trVal)
        return false;


    return true;
}


bool VaporPrep::vdfExists(MvRequest& in, bool& err)
{
    string errTxt;
    err = false;

    //Add outpath
    string outPath;
    if (!in.getPath("VAPOR_OUTPUT_PATH", outPath, true)) {
        setError(0, "VaporPrepare-> parameter VAPOR_OUTPUT_PATH not defined");
        err = true;
        return false;
    }

    string vdfName;
    getParamValue(vdfName, in, "VAPOR_VDF_NAME");

    string vdfFile = outPath + "/" + vdfName + ".vdf";

    if (::access(vdfFile.c_str(), F_OK) != 0) {
        return false;
    }

    return true;
}


void VaporPrep::vapor_visualise(MvRequest& in, MvRequest& /*out*/)
{
    string vaporScript;
    char* mvbin = getenv("METVIEW_BIN");
    if (mvbin == 0) {
        setError(1, "VaporPrepare-> No METVIEW_BIN env variable is defined. Cannot locate mv_vapor_gui.mv macro!");
        return;
    }
    else {
        vaporScript = string(mvbin) + "/mv_vapor_gui";
    }

    string errTxt;

    //Add outpath
    string outPath;
    if (!in.getPath("VAPOR_OUTPUT_PATH", outPath, true)) {
        setError(1, "VaporPrepare-> parameter VAPOR_OUTPUT_PATH not defined");
        return;
    }

    //Add vdf filename
    string vdfName;
    VAPOR_CHK(getParamValue(vdfName, in, "VAPOR_VDF_NAME"));

    string logFile(marstmp());
    string vdfFile = outPath + "/" + vdfName + ".vdf";
    string cmd     = vaporScript + " \"" + vdfFile + "\" \"" + logFile + "\"";

    int ret = system(cmd.c_str());

    //If the script failed read log file and
    //write it into LOG_EROR
    if (ret == -1 || WEXITSTATUS(ret) != 0) {
        ifstream in(logFile.c_str());
        string line;

        if (WEXITSTATUS(ret) == 1) {
            marslog(LOG_EROR, "Failed to startup VAPOR!");
            while (getline(in, line)) {
                marslog(LOG_EROR, "%s", line.c_str());
            }
            in.close();
            setError(1, "VaporPrepare-> Failed to startup VAPOR!");
            return;
        }
        else if (WEXITSTATUS(ret) > 1) {
            marslog(LOG_EROR, "VAPOR startup failed with exit code: %d !", WEXITSTATUS(ret));
            while (getline(in, line)) {
                marslog(LOG_EROR, "%s", line.c_str());
            }
            in.close();
            setError(1, "VaporPrepare-> VAPOR startup failed with exit code: %d !", WEXITSTATUS(ret));
            return;
        }
    }
}

void VaporPrep::vapor_execute(MvRequest& in, MvRequest& out)
{
    string vaporMacro;
    char* mvbin = getenv("METVIEW_BIN");
    if (mvbin == 0) {
        setError(1, "VaporPrepare-> No METVIEW_BIN env variable is defined. Cannot locate mv_vapor_prep.mv macro!");
        return;
    }
    else {
        vaporMacro = string(mvbin) + "/mv_vapor_prep.mv";
    }

    vector<string> param;
    string str, errTxt;

    //Add outpath
    string outPath;
    if (!in.getPath("VAPOR_OUTPUT_PATH", outPath, true)) {
        setError(1, "VaporPrepare-> parameter VAPOR_OUTPUT_PATH not defined");
        return;
    }
    param.push_back(outPath);

    //Add vdf filename
    string vdfName;
    VAPOR_CHK(getParamValue(vdfName, in, "VAPOR_VDF_NAME"));
    param.push_back(vdfName);

    //==============================
    //
    // Execute
    //
    //==============================

    //Add reuse(check) input status
    string reuseVdf;
    VAPOR_CHK(getParamValue(str, in, "VAPOR_REUSE_VDF"));
    param.push_back((str == "ON") ? "1" : "0");


    //Add refinement level
    string refLevel;
    VAPOR_CHK(getParamValue(refLevel, in, "VAPOR_REFINEMENT_LEVEL"));
    param.push_back(refLevel);

    //Add vertical grid type
    string vGrid;
    VAPOR_CHK(getParamValue(vGrid, in, "VAPOR_VERTICAL_GRID_TYPE"));
    param.push_back(vGrid);

    if (vGrid == "LAYERED") {
        string hVar;
        VAPOR_CHK(getParamValue(hVar, in, "VAPOR_ELEVATION_PARAM"));
        param.push_back(hVar);

        //Add min-max vertical coordinate
        string vMin;
        VAPOR_CHK(getParamValue(vMin, in, "VAPOR_BOTTOM_COORDINATE"));
        param.push_back(vMin);

        string vMax;
        VAPOR_CHK(getParamValue(vMax, in, "VAPOR_TOP_COORDINATE"));
        param.push_back(vMax);
    }

    //Add are selection
    string areaSelect;
    VAPOR_CHK(getParamValue(areaSelect, in, "VAPOR_AREA_SELECTION"));
    param.push_back((areaSelect == "INTERPOLATE") ? "1" : "0");

    if (areaSelect == "INTERPOLATE") {
        //Add area
        string area;
        VAPOR_CHK(getParamValue(area, in, "VAPOR_AREA"));
        param.push_back(area);

        //Add grid resolution
        string grid;
        VAPOR_CHK(getParamValue(grid, in, "VAPOR_GRID"));
        param.push_back(grid);

        if (!checkGeometry(area, grid)) {
            setError(1, "VaporPrepare-> Inconsistency between grid area and resolution!");
            return;
        }
    }

    //Add steps numbers
    string stepParams;
    VAPOR_CHK(getParamValue(stepParams, in, "VAPOR_STEP_NUMBER"));
    param.push_back(stepParams);

    //Add surf params
    string surfParams;
    VAPOR_CHK(getParamValue(surfParams, in, "VAPOR_2D_PARAMS", true));
    param.push_back(surfParams);

    //Add upper params
    string upperParams;
    VAPOR_CHK(getParamValue(upperParams, in, "VAPOR_3D_PARAMS"));
    param.push_back(upperParams);

    //Create  tmp dir for vapor
    string tmpPath;
    if (!metview::createWorkDir("vapor", tmpPath, errTxt)) {
        setError(1, "VaporPrepare-> %s", errTxt.c_str());
        return;
    }
    param.push_back(tmpPath);

    //Add perparation mode
    string inputMode;
    VAPOR_CHK(getParamValue(inputMode, in, "VAPOR_INPUT_MODE"));
    param.push_back(inputMode);

    //Add input files
    if (inputMode == "ICON") {
        //Add mars expver
        vector<string> gribFiles;
        VAPOR_CHK(getInputFiles(gribFiles, in, "VAPOR_INPUT_DATA"));
        for (int i = 0; i < static_cast<int>(gribFiles.size()); i++)
            param.push_back(gribFiles[i]);
    }
    else {
    }

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

    string processName = MakeProcessName("VaporPrepare");
    MvRequest macroReq("MACRO");
    req("PATH")    = vaporMacro.c_str();
    req("_CLASS")  = "MACRO";
    req("_ACTION") = "execute";
    req("_REPLY")  = processName.c_str();

    //Define argument list for the macro!
    for (vector<string>::iterator it = param.begin(); it != param.end(); it++) {
        req.addValue("_ARGUMENTS", (*it).c_str());
    }

    //Run macro
    int error;
    //marslog(LOG_INFO,"Execute macro: %s",vaporMacro.c_str());
    MvRequest reply = MvApplication::waitService("macro", req, error);

    string logFile = tmpPath + "/m_log.txt";
    string errFile = tmpPath + "/m_err.txt";
    if (!error && reply) {
        marslog(LOG_INFO, "Log file is available at:");
        marslog(LOG_INFO, "---------------------------");
        marslog(LOG_INFO, "  %s", logFile.c_str());

        //const char* myname = reply("_REPLY");
        //MvApplication::callService(myname,reply,0);
    }
    else {
        ifstream in(errFile.c_str());
        string line;
        marslog(LOG_EROR, "Failed to perform VAPOR Prepare!");
        marslog(LOG_EROR, "Error message:");
        while (getline(in, line)) {
            marslog(LOG_EROR, "%s", line.c_str());
        }
        in.close();

        marslog(LOG_EROR, "Log file is available at:");
        marslog(LOG_EROR, "---------------------------");
        marslog(LOG_EROR, "  %s", logFile.c_str());

        setError(1, "VaporPrepare-> Failed to perform VAPOR Prepare!");
        return;
    }
    //reply.print();

    out                    = MvRequest("VAPOR_INPUT");
    out("INPUT_DATA_PATH") = outPath.c_str();
    //out("AVAILABLE_FILE_PATH")=fAvailable.c_str();
}


void VaporPrep::serve(MvRequest& in, MvRequest& out)
{
    cout << "--------------VaporPrepare::serve() in--------------" << endl;
    in.print();

    // Get user action mode
    const char* modeC = (const char*)in("_ACTION");
    string mode       = "execute";
    if (modeC)
        mode = string(modeC);

    if (mode == "visualise") {
        bool err    = false;
        bool hasVdf = vdfExists(in, err);

        if (err) {
            return;
        }
        else if (!hasVdf) {
            vapor_execute(in, out);
        }

        vapor_visualise(in, out);
    }
    else if (mode == "execute") {
        vapor_execute(in, out);
    }

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


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

    VaporPrep vapor;

    vapor.saveToPool(false);

    theApp.run();
}
