/***************************** 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 <filesystem>
#include <iomanip>
#include <iostream>
//#include <cstdlib>

#include "MvMiscellaneous.h"
#include "MvRequestUtil.hpp"

//-- DEFINITIONS -----------------------------------------------------
// main repository path
#define GAIA_PATH_PREFIX_DEFAULT "/home/operador/gaia/"
//F #define GAIA_PATH_PREFIX_DEFAULT "/Users/fernandoii/metview/Navy/"

// secondary repository path
#define GAIA_SECONDARY_PATH_PREFIX_DEFAULT "/home/operador/gaia/data2/backup/"
//#define GAIA_SECONDARY_PATH_PREFIX_DEFAULT "/Users/fernandoii/metview/Navy/SECONDARY/"

// number of days data will be kept in the main repository
#define GAIA_NDAYS_MR_DEFAULT 3
//-------------------------------------------------------------------

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;

    // Get data's minimum date to be kept in the main repository
    min_date_ = get_minimum_date_mr();

    // Build paths
    build_path(in);

    // Build all filenames
    std::vector<std::string> vfiles;
    if (!build_filenames(in, vfiles)) {
        if (vfiles.empty()) {
            std::string msg = "Gaia-> File(s) not found";
            setError(!expect_, msg.c_str());
            return;
        }
        if (expect_ == FALSE) {
            std::string msg = "Gaia-> Some files are missing";
            setError(!expect_, msg.c_str());
            return;
        }
        return;
    }

    // Add full path to filenames
    add_full_path(in, vfiles);

    for (auto & sfn : vfiles)
        std::cout << "FILENAMES: " << sfn << std::endl;

    // Handling Observation data
    std::string outname;
    std::string stype = (const char*)in("TYPE");
    if (stype == "OBSERVATION") {
        long ltime1 = (int)in("INITIAL_TIME");
        long ltime2 = (int)in("FINAL_TIME");
        outname = this->bufr_obs(ltime1, ltime2, vfiles);
        if (outname.empty()) {
            setError(!expect_, "Gaia-> Empty output BUFR file");
            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_ICLM" ||
            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: " + ffile;
                setError(!expect_, error.c_str());
                return;
            }
            fclose(f);

            // Create output request
            out.setVerb("NETCDF");
            out("PATH") = ffile.c_str();
        }
	    else if (smodel == "HYCOM_112" || smodel == "HYCOM_124" ) {
            outname = this->netcdf_hycom_model(vfiles);

            if (outname.empty())
                return;

	        // Create output request
            out.setVerb("NETCDF");
            out("PATH") = outname.c_str();
	    }
        else {
            if (smodel == "ICON_13KM")
                outname = this->grib_icon13km_model(vfiles);
            //F else if (smodel == "ICON_SINGLE")
            //F    outname = this->grib_icon_single_model(vfiles);
            else if (smodel == "COSMO" || smodel == "COSMO_ANT")
                //F29 outname = this->grib_cosmo_wrf_gfs_model(vfiles);
                outname = this->grib_get_messages(vfiles);
            else if (smodel == "ICONLAM_SAM65KM" || smodel == "ICONLAM_ANT65KM" || smodel == "ICONLAM_SSE21KM") {
                //F29 outname = this->grib_cosmo_wrf_gfs_model(vfiles);
                bignore_level_type_ = TRUE;  // ignore level_type when selecting messages
                outname = this->grib_get_messages(vfiles);
            }
            else if (smodel == "WRF")
                //F29 outname = this->grib_cosmo_wrf_gfs_model(vfiles);
                outname = this->grib_get_messages(vfiles);
           else if (smodel == "GFS")
                //F29 outname = this->grib_cosmo_wrf_gfs_model(vfiles);
                outname = this->grib_get_messages(vfiles);
            else {
                std::string error = "Gaia-> ERROR: MODEL " + smodel + " not implemented yet";
                setError(1, error.c_str());
                return;
            }

            if (outname.empty()) {
                //setError(!expect_, "Gaia-> Empty output GRIB file");
                return;
            }

            // Pos-process data: regrid and/or crop area
            if (!pos_processing_data(in, outname))
                return;

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

    // Handling GeonetCast data
    else if (stype == "GEONETCAST") {
        setError(1, "Gaia-> ERROR: TYPE GEONETCAST not implemented yet");
        return;
    }

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

    return;
}

bool Gaia::get_common_input_info(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
    times_.clear();
    if (metview::IsParameterSet(in, "TIME")) {
        int ntimes = in.countValues("TIME");
        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);
    // }

    // EXPECT=ON means that a partial output dataset is accepted
    if (strcmp((const char*)in("EXPECT"), "ON") == 0)
        expect_ = TRUE;

    return true;
}

void Gaia::date_plus_ndays(struct tm* date, int ndays)
{
    const time_t ONE_DAY = 24 * 60 * 60 ;

    // Seconds since start of epoch
    time_t date_seconds = mktime(date) + (ndays * ONE_DAY) ;

    // Update caller's date
    // Use localtime because mktime converts to UTC so may change date
    *date = *localtime(&date_seconds);
}

long Gaia::get_minimum_date_mr()
{
    // Get number of days to be subtracted from the current date
    const char* cndays = getenv("METVIEW_GAIA_NDAYS_MR");
    int ndays = cndays ? atoi(cndays) : GAIA_NDAYS_MR_DEFAULT;

    // Get current date
    time_t rawtime;
    struct tm * timeinfo;
    time (&rawtime);
    timeinfo = localtime (&rawtime);

    // Compute minimum date available in the main repository
    date_plus_ndays(timeinfo, -ndays);

    // Return minimum date as type long
    char buf [80];
    strftime (buf, 80, "%Y%m%d", timeinfo);
    long min_date = atol(buf);

    return min_date;
}

void Gaia::build_path(MvRequest& in)
{
    // 1. Fixed part of the path. Try to get it from environment variables:
    // METVIEW_GAIA_PATH and METVIEW_GAIA_SECONDARY_PATH. If not defined, 
    // use default values.
    const char* fpath = getenv("METVIEW_GAIA_PATH");
    spath_ = fpath ? fpath : GAIA_PATH_PREFIX_DEFAULT;
    const char* fpath_b = getenv("METVIEW_GAIA_SECONDARY_PATH");
    spath_s_ = fpath_b ? fpath_b : GAIA_SECONDARY_PATH_PREFIX_DEFAULT;

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

    return;
}

void Gaia::add_full_path(MvRequest& in, std::vector<std::string>& vfn)
{
    // Add the path to the filename according to the date of the data
    for (auto & sfn : vfn) {
        long ldate = atol(sfn.substr (0,8).c_str());  // get date
        sfn = (ldate >= min_date_) ? spath_ + sfn : spath_s_ + sfn;
    }

    return;
}

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") {
        setError(1, "Gaia-> ERROR: GEONETCAST not implemented yet");
        return false;
    }
    else if (stype == "OBSERVATION") {
        if (!build_filenames_observation(in, vfn))
            return false;
    }
    else {
        setError(1, "Gaia-> ERROR: TYPE not recognized");
        return false;
    }

    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_ICLM" || smodel == "WW3_GFS")
        return build_filenames_ww3(in, stimes, vfn);

    //Handle model HYCOM
    if (smodel == "HYCOM_112" || smodel == "HYCOM_124")
        return build_filenames_hycom(in, 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));
        }
    }

    // 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
    std::vector<std::string> ssteps;
    if (smodel == "ICON_13KM") {
        if (!build_filenames_icon13km(stimes, slevels, sltype, vfn))
            return false;
    }
    else if (smodel == "COSMO" || smodel == "COSMO_ANT") {
        left_pad_string(isteps_, 3, ssteps);
        if (!build_filenames_cosmo(smodel, stimes, ssteps, vfn))
            return false;
    }
    else if (smodel == "ICONLAM_SAM65KM" || smodel == "ICONLAM_ANT65KM" || smodel == "ICONLAM_SSE21KM") {
        left_pad_string(isteps_, 3, ssteps);
        if (!build_filenames_iconlam(smodel, stimes, sltype, ssteps, vfn))
            return false;
    }
    else if (smodel == "WRF") {
        std::string region = (const char*)in("REGION_WRF");
        std::string resol = (const char*)in("RESOLUTION_WRF");
        left_pad_string(isteps_, 3, ssteps);
        if (!build_filenames_wrf(region, resol, stimes, ssteps, vfn))
            return false;
    }
    else if (smodel == "GFS") {
        left_pad_string(isteps_, 3, ssteps);
        if (!build_filenames_gfs(stimes, ssteps, vfn))
            return false;
    }
    else {
        setError(1, "Gaia-> ERROR: MODEL not recognized ");
        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");
    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";
        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 = dates_[i] + "/";
            fn_prefix = sfn + dates_[i];
            std::string fname = sdate_dir + fn_prefix + "_*_" + ssat + "_*_" + fn_suffix;
            vfn.push_back(fname);
        }
    }
    else {
        std::string error = "Gaia-> ERROR: OBSTYPE not recognized ";
        setError(!expect_, 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_ICLM")
        fn_prefix = "ww3iclm_";
    else if (smodel == "WW3_ICON")
        fn_prefix = "ww3icon_";
    else if (smodel == "WW3_GFS")
        fn_prefix = "ww3gfs_";

    std::string val;
    if (!in.getValue("REGION_WW3", val, false))
        return false;
    fn_prefix = fn_prefix + 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 = 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_hycom(MvRequest& in, std::vector<std::string>& vfn)
{
    // Build filename prefix
    std::string fn_prefix;
    //std::string levels;
    //std::vector<std::string> pars;
    //std::vector<std::string> partnames;
    std::string smodel = (const char*)in("MODEL");
    if (smodel == "HYCOM_112")
        fn_prefix = "hycom112_";
    else
        fn_prefix = "HYCOM_SUP_";

    // Get params
    //if (!in.getValue("LEVELS", levels, false))
	//    return false;

    //if ( levels == "ONE_LEVEL"){
      //  if (!in.getValue("Param_OneLevel", pars, false))
        //    return false;
    //}
    //else{
	//    if (!in.getValue("Param_MultiLevel", pars, false))
        //    return false;  
    //}

    //sparams_ = pars;

    //for (std::size_t j = 0; j < pars.size(); j++){
    //    if (smodel == "HYCOM_124" && pars[j] == "u_velocity" || pars[j] == "v_velocity")
    //        partnames.push_back("2du_");
    //    else if(pars[j] == "temperature" || pars[j] == "salinity")
    //        partnames.push_back("3zt_");
    //    else if (pars[j] == "u" || pars[j] == "v" || pars[j] == "bathymetry")
    //        partnames.push_back("3zu_");
    //    else if (pars[j] == "mixed_layer_thickness" || pars[j] == "ssh")
    //        partnames.push_back("fsd_");
    //}

    //std::set<std::string> conjunto;
    //for (std::string valor : partnames) {
    //    conjunto.insert(valor);
    //}

    // Criar uma nova lista com valores únicos
    //std::vector<std::string> partnames2;
    //for (std::string valor : conjunto)
    //    partnames2.push_back(valor);

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

	directory_.push_back(sdate_dir);
    }

    return vfn.empty() ? false : 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 = 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_icon13km(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(sdate_dir + spartime);
                else {
                    for (std::size_t l = 0; l < vlevels.size(); l++) {
                        slevel = vlevels[l] + "_";
                        vfn.push_back(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 = 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 = dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            stime = time_prefix + vtimes[j] + "_" + dates_[i];
            if (vsteps.empty())
                vfn.push_back(sdate_dir + stime + "*");
            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_iconlam(std::string& type,
                                 std::vector<std::string>& vtimes,
				                 std::string& sltype,
                                 std::vector<std::string>& vsteps,
                                 std::vector<std::string>& vfn)
{
    // Build filename prefix
    vfn.clear();
    std::string fn_prefix;
    if (type == "ICONLAM_SAM65KM")
        fn_prefix = "iconlam_sam6.5_";
    else if (type == "ICONLAM_ANT65KM")
        fn_prefix = "iconlam_ant6.5_";
    else if (type == "ICONLAM_SSE21KM")
        fn_prefix = "iconlam_sse2.1_";

    // Build filename suffix
    std::string fn_suffix = (sltype != "pl") ? "_ml.grb" : "_pl.grb";

    // Build filenames
    std::string sdate_dir, stime;
    for (std::size_t i = 0; i < dates_.size(); i++) {
        sdate_dir = dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            stime = fn_prefix + vtimes[j] + "_" + dates_[i] + "_";
            // option step=all 
            if (vsteps.empty())
                vfn.push_back(sdate_dir + stime + "*" + fn_suffix);
            else {
                for (std::size_t k = 0; k < vsteps.size(); k++)
                    vfn.push_back(sdate_dir + stime + vsteps[k] + fn_suffix);
            }
        }
    }

    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 = dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            stime = time_prefix + vtimes[j] + "_" + dates_[i];
            if (vsteps.empty())
                vfn.push_back(sdate_dir + stime + "*");
            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 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 = dates_[i] + "/";
        for (std::size_t j = 0; j < vtimes.size(); j++) {
            stime = time_prefix + vtimes[j] + time_suffix;
            if (vsteps.empty())
                vfn.push_back(sdate_dir + stime + "*");
            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::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) {
        std::string error = "Failed to open directory: " + path;
        setError(!expect_, error.c_str());
        return false;
    }

    // Search files
    int count = 0;
    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);
        count++;
    }
    closedir(dp);

    if (count > 0)
        return true;
    else {
        std::string error = "Gaia-> SUITABLE FILE(S) NOT FOUND IN THIS DIRECTORY: " + path;
        setError(!expect_, error.c_str());
        return false;
    }
}

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) {
        std::string error = "Gaia-> Failed to open directory: " + path;
        setError(!expect_, error.c_str());
        return false;
    }

    // Search files
    bool found = false;
    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(), 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, ssat.size(), ssat))
            continue;

        found = true;
        vfn.push_back(path + "/" + name);
    }
    closedir(dp);

    if (found)
        return true;
    else {
        std::string error = "Gaia-> SUITABLE OBSERVATION FILE(S) NOT FOUND IN THIS DIRECTORY: " + path;
        setError(!expect_, error.c_str());
        return false;
    }
}


std::string Gaia::bufr_obs(long ltime1, long ltime2, const std::vector<std::string>& vfiles)
{
    // Get all filenames by inspecting each directory and retrieving all
    // bufr files according to time interval, satellite type and resolution 
    std::vector<std::string> vfn;
    for (auto sfn: vfiles) {
        // Extract directory path and filename
        std::filesystem::path filePath(sfn);
        std::string directory = filePath.parent_path().string();
        std::string filename = filePath.filename().string();

        // Get fixed part of the filename: prefix, sufix and satellite type
        size_t first_pos = filename.find('*');
        size_t second_pos = filename.find('*', first_pos + 1);
        if (first_pos == std::string::npos || second_pos == std::string::npos) {
            std::string error = "Gaia-> Error to process observation filename: " + filename;
            setError(!expect_, error.c_str());
            if (expect_)
                continue;

            return std::string();
        }
        std::string fn_prefix = filename.substr(0, first_pos);
        std::string ssat = filename.substr(first_pos+1, second_pos-first_pos-1);
        std::string fn_suffix = filename.substr(second_pos+1);

        // Get filename(s)
        if (!get_filenames(directory, fn_prefix, fn_suffix, ssat, ltime1,
                          ltime2, vfn)) {
            if (expect_)
                continue;

            return std::string();
        }
    }

    // No file(s) found
    if (vfn.empty())
        return std::string();

    // Build the output bufr file by concatenating each input bufr file
    std::string outname(marstmp());
    std::string scmd("cat ");
    for (auto sfn: vfn) {
        scmd.append(sfn);
        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(!expect_, error.c_str());
        return std::string();
    }

    return outname;
}

std::string Gaia::netcdf_hycom_model(const std::vector<std::string>& vfiles)
{
    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(!expect_, error.c_str());
        return std::string();
    }
    fclose(f);

    return ffile;


/*
 * Atualmente o HYCOM so esta pegando um arquivo por vez devido à complexidade
 * da manipulacao dos arquivos NETCDF.
 *
 * Eh NEC desenvolver o codigo abaixo para extrair variaveis e tempos especificos via NETCDF ou
 * Converter os NETCDFs para GRIBs e ter uma vida muito mais simples.
 *
 * Deve-se verificar como o ECMWF lida com arquivos NETCDF ou se realizam a conversao para GRIB.
 * CC Gadelha
 * 19ABR2024
 *
 *
    std::string scommand;
    scommand="ncks -v ";
//  scommand+= sparams_[0] + " " + vfiles[0] + " " + "/home/operador/temp.nc";

    std::cout << scommand << std::endl;

// int ret = system(scommand.c_str());

    std::vector<std::string> lfnames;

    for (std::size_t j = 0; j < vfiles.size(); j++){
std::string outname(marstmp());
lfnames.push_back(outname);
        //scommand= scommand + sparams_[j] + " " + vfiles[j] + " " + directory_[j] + "temp" + [j] + ".nc";
        scommand= scommand + "u_velocity,v_velocity " + vfiles[j] + " " + outname;
	std::cout << scommand << std::endl;
	int ret = system(scommand.c_str());
    }

std::string arqs;
std::string outname2(marstmp());
for (std::size_t j = 0; j < lfnames.size(); j++){
//ncrcat file1 file2 –O outfile
    arqs+=lfnames[j] + " ";
}
arqs+="-O " + outname2;
std::cout << arqs << std::endl;

int ret = system(arqs.c_str());
    
    return "false";
*/
}

std::string Gaia::grib_icon13km_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: " + ffile;
            setError(!expect_, error.c_str());
            if (expect_)
                continue;
            else
                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());
//     int count = 0;
//     for (int i = 0; i < (signed)vfiles.size(); ++i) {
//         // Add requested messages to the output file
//         if (!this->add_messages(vfiles[i], outname)) {
//             if (!expect_)
//                 return std::string();
//         }
//         else
//             count++;
//     }
//
//     return count ? outname : std::string();
// }

//F29 std::string Gaia::grib_cosmo_wrf_gfs_model(const std::vector<std::string>& vfiles)
std::string Gaia::grib_get_messages(const std::vector<std::string>& vfiles)
{
    // Handle steps=ALL
    std::vector<std::string> vfn;
    if (isteps_.size() == 0) {
        for (auto fn: vfiles) {
            // Extract directory path and filename
            std::filesystem::path filePath(fn);
            std::string directory = filePath.parent_path().string();
            std::string filename = filePath.filename().string();

            // Get substrings before and after *
            size_t pos = filename.find('*');
            if (pos == std::string::npos) {
                std::string error = "Gaia-> Error: expect character '*' in the filename: " + fn;
                setError(!expect_, error.c_str());
                if (expect_)
                    continue;

                return std::string();
            }

            std::string fn_prefix = filename.substr(0, pos);
            std::string fn_suffix = filename.substr(pos+1);
            if (!get_filenames(directory, fn_prefix, fn_suffix, vfn)) {
                if (expect_)
                    continue;

                return std::string();
            }
        }
    }
    else {
        // Check if file(s) exist(s)
        vfn = vfiles;
        for (auto fn: vfn) {
            if (!std::filesystem::exists(fn)) {
                std::string error = "Gaia-> Error: file not found: " + fn;
                setError(!expect_, error.c_str());
                if (expect_)
                    continue;

                return std::string();
            }
        }
    }

    // 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());
    int count = 0;
    for (auto fn: vfn) {
        // Add requested messages to the output file
        if (!this->add_messages_tpl(fn, outname)) {
            if (!expect_)
                return std::string();
        }
        else
            count++;
    }

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

// 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: " + ffile;
//         setError(!expect_, 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)
{
    // 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: " + ffile;
        setError(!expect_, 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 (bignore_level_type_ || levelType == slevel_type_)  //F29
                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;
}

bool Gaia::pos_processing_data(const MvRequest& in, std::string& outname)
{
    // Get GRID values
    bool bgrid = false;
    double grid_lat, grid_lon;
    if ((const char*)in("GRID")) {
        int n = in.countValues("GRID");
        if (n == 1 && strcmp((const char*)in("GRID", 0), "OFF") == 0) {
            // input value is OFF -> continue
        }
        else if (n == 2) {
            grid_lat = (double)in("GRID", 0);
            grid_lon = (double)in("GRID", 1);
            bgrid = true;
        }
        else {
            std::string error = "Gaia-> GRID parameter not defined correctly. Use: nlat/nlon (regular lat/lon grid with nlatxnlon degree point spacing)";
            setError(!expect_, error.c_str());
            return false;
        }
    }

    // Get AREA values
    bool barea = false;
    double south, west, north, east;
    if ((const char*)in("AREA")) {
        int n = in.countValues("AREA");
        if (n == 1 && strcmp((const char*)in("AREA", 0), "OFF") == 0) {
            // input value is OFF -> continue
        }
        else if (n == 4) {
            south = (double)in("AREA", 0);
            west = (double)in("AREA", 1);
            north = (double)in("AREA", 2);
            east = (double)in("AREA", 3);
            barea = true;
        }
        else {
            std::string error = "Gaia-> AREA parameter not defined correctly. Use: south/west/north/east";
            setError(!expect_, error.c_str());
            return false;
        }
    }

    // Create a request and delegate the regrid to the Regrid module
    if (bgrid || barea) {
        MvRequest regridReq("REGRID");
        regridReq("SOURCE") = outname.c_str();

        if (bgrid) {
            regridReq.setValue("GRID", grid_lat);
            regridReq.addValue("GRID", grid_lon);
        }

        if (barea) {
            regridReq.setValue("AREA", south);
            regridReq.addValue("AREA", west);
            regridReq.addValue("AREA", north);
            regridReq.addValue("AREA", east);
        }

        MvRequest regridOut;
        callService((char*)"regrid", regridReq, regridOut);
        if (std::string(regridOut.getVerb()) == "ERROR") {
            std::string error = std::string(regridOut("MESSAGE"));
            setError(!expect_, error.c_str());
            return false;
        }

        outname = std::string(regridOut("PATH"));
    }

    return true;
}

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

    theApp.run();
}
