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

//  Methods for the MacroConverter classes

#include "MacroConverter.h"

#include <fstream>
#include <set>
#include <string>
#include <vector>

#include "fstream_mars_fix.h"

#include <MvRequestUtil.hpp>
#include <MvPath.hpp>
#include "ObjectInfo.h"
#include "ObjectList.h"

// All the instances
DefaultMacroConverter defaultConverterInstance("Default");
ReadMacroConverter readConverterInstance("READ");
EmptyMacroConverter emptyConverterInstance("MULTI");

// Family converter
FamilyMacroConverter familyFMC("FAMILY");
FamilyMacroConverter divrotFMC("DIVROT_FAMILY");
FamilyMacroConverter vectorsFMC("VECTORS_FAMILY");
FamilyMacroConverter spectraFMC("SPECTRA_FAMILY");
FamilyMacroConverter pottFMC("POTT_FAMILY");
FamilyMacroConverter velstrFMC("VELSTR_FAMILY");
FamilyMacroConverter tephigramFMC("TEPHIGRAM_FAMILY");
FamilyMacroConverter tephidataFMC("TEPHIDATA_FAMILY");
FamilyMacroConverter odbFMC("ODBVISUALISER_FAMILY");
FamilyMacroConverter netcdfFMC("NETCDFPLUS_FAMILY");

// Hovmoeller application uses the Family converter
FamilyMacroConverter lineHFMC("LINE_HOVM");
FamilyMacroConverter areaHFMC("AREA_HOVM");
FamilyMacroConverter verticalHFMC("VERTICAL_HOVM");
FamilyMacroConverter expandHFMC("EXPAND_HOVM");

// Thermo dynamics application uses the Family converter
FamilyMacroConverter gribThermo("GRIB_THERMO");
FamilyMacroConverter bufrThermo("BUFR_THERMO");

CurveFamilyMacroConverter curveMC("CURVES_FAMILY");

// Simple Formula Family converter
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance("SIMPLE_FORMULA_FAMILY");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance1("SAMPLE_FORMULA_DOD");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance2("SAMPLE_FORMULA_DON");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance3("SAMPLE_FORMULA_NOD");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance4("SAMPLE_FORMULA_NON");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance5("SAMPLE_FORMULA_FD");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance6("SAMPLE_FORMULA_FN");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance7("SAMPLE_FORMULA_FDD");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance8("SAMPLE_FORMULA_FDN");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance9("SAMPLE_FORMULA_FND");
FormulaFamilyMacroConverter simpleformulafamilyConverterInstance10("SAMPLE_FORMULA_FNN");

ComputeMacroConverter computeConverterInstance("COMPUTE");

// Data converter
DataMacroConverter gribConverterInstance("GRIB");
DataMacroConverter bufrConverterInstance("BUFR");
DataMacroConverter cdfConverterInstance("NETCDF");
DataMacroConverter geoConverterInstance("GEOPOINTS");
DataMacroConverter odbConverterInstance("ODB_DB");
DataMacroConverter llmatrixConverterInstance("LLMATRIX");

MacroConverter&
ConverterTraits::DefaultObject()
{
    return defaultConverterInstance;
}

/////////////// DefaultMacroConverter ///////////////
Cached DefaultMacroConverter::Convert(MvRequest& req, ObjectInfo* oi)
{
    // Convert the request and return the object name
    Cached duName = ObjectInfo::GenerateName(req);
    Cached macroName = ObjectList::MacroName(req.getVerb());

    oi->ConvertRequestToMacro(req, PUT_END, duName, macroName);
    return duName;
}

Cached ReadMacroConverter::Convert(MvRequest& req, ObjectInfo* oi)
{
    Cached duName = ObjectInfo::GenerateName(req);
    oi->ConvertRequestToMacro(req, PUT_END, duName, "read");

    return duName;
}

Cached FamilyMacroConverter::Convert(MvRequest& req, ObjectInfo* oi)
{
    Cached returnName;
    MvRequest tmpReq = req;

    const char* name = tmpReq("_NAME");
    Cached duName = ObjectInfo::GenerateName(req);

    // Usually, first request contains just the family part.
    // If this is the case then skip it
    std::string sverb = (const char*)tmpReq.getVerb();
    if (sverb.empty()) {
        oi->PutNewLine("# ERROR: Could not convert the Family icon");
        returnName = "error";
    }
    std::size_t found = sverb.find("_FAMILY");
    if (found != std::string::npos)
        tmpReq.advance();

    if (name && !(const char*)tmpReq("_NAME"))
        tmpReq("_NAME") = name;

    // Get the macroname to call and convert it to lowercase.
    Cached familyType = ObjectList::MacroName(tmpReq.getVerb());

    returnName = ObjectInfo::SpaceToUnderscore(duName);

    oi->ConvertRequestToMacro(tmpReq, PUT_END, returnName, familyType);

    return returnName;
}

Cached FormulaFamilyMacroConverter::Convert(MvRequest& req, ObjectInfo* oi)
{
    // The new algorithm uses the mvimportDesktop module, which is used
    // by the MacroEditor (dropping icon).
    // The old algorithm uses its own code.
    // We need to review the whole MacroConverter function trying to
    // use a single algorithm for both MacroConverter and MacroEditor
#if 1
    // Get initial information
    const char* name = req("_NAME");
    std::string duName = (const char*)ObjectInfo::GenerateName(req);

    // First request contains just the family part, get the name and skip it
    //    const char* familyName = req.getVerb();

    // Call an external function
    std::string tmpName = marstmp();
    char buf[1024];
    //    sprintf(buf,"$METVIEW_BIN/mvimportDesktop \"%s\" %s \"%s\" 4", name, tmpName.c_str(), familyName);
    sprintf(buf, "$METVIEW_BIN/mvimportDesktop \"%s\" %s", name, tmpName.c_str());
    system(buf);

    // Decode commands
    std::ifstream in(tmpName.c_str());
    if (!in)  // Fall back on the incomplete way
    {
        oi->PutNewLine("# ERROR: Could not convert the Simple Formula Family icon");
    }
    else {
        // Copy command lines
        while (in.getline(buf, 1023)) {
            // Strip of empty and "#Importing ..." lines
            if (strlen(buf) != 0 && strncmp("# Importing :", buf, 13) != 0)
                oi->PutNewLine(buf);
        }
        in.close();

        // Convert duName to lowercase, as function mvimport uses lowercase
        std::transform(duName.begin(), duName.end(), duName.begin(), ::tolower);
    }

    unlink(tmpName.c_str());

    return duName.c_str();
#else

    MvRequest tmpReq = req;
    const char* name = req("_NAME");
    Cached duName = ObjectInfo::GenerateName(req);
    Cached returnName = ObjectInfo::SpaceToUnderscore(duName);

    // First request contains just the family part, so skip it
    tmpReq.advance();
    tmpReq = ObjectList::ExpandRequest(tmpReq, EXPAND_DEFAULTS);

    if (name && !(const char*)tmpReq("_NAME"))
        tmpReq("_NAME") = name;

    MvRequest defReq = ObjectList::CreateDefaultRequest(tmpReq.getVerb());

    const char *f, *p1, *p2, *oper;
    bool twoParams = false;
    std::string formula = " = ";
    std::string cp1, cp2;
    if (IsParameterSet(tmpReq, "PARAMETER_2")) {
        p1 = tmpReq("PARAMETER_1");
        p2 = tmpReq("PARAMETER_2");
        if (ObjectInfo::IsAName(p2))
            cp2 = ObjectInfo::SpaceToUnderscore(mbasename(p2));
        else
            cp2 = p2;

        twoParams = true;
    }
    else {
        p1 = tmpReq("PARAMETER");
        if (!p1)
            p1 = tmpReq("PARAMETER_1");

        if (!p1)
            return "ERROR CONVERTING FAMILY FORMULA";
    }

    if (ObjectInfo::IsAName(p1))
        cp1 = ObjectInfo::SpaceToUnderscore(mbasename(p1));
    else
        cp1 = p1;

    if (IsParameterSet(defReq, "FUNCTION")) {
        if (IsParameterSet(tmpReq, "FUNCTION"))
            f = tmpReq("FUNCTION");
        else
            f = defReq("FUNCTION");

        if (twoParams)
            formula = formula + f + "( " + cp1 + ", " + cp2 + ")";
        else
            formula = formula + f + "( " + cp1 + ")";
    }
    else {
        if (IsParameterSet(tmpReq, "OPERATOR"))
            oper = tmpReq("OPERATOR");
        else
            oper = defReq("OPERATOR");

        formula = formula + cp1 + " " + oper + " " + cp2;
    }

    Cached familyType = ObjectList::MacroName(tmpReq.getVerb());
    std::set<Cached> skipSet;
    bool onlySub = true;  // Tell ConvertRequestToMacro to only expand subrequests, not the current one

    oi->ConvertRequestToMacro(tmpReq, PUT_END, returnName, familyType, skipSet, onlySub);

    oi->PutNewLine(returnName + formula.c_str() + Cached("\n"));
    return returnName;
#endif
}


Cached CurveFamilyMacroConverter::Convert(MvRequest& req, ObjectInfo* oi)
{
    Cached returnName;

    Cached duName = ObjectInfo::GenerateName(req);
    Cached path = ObjectInfo::ObjectPath(req);

    // First request contains just the family part, so skip it
    req.advance();

    // Get the macroname to call and convert it to lowercase.
    const char* curveType = req.getVerb();

    returnName = ObjectInfo::SpaceToUnderscore(duName);

    if (!strcmp(curveType, "GRAPH")) {
        // Get the names for the curves and try to exand them.
        std::vector<Cached> curveNames;
        const char* name = nullptr;
        Cached oneName;
        for (int i = 0; i < req.countValues("CURVES"); i++) {
            req.getValue(name, "CURVES", i);
            Cached fileName = path + Cached("/") + Cached(name);
            if (FileCanBeOpened(fileName, "r")) {
                MvRequest iconRequest;
                iconRequest.read(fileName);
                oneName = oi->Convert(iconRequest);
                if (returnName)
                    curveNames.push_back(oneName);
            }
        }

        std::set<Cached> skipSet;
        skipSet.insert("CURVES");
        oi->ConvertRequestToMacro(req, PUT_LAST_COMMA, returnName, curveType, skipSet);
        if (curveNames.size() > 0) {
            Cached nameList = "[";
            for (unsigned int j = 0; j < curveNames.size(); j++) {
                if (j > 0)
                    nameList = nameList + Cached(", ");
                nameList = nameList + curveNames[j];
            }
            nameList = nameList + Cached(" ] ");
            oi->FormatLine("CURVES", nameList, "");
        }
        oi->PutNewLine(Cached("   ) "));
    }
    else
        oi->ConvertRequestToMacro(req, PUT_END, returnName, curveType);

    return returnName;
}

// Let mvimport do the work here. It means the formatting will be slightly
// different.
Cached ComputeMacroConverter::Convert(MvRequest& req, ObjectInfo* oi)
{
    std::string tmpName = marstmp();

    char buf[1024];
    sprintf(buf, "$METVIEW_BIN/mvimport \"%s\" %s",
            (const char*)req("_NAME"),
            tmpName.c_str());
    system(buf);

    Cached duName = ObjectInfo::GenerateName(req);

    std::ifstream in(tmpName.c_str());

    if (!in)  // Fall back on the incomplete way
    {
        const char* formula = req("FORMULA");
        oi->PutNewLine("# Remember to import any icons used in formula");
        oi->PutNewLine("# Just click in macro after this line, and drop the icons");
        oi->PutNewLine(duName + Cached("  = ") + Cached(formula));
    }
    else {
        while (in.getline(buf, 1023)) {
            // Strip of #Importing ... lines
            if (strncmp("# Importing :", buf, 13) != 0)
                oi->PutNewLine(buf);
        }
        in.close();

        // Convert duName to lowercase, as mvimport uses lowercase.
        char* dulower = new char[strlen(duName) + 1];
        strcpy(dulower, duName);

        for (unsigned int i = 0; i < strlen(dulower); i++)
            dulower[i] = tolower(dulower[i]);

        duName = dulower;
        delete[] dulower;
    }
    unlink(tmpName.c_str());

    return duName;
}


Cached DataMacroConverter::Convert(MvRequest& req, ObjectInfo* oi)
{
    // Obtain the object name
    Cached duName = ObjectInfo::GenerateName(req);

    // Obtain the fully qualified path to the object
    std::string filename;
    const char* mvPath = req("_PATH");
    if (mvPath)
        filename = mvPath;
    else {
        mvPath = req("_NAME");
        if (mvPath)
            filename = MakeUserPath(mvPath);
    }

    if (mvPath) {
        // Get the full path and check if it can be opened
        if (!FileCanBeOpened(filename.c_str(), "r"))
            filename = "Please provide here an input filename";

        oi->PutNewLine("");
        oi->PutNewLine(Cached("# Importing ") + duName);
        oi->PutNewLine(duName + Cached("  = read  ( \"") + filename.c_str() + Cached("\")"));
    }
    else
        duName = "";

    return duName;
}

Cached EmptyMacroConverter::Convert(MvRequest& /*req*/, ObjectInfo* /*oi*/)
{
    return {""};
}
