/***************************** 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 "HovToolkit.h"

#include <iomanip>
#include <sstream>

#include "MvException.h"
#include "MvRequestUtil.hpp"
#include "StatsCompute.h"
#include "MvLog.h"

std::string HovToolkit::moduleLabel_ = "Hovmoeller-> ";
std::map<std::string, HovToolkit::Statistics> HovToolkit::statsMap_ = {
    {"MEAN", HovToolkit::MeanStats},
    {"MAXIMUM", HovToolkit::MaxStats},
    {"MINIMUM", HovToolkit::MinStats},
    {"MEDIAN", HovToolkit::MedianStats},
    {"MODE", HovToolkit::ModeStats},
    {"STDEV", HovToolkit::StdevStats},
    {"VARIANCE", HovToolkit::VarianceStats}};


HovToolkit::~HovToolkit()
{
    this->ReleaseAllMemory();
}

void HovToolkit::ReleaseAllMemory()
{
    this->ReleaseMemory();
    this->ReleaseMemoryParam();
}

void HovToolkit::ReleaseMemory()
{
    coord1_.clear();
    coord2_.clear();
    if (xint_ != nullptr) {
        delete[] xint_;
        xint_ = nullptr;
    }
    if (netcdf_) {
        delete netcdf_;
        netcdf_ = nullptr;
    }

    ExpandCloseNcFile();

    return;
}

void HovToolkit::ReleaseMemoryParam()
{
    if (params_.size()) {
        for (auto& param : params_)
            delete param.second;

        params_.clear();
    }

    return;
}

// Main function. It is called from the Expand option
bool HovToolkit::Compute(std::string& netfnameIn, MvRequest& data, std::string action, MvRequest& out)
{
    // Initialise variables
    expand_ = true;
    netfnameIn_ = netfnameIn;
    nrIndexesIn_ = -1;
    actionMode_ = action;

    // Open previous netcdf file
    ExpandOpenNcFile();

    // Initialize HovToolkit object from a netcdf file
    if (!GetInputInfo(netcdfIn_))
        return false;

    // Create Param Info from the fieldset
    if (!CreateParamInfo(data))
        return false;

    // Generate data
    if (!this->GenerateData(data))
        return false;

    // Create output request
    out = CreateOutputRequest();

    // Close previous netcdf file
    ExpandCloseNcFile();

    return true;
}

// Main function. It is called from the basic option (Line,Area,Height)
bool HovToolkit::Compute(MvRequest& in, MvRequest& out)
{
    nrIndexesIn_ = 0;  // option is not Expand

    // Initialize HovToolkit object from the user interface
    if (!GetInputParameters(in))
        return false;

    // Process data
    if (strcmp(dataRequest_.getVerb(), "GRIB") == 0) {
        // Process a grib data and build the output request
        // Create Param Info from the fieldset. Start allocating memory.
        if (!CreateParamInfo(dataRequest_))
            return false;

        // Initialize reference time
        SetRefTime(dateMinD_);

        // Generate data. It allocates more memory.
        if (!this->GenerateData(dataRequest_))
            return false;

        // Create output request
        out = CreateOutputRequest();
    }
    else {
        // It is a netCDF data and it has been already processed.
        // Build the output request
        out = this->CreateOutputRequest(in);
    }

    // Add hidden values to tne output request
    if ((const char*)in("_NAME"))
        out("_NAME") = in("_NAME");

    return true;
}

MvRequest HovToolkit::CreateOutputRequest()
{
    // Create netCDF data request
    MvRequest req("NETCDF");
    req("PATH") = ncdfPath_.c_str();
    req("TEMPORARY") = 1;

    // Currently, the netcdf file contains only one parameter.
    // Update this code for more than one parameter
    auto ii = params_.begin();
    ParamInfo* par = (*ii).second;

    // Create netCDF output request
    MvRequest out1("NETCDF_XY_MATRIX");
    out1("NETCDF_PLOT_TYPE") = "XY_MATRIX";
    out1("NETCDF_DATA") = req;
    const char* secondCoordName = GetSecondCoordName();
    if (verticalAxis_ == HOV_TIME)  // TIME is vertical axis
    {
        out1("NETCDF_Y_VARIABLE") = HOV_VARTIME.c_str();

        out1("NETCDF_X_VARIABLE") = secondCoordName;
        if (SecondAuxiliaryCoord()) {
            out1("NETCDF_X_AUXILIARY_VARIABLE") = GetSecondAuxiliaryCoordName();
            out1("NETCDF_X_GEOLINE_CONVENTION") = (const char*)GeolineConvention().c_str();
        }
    }
    else  // TIME is horizontal axis
    {
        out1("NETCDF_X_VARIABLE") = HOV_VARTIME.c_str();
        out1("NETCDF_Y_VARIABLE") = secondCoordName;
        if (SecondAuxiliaryCoord()) {
            out1("NETCDF_Y_AUXILIARY_VARIABLE") = GetSecondAuxiliaryCoordName();
            out1("NETCDF_Y_GEOLINE_CONVENTION") = (const char*)GeolineConvention().c_str();
        }
    }

    out1("NETCDF_VALUE_VARIABLE") = getNetcdfVarname(par->ParamName()).c_str();
    out1("NETCDF_MISSING_ATTRIBUTE") = "_FillValue";
    out1("_VERB") = "NETCDF_XY_MATRIX";

    // Add information to help the Macro Converter to translate
    // the output request to a Macro code
    out1("_ORIGINAL_REQUEST") = this->buildMacroConverterRequest();

    // Create the View request taking values from the input application
    MvRequest viewReq = CreateViewRequest();

    // Save min/max date. This info is important if the dates in the original
    // View are 'automatic'.
    // Below, use value GetRefTime() instead of dateMinD_. Variavel dateMinD_
    // stores the minimum date value of the current input data file. Variavel
    // refTime_ (reference time) stores the minimum date value of the first
    // input data file. These two values are the same for applications LINE,
    // AREA and HEIGHT but not for EXPAND.
    MvDate dd(GetRefTime());
    viewReq("DATE_MIN") = dd.magicsDate().c_str();
    dd = dateMaxD_;
    viewReq("DATE_MAX") = dd.magicsDate().c_str();

    // If action is not visualisation related then return the netcdf data.
    // Also, add the visualisation and original requests as hidden parameters.
    // These may be used later to help the visualisation of the netcdf data.
    if (!IsVisualise()) {
        req("_VIEW") = "MHOVMOELLERVIEW";
        req("_VIEW_REQUEST") = viewReq;
        req("_VISUALISE") = out1;
        return req;
    }

    // Final output request
    MvRequest out = viewReq + out1;

    return out;
}

MvRequest HovToolkit::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;
}

// 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 HovData 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 HovToolkit::GetInputParameters(MvRequest& in)
{
    // Retrieve fieldset
    in.getValue(dataRequest_, "DATA");
    if (!in.countValues("DATA"))
        throw MvException(moduleLabel_ + "No Data file specified");

    // Get and save action mode. Default value is "prepare"
    std::string actionMode = (const char*)in("_ACTION") ? (const char*)in("_ACTION") : "prepare";
    this->ActionMode(actionMode);

    // Retrieve the Cartesian View request if exists, e.g. if a
    // grib data or a HovData module was dropped into a view
    MvRequest viewRequest;
    if ((const char*)in("_CONTEXT"))
        viewRequest = in("_CONTEXT");

    // Check if a HovData module was dropped
    const char* verb = (const char*)in("_VERB");
    bool moduleDropped = this->IsDataModule(verb);

    // Check the type of input request to be processed
    if (viewRequest)
        procType_ = moduleDropped ? HOV_DATA_MODULE_DROPPED : HOV_DATA_DROPPED;
    else
        procType_ = HOV_DATA_MODULE;

    // Type of input request is a data dropped into a View
    // Get information from the View request
    if (procType_ == HOV_DATA_DROPPED) {
        // Retrieve the HovmoellerView request from the CartesianView
        MvRequest hovViewReq = this->GetAppView(viewRequest);

        // Retrieve parameters
        if (!this->GetInputInfo(hovViewReq))
            return false;
    }
    // Process a data module
    // Get information from the module icon parameters
    else if (procType_ == HOV_DATA_MODULE) {
        // Retrieve parameters
        if (!this->GetInputInfo(in))
            return false;
    }
    // Process a data module dropped into a View.
    // Get information from the Module and View and check if they are compatible.
    else if (procType_ == HOV_DATA_MODULE_DROPPED) {
        // Retrieve the HovmoellerView request from the CartesianView
        MvRequest hovViewReq = this->GetAppView(viewRequest);

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

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

        // Retrieve parameters
        if (!this->GetInputInfo(hovViewReq))
            return false;
    }

    return true;
}

bool HovToolkit::GetInputCommonInfo(MvNetCDF* netcdf)
{
    // Retrieve information from the 'time' variable
    MvNcVar* var = netcdf->getVariable(HOV_VARTIME);
    long* edges = var->edges();
    nrIndexesIn_ = edges[0];
    MvNcAtt* tmpatt = var->getAttribute("reference_date");
    if (!tmpatt)
        throw MvException(moduleLabel_ + "Netcdf file: REFERENCE_DATE attribute not found");

    MvDate d1(tmpatt->as_string(0).c_str());
    refTime_ = d1.YyyyMmDd_r();

    // Retrieve information from the second axis variable
    var = netcdf->getVariable(GetSecondCoordName());
    edges = var->edges();
    nrPoints_ = edges[0];
    if (nrIndexesIn_ <= 0 || nrPoints_ <= 0)
        throw MvException(moduleLabel_ + "Netcdf file: invalid dimension for variables TIME and/or GEOGRAPHICAL");

    // Retrieve attributes values
    // Retrieve Grid values
    MvRequest req = netcdf->getRequest();
    const char* cp = (const char*)req("grid");
    if (cp == nullptr)
        throw MvException(moduleLabel_ + "Netcdf file: GRID attribute not found");

    std::string str = cp;
    gridEW_ = atof(strtok((char*)str.c_str(), "/"));
    gridNS_ = atof(strtok(nullptr, "/"));

    // Retrieve Coordinates values
    cp = (const char*)req("coordinates");
    if (cp == nullptr)
        throw MvException(moduleLabel_ + "Netcdf file: COORDINATES attribute not found");

    str = cp;
    y1_ = atof(strtok((char*)str.c_str(), "/"));
    x1_ = atof(strtok(nullptr, "/"));
    y2_ = atof(strtok(nullptr, "/"));
    x2_ = atof(strtok(nullptr, "/"));

    // Retrieve vertical axis flag
    cp = (const char*)req("vertical_axis");
    verticalAxis_ = (cp == nullptr) ? HOV_TIME : atoi(cp);

    return true;
}

bool HovToolkit::CreateParamInfo(MvRequest& data)
{
    // Clean param info structure
    this->ReleaseMemoryParam();

    // For ML to PL conversion the LNSP should be
    // collected before the data is processed
    bool skipLnsp = collectLnsp(data);

    // Initialize fieldset
    MvFieldSet fs(data);
    MvFieldSetIterator iter(fs);
    MvField field;

    // Retrieve basic information from the fieldset
    dateMinD_ = 99999999.;
    dateMaxD_ = 0.;
    std::string keystr, timekey;
    int iparam = 0;
    double level = 0.;
    const char* expver = nullptr;
    iter.rewind();
    iter.sort("LEVELIST", '>');
    //   iter.sort("STEP");
    //   iter.sort("TIME");
    //   iter.sort("DATE");
    iter.sort("EXPVER");
    iter.sort("PARAM");

    while ((field = iter())) {
        // MvFieldExpander	x(field);
        MvRequest rq = field.getRequest();
        iparam = rq("PARAM");
        level = rq("LEVELIST", 0);
        expver = rq("EXPVER");
        std::string ev = expver ? expver : "_";

        // Save basic information
        GenerateKey(keystr, rq);
        GenerateTimeKey(timekey, rq);

        // LNSP may be handled separately from the rest of the data
        if (!(skipLnsp && isParamLnsp(iparam))) {
            collectVerticalData(field, timekey, level);
            if (!isParamVertCoord(iparam)) {
                MvDate dd = GetDate(field);
                double d1 = dd.YyyyMmDd_r();

                // compute min/max time values
                if (dateMinD_ > d1)
                    dateMinD_ = d1;
                if (dateMaxD_ < d1)
                    dateMaxD_ = d1;

                // TODO: This seems to be a memory leak
                auto* par = new ParamInfo(iparam, d1, level, ev,
                                          field.getGribKeyValueString("shortName"),
                                          field.getGribKeyValueString("name"),
                                          field.getGribKeyValueString("units"));

                ParamInsertPair inserted = params_.insert(ParamPair(keystr, par));
                ParamInfo* pp = (*(inserted.first)).second;
                pp->AddIndex(timekey);
            }
        }
    }

    // Check if basic information was retrieved
    if (params_.size() == 0)
        throw MvException(moduleLabel_ + "GRIB file invalid, no parameters found");

    // Maybe this restriction should be removed in the future
    if (params_.size() > 1) {
        throw MvException(moduleLabel_ + "Creation of Hovmoeller diagrams from multiple parameters or vertical levels is not yet supported. This data has " + std::to_string(params_.size()) + " parameters or levels");
    }

    return true;
}

void HovToolkit::GenerateKey(std::string& str, MvRequest& rq)
{
    int par = rq("PARAM");
    int level = rq("LEVELIST", 0);

    std::ostringstream oss;
    oss << std::setfill('0')
        << std::setw(1) << 'p'
        << std::setw(3) << par
        << std::setw(4) << level
        << std::ends;

    str = oss.str();

    return;
}

void HovToolkit::GenerateTimeKey(std::string& str, MvRequest& rq)
{
    int istep = rq("STEP");
    int itime = rq("TIME");
    int idate = rq.getBaseDate();

    std::ostringstream oss;
    oss << std::setfill('0')
        << std::setw(8) << idate
        << std::setw(4) << itime
        << std::setw(HOV_STEPSIZE) << istep
        << std::ends;

    str = oss.str();

    return;
}

double HovToolkit::GetDateDif(std::string& timekey)
{
    // Extract date information
    double ddate = atof(timekey.substr(0, 8).c_str());
    double dhour = atof(timekey.substr(8, 2).c_str());
    double dmin = atof(timekey.substr(10, 2).c_str());
    double dstep = 0.;
    std::string strStep = timekey.substr(12, HOV_STEPSIZE);


    // Note that the step can be a negative number. Since it has been padded
    // with leading zeros, it could look like "000-72", which is not
    // correctly interpreted as a number. Therefore we need to remove leading
    // zeros before parsing it.
    std::size_t found = strStep.find_first_not_of("0");

    if (found != std::string::npos) {
        dstep = atof(strStep.substr(found, std::string::npos).c_str());  // parse from the first non-zero character
    }
    else {
        dstep = 0;  // nothing but zeros
    }

    // Compute hour+min+step in fraction of days
    double frac = dhour * 3600. / 86400. + dmin * 60. / 86400. + dstep * 3600. / 86400.;

    // If it is a monthly mean parameter, add 1 to original date
    if (fmod(ddate, 100) == 0.)
        ddate++;

    // Compute new date
    MvDate ndate(ddate);
    ndate += frac;

    // Compute the difference from the reference date
    MvDate ref(GetRefTime());
    double dif = ref.time_interval_hours(ndate);

    return dif;
}

MvDate HovToolkit::GetDate(MvField& field)
{
    // Compute initial date
    MvDate d1(field.yyyymmddFoh());

    // Add step
    double d2 = field.stepFoh();

    return (d1 + d2);
}

bool HovToolkit::GetTimeInfo()
{
    // Empty time variable
    vtime_.clear();

    // Get time series from the netCDF file (expand option)
    if (!this->ExpandGetTimeInfo())
        return false;

    // Get time series from the data
    this->GetTimeInfoFromParam();

    // Update number of times
    nrIndexes_ = vtime_.size();

    return true;
}

void HovToolkit::GetTimeInfoFromParam()
{
    // Get time series from the first variable
    double dd = 0.;
    std::string timekey;
    ParamInfo* par = (*params_.begin()).second;
    IndexesMap lmap = par->Indexes();
    for (auto& ii : lmap) {
        timekey = ii.first;
        dd = this->GetDateDif(timekey);
        vtime_.push_back(dd);
    }

    return;
}

bool HovToolkit::GenerateData(MvRequest& data)
{
    // Write initial info and variables to the netCDF
    if (!this->Initialiser())
        return false;

    // Allocate internal variables
    if (xint_ != nullptr) {
        delete[] xint_;
        xint_ = nullptr;
    }
    xint_ = new double[nrPoints_];

    // Sort data by level, expver and param
    MvFieldSet fs(data);
    MvFieldSetIterator iter(fs);
    iter.rewind();
    iter.sort("LEVELIST", '>');
    iter.sort("EXPVER");
    iter.sort("PARAM");

    // Generate data
    int currentGenerated = 0, lastNrGenerated = -1;
    std::string timekey, keystr;
    std::string lastKey = "FIRSTFIELD";
    MvField field, lastField;
    ParamIterator paramIter;
    while ((field = iter())) {
        currentGenerated++;

        // Create keys
        MvRequest rq = field.getRequest();
        GenerateKey(keystr, rq);
        GenerateTimeKey(timekey, rq);

        // Compute data
        if (!ComputeValues(field))
            return false;

        // Save data in the ParamInfo structure
        ParamInfo* par = this->GetParamInfo(keystr);
        if (!par)
            return false;

        par->FillIndex(xint_, timekey, nrPoints_);

        // Write out data, if it can be grouped together
        if (lastKey != keystr && lastKey != std::string("FIRSTFIELD")) {
            if (!NcWriteData(lastKey))
                return false;
            lastNrGenerated = currentGenerated;
        }

        lastKey = keystr;
        lastField = field;
    }

    // Write out the data in the netcdf file
    if (lastNrGenerated <= currentGenerated) {
        if (!NcWriteData(lastKey))
            return false;
    }
    netcdf_->close();

    return true;
}

std::string HovToolkit::statsName(Statistics s)
{
    for (auto it : statsMap_) {
        if (it.second == s) {
            return it.first;
        }
    }
    return {};
}

HovToolkit::Statistics HovToolkit::statsType(MvRequest& in)
{
    Statistics s = NoStats;
    if (const char* ch = (const char*)in("AREA_STATISTICS")) {
        return statsType(ch);
    }
    return s;
}

HovToolkit::Statistics HovToolkit::statsType(const char* name)
{
    Statistics s = NoStats;
    if (name) {
        std::string stStr(name);
        auto it = statsMap_.find(name);
        if (it != statsMap_.end()) {
            s = it->second;
        }
        else {
            throw MvException(moduleLabel_ + "Invalid AREA_STATISTICS=" + stStr);
        }
    }
    return s;
}


StatsComputePtr HovToolkit::makeStatsCompute() const
{
    StatsComputePtr comp = nullptr;
    switch (stats_) {
        case MeanStats:
            comp = metview::StatsComputePtr(new metview::MeanStatsCompute);
            break;
        case MaxStats:
            comp = metview::StatsComputePtr(new metview::MaxStatsCompute);
            break;
        case MinStats:
            comp = metview::StatsComputePtr(new metview::MinStatsCompute);
            break;
        case StdevStats:
            comp = metview::StatsComputePtr(new metview::StdevStatsCompute);
            break;
        case VarianceStats:
            comp = metview::StatsComputePtr(new metview::VarianceStatsCompute);
            break;
        case MedianStats:
            comp = metview::StatsComputePtr(new metview::MedianStatsCompute);
            break;
        default:
            throw MvException(moduleLabel_ + "Invalid internal statistics type=" + std::to_string(stats_));
            break;
    }
    return comp;
}

bool HovToolkit::Initialiser()
{
    // Create temporary netcdf file name
    ncdfPath_ = marstmp();
    if (netcdf_)
        delete netcdf_;
    netcdf_ = new MvNetCDF();
    netcdf_->init(ncdfPath_, 'w');

    // Write main attributes to the netCDF
    this->NcWriteGlobalAttributes();

    // Initialise time variable: compute and write to the netCDF
    if (!this->GetTimeInfo())
        return false;
    if (!this->NcWriteTimeInfo())
        return false;

    // Compute second coordinates: compute and write to the netCDF
    if (!this->ComputeSecondCoord())
        return false;
    if (!this->NcWriteSecondCoord())
        return false;

    return true;
}

void HovToolkit::NcWriteGlobalAttributes()
{
    // Write main attributes
    netcdf_->addAttribute("_View", "MHOVMOELLERVIEW");
    netcdf_->addAttribute("_FillValue", HMISSING_VALUE);
    netcdf_->addAttribute("type", ApplicationType());

    char buf[32];
    sprintf(buf, "%7.2f/%7.2f/%7.2f/%7.2f", y1_, x1_, y2_, x2_);
    netcdf_->addAttribute("coordinates", buf);

    sprintf(buf, "%5.2f/%5.2f", gridEW_, gridNS_);
    netcdf_->addAttribute("grid", buf);

    netcdf_->addAttribute("vertical_axis", verticalAxis_);

    // Write attributes specific to an application
    this->NcWriteGlobalAttributesApp();

    // Add title to the global attributes.
    // Currently, because Magics only checks the Global Attributes to
    // produce a title and Hovmoeller 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.
    auto ii = params_.begin();
    ParamInfo* par = (*ii).second;
    netcdf_->addAttribute("title", this->GetTitle(par).c_str());
}

// Write Time info to the netCDF file
bool HovToolkit::NcWriteTimeInfo()
{
    // Add 'time' variable to netCDF
    std::vector<long> v_ndim(1, nrIndexes_);
    std::vector<std::string> v_sdim(1, HOV_VARTIME);
    MvNcVar* nctime = netcdf_->addVariable(v_sdim[0], ncDouble, v_ndim, v_sdim);

    char buf[128];
    MvDate d1(this->GetRefTime());
    d1.Format(d1.StringFormat(), buf);
    nctime->addAttribute("long_name", "Time");
    nctime->addAttribute("units", "hours");
    nctime->addAttribute("reference_date", buf);
    nctime->put(vtime_, nrIndexes_);

    return true;
}

// We have all the values for one set of data
// Call functions to write the values to netcdf file
bool HovToolkit::NcWriteData(const std::string& key)
{
    // Get parameter info
    ParamInfo* par = this->GetParamInfo(key);
    if (!par)
        return false;

    // Generate the CDF variable
    std::vector<long> vdim(2);
    std::vector<std::string> vname(2);
    int nrIndexes = this->NrIndexes();
    if (verticalAxis_ == HOV_TIME) {
        vdim[0] = nrIndexes;
        vdim[1] = nrPoints_;
        vname[0] = HOV_VARTIME;
        vname[1] = this->GetSecondCoordName();
    }
    else {
        vdim[0] = nrPoints_;
        vdim[1] = nrIndexes;
        vname[0] = this->GetSecondCoordName();
        vname[1] = HOV_VARTIME;
    }
    MvNcVar* ncvar = netcdf_->addVariable(getNetcdfVarname(par->ParamName()), ncDouble, vdim, vname);

    // Write some attributes in the netcdf file
    this->NcAddMetadata(ncvar, par);

    // Write matrix in the netcdf file
    this->NcFillValues(ncvar, par);

    return true;
}

void HovToolkit::NcAddMetadata(MvNcVar* ncvar, ParamInfo* par)
{
    // Write parameter info
    ncvar->addAttribute("long_name", par->ParamLongName().c_str());
    ncvar->addAttribute("units", par->Units().c_str());
    ncvar->addAttribute("level", par->Level());
    ncvar->addAttribute("expver", par->ExpVer().c_str());
    ncvar->addAttribute("_FillValue", HMISSING_VALUE);

    // Write title
    std::string title = this->GetTitle(par);
    ncvar->addAttribute("title", title.c_str());

    return;
}

// Internal data organization:
//   .each line contains a set of averaged lat/long values related to
//    an especific time
//   .first line is related to the earliest time
//   .first value in each line is related to X1 (first longitude west) or
//    Y1 (first latitude south) or X1/Y1 (first longitude/latitude point).
// Output data organization:
//   .the matrix coordinates (0,0) corresponds to bottom/left corner
//   .2 output layouts:
//
//      A)           min lng     max lng
//         max time  -------------------
//                   ...................
//         min time  -------------------
//
//
//      B)           min time   max time
//         max lat   -------------------
//                   ...................
//         min lat   -------------------
//
void HovToolkit::NcFillValues(MvNcVar* ncvar, ParamInfo* par)
{
    // Create data matrix according to the input parameters
    // Write matrix to the netcdf file
    IndexesMap lmap = par->Indexes();
    int k = 0;

    if (verticalAxis_ == HOV_TIME)  // vertical time axis
    {
        // 'Expand' option: write previous values first
        int pos = 0;
        this->ExpandFillValues(ncvar, pos);
        IndexIterator ii;
        for (ii = lmap.begin(), k = 0; ii != lmap.end(); ii++, k++) {
            IndexesInfo* lInfo = (*ii).second;
            NcWriteValues(ncvar, lInfo, k + pos);
        }
    }
    else  // vertical geo/height axis: transpose values
    {
        // Retrieve all values from the internal structure
        int i = 0, j = 0;
        int nrInd = lmap.size();  // or nrIndexes_ - nrIndexesIn_
        std::vector<double> mat(nrInd * nrPoints_);
        IndexIterator ii;
        for (ii = lmap.begin(); ii != lmap.end(); ii++) {
            IndexesInfo* lInfo = (*ii).second;
            for (i = 0; i < nrPoints_; i++)
                mat[j++] = lInfo->Values()[i];
        }

        // Write matrix data to the netcdf file
        const char* key = ncvar->name();  // get variable name
        std::vector<double> cp(nrIndexes_);
        for (i = 0; i < nrPoints_; i++) {
            // 'Expand' option: write previous values
            if (!this->ExpandFillValues(key, i, cp))
                return;

            // Add values
            k = nrIndexesIn_;
            for (j = i; j < nrInd * nrPoints_; j += nrPoints_)
                cp[k++] = mat[j];

            // Update netcdf file
            ncvar->setCurrent(i);
            ncvar->put(cp, 1, (long)nrIndexes_);
        }
    }
}

void HovToolkit::NcWriteValues(MvNcVar* ncvar, IndexesInfo* lInfo, int pos)
{
    // Get values from the structure
    std::vector<double> cp(nrPoints_);
    for (int i = 0; i < nrPoints_; i++)
        cp[i] = lInfo->Values()[i];

    // Write values to a netcdf file
    ncvar->setCurrent(pos);
    ncvar->put(cp, 1, (long)nrPoints_);
}

ParamInfo* HovToolkit::GetParamInfo(const std::string& key)
{
    // Check if field is valid
    auto ii = params_.find(key);
    if (ii == params_.end()) {
        throw MvException(moduleLabel_ + "GetParamInfo: Could not find the requested field " + key);
    }

    // Get parameter info
    return (*ii).second;
}

MvRequest HovToolkit::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");
    else
        return viewRequest;
}

bool HovToolkit::IsVisualise()
{
    // If the output is not 'visualisation' related: actions
    // "not visualise" or "prepare" (in certain cases), then
    // send back the "netCDF" request
    if (actionMode_ != "visualise" &&
        !(actionMode_ == "prepare" && procType_ == HOV_DATA_MODULE_DROPPED) &&
        !(actionMode_ == "prepare" && procType_ == HOV_DATA_DROPPED))
        return false;
    else
        return true;
}

bool HovToolkit::IsDataModule(const char* verb)
{
    return (verb && (strcmp(verb, "LINE_HOVM") == 0 ||
                     strcmp(verb, "AREA_HOVM") == 0 ||
                     strcmp(verb, "VERTICAL_HOVM") == 0 ||
                     strcmp(verb, "LINE_HOV") == 0 ||
                     strcmp(verb, "AREA_HOV") == 0 ||
                     strcmp(verb, "HEIGHT_HOV") == 0));
}

// Variable name in netCDF has some restrictions, e.g. can not
// start with a non-alphabetic letter.
std::string HovToolkit::getNetcdfVarname(std::string name)
{
    // Netcdf only accepts variable name starting with a alphabetic letter
    if (!isalpha(name[0])) {
        std::string newname = "a_" + name;
        return newname;
    }

    return name;
}

void HovToolkit::ExpandOpenNcFile()
{
    // Return if option is not Expand
    if (!expand_)
        return;

    // Initialize the input netcdf file
    if (netcdfIn_)
        delete netcdfIn_;

    netcdfIn_ = new MvNetCDF();
    netcdfIn_->init(netfnameIn_, 'r');

    return;
}

void HovToolkit::ExpandCloseNcFile()
{
    if (netcdfIn_) {
        delete netcdfIn_;
        netcdfIn_ = nullptr;
    }

    return;
}

void HovToolkit::ExpandFillValues(MvNcVar* ncvar, int& pos)
{
    // Return if option is not Expand
    if (!expand_)
        return;

    // Copy values from the input netcdf file
    std::vector<double> vals;
    std::string key = ncvar->name();  // get variable name
    MvNcVar* ncvarIn = netcdfIn_->getVariable(key.c_str());
    long* edges = ncvarIn->edges();
    ncvarIn->get(vals, edges);
    ncvar->setCurrent(pos);
    ncvar->put(vals, edges[0], edges[1]);
    pos += edges[0];

    return;
}

bool HovToolkit::ExpandFillValues(const char* key, int pos, std::vector<double>& cp)
{
    // Return if option is not Expand
    if (!expand_)
        return true;

    // Get values from the input netcdf file
    std::vector<double> vals;
    MvNcVar* ncvarIn = netcdfIn_->getVariable(key);
    long* edges = ncvarIn->edges();
    ncvarIn->setCurrent(pos);
    ncvarIn->get(vals, 1, edges[1]);

    // Copy values
    for (unsigned int i = 0; i < vals.size(); i++)
        cp[i] = vals[i];

    return true;
}

bool HovToolkit::ExpandGetTimeInfo()
{
    // Return if option is not Expand
    if (!expand_)
        return true;

    // Check input file
    if (!netcdfIn_)
        throw MvException(moduleLabel_ + "Input NetCDF file not accessible");

    // Copy 'time' values from the input file
    std::vector<double> vals;
    MvNcVar* tvar = netcdfIn_->getVariable(HOV_VARTIME);
    long* edges = tvar->edges();
    nrIndexesIn_ = edges[0];
    tvar->get(vals, edges);
    for (double& val : vals)
        vtime_.push_back(val);

    return true;
}

MvRequest HovToolkit::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;
}

bool HovToolkit::isParamLnsp(int paramId) const
{
    return paramId == lnspParamId_;
}

bool HovToolkit::isParamVertCoord(int paramId) const
{
    return paramId == vertCoordParamId_;
}
