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

#include <dirent.h>
#include <iomanip>
#include <iostream>

#include "MvMiscellaneous.h"

#define GAIA_PATH_PREFIX_DEFAULT "/home/operador/gaia/"

void Gaia::serve(MvRequest& in, MvRequest& out)
{
    std::cout << "Gaia::serve in..." << std::endl;
    in.print();

    // Get some input arguments
    if (!get_common_input_info(in))
        return;

    // Build the path
    if (!build_path(in))
        return;

    // Build the filename(s) (full path)
    std::vector<std::string> vfiles;
    if (!build_filenames(in, vfiles)) {
        std::string error = "Gaia-> FILE(S) NOT FOUND: ";
        if (!vfiles.empty())
            error += vfiles[0].c_str();
        setError(1, error.c_str());
        return;
    }

    // Handling Observation data
    std::string outname;
    std::string stype = (const char*)in("TYPE");
    if (stype == "OBSERVATION") {
        outname = this->bufr_obs(vfiles);
        if (outname.empty()) {
            std::string error = "Gaia-> ERROR: Empty BUFR output file";
            setError(1, error.c_str());
            return;
        }

        // Create output request
        out.setVerb("BUFR");
        out("PATH") = outname.c_str();
    }

    // Handling model data
    else if (stype == "MODEL") {
        std::string smodel = (const char*)in("MODEL");
        if (smodel == "WW3_ICON" || smodel == "WW3_COSMO" ||
            smodel == "WW3_GFS" || smodel == "ADCIRC") {
            // FAMII20200812: at the moment, returns only one netcdf file
            // Check if file exists
            std::string ffile = vfiles[0];
            FILE* f = fopen(ffile.c_str(), "r");
            if (!f) {
                std::string error = "Gaia-> FILE NOT FOUND: ";
                error += ffile.c_str();
                setError(1, error.c_str());
                return;
            }
            fclose(f);

            // Create output request
            out.setVerb("NETCDF");
            out("PATH") = ffile.c_str();
        }
        else {
            if (smodel == "ICON")
                outname = this->grib_icon_model(vfiles);
            else if (smodel == "ICON_SINGLE")
                outname = this->grib_icon_single_model(vfiles);
            else if (smodel == "COSMO" || smodel == "COSMO_ANT")
                outname = this->grib_cosmo_wrf_gfs_model(vfiles);
            else if (smodel == "WRF")
                outname = this->grib_cosmo_wrf_gfs_model(vfiles);
            else if (smodel == "GFS")
                outname = this->grib_cosmo_wrf_gfs_model(vfiles);
            else {
                std::string error = "Gaia-> ERROR: MODEL GEONETCAST not implemented yet";
                setError(1, error.c_str());
                return;
            }

            if (outname.empty()) {
                std::string error = "Gaia-> ERROR: Empty GRIB output file";
                setError(1, error.c_str());
                return;
            }

            // Create output request
            out.setVerb("GRIB");
            out("PATH") = outname.c_str();
        }
    }

    // Handling GeonetCast data
    else if (stype == "GEONETCAST") {
        std::string error = "Gaia-> ERROR: MODEL GEONETCAST not implemented yet";
        setError(1, error.c_str());
        return;
    }

    std::cout << "Gaia::serve out..." << std::endl;
    out.print();

    return;
}

bool Gaia::get_common_input_info(const MvRequest& in)
{
    // Get all values related to parameter DATE
    if (!in.getValue("DATE", dates_, false))
        return false;

    // Get all values related to parameter TIME
    // OBS: this version only allowed one time value, but
    // the algorithm below accepts many values
    int ntimes = in.countValues("TIME");
    times_.clear();
    times_.reserve(ntimes);
    for (int i = 0; i < ntimes; ++i)
        times_.push_back((int)in("TIME", i) / 100);  // hhmm

    // Get grid resolution
    if (strcmp((const char*)in("GRID"), "OFF") == 0)
        resX_ = resY_ = 0.;
    else {
        resX_ = (double)in("GRID", 0);
        resY_ = (double)in("GRID", 1);
    }

    return true;
}

bool Gaia::build_path(MvRequest& in)
{
    // 1. Fixed part of the path. Try to get it from the environment variable:
    // METVIEW_GAIA_PATH. If not defined, use a default value.
    const char* fpath = getenv("METVIEW_GAIA_PATH");
    spath_ = fpath ? fpath : GAIA_PATH_PREFIX_DEFAULT;

    // 2. Part of the path related to the TYPE
    std::string stype = (const char*)in("TYPE");
    spath_ = spath_ + stype + "/";
    if (stype == "MODEL") {
        std::string smodel = (const char*)in("MODEL");
        spath_ = spath_ + smodel + "/";
    }
    else if (stype == "OBSERVATION") {
        std::string sobs = (const char*)in("OBSTYPE");
        spath_ = spath_ + sobs + "/";
    }
    else if (stype == "GEONETCAST") {
        // spath = spath + ;
    }

    return true;
}

bool Gaia::build_filenames(MvRequest& in, std::vector<std::string>& vfn)
{
    // Part of the filename related to the model
    std::string stype = (const char*)in("TYPE");
    if (stype == "MODEL") {
        if (!build_filenames_model(in, vfn))
            return false;
    }
    else if (stype == "GEONETCAST") {
        std::string error = "Gaia-> ERROR: GEONETCAST not implemented yet ";
        setError(1, error.c_str());
        return false;
    }
    else if (stype == "OBSERVATION") {
        if (!build_filenames_observation(in, vfn))
            return false;
    }
    else {
        std::string error = "Gaia-> ERROR: TYPE not recognized ";
        setError(1, error.c_str());
        return false;
    }

    for (std::size_t i = 0; i < vfn.size(); i++)
        std::cout << "FILENAMES: " << vfn[i] << std::endl;

    return true;
}

void Gaia::left_pad_string(const std::vector<int>& vin, int npad,
                           std::vector<std::string>& vout)
{
    std::stringstream ss;
    vout.clear();
    for (std::size_t i = 0; i < vin.size(); ++i) {
        ss << std::setw(npad) << std::setfill('0') << vin[i];
        vout.push_back(ss.str());
        ss.str("");
    }
}

bool Gaia::build_filenames_model(MvRequest& in, std::vector<std::string>& vfn)
{
    int i, n;

    // Convert TIME values from int to string
    std::vector<std::string> stimes;
    left_pad_string(times_, 2, stimes);

    // Handle model WW3
    std::string smodel = (const char*)in("MODEL");
    if (smodel == "WW3_ICON" || smodel == "WW3_COSMO" || smodel == "WW3_GFS")
        return build_filenames_ww3(in, stimes, vfn);

    // Handle model ADCIRC
    if (smodel == "ADCIRC")
        return build_filenames_adcirc(in, vfn);

    // Get STEP values: int and string formats
    // If STEP is not given or its single value is ALL then variable
    // isteps_ is empty
    isteps_.clear();
    if ((const char*)in("STEP")) {
        n = in.countValues("STEP");
        if (n == 1 && strcmp((const char*)in("STEP", 0), "ALL") == 0) {
            // input value is ALL -> continue
        }
        else {
            isteps_.reserve(n);
            for (i = 0; i < n; ++i)
                isteps_.push_back((int)in("STEP", i));
        }
    }
    std::vector<std::string> ssteps;
    left_pad_string(isteps_, 3, ssteps);

    // Get LEVEL_TYPE value
    slevel_type_ = (const char*)in("LEVEL_TYPE");
    std::string sltype;
    if (slevel_type_ == "10_METER")
        sltype = "sfc";
    else
        sltype = slevel_type_;

    // Get LEVEL_LIST values: int and string formats
    // If LEVEL_LIST is not given or its single value is ALL then variable
    // ilevels_ is empty
    ilevels_.clear();
    if ((const char*)in("LEVEL_LIST")) {
        n = in.countValues("LEVEL_LIST");
        if (n == 1 && strcmp((const char*)in("LEVEL_LIST", 0), "ALL") == 0) {
            // input value is ALL -> continue
        }
        else {
            ilevels_.reserve(n);
            for (i = 0; i < n; ++i)
                ilevels_.push_back((int)in("LEVEL_LIST", i));
        }
    }
    std::vector<std::string> slevels;
    left_pad_string(ilevels_, 0, slevels);

    // Get PARAMETER values
    if (!in.getValue("PARAMETER", sparams_, false))
        return false;

    // Build filenames
    if (smodel == "ICON") {
        if (!build_filenames_icon(stimes, slevels, sltype, vfn))
            return false;
    }
    else if (smodel == "ICON_SINGLE") {
        if (!build_filenames_icon_single(stimes, vfn))
            return false;
    }
    else if (smodel == "COSMO" || smodel == "COSMO_ANT") {
        std::string cosmo_type = (const char*)in("MODEL");
        if (!build_filenames_cosmo(cosmo_type, stimes, ssteps, vfn))
            return false;
    }
    else if (smodel == "WRF") {
        std::string region = (const char*)in("REGION_WRF");
        std::string resol = (const char*)in("RESOLUTION_WRF");
        if (!build_filenames_wrf(region, resol, stimes, ssteps, vfn))
            return false;
    }
    else if (smodel == "GFS") {
        if (!build_filenames_gfs(stimes, ssteps, vfn))
            return false;
    }
    else {
        std::string error = "Gaia-> ERROR: MODEL not recognized ";
        setError(1, error.c_str());
        return false;
    }

    return true;
}

bool Gaia::build_filenames_observation(MvRequest& in,
                                       std::vector<std::string>& vfn)
{
    // Get user parameters
    std::string ssat = (const char*)in("SATELLITE_TYPE");
    metview::toLower(ssat);
    std::string stype = (const char*)in("OBSTYPE");
    long ltime1 = (int)in("INITIAL_TIME");
    long ltime2 = (int)in("FINAL_TIME");
    double dres = (double)in("RESOLUTION_SAT");

    // Build filenames
    vfn.clear();
    if (stype == "ASCAT") {
        std::string sdate_dir, fn_prefix, fn_suffix;
        std::string sfn = "ascat_";
        if (dres == 25.)
            fn_suffix = "eps_o_250.l2_bufr";  // "eps_0_250.l2_bufr";
        else
            fn_suffix = "eps_o_coa_ovw.l2_bufr";

        // Build filename with wildcards representing time and orbit
        for (std::size_t i = 0; i < dates_.size(); i++) {
            sdate_dir = spath_ + dates_[i] + "/";
            // sfn = fn_prefix + dates_[i] + "_*_" + ssat + "_*_" + fn_suffix;
            fn_prefix = sfn + dates_[i];
            if (!get_filenames(sdate_dir, fn_prefix, fn_suffix, ssat, ltime1,
                               ltime2, vfn)) {
                vfn.insert(vfn.begin(), sdate_dir + fn_prefix + "*");
                return false;
            }
        }
    }
    else {
        std::string error = "Gaia-> ERROR: OBSTYPE not recognized ";
        setError(1, error.c_str());
        return false;
    }

    return vfn.empty() ? false : true;
}

bool Gaia::build_filenames_ww3(MvRequest& in, std::vector<std::string>& vtimes,
                               std::vector<std::string>& vfn)
{
    // Build filename prefix
    std::string fn_prefix;
    std::string smodel = (const char*)in("MODEL");
    if (smodel == "WW3_COSMO")
        fn_prefix = "ww3cosmo_met_";
    else {
        std::string val;
        if (!in.getValue("REGION_WW3", val, false))
            return false;
        if (smodel == "WW3_ICON")
            fn_prefix = "ww3icon_" + val + "_";
        else if (smodel == "WW3_GFS")
            fn_prefix = "ww3gfs_" + val + "_";
    }

    // Build filenames
    std::string sdate_dir, stime;
    std::string fn_suffix = ".nc";
    for (std::size_t i = 0; i < dates_.size(); i++) {
        sdate_dir = spath_ + dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            stime = fn_prefix + dates_[i] + vtimes[j];
            vfn.push_back(sdate_dir + stime + fn_suffix);
        }
    }

    return true;
}

bool Gaia::build_filenames_adcirc(MvRequest& in, std::vector<std::string>& vfn)
{
    // Get PARAMETER values
    if (!in.getValue("PARAMETER", sparams_, false))
        return false;

    // Get AREA
    std::string sarea;
    if (!in.getValue("REGION_ADC", sarea, false))
        return false;

    // Initializations
    std::string fn_prefix = std::string("ADCIRC_") + sarea + std::string("_");
    ;
    std::string fn_suffix = "_graderegular.nc";

    // Build filenames
    std::string sdate_dir, spar;
    for (std::size_t i = 0; i < dates_.size(); i++) {
        sdate_dir = spath_ + dates_[i] + "/";
        for (std::size_t j = 0; j < sparams_.size(); j++) {
            spar = sparams_[j] + std::string("_") + dates_[i];
            vfn.push_back(sdate_dir + fn_prefix + spar + fn_suffix);
        }
    }

    return true;
}

bool Gaia::build_filenames_icon(std::vector<std::string>& vtimes,
                                std::vector<std::string>& vlev,
                                std::string& sltype, std::vector<std::string>& vfn)
{
    // Level list is ALL
    std::vector<std::string> vlevels = vlev;
    if (sltype != "sfc" && vlevels.empty())
        vlevels = {"1000", "850", "500", "250"};

    // Convert all parameter names to uppercase; so users can provide input
    // parameter names in lower or uppercase
    std::vector<std::string> vparams = sparams_;
    for (std::string& s : vparams)
        metview::toUpper(s);

    // Build filenames
    std::string sdate_dir, spartime, slevel;
    std::string fn_prefix = "icon13km_";
    std::string fn_suffix = ".grib2";
    for (std::size_t i = 0; i < dates_.size(); i++) {
        sdate_dir = dates_[i] + "/" + fn_prefix;
        for (std::size_t j = 0; j < vparams.size(); j++) {
            for (std::size_t k = 0; k < vtimes.size(); k++) {
                spartime = vparams[j] + "_" + vtimes[k] + fn_suffix;
                if (vlevels.empty())
                    vfn.push_back(spath_ + sdate_dir + spartime);
                else {
                    for (std::size_t l = 0; l < vlevels.size(); l++) {
                        slevel = vlevels[l] + "_";
                        vfn.push_back(spath_ + sdate_dir + slevel + spartime);
                    }
                }
            }
        }
    }

    return true;
}

bool Gaia::build_filenames_icon_single(const std::vector<std::string>& vtimes,
                                       std::vector<std::string>& vfn)
{
    std::string sdate_dir;
    std::string fn_prefix = "icon13km_";
    std::string fn_suffix = ".grib2";
    for (std::size_t i = 0; i < dates_.size(); i++) {
        sdate_dir = spath_ + dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            vfn.push_back(sdate_dir + fn_prefix + vtimes[j] + fn_suffix);
        }
    }

    return true;
}

bool Gaia::build_filenames_cosmo(std::string& type,
                                 std::vector<std::string>& vtimes,
                                 std::vector<std::string>& vsteps,
                                 std::vector<std::string>& vfn)
{
    // Build filename prefix
    vfn.clear();
    std::string time_prefix;
    if (type == "COSMO")
        time_prefix = "cosmo_met5_";
    else if (type == "COSMO_ANT")
        time_prefix = "cosmo_ant_";

    // Build filenames
    std::string sdate_dir, stime;
    for (std::size_t i = 0; i < dates_.size(); i++) {
        sdate_dir = spath_ + dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            stime = time_prefix + vtimes[j] + "_" + dates_[i];
            if (vsteps.empty()) {
                if (!get_filenames(sdate_dir, stime, std::string(), vfn)) {
                    vfn.insert(vfn.begin(), sdate_dir + stime + "*");
                    return false;
                }
            }
            else {
                for (std::size_t k = 0; k < vsteps.size(); k++)
                    vfn.push_back(sdate_dir + stime + vsteps[k]);
            }
        }
    }

    return vfn.empty() ? false : true;
}

bool Gaia::build_filenames_wrf(const std::string& region, const std::string& resol,
                               const std::vector<std::string>& vtimes,
                               const std::vector<std::string>& vsteps,
                               std::vector<std::string>& vfn)
{
    // Initialise constants
    vfn.clear();
    std::string time_prefix;
    if (region == "METAREA5" && resol == "20_KM")
        time_prefix = "wrf_metarea5_";
    else if (region == "METAREA5" && resol == "10_KM")
        time_prefix = "wrf_met510km_";
    else if (region == "METAREA5" && resol == "5_KM")
        time_prefix = "wrf_met55km_";
    else if (region == "ANTARCTICA" && resol == "10_KM")
        time_prefix = "wrf_antartica_";
    else if (region == "ANTARCTICA" && resol == "5_KM")
        time_prefix = "wrf_ant5km_";
    else if (region == "SPECIAL" && resol == "10_KM")
        time_prefix = "wrf_especial10km_";
    else if (region == "SPECIAL" && resol == "5_KM")
        time_prefix = "wrf_especial5km_";
    else {
        std::string er = "Gaia-> ERROR: WRF option not available yet: REGION_WRF=" +
                         region + " and RESOLUTION_WRF=" + resol;
        setError(1, er.c_str());
        return false;
    }

    // Build filenames
    std::string sdate_dir, stime;
    for (std::size_t i = 0; i < dates_.size(); i++) {
        sdate_dir = spath_ + dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            stime = time_prefix + vtimes[j] + "_" + dates_[i];
            if (vsteps.empty()) {
                if (!get_filenames(sdate_dir, stime, std::string(), vfn)) {
                    vfn.insert(vfn.begin(), sdate_dir + stime + "*");
                    return false;
                }
            }
            else {
                for (std::size_t k = 0; k < vsteps.size(); k++)
                    vfn.push_back(sdate_dir + stime + vsteps[k]);
            }
        }
    }

    return vfn.empty() ? false : true;
}

bool Gaia::build_filenames_gfs(std::vector<std::string>& vtimes,
                               std::vector<std::string>& vsteps,
                               std::vector<std::string>& vfn)
{
    // Initialise constants
    vfn.clear();
    std::string suffix;
    std::string time_prefix("gfs.t");
    std::string time_suffix("z.pgrb2.0p25.f");

    // Loops by date, time and steps
    std::string sdate_dir, stime;
    for (std::size_t i = 0; i < dates_.size(); i++) {
        sdate_dir = spath_ + dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            stime = time_prefix + vtimes[j] + time_suffix;
            if (vsteps.empty()) {
                if (!get_filenames(sdate_dir, stime, suffix, vfn)) {
                    vfn.insert(vfn.begin(), sdate_dir + stime + "*");
                    return false;
                }
            }
            else {
                for (std::size_t k = 0; k < vsteps.size(); k++)
                    vfn.push_back(sdate_dir + stime + suffix + vsteps[k]);
            }
        }
    }

    return vfn.empty() ? false : true;
}

bool Gaia::get_filenames(const std::string& path, const std::string& prefix,
                         const std::string& suffix, std::vector<std::string>& vfn)
{
    // Open directory
    DIR* dp;
    struct dirent* dirp;
    if ((dp = opendir(path.c_str())) == nullptr) {
        marslog(LOG_EROR, "Failed to open directory: %s", path.c_str());
        return false;
    }

    // Search files
    while ((dirp = readdir(dp)) != nullptr) {
        std::string name(dirp->d_name);
        if (name.compare(0, prefix.size(), prefix))
            continue;

        if (name.compare(name.size() - suffix.size(), suffix.size(), suffix))
            continue;

        vfn.push_back(path + name);
    }
    closedir(dp);

    return true;
}

bool Gaia::get_filenames(const std::string& path, const std::string& prefix,
                         const std::string& suffix, const std::string& ssat,
                         const long ltime1, const long ltime2,
                         std::vector<std::string>& vfn)
{
    // Open directory
    DIR* dp;
    struct dirent* dirp;
    if ((dp = opendir(path.c_str())) == nullptr) {
        marslog(LOG_EROR, "Failed to open directory: %s", path.c_str());
        vfn.insert(vfn.begin(), path);
        return false;
    }

    // Search files
    int time_length = 6;
    while ((dirp = readdir(dp)) != nullptr) {
        std::string name(dirp->d_name);

        // Check prefix
        if (name.compare(0, prefix.size(), prefix))
            continue;

        // Check suffix
        if (name.compare(name.size() - suffix.size(), suffix.size(), suffix))
            continue;

        // Check time interval
        auto stime = name.substr(prefix.length() + 1, time_length);
        if (static_cast<int>(stime.length()) != time_length ||
            !std::all_of(stime.begin(), stime.end(), ::isdigit))
            continue;

        long ltime = stol(stime);
        if (ltime < ltime1 || ltime >= ltime2)
            continue;

        // Check satellite type
        if (name.compare(prefix.length() + time_length + 2, ssat.size(), ssat))
            continue;

        vfn.push_back(path + name);
    }
    closedir(dp);

    return true;
}

std::string Gaia::bufr_obs(const std::vector<std::string>& vfiles)
{
    // Check if all files exist
    struct stat buffer;
    for (int i = 0; i < (signed)vfiles.size(); ++i) {
        // Check if file exists
        std::string ffile = vfiles[i];
        if (stat(ffile.c_str(), &buffer) != 0) {
            std::string error = "Gaia-> FILE NOT FOUND: ";
            error += ffile.c_str();
            setError(1, error.c_str());
            return std::string();
            ;
        }
    }

    // Build the output bufr file by concatenating each input bufr file
    std::string outname(marstmp());
    std::string scmd("cat ");
    for (int i = 0; i < (signed)vfiles.size(); ++i) {
        scmd.append(vfiles[i]);
        scmd.append(" ");
    }
    scmd.append("> ");
    scmd.append(outname);

    // Call system command to perform the concatenation files
    int ret = system(scmd.c_str());
    if (ret != 0) {
        std::string error = "Gaia-> ERROR ON CONCATENATING BUFR FILES";
        setError(1, error.c_str());
        return std::string();
    }

    return outname;
}

std::string Gaia::grib_icon_model(const std::vector<std::string>& vfiles)
{
    // Build the requested fieldset
    // Open each input grib file and add them to the output GRIB file

    // Auxilliary variables for GribApi
    int error = 0;
    grib_handle* h = nullptr;
    grib_context* c = grib_context_get_default();

    // Create output file name
    int count = 0;
    std::string outname(marstmp());
    for (int i = 0; i < (signed)vfiles.size(); ++i) {
        // Open the input file
        std::string ffile = vfiles[i];
        FILE* f = fopen(ffile.c_str(), "r");
        if (!f) {
            std::string error = "Gaia-> FILE NOT FOUND: ";
            error += ffile.c_str();
            setError(1, error.c_str());
            return std::string();
        }

        // Loop on all GRIB messages in file
        long istep;
        while ((h = grib_handle_new_from_file(c, f, &error)) != nullptr) {
            grib_get_long(h, "endStep", &istep);
            if (isteps_.empty() || (std::find(isteps_.begin(), isteps_.end(),
                                              istep) != isteps_.end())) {
                grib_write_message(h, outname.c_str(), "a");
                count++;
            }

            grib_handle_delete(h);
        }
        fclose(f);
    }

    return count ? outname : std::string();
}

std::string Gaia::grib_icon_single_model(const std::vector<std::string>& vfiles)
{
    // Build the requested fieldset
    // For each input file filter the requested fields by PARAM/LEVEL/STEP
    // and add them to the output GRIB file
    std::string outname(marstmp());
    for (int i = 0; i < (signed)vfiles.size(); ++i) {
        // Add requested messages to the output file
        if (!this->add_messages(vfiles[i], outname))
            return std::string();
    }

    return outname;
}

std::string Gaia::grib_cosmo_wrf_gfs_model(const std::vector<std::string>& vfiles)
{
    // Build the requested fieldset
    // For each input file filter the requested fields by TYPE/PARAM/LEVEL
    // and add them to the output GRIB file
    std::string outname(marstmp());
    for (int i = 0; i < (signed)vfiles.size(); ++i) {
        // Add requested messages to the output file
        if (!this->add_messages_tpl(vfiles[i], outname))
            return std::string();
    }

    return outname;
}

bool Gaia::add_messages(const std::string& ffile, const std::string& outname)
{
    // Auxilliary variables for GribApi
    char caux[40];
    size_t len;
    int error = 0;
    grib_handle* h = nullptr;
    grib_context* c = grib_context_get_default();

    // Open input grib file
    FILE* f = fopen(ffile.c_str(), "r");
    if (!f) {
        std::string error = "Gaia-> FILE NOT FOUND: ";
        error += ffile.c_str();
        setError(1, error.c_str());
        return false;
    }

    // Convert all parameter names to uppercase; so users can provide input
    // parameter names in lower or uppercase
    for (std::string& s : sparams_)
        metview::toUpper(s);

    // Loop on all GRIB messages in file
    int count = 0;
    long ilevel, istep;
    std::string par, levelType;
    while ((h = grib_handle_new_from_file(c, f, &error)) != nullptr) {
        // Check level type
        len = 40;
        grib_get_string(h, "mars.levtype", caux, &len);
        levelType = std::string(caux);
        if (levelType != slevel_type_)
            continue;

        len = 40;
        grib_get_string(h, "shortName", caux, &len);
        par = std::string(caux);
        metview::toUpper(par);
        grib_get_long(h, "level", &ilevel);
        grib_get_long(h, "endStep", &istep);

        if (std::find(sparams_.begin(), sparams_.end(), par) != sparams_.end())
            if (isteps_.empty() || std::find(isteps_.begin(), isteps_.end(),
                                             istep) != isteps_.end())
                if (ilevels_.empty() || (std::find(ilevels_.begin(),
                                                   ilevels_.end(), ilevel) != ilevels_.end())) {
                    grib_write_message(h, outname.c_str(), "a");
                    count++;
                }

        grib_handle_delete(h);
    }

    return count ? true : false;
}

bool Gaia::add_messages_tpl(const std::string& ffile, const std::string& outname)
{
    // FAMI20210607
    //  This version does not check parameter level type, only param and level
    //  What to do with parameter level type?
    //  File wrf_metarea5_00_20210112033 contains the following values:
    //  mars.levtype     typeOfLevel
    //  ml               hybrid
    //  sfc              entireAtmosphere
    //  sfc              surface
    //  sfc              meanSea
    //  sfc              depthBelowLandLayer
    //  sfc              depthBelowLand
    //  sfc              heightAboveGround
    //  sfc              cloudBase
    //  sfc              cloudTop
    //  sfc              nominalTop
    //  sfc              isothermZero
    //  pl               isobaricInhPa
    //  101              isobaricLayer
    //  106              heightAboveGroundLayer
    //  116              pressureFromGroundLayer
    //  204              unknown
    //  214              unknown
    //  215              unknown
    //  224              unknown
    //  234              unknown

    // Auxilliary variables for GribApi
    char caux[40];
    size_t len;
    int error = 0;
    grib_handle* h = nullptr;
    grib_context* c = grib_context_get_default();

    // Open input grib file
    FILE* f = fopen(ffile.c_str(), "r");
    if (!f) {
        std::string error = "Gaia-> FILE NOT FOUND: ";
        error += ffile.c_str();
        setError(1, error.c_str());
        return false;
    }

    // Convert all parameter names to uppercase; so users can provide input
    // parameter names in lower or uppercase
    for (std::string& s : sparams_)
        metview::toUpper(s);

    // Search and save requested messages
    int count = 0;
    long ilevel;
    std::string sparam, levelType;
    while ((h = grib_handle_new_from_file(c, f, &error)) != nullptr) {
        len = 40;
        grib_get_string(h, "shortName", caux, &len);
        sparam = std::string(caux);
        metview::toUpper(sparam);
        len = 40;
        grib_get_string(h, "mars.levtype", caux, &len);
        levelType = std::string(caux);
        grib_get_long(h, "level", &ilevel);
        if (std::find(sparams_.begin(), sparams_.end(), sparam) !=
            sparams_.end())
            if (levelType == slevel_type_)
                if (ilevels_.empty() ||
                    (std::find(ilevels_.begin(), ilevels_.end(), ilevel) !=
                     ilevels_.end())) {
                    grib_write_message(h, outname.c_str(), "a");
                    count++;
                }

        grib_handle_delete(h);
    }

    return count ? true : false;
}

std::vector<std::string> Gaia::split(const std::string& s, char delim)
{
    std::vector<std::string> result;
    std::stringstream ss(s);
    std::string item;

    while (std::getline(ss, item, delim))
        result.push_back(item);

    return result;
}

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);
    Gaia data((char*)"GAIA");

    theApp.run();
}
