/***************************** 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 <algorithm>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include "fstream_mars_fix.h"

#include <unistd.h>

#include "MvMiscellaneous.h"


#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 std::string& dd, std::string& res, const std::string& parName);
    bool getParamValue(std::string& resVal, MvRequest& in, const std::string& parMv, bool canBeEmpty = false);
    bool checkGeometry(const std::string& areaStr, const std::string& gridStr);
    bool vdfExists(MvRequest&, bool&);
    bool getInputFiles(std::vector<std::string>& resVal, MvRequest& in, const std::string& parMv);
    void vapor_visualise(MvRequest&, MvRequest&);
    void vapor_execute(MvRequest&, MvRequest&);
};


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

    do {
        MvRequest rs = r.justOneRequest();
        if (const char* c = rs("PATH")) {
            resVal.push_back(std::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 std::string& dd, std::string& res, const std::string& parName)
{
    res = dd;

    if (dd.size() < 8) {
        std::istringstream iss(dd);
        double d;
        iss >> d;
        char buf[9];
        MvDate md(mars_julian_to_date(today() + d, 1));
        md.Format("yyyymmdd", buf);
        res = std::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(std::string& resVal, MvRequest& in, const std::string& parMv, bool canBeEmpty)
{
    int cnt = in.countValues(parMv.c_str());

    if (cnt == 1) {
        const char* cval = in(parMv.c_str());
        if (cval)
            resVal = std::string(cval);
    }
    else {
        std::vector<std::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(std::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;
            std::istringstream issF(vals[0]);
            issF >> fromValue;

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

            int byValue;
            std::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) {
                    std::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)
    {
        std::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 std::string& areaStr, const std::string& gridStr)
{
    std::vector<std::string> vArea;
    Tokenizer parseA("/");
    parseA(areaStr, vArea);

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

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

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

    std::vector<float> grid;
    for (unsigned int i = 0; i < 2; i++) {
        std::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)
{
    std::string errTxt;
    err = false;

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

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

    std::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*/)
{
    std::string vaporScript;
    char* mvbin = getenv("METVIEW_BIN");
    if (mvbin == nullptr) {
        setError(1, "VaporPrepare-> No METVIEW_BIN env variable is defined. Cannot locate mv_vapor_gui.mv macro!");
        return;
    }
    else {
        vaporScript = std::string(mvbin) + "/mv_vapor_gui";
    }

    std::string errTxt;

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

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

    std::string logFile(marstmp());
    std::string vdfFile = outPath + "/" + vdfName + ".vdf";
    std::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) {
        std::ifstream in(logFile.c_str());
        std::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)
{
    std::string vaporMacro;
    char* mvbin = getenv("METVIEW_BIN");
    if (mvbin == nullptr) {
        setError(1, "VaporPrepare-> No METVIEW_BIN env variable is defined. Cannot locate mv_vapor_prep.mv macro!");
        return;
    }
    else {
        vaporMacro = std::string(mvbin) + "/mv_vapor_prep.mv";
    }

    std::vector<std::string> param;
    std::string str, errTxt;

    // Add outpath
    std::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
    std::string vdfName;
    VAPOR_CHK(getParamValue(vdfName, in, "VAPOR_VDF_NAME"));
    param.push_back(vdfName);

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

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


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

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

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

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

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

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

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

        // Add grid resolution
        std::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
    std::string stepParams;
    VAPOR_CHK(getParamValue(stepParams, in, "VAPOR_STEP_NUMBER"));
    param.push_back(stepParams);

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

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

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

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

    // Add input files
    if (inputMode == "ICON") {
        // Add mars expver
        std::vector<std::string> gribFiles;
        VAPOR_CHK(getInputFiles(gribFiles, in, "VAPOR_INPUT_DATA"));
        for (auto& gribFile : gribFiles)
            param.push_back(gribFile);
    }
    else {
    }

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

    std::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 (auto& it : param) {
        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);

    std::string logFile = tmpPath + "/m_log.txt";
    std::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 {
        std::ifstream in(errFile.c_str());
        std::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)
{
    std::cout << "--------------VaporPrepare::serve() in--------------" << std::endl;
    in.print();

    // Get user action mode
    const char* modeC = (const char*)in("_ACTION");
    std::string mode = "execute";
    if (modeC)
        mode = std::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);
    }

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


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

    VaporPrep vapor;

    vapor.saveToPool(false);

    theApp.run();
}
