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

/********************************************************************************
 Important information:

 1. The Bottom/Top Levels will be computed automatically from the data, if
    they are not given as a input parameters.

 2. Relationship among fields and interpolation:

                  ML    ML&LNSP   PL    PL&LNSP
         INTY    ok      ok       ok     ok1
         INTN    ok     error     ok     ok1

   INTY/N = interpolation yes/no
   ok1    = LNSP is ignored

 3. If fieldset contains ML&LNSP, a conversion to PL will be computed automatically
 ********************************************************************************/

#define INITIALISE_HERE

#include "Xsect.h"

#include <cmath>
#include <iomanip>

#include "Average.h"
#include "CrossS.h"
#include "MvException.h"
#include "MvMiscellaneous.h"
#include "Vprofile.h"

Xsect::Xsect(const char* kw) :
    MvService(kw)
{
    init();
}

// we need an init function for the case were we don't fork - here, we
// will have to reset things between each request
void Xsect::init()
{
    ncName_ = "toto.nc";
    ntime_ = 0;
    geoIndexVar_ = XS_VARLON;
}

void Xsect::serve(MvRequest& in, MvRequest& out)
{
    // Error messages either via MvException or setError will be handled
    // by the caller (MvService::serve)

    init();

    std::cout << "request IN" << std::endl;
    in.print();

    // Get Input parameters
    // Error messages can come from an exception or from
    // a setError command
    origReq_ = in;
    MvRequest data;
    ApplicationInfo appInfo;
    if (!this->getInputParameters(in, data, appInfo))
        return;

    // Process data
    if (strcmp(data.getVerb(), "GRIB") == 0) {
        // Process a grib data and build the output request
        if (!this->processData(data, appInfo, out))
            return;
    }
    else {
        // It is a netCDF data and it has been already processed.
        // Build the output request
        out = this->createOutputRequest(in);
    }

    // Add hidden values to the output request
    // out1("_VERB") = "NETCDF_MATRIX";
    if ((const char*)in("_NAME"))
        out("_NAME") = in("_NAME");

    std::cout << "request OUT" << std::endl;
    out.print();

    return;
}

bool Xsect::processData(MvRequest& data, ApplicationInfo& appInfo, MvRequest& out)
{
    // Initialize fildset iterator
    MvFieldSet fs(data);
    MvFieldSetIterator iter(fs);
    MvField field = iter();

    // Retrieve initial info
    double X1 = 0., X2 = 0., Y1 = 0., Y2 = 0.;
    double north = 0., south = 0., east = 0., west = 0.;
    // TODO: figure oiut why this call sets the levelType_
    this->getInitialFieldInfo(field, appInfo, north, south, east, west);
    appInfo.getAreaLine(X1, X2, Y1, Y2);

    // Compute number of section points
    int npoint = this->computeGeographicalPoints(appInfo, &field);
    if (npoint == 0) {
        setError(1, "Xsect-> Invalid Area/Line!");
        return false;
    }
    if (appInfo.viaPole())
        setError(0, "Xsect line via Pole");

    // Allocate memory/initialise auxiliary variables
    auto* xint = new double[npoint];
    ParamMap params;

    // First loop: build field keystring and get the Level info.
    // The LNSP matrix data is computed, if it exists, but the other
    // matrices are computed in the second loop.
    boolean isPL = false;
    boolean isML = false;
    std::string keystr;
    bool foundU = false, foundV = false, foundW = false, foundT = false;
    bool foundGHBC = false;
    int iparam = 0, idate = 0, itime = 0, istep = 0;
    const char* expver = nullptr;
    iter.rewind();
    while ((field = iter())) {
        // Retrieve info from the field request
        MvRequest rq = field.getRequest();
        iparam = rq("PARAM");
        const char* levc = rq("LEVELIST");
        std::string lev = (levc) ? levc : "";
        istep = rq("STEP");
        idate = rq.getBaseDate();
        itime = rq("TIME");
        expver = rq("EXPVER");
        isML = field.isModelLevel();
        if (!isML)
            isPL = field.isPressureLevel();

        // Check that this field's vital statistics match those of the others
        // - this is important only in the Average application
        if (!fieldConsistencyCheck(field, north, south, east, west)) {
            deleteAuxiliaryMemory(xint, params);
            return false;
        }

        // Update level info accordingly
        if (lev.empty() || lev != "0" || field.levelTypeString() == cML_UKMO_ND_STR) {
            double dlev = field.level();
            if (field.levelTypeString() == cML_UKMO_ND_STR)
                dlev = field.level_L2();

            std::ostringstream s;
            s << dlev << std::ends;

            lev = s.str();
        }

        // Generate keys and insert level info
        // Calculate the data matrix, only for LNSP or GHBC fields
        generateKey(keystr, iparam, idate, itime, istep, expver);
        if (appInfo.isLNSP(iparam) || appInfo.isGHBC(iparam)) {
            if (appInfo.isLNSP(iparam))
                appInfo.haveLNSP(true);
            else
                // appInfo.haveGHBC(true);
                foundGHBC = true;

            // Calculate values along the transection line
            bool ok = fillValues(appInfo, field, xint);
            if (!ok) {
                // Error in computing data values. The error message is already written
                // in the marslog file. That said, the user should be told, otherwise
                // they stare at  a blank plot!
                setError(1, "Xsect-> Error when filling the values.");
                deleteAuxiliaryMemory(xint, params);
                return false;
            }

            // Create a new ParamInfo instance
            auto* par = new ParamInfo(iparam, idate, itime, istep,
                                      field.getGribKeyValueString("shortName"),
                                      field.getGribKeyValueString("name"),
                                      field.getGribKeyValueString("units"),
                                      field.getGribKeyValueString("levelType"));

            // Insert ParamInfo and the 'level' values
            ParamInsertPair inserted = params.insert(ParamPair(keystr, par));
            ParamInfo* pp = (*(inserted.first)).second;
            pp->Level(xint, lev, npoint, true);
            pp->ExpVer(expver ? expver : "_");
        }
        else {
            if (iparam == U_FIELD)
                foundU = true;
            else if (iparam == V_FIELD)
                foundV = true;
            else if (iparam == W_FIELD)
                foundW = true;
            else if (iparam == T_FIELD)
                foundT = true;

            // Create a new ParamInfo instance
            auto* par = new ParamInfo(iparam, idate, itime, istep,
                                      field.getGribKeyValueString("shortName"),
                                      field.getGribKeyValueString("name"),
                                      field.getGribKeyValueString("units"),
                                      field.getGribKeyValueString("levelType"));

            // Insert ParamInfo and only the 'level'info (not the values)
            ParamInsertPair inserted = params.insert(ParamPair(keystr, par));
            ParamInfo* pp = (*(inserted.first)).second;
            pp->AddLevel(lev);
            pp->ExpVer(expver ? expver : "_");
        }
    }  // end while (first loop)

    if (appInfo.haveGHBC() && !foundGHBC) {
        setError(1, "Xsect-> No verical coordinate field (paramId=%d) found in input!", appInfo.GHBC());
        return false;
    }

    // Update level processing type, i.e. XS_ML/XS_PL/XS_ML_LNSP/XS_ML_GHBC
    // This function will update an internal flag according to the type of
    // level from the input data. This information is only available after
    // the end of the First Loop above.
    appInfo.determineLevelType(isML, isPL);

    // Check if the input fieldset is compatible with the application
    // and with the parameters given in the UI
    if (this->consistencyCheck(params, appInfo) == false) {
        setError(1, "Xsect-> Fieldset consistency check failed");
        deleteAuxiliaryMemory(xint, params);
        return false;
    }

    // Update/initialize level type info
    appInfo.updateLevels(params);
    bool needScaleToPa = appInfo.levelType() == XS_PL;

    // Set default interpolation flag.
    // This can be updated later, e.g. velocityValues
    appInfo.setVerticalInterpolationFlag();

    // Update flags
    this->setHorCompFlags(foundU, foundV, foundW, foundT);

    // TODO: Review usints handling in minmax computations!!
    // Update min/max vertical axis values and number of levels
    int nlevel = 0;
    double pBottom = 0., pTop = 0.;
    appInfo.getMinMaxLevels(pBottom, pTop, nlevel);
    if (this->updateTopBottomLevels(appInfo, params, iter, pBottom, pTop, nlevel))
        appInfo.setMinMaxLevels(pBottom, pTop, nlevel);

    // Create/initialise netCDF structure
    MvNetCDF netcdf;
    iter.rewind();
    field = iter();
    int ntimes = 0;
    if (!this->initialiseNetcdf(netcdf, params, appInfo, ntimes, field, xint)) {
        deleteAuxiliaryMemory(xint, params);
        return false;
    }

    // Update application info: number of times
    appInfo.NTimes(ntimes);

    // Initialize variables
    std::string lastKey = "FIRSTFIELD";
    int currentGenerated = 0, lastNrGenerated = -1;
    MvField lastField;

    // Initialize fieldset iterator
    iter.rewind();
    iter.sort("LEVELIST");
    iter.sort("STEP");
    iter.sort("EXPVER");
    iter.sort("TIME");
    iter.sort("DATE");
    iter.sort("PARAM");

    // Second loop: generate data
    ParamIterator paramIter;
    while ((field = iter())) {
        currentGenerated++;
        MvRequest r = field.getRequest();
        iparam = r("PARAM");
        idate = r.getBaseDate();
        itime = r("TIME");
        istep = r("STEP");
        expver = r("EXPVER");

        // No need to process GHBC fields
        if (appInfo.isGHBC(iparam))
            continue;

        // Update level info accordingly
        const char* clevc = r("LEVELIST");
        std::string clev = (clevc) ? clevc : "";
        if (clev.empty() || clev != "0" || field.levelTypeString() == cML_UKMO_ND_STR) {
            double myLevel = field.level();
            if (field.levelTypeString() == cML_UKMO_ND_STR)
                myLevel = field.level_L2();

            std::ostringstream s;
            s << myLevel << std::ends;

            clev = s.str();
        }

        // Generate key and insert level info
        // Calculate the data matrix for non LNSP and GHBC fields
        generateKey(keystr, iparam, idate, itime, istep, expver);
        if (!appInfo.isLNSP(iparam) && !appInfo.isGHBC(iparam))  // already done in the first loop
        {
            bool ok = fillValues(appInfo, field, xint);
            if (!ok) {
                // Error in computing data values. The error message is already written
                // in the marslog file. That said, the user should be told, otherwise
                // they stare at  a blank plot!
                setError(1, "Xsect-> Error when filling the values.");
                deleteAuxiliaryMemory(xint, params);
                return false;
            }

            if ((paramIter = params.find(keystr)) == params.end()) {
                setError(0, "Xsect-> Something strange ??");
                continue;
            }
            else
                (*paramIter).second->Level(xint, clev, npoint, needScaleToPa);
        }

        // Write out the data if we have the end of what can be grouped together.
        if (lastKey != keystr && lastKey != std::string("FIRSTFIELD")) {
            if (!generateData(appInfo, params, netcdf, lastField, lastKey)) {
                setError(1, "Xsect-> Could not generate NETCDF data: error on fieldset or fieldset not suitable");
                deleteAuxiliaryMemory(xint, params);
                return false;
            }
            lastNrGenerated = currentGenerated;
        }

        lastKey = keystr;
        lastField = field;
    }  // end while (second loop)

    // Write out the last input field data
    if (lastNrGenerated <= currentGenerated) {
        if (!generateData(appInfo, params, netcdf, lastField, lastKey)) {
            setError(1, "Xsect-> Could not generate NETCDF data: error on fieldset or fieldset not suitable");
            deleteAuxiliaryMemory(xint, params);
            return false;
        }
    }

    // Write additional fields, if it is needed
    if (!this->generateExtraData(netcdf, appInfo, params, iter)) {
        setError(1, "Xsect-> Could not generate NETCDF extra data");
        deleteAuxiliaryMemory(xint, params);
        return false;
    }

    // Close netCDF file
    netcdf.close();

    // Get default parameter to be plotted
    ParamInfo* parInfo = plotVariable(appInfo, params);
    if (!parInfo) {
        deleteAuxiliaryMemory(xint, params);
        return false;
    }

    // Create output request
    out = this->createOutputRequest(appInfo, parInfo);

    deleteAuxiliaryMemory(xint, params);

    return true;
}

void Xsect::generateKey(std::string& str, int par, int date, int time,
                        int step, const char* expver)
{
    // Format: pPPPPPPYYYYMMDDHHmmSSSS
    std::ostringstream oss;
    oss << std::setfill('0')
        << "p"
        << std::setw(6) << par
        << std::setw(8) << date
        << std::setw(4) << time
        << std::setw(4) << step
        << std::setw(4) << (expver ? expver : "_");
    //<< std::ends;  // be careful, it adds one more character

    str = oss.str();
}

bool Xsect::updateTopBottomLevels(ApplicationInfo& appInfo, ParamMap& params, MvFieldSetIterator& iter, double& pBottom, double& pTop, int& nlevel)
{
    // TODO: improve this condition check!!!!!
    // Only computes min/max if bottom and top levels are not initialized
    const double eps = 1e-8;
    if (std::fabs(pTop) > eps || std::fabs(pBottom) > eps)
        return false;

    // Find the min/max levels for the first parameter that
    // is not LNSP or a surface field. It assumes that the
    // remaining fields have the same number of levels.
    double p = 0.;
    pTop = 50000000.;
    pBottom = -50000000.;
    ParamIterator ii;
    LevelMap lmap;
    LevelIterator jj;
    for (ii = params.begin(); ii != params.end(); ii++) {
        ParamInfo* par = (*ii).second;
        if (appInfo.isLNSP(par->Parameter()) || par->IsSurface()) {
            if (params.size() == 1) {
                // There is only the LNSP or a surface field
                nlevel = 1;
                pTop = pBottom = 1;
            }
            continue;
        }

        lmap = par->Levels();
        nlevel = lmap.size();
        for (jj = lmap.begin(); jj != lmap.end(); jj++) {
            p = (*jj).second->YValue();
            pTop = MIN(pTop, p);
            pBottom = MAX(pBottom, p);
        }
        break;
    }

    // Compute min/max pressure levels from ML&LNSP fieldset
    if (appInfo.levelType() == XS_ML_LNSP) {
        MvField field;
        iter.rewind();
        while ((field = iter())) {
            // Retrieve info from the field
            MvRequest rq = field.getRequest();
            int iparam = rq("PARAM");
            if (appInfo.isLNSP(iparam)) {
                double lnspMax = field.getGribKeyValueDouble("maximum");
                double lnspMin = field.getGribKeyValueDouble("minimum");
                pTop = field.meanML_to_Pressure_byLNSP(lnspMin, pTop);
                pBottom = field.meanML_to_Pressure_byLNSP(lnspMax, pBottom);
                break;
            }
        }
    }

    return true;
}

bool Xsect::isVisualise(ApplicationInfo& appInfo)
{
    // If the output is not 'visualisation' related: actions
    // "not visualise" or "prepare" (in certain cases), then
    // send back the "netCDF" request
    std::string actionMode = appInfo.actionMode();
    int procType = appInfo.processType();
    if (actionMode != "visualise" &&
        !(actionMode == "prepare" && procType == XS_DATA_MODULE_DROPPED) &&
        !(actionMode == "prepare" && procType == XS_DATA_DROPPED))
        return false;
    else
        return true;
}

// Retrieve parameters from the input request and make a consistency check.
// There are 4 types of input request:
// 1. DATA_DROPPED: Grib dropped into a View: MXSECT, DATA(), _CONTEXT(),...
// 2. DATA_MODULE_DROPPED: Data module dropped into a View: MXSECT, DATA(),...
// 3. DATA_MODULE: Process a data module: MXSECT, ...
// 4. netCDF dropped in a View:
//
// Actions for each 4 types:
// 1. Use the original View.
// 2. Use the original View and check if the input XSectData parameters are
//    consistent with the View.
// 3. Build a new View.
// 4. Use the original View and check if the netCDF parameters are consistent
//    with the View.
//
bool Xsect::getInputParameters(MvRequest& in, MvRequest& data, ApplicationInfo& appInfo)
{
    // Retrieve fieldset
    in.getValue(data, "DATA");
    if (!(int)in.countValues("DATA")) {
        setError(1, "Xsect-> No Data files specified...");
        return false;
    }

    // Retrieve original View if exists, e.g. if a data or a
    // xs/av/vpdata module was dropped into a view
    MvRequest viewRequest;
    if ((const char*)in("_CONTEXT"))
        viewRequest = in("_CONTEXT");

    // Retrieve xsectdata parameters if exist, e.g. if a xs/av/vpata module
    // was dropped into a view or if it was called to be processed
    const char* verb = (const char*)in("_VERB");
    bool moduleDropped = this->isDataModuleDropped(verb);

    // Check the type of input request to be processed
    MvRequest commonRequest;
    int procType;
    if (viewRequest)
        procType = moduleDropped ? XS_DATA_MODULE_DROPPED : XS_DATA_DROPPED;
    else
        procType = XS_DATA_MODULE;

    // Type of input request is a DATA_DROPPED into a View
    // Get information from the View request
    if (procType == XS_DATA_DROPPED) {
        // Retrieve 'original' request from the View
        commonRequest = getAppView(viewRequest);

        // Retrieve parameters
        if (!getAppParameters(commonRequest, appInfo))
            return false;
    }
    // Process a data module
    // Get information from the module icon parameters
    else if (procType == XS_DATA_MODULE) {
        // Retrieve parameters
        if (!this->getAppParameters(in, appInfo))
            return false;
    }
    // Process a data module dropped into a view
    // Get information from the module and view. Check if they are compatible.
    else if (procType == XS_DATA_MODULE_DROPPED) {
        // Retrieve 'original' request from the View
        commonRequest = getAppView(viewRequest);

        // Retrieve 'original' request from the data module
        MvRequest modRequest = data.getSubrequest("_ORIGINAL_REQUEST");
        if (!modRequest)
            modRequest = in;

        // Consistency check, only for non-default View request
        if (!((const char*)commonRequest("_DEFAULT") && (int)commonRequest("_DEFAULT") == 1))
            if (!this->consistencyCheck(commonRequest, modRequest))
                return false;

        // Retrieve application specific parametes
        if (!getAppParameters(modRequest, appInfo))
            return false;
    }

    // Retrieve action mode. Default value is "prepare"
    std::string actionMode = (const char*)in("_ACTION") ? (const char*)in("_ACTION") : "prepare";

    // Save info
    appInfo.actionMode(actionMode);
    appInfo.processType(procType);

    return true;
}

bool Xsect::checkCoordinates(double& X1, double& Y1, double& X2, double& Y2)
{
    // Update coordinates
    if (X1 > X2) {
        double W = X1;
        X1 = X2;
        X2 = W;
    }

    if (Y2 > Y1) {
        double W = Y1;
        Y1 = Y2;
        Y2 = W;
    }

    if (X1 == X2 && Y1 == Y2) {
        setError(1, "Xsect-> Not valid AREA or LINE coordinates");
        return false;
    }

    return true;
}

void Xsect::getInitialFieldInfo(MvField& field, ApplicationInfo& appInfo, double& north, double& south, double& east, double& west)
{
    // Get area and grid from the field
    north = field.north();
    south = field.south();
    east = field.east();
    west = field.west();
    double Grid_ns = field.gridNS();  //-- may return cValueNotGiven!
    double Grid_ew = field.gridWE();  //-- may return cValueNotGiven!

    // appInfo.setGlobe(North,South,East,West);
    appInfo.Grid(Grid_ns, Grid_ew);

// TODO: make absolutley sure this code is not needed!!
#if 0
    // Check model/pressure level settings
    int levType = XS_PL;  // -1 = ML, 0 = PL, 1 = LNSP+ML, cML_UKMO_ND = UKMO New Dynamics
    if (field.isModelLevel()) {
        if (field.levelTypeString() == cML_UKMO_ND_STR)
            levType = field.levelType();
        else
            levType = XS_ML;
    }
    appInfo.levelType(levType);
#endif
    // Check field's geographical grid point positions
    fieldConsistencyCheck(field, appInfo);
}

MvRequest Xsect::getAppView(MvRequest& viewRequest)
{
    // Check if the original view request has been already processed,
    // e.g. it has been already replaced by the CartesianView
    if ((const char*)viewRequest("_ORIGINAL_REQUEST"))
        return viewRequest("_ORIGINAL_REQUEST");

    // The original view request has not been processed yet.
    // Retrieve it from the input request
    std::string sview = viewRequest.getVerb();
    if (view_ == sview)
        return viewRequest;

    return {};
}

bool Xsect::consistencyCheck(ParamMap& params, ApplicationInfo& appInfo)
{
    // 1) Number of parameters to be processed must be greater than 0
    if (params.size() == 0) {
        setError(1, "Xsect-> GRIB file invalid, no parameters found");
        return false;
    }

    // 2) If there is only one parameter, it must contain several levels
    if (params.size() == 1) {
        auto ii = params.begin();
        if ((*ii).second->NrLevels() <= 1) {
            setError(1, "Xsect-> Number of levels must be greater than 1");
            return false;
        }
    }

    // 3) Options related to the level_selection_type parameters in the
    // user interface must be compatible to the input fieldset
    if (appInfo.levelType() == XS_ML && appInfo.levelSelectionType() != "FROM_DATA") {
        setError(1,
                 "Xsect-> Invalid value for parameter LEVEL_SELECTION_TYPE (=%s).\
                    It must be set to FROM_DATA because the input model level data does\
                    not contain LNSP!",
                 appInfo.levelSelectionType().c_str());
        return false;
    }

    return true;
}

// Write Time info to the netCDF file.
// The current implementation performs only a simple consistency check:
// the dimension has to be the same for all variables.
bool Xsect::writeTimeInfoNetcdf(MvNetCDF& cdf, ParamMap& params, int& ntimes)
{
    // Loop through all parameters
    std::vector<int> stepS;
    int idNew = 0;
    bool first = true;
    int ntimesNew = 0;
    auto ii = params.begin();
    int id = (*ii).second->Parameter();  // parameter id
    MvDate refDate = (*ii).second->ReferenceDate();
    while (ii != params.end()) {
        // Accumulate and check time consistency
        idNew = (*ii).second->Parameter();
        if (id != idNew) {
            if (first) {
                // Save number of times
                ntimes = ntimesNew;
                first = false;
            }
            else {
                // Check time consistency
                if (ntimes != ntimesNew) {
                    setError(1, "Xsect-> Data has more than one Time range");
                    return false;
                }
            }
            ntimesNew = 0;
            id = idNew;
        }
        else if (first)  // Add new time to the list
        {
            MvDate newDate = (*ii).second->VerificationDate();
            double diff = (newDate - refDate);
            int step = (int)(diff * 86400.);  // seconds
            stepS.push_back(step);
        }
        ntimesNew++;
        ii++;
    }

    // To handle the last variable time range
    if (first)
        ntimes = ntimesNew;
    else {
        // Check time consistency
        if (ntimes != ntimesNew) {
            setError(1, "Xsect-> Data has more than one Time range");
            return false;
        }
    }

    // Add the 'time' variable to netCDF
    std::vector<long> levels_dimsize(1, ntimes);
    std::vector<std::string> levels_name(1, XS_VARTIME);
    MvNcVar* nctime = cdf.addVariable(levels_name[0], ncInt, levels_dimsize, levels_name);
    char referenceDateString[128];
    refDate.Format(refDate.StringFormat(), referenceDateString);
    char unitsString[160];
    sprintf(unitsString, "seconds since %s", referenceDateString);
    nctime->addAttribute("long_name", "time");
    nctime->addAttribute("units", unitsString);
    nctime->addAttribute("reference_date", referenceDateString);
    nctime->addAttribute("calendar", "gregorian");
    nctime->put(stepS, (int)ntimes);

    return true;
}

bool Xsect::writeGlobalAttributesNetcdf(MvNetCDF& netcdf, ParamMap& params, ApplicationInfo& appInfo)
{
    // Add application info
    netcdf.addAttribute("_FillValue", XMISSING_VALUE);
    netcdf.addAttribute("_View", view_.c_str());
    netcdf.addAttribute("type", type_.c_str());
    std::string xsHorizontalMethod = (appInfo.isHorInterpolate()) ? "interpolate" : "nearest";
    netcdf.addAttribute("xsHorizontalMethod", xsHorizontalMethod.c_str());

    // Add title to the Global Attributes.
    // Currently, because Magics only checks the Global Attributes to
    // produce a title and this module plots only the first variable then
    // we are adding this code below. REMOVE it when Magics is capable to
    // produce a title from the Variable Attributes.
    ParamInfo* parInfo = this->plotVariable(appInfo, params);
    if (!parInfo)
        return false;

    std::string title = this->titleVariable(appInfo, parInfo);
    return netcdf.addAttribute("title", title.c_str());
}

ParamInfo* Xsect::plotVariable(ApplicationInfo& appInfo, ParamMap& params)
{
    // Return the first variable that has more than 1 level and
    // it is not a GHBC
    for (auto& param : params) {
        if (param.second->NrLevels() > 1 && !appInfo.isGHBC(param.second->Parameter()))
            return param.second;
    }

    setError(1, "Xsect-> Fieldset to be processed must have more than 2 levels");

    return nullptr;
}

bool Xsect::writeGeoCoordsNetcdf(MvNetCDF& cdf, ApplicationInfo& appInfo)
{
    // Compute lat/long interpolation points
    int npoint = appInfo.NrPoints();
    const std::vector<double>& lon = appInfo.getLongitude();
    const std::vector<double>& lat = appInfo.getLatitude();

    // Add Latitude value
    std::vector<std::string> coord_sdim(1, XS_VARLAT);
    std::vector<long> coord_ndim(1, npoint);
    MvNcVar* nccoord = cdf.addVariable(coord_sdim[0], ncDouble, coord_ndim, coord_sdim);
    nccoord->addAttribute("long_name", "latitude");
    nccoord->addAttribute("units", "degrees_north");
    nccoord->put(lat, (long)npoint);

    // Add Longitude value
    coord_sdim[0] = XS_VARLON;
    nccoord = cdf.addVariable(coord_sdim[0], ncDouble, coord_ndim, coord_sdim);
    nccoord->addAttribute("long_name", "longitude");
    nccoord->addAttribute("units", "degrees_east");
    nccoord->put(lon, (long)npoint);

    // Add a 'latitude for plotting' variable?
    if (appInfo.viaPole()) {
        if (appInfo.haveLatsForPlotting()) {
            const std::vector<double>& latForPlotting = appInfo.getLatitudeForPlotting();
            coord_sdim[0] = XS_VARLATPLOT;
            nccoord = cdf.addVariable(coord_sdim[0], ncDouble, coord_ndim, coord_sdim);
            nccoord->addAttribute("long_name", "latitude_plot");
            nccoord->addAttribute("units", "degrees_north");
            nccoord->put(latForPlotting, (long)npoint);
        }

        if (appInfo.haveLonsForPlotting()) {
            const std::vector<double>& lonForPlotting = appInfo.getLongitudeForPlotting();
            coord_sdim[0] = XS_VARLONPLOT;
            nccoord = cdf.addVariable(coord_sdim[0], ncDouble, coord_ndim, coord_sdim);
            nccoord->addAttribute("long_name", "longitude_plot");
            nccoord->addAttribute("units", "degrees_east");
            nccoord->put(lonForPlotting, (long)npoint);
        }
    }

    return true;
}

// Generate contour data ( for xsection or average).
bool Xsect::contourValues(ApplicationInfo& appInfo, ParamMap& params,
                          MvNetCDF& cdf, MvField& field, std::string key)
{
    std::vector<double> y_values;
    std::vector<double> cp;
    if (!appInfo.computeLevelMatrixValues(params, key, y_values, cp, field)) {
        setError(1, "Xsect-> Could not find the requested key field: %s", key.c_str());
        return false;
    }

    // Get parameter info
    auto ii = params.find(key);
    ParamInfo* par = (*ii).second;

    // Add/update variable to the netCDF
    int nrLevels = appInfo.outputLevels();
    int nrPoints = appInfo.NrPoints();
    std::string varname = this->getNetcdfVarname(par->ParamName());
    std::string levname = this->getNetcdfLevelVarname(varname);
    MvNcVar* nclevels = cdf.getVariable(levname);
    MvNcVar* ncv = cdf.getVariable(varname);
    if (!ncv || !nclevels)  // Create new variables
    {
        ntime_ = -1;

        // Initialise dimensions
        std::vector<long> values_ndim;
        values_ndim.push_back(appInfo.NTimes());
        values_ndim.push_back(nrLevels);
        std::vector<std::string> values_sdim;
        values_sdim.push_back(XS_VARTIME);
        values_sdim.push_back(XS_VARLEVEL);

        // Create variable 'levels'
        if (!nclevels) {
            std::string levname = getNetcdfLevelVarname(varname);
            nclevels = cdf.addVariable(levname, ncDouble, values_ndim, values_sdim);
            nclevels->addAttribute("long_name", "Atmospheric Levels");
            if (appInfo.levelType() == XS_PL || appInfo.levelType() == XS_ML_LNSP) {
                nclevels->addAttribute("units", "hPa");
                nclevels->addAttribute("positive", "down");
            }
        }

        // Create variable matrix to be processed
        if (!ncv) {
            values_ndim.push_back(nrPoints);
            values_sdim.push_back(getGeoIndexVar());
            ncv = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
            ncv->addAttribute("long_name", par->ParamLongName().c_str());
            ncv->addAttribute("units", par->Units().c_str());
            ncv->addAttribute("_FillValue", XMISSING_VALUE);
            ncv->addAttribute("title", titleVariable(appInfo, par).c_str());
        }
    }

    // Write level values to tne netCDF file
    int i = 0;
    ntime_++;
    nclevels->setCurrent(ntime_);

    // Pa -> hPa
    if (appInfo.levelType() == XS_PL || appInfo.levelType() == XS_ML_LNSP) {
        for (int j = 0; j < nrLevels; j++) {
            y_values[j] = appInfo.tohPa(y_values[j]);
        }
    }

    nclevels->put(y_values, 1, (long)y_values.size());

    // Write matrix values to the netCDF file
    for (i = 0; i < nrLevels; i++) {
        ncv->setCurrent(ntime_, i);
        ncv->put(&cp[i * nrPoints], 1, 1, (long)nrPoints);
    }

    return true;
}

// Variable name in netCDF has some restrictions, e.g. can not
// start with a non-alphabetic letter.
// RTTOV specifics:
// a) variable name SKT is replaced by t_ski
// b) variable name starting with non-alphabetic letter is
//    replaced by the following scheme: all non-alphabetic
//    letters are moved to the end, e.g. 10u becomes u_10
std::string Xsect::getNetcdfVarname(const std::string& name)
{
    // RTTOV specific
    if (name == "skt")
        return {"t_skin"};

    // Netcdf only accepts variable name starting with a alphabetic letter
    // Copy initial numbers
    unsigned int j = 0, i = 0;
    std::string snum;
    for (i = 0; i < name.size(); i++) {
        if (isalpha(name[i]))
            break;

        snum += name[i];
        j++;
    }

    // The name does not have numbers in the begining
    if (j == 0)
        return name;

    // The name has only numbers. Add a prefix.
    else if (j == name.size()) {
        std::string newname = "a_" + snum;
        return newname;
    }

    // The name has numbers in the begining. Move them to the end.
    std::string newname = name.substr(i, name.size() - i) + "_";
    newname += snum;

    return newname;
}

bool Xsect::isDataModuleDropped(const char* verb)
{
    return (verb && (strcmp(verb, "MXSECTION") == 0 || strcmp(verb, "MXAVERAGE") == 0 ||
                     strcmp(verb, "MVPROFILE") == 0 || strcmp(verb, "PM_XSECT") == 0 ||
                     strcmp(verb, "PM_AVERAGE") == 0 || strcmp(verb, "PM_VPROF") == 0));
}

MvRequest Xsect::createOutputRequest(MvRequest& in)
{
    // Create NetCDF output request
    MvRequest data = in.getSubrequest("DATA");
    MvRequest out1 = data.getSubrequest("_VISUALISE");
    MvRequest viewReq = data.getSubrequest("_VIEW_REQUEST");
    data.unsetParam("_VISUALISE");         // to avoid duplication of info
    data.unsetParam("_VIEW_REQUEST");      // to avoid duplication of info
    data.unsetParam("_ORIGINAL_REQUEST");  // to avoid duplication of info
    out1("NETCDF_DATA") = data;

    // Final output request
    // If an icon was dropped into a view, uPlot will ignore it.
    // Mode-specific options
    MvRequest out = viewReq + out1;
    return out;
}

void Xsect::deleteAuxiliaryMemory(double* xint, ParamMap& params)
{
    // Delete auxiliary array
    if (xint) {
        delete[] xint;
        xint = nullptr;
    }

    // Free memory for parameters
    for (auto& param : params)
        delete param.second;
}

bool Xsect::initialiseNetcdf(MvNetCDF& netcdf, ParamMap& params, ApplicationInfo& appInfo, int& ntimes, MvField& field, double* xint)
{
    // Create a temporary netCDF file
    ncName_ = (const char*)marstmp();
    netcdf.init(ncName_, 'w');
    if (!netcdf.isValid()) {
        setError(1, "Xsect-> Could not open netCDF file");
        return false;
    }

    // Write netCDF global attributes
    if (!this->writeGlobalAttributesNetcdf(netcdf, params, appInfo))
        return false;

    // Write initial variables to the netCDF file: levels, time,
    // geographical coordinates
    if (!this->writeLevelInfoNetcdf(netcdf, appInfo, params, &field, xint))
        return false;

    if (!this->writeTimeInfoNetcdf(netcdf, params, ntimes))
        return false;

    if (!this->writeGeoCoordsNetcdf(netcdf, appInfo))
        return false;

    return true;
}

MvNcVar* Xsect::getNetcdfVariable(MvNetCDF& cdf, const std::string& varname, ApplicationInfo& appInfo, const std::string& longname, ParamInfo* param)
{
    MvNcVar* ncv = cdf.getVariable(varname);
    if (!ncv)  // Create new variable
    {
        // Initialise dimensions
        std::vector<long> values_ndim;
        values_ndim.push_back(appInfo.NTimes());
        values_ndim.push_back(appInfo.outputLevels());
        values_ndim.push_back(appInfo.NrPoints());
        std::vector<std::string> values_sdim;
        values_sdim.push_back(XS_VARTIME);
        values_sdim.push_back(XS_VARLEVEL);
        values_sdim.push_back(XS_VARLON);

        // Create variable
        ncv = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
        ncv->addAttribute("long_name", longname.c_str());
        ncv->addAttribute("units", param->Units().c_str());
        ncv->addAttribute("_FillValue", XMISSING_VALUE);
        ncv->addAttribute("title", titleVariable(appInfo, param).c_str());
    }

    return ncv;
}

MvNcVar* Xsect::getNetcdfVariableLevel(MvNetCDF& cdf, const std::string& varname, ApplicationInfo& appInfo)
{
    MvNcVar* ncv = cdf.getVariable(varname);
    if (!ncv)  // Create new variable
    {
        // Initialise dimensions
        std::vector<long> values_ndim;
        values_ndim.push_back(appInfo.NTimes());
        values_ndim.push_back(appInfo.outputLevels());
        std::vector<std::string> values_sdim;
        values_sdim.push_back(XS_VARTIME);
        values_sdim.push_back(XS_VARLEVEL);

        // Create variable
        ncv = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
        ncv->addAttribute("long_name", "Atmospheric Levels");
        ncv->addAttribute("units", "hPa");
        ncv->addAttribute("positive", "down");
        ncv->addAttribute("_FillValue", XMISSING_VALUE);
    }

    return ncv;
}

MvRequest Xsect::buildMacroConverterRequest()
{
    const char* caux = origReq_.getVerb();
    MvRequest oreq(caux);
    oreq("_MACRO_DECODE_TAG") = 1;  // extra info for the Macro Converter
    oreq("_CLASS") = (const char*)origReq_("_CLASS") ? (const char*)origReq_("_CLASS") : caux;
    oreq("_VERB") = (const char*)origReq_("_VERB") ? (const char*)origReq_("_VERB") : caux;
    if (strcmp((const char*)oreq("_CLASS"), "RETRIEVE") == 0 ||
        strcmp((const char*)oreq("_CLASS"), "GRIB") == 0) {
        MvRequest retReq = origReq_("DATA");
        if ((const char*)retReq("_NAME"))
            oreq("_NAME") = (const char*)retReq("_NAME");
    }
    else if ((const char*)origReq_("_NAME"))
        oreq("_NAME") = (const char*)origReq_("_NAME");

    return oreq;
}

//------------------------------------------------------------------------------

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);
    CrossS c;
    Average a;
    Vprofile v;

    // The applications don't try to read or write from pool, this should
    // not be done with the new PlotMod.
    a.saveToPool(false);
    v.saveToPool(false);
    c.saveToPool(false);

    // These are Metview 3 definitions used for backwards compatibility.
    // Remove them later.
    CrossSM3 cM3;
    AverageM3 aM3;
    VprofileM3 vM3;
    cM3.saveToPool(false);
    aM3.saveToPool(false);
    vM3.saveToPool(false);

    theApp.run();
}
