/***************************** LICENSE START ***********************************

 Copyright 2014 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 <iostream>
#include <sstream>
#include <cstdio>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <cassert>
#include <memory>

#include "GribMetaData.h"
#include "LogHandler.h"
#include "MvKeyProfile.h"
#include "MvMessageMetaData.h"
#include "MvMiscellaneous.h"
#include "MvTmpFile.h"
#include "MvWindef.h"
#include "Tokenizer.h"
#include "MvLog.h"

#include "eccodes.h"

static std::string GRIB_DEFINITION_PATH_ENV;
static std::string GRIB_DUMP_EXE;

std::map<int, std::string> GribMvDump::keyMap_;
std::vector<std::string> GribTableDump::definitionsDir_;

#define MV_CODES_CHECK(a, msg) codesCheck(#a, __FILE__, __LINE__, a, msg)

#ifdef ECCODES_UI
#ifndef ABS
#define ABS(x) (((x) >= 0) ? (x) : -(x))
#endif
#endif

static bool codesCheck(const char* call, const char* /*file*/, int /*line*/, int e, const char* /*msg*/)
{
    if (e) {
        GuiLog().error() << call << grib_get_error_message(e);
        return false;
    }
    return true;
}

static bool shellCommand(const std::string& command, std::stringstream& out, std::stringstream& err,
                         std::stringstream& otherErr, int& exitCode)
{
    FILE* in;
    char cbuf[1024];
    MvTmpFile tmpFile;

    exitCode = -1;

    std::string cmd;
    if (!GRIB_DEFINITION_PATH_ENV.empty()) {
        cmd += "export GRIB_DEFINITION_PATH=" + GRIB_DEFINITION_PATH_ENV + ";";
    }
    cmd += command + " 2>" + tmpFile.path();

    if (!(in = popen(cmd.c_str(), "r"))) {
        otherErr << "Failed to execute command: " << command;
        return false;
    }

    while (fgets(cbuf, sizeof(cbuf), in) != nullptr) {
        out << cbuf;
    }

    int ret = pclose(in);
    exitCode = WEXITSTATUS(ret);

    if (!(in = fopen(tmpFile.path().c_str(), "r"))) {
        otherErr << "Failed to read file " << tmpFile.path() << " containing STDERR of command";
        return false;
    }

    while (fgets(cbuf, sizeof(cbuf), in) != nullptr) {
        err << cbuf;
    }

    fclose(in);

    return true;
}

#if 0
static void shellCommand(std::string &command, std::stringstream& out,std::stringstream& err, std::string &ftmp)
{
	FILE *in;
	char cbuf[512];

	std::string cmd;
	if(!GRIB_DEFINITION_PATH_ENV.empty())
	{
		cmd+="export GRIB_DEFINITION_PATH=" + GRIB_DEFINITION_PATH_ENV + ";";
	}
	
	cmd+=command + " 2>" + ftmp;
		
    if (!(in = popen(void setValue(long val, const std::string& strVal ={}) {
                     value_ = val; strValue_ = strVal.empty()?val:strVal;}
                 void setValue(double val, const std::string& strVal ={}) {
                     value_ = val; strValue_ = strVal.empty()?val:strVal;}
                 void setValue(const std::string& val) {
                     value_ = val; strValue_ = val;}cmd.c_str() ,"r")) )
	{
		return;
	}

	while(fgets(cbuf, sizeof(cbuf), in) != nullptr)
	{
		out << cbuf;		
	}	
	
	pclose(in);


	if (!(in = fopen(ftmp.c_str() ,"r")) )
	{
		return;
	}

	while(fgets(cbuf, sizeof(cbuf), in) != nullptr)
	{
		err << cbuf;		
	}	
	
	fclose(in);

}
#endif


GribSection::~GribSection()
{
    for (auto& it : item_) {
        delete it;
    }
}


//===================================================
//
//  GribWmoDump
//
//===================================================

GribWmoDump::~GribWmoDump()
{
    clear();
}

void GribWmoDump::clear()
{
    for (auto& it : section_) {
        delete it;
    }
    section_.clear();
    text_.clear();
}

// msgCnt starts from 1!!!
bool GribWmoDump::read(const std::string& fgrib, int msgCnt)
{
    // Get grib dump for the given field
    // int count=1;
    std::string errOut;

    std::string cmd = GRIB_DUMP_EXE + " -O -w count=" + std::to_string(msgCnt) + " " + "\"" + fgrib + "\"";

    GuiLog().task() << "Generating WMO-style dump for message: " << msgCnt << GuiLog::commandKey() << cmd;

    std::stringstream in, err;
    bool hasError = false;
    int exitCode;
    std::stringstream shellErr;
    bool ret = shellCommand(cmd, in, err, shellErr, exitCode);

    if (exitCode > 0) {
        hasError = true;
        GuiLog().error() << "Command exited with code: " << exitCode;
        if (err.str().empty())
            errOut += "<b>Command</b>" + cmd + " exited with <b>code:</b> " + std::to_string(exitCode) + " ";
    }

    if (!ret) {
        hasError = true;
        GuiLog().error() << shellErr.str();
        errOut += shellErr.str();
    }

    // Get stderr of command
    if (!err.str().empty()) {
        hasError = true;
        GuiLog().error() << err.str();
        errOut = "<b>Command </b>" + cmd + " <b>failed.</b> <br>" + err.str();
    }

    if (hasError)
        return false;

    // Run dump command
    // shellCommand(cmd,in,err,tmpFile);

    // Get stdout of command
    text_ = in.str();

    // Get stderr of command
    // if(!err.str().empty())
    //{
    //     GuiLog().error() << err.str();
    //    return false;
    // }

    GuiLog().task() << "Parsing default dump for message: " << msgCnt;
    parse(in);

    return (section_.size() > 0) ? true : false;
}

void GribWmoDump::parse(std::stringstream& in)
{
    std::string stmp;
    char c[1024];
    GribSection* asec = 0;

    while (in.getline(c, 1024)) {
        std::string::size_type pos, pos1;

        std::string buf;  // Have a buffer string
        stmp = c;
        std::stringstream ss(stmp);  // Insert the string into a stream

        if (stmp.find("MESSAGE") != std::string::npos) {
        }
        else if ((pos = stmp.find("SECTION_")) != std::string::npos ||
                 (pos = stmp.find("SECTION")) != std::string::npos) {
            auto* sec = new GribSection;
            std::string::size_type posNumEnd = stmp.find(" ", pos + 8);
            if (posNumEnd != std::string::npos) {
                sec->name("Section " + stmp.substr(pos + 8, posNumEnd - pos - 8));
            }
            else {
                sec->name("Section ??");
            }

            asec = sec;
            section_.push_back(sec);
        }

        else if (asec != 0) {
            int i = 0;
            auto* item = new GribItem;
            while (ss >> buf) {
                if (i == 0) {
                    item->setPos(buf);
                }
                else if (i == 1) {
                    item->setName(buf);
                }
                else if (i == 2 && buf == "=") {
                    pos = stmp.find("=");
                    pos1 = stmp.rfind("{");
                    if (pos1 != std::string::npos) {
                        item->setValue(stmp.substr(pos, pos1 - pos + 1));

                        in.getline(c, 256);
                        stmp = c;
                        while (stmp.find("}") == std::string::npos) {
                            if (stmp.find("}") == std::string::npos) {
                                if (stmp.find("...") == std::string::npos) {
                                    std::stringstream ssdata(stmp);
                                    while (ssdata >> buf) {
                                        item->addArrayData(buf);
                                    }
                                }
                                else {
                                    item->addArrayData(stmp);
                                }
                            }

                            if (!in.getline(c, 256)) {
                                GuiLog().error() << "GribWmoDump::read() ---> Fatal formatting error in parsing WMO style dump!\n";
                                return;
                            }
                            stmp = c;
                        }
                        if (stmp.find("}") != std::string::npos) {
                            item->addArrayData(stmp);
                        }
                        // stmp now contains the "}" character
                    }

                    else  // if(buf.find("[") ==std::string::npos || buf.find("]") == string::npos )
                    {
                        item->setValue(stmp.substr(pos + 1));
                        // item->value(buf);
                    }


                    break;
                }
                i++;
            }
            asec->addItem(item);
        }
        // GribFieldMetaData* field = new GribFieldMetaData;
    }
}

//===================================================
//
//  GribStdDump
//
//===================================================

GribStdDump::~GribStdDump()
{
    clear();
}

void GribStdDump::clear()
{
    for (auto& it : item_) {
        delete it;
    }
    item_.clear();
    text_.clear();
}

// msgCnt starts from 1!!!
bool GribStdDump::read(const std::string& fname, int msgCnt)
{
    std::string errOut;
    std::string cmd = GRIB_DUMP_EXE + " -w count=" + std::to_string(msgCnt) + " " + "\"" + fname + "\"";  //+ "  > " + tmpFile_;

    GuiLog().task() << "Generating default dump for message: " << msgCnt << GuiLog::commandKey() << cmd;

    // Run the command
    std::stringstream in, err;
    bool hasError = false;
    int exitCode;
    std::stringstream shellErr;
    bool ret = shellCommand(cmd, in, err, shellErr, exitCode);

    if (exitCode > 0) {
        hasError = true;
        GuiLog().error() << "Command exited with code: " << exitCode;
        if (err.str().empty())
            errOut += "<b>Command</b>" + cmd + " exited with <b>code:</b> " + std::to_string(exitCode) + " ";
    }

    if (!ret) {
        hasError = true;
        GuiLog().error() << shellErr.str();
        errOut += shellErr.str();
    }

    // Get stderr of command
    if (!err.str().empty()) {
        hasError = true;
        GuiLog().error() << err.str();
        errOut = "<b>Command </b>" + cmd + " <b>failed.</b> <br>" + err.str();
    }

    if (hasError)
        return false;

    // Get stdout of command
    text_ = in.str();

    GuiLog().task() << "Parsing default dump for message: " << msgCnt;
    parse(in);

    return (item_.size() > 0) ? true : false;
}

void GribStdDump::parse(std::stringstream& in)
{
    std::string stmp, descText;
    char c[512];
    while (in.getline(c, 512)) {
        stmp = c;
        if (stmp.find("GRIB {") != std::string::npos) {
            break;
        }
    }

    while (in.getline(c, 512)) {
        // std::cout << c << std::endl ;

        std::string buf;  // Have a buffer string
        stmp = c;
        std::stringstream ss(stmp);  // Insert the string into a stream

        if (stmp.find("#") != std::string::npos && stmp.find("#-READ ONLY-") == std::string::npos) {
            descText = stmp;
        }
        else {
            // std::vector<std::string> sv;
            int i = 0;

            auto* item = new GribItem;
            item_.push_back(item);

            while (ss >> buf) {
                if (buf.find("#-READ") != std::string::npos ||
                    buf.find("ONLY-") != std::string::npos) {
                    continue;
                }

                if (stmp.find("=") == std::string::npos) {
                    return;
                }

                if (i == 0) {
                    item->setName(buf);
                }
                else if (i == 2 && buf != "{") {
                    item->setValue(buf.substr(0, buf.size() - 1));

                    if (descText.size() > 0) {
                        item->setDescription(descText);
                        descText.clear();
                    }
                }

                else if (i == 2) {
                    in.getline(c, 512);
                    stmp = c;
                    while (stmp.find("}") == std::string::npos) {
                        if (stmp.find("}") == std::string::npos) {
                            if (stmp.find("...") == std::string::npos) {
                                std::stringstream ssdata(stmp);
                                while (ssdata >> buf) {
                                    item->addArrayData(buf);
                                }
                            }
                            else {
                                item->addArrayData(stmp);
                            }
                        }

                        if (!in.getline(c, 512)) {
                            GuiLog().error() << "Fatal formatting error in parsing default style dump!";
                            return;
                        }
                        stmp = c;
                    }
                    if (stmp.find("}") != std::string::npos) {
                        item->addArrayData(stmp);
                    }
                    // stmp now contains the "}" character
                }

                i++;
            }
        }
    }
}

//===================================================
//
//  GribMvDump
//
//===================================================

GribMvDump::GribMvDump()
{
    // tmpfilename_ = tmpfilename;

    if (keyMap_.empty()) {
        keyMap_[CODES_TYPE_STRING] = "string";
        keyMap_[CODES_TYPE_LONG] = "long";
        keyMap_[CODES_TYPE_DOUBLE] = "double";
    }
}

GribMvDump::~GribMvDump()
{
    clear();
}

void GribMvDump::clear()
{
    for (auto& it : item_) {
        delete it;
    }
    item_.clear();
}

// msgCnt starts from 1!!!
bool GribMvDump::read(const std::string& fname, int count)
{
    std::vector<std::string> name_space = {"Default", "geography", "ls", "mars",
                                           "parameter", "statistics", "time", "vertical"};

    // list of skipped keys - these can be very expensive to compute; we can
    // always put them back in if someone really really needs them...
    // to be considered: minimum, maximum
    std::vector<std::string> key_skiplist = {"distinctLatitudes", "distinctLongitudes", "distinctLatitudes"};

    // keys which are redundant or not properly defined
    key_skiplist.push_back("GRIBEditionNumber");

    // Other keys which define constant values and are not really useful for users
    key_skiplist.push_back("7777");
    key_skiplist.push_back("x");

    FILE* fp;
    grib_handle* gh = nullptr;
    grib_keys_iterator* kiter = nullptr;
    // const int MAX_KEY_LEN=255;
    const int MAX_VAL_LEN = 1024;
    unsigned long key_iterator_filter_flags = GRIB_KEYS_ITERATOR_ALL_KEYS;

    int err = 0;
    int grib_count = 0;

    int keyType;
    std::string svalue;
    char value[MAX_VAL_LEN];
    size_t vlen = MAX_VAL_LEN;

    GuiLog().task() << "Generating namespace dump for message: " << count << GuiLog::methodKey() << "ecCodes C interface";

    // open grib file for reading
    fp = fopen(fname.c_str(), "rb");
    if (!fp) {
        GuiLog().error() << "Cannot open grib file: \n        " << fname << "\n";
        return false;
    }

    // Get messages form the file
    while ((gh = grib_handle_new_from_file(0, fp, &err)) != nullptr || err != CODES_SUCCESS) {
        grib_count++;

        // if(!gh)
        //{
        //	log->error("GribMvDump::read() ---> Unable to create grib handle\n");
        //	return;
        // }

        if (grib_count == count) {
            // std::cout << "-- GRIB N. " << grib_count << "---/n";

            if (!gh) {
                GuiLog().error() << "Unable to create grib handle for message no: " << grib_count << "\n";
                return false;
            }

            for (auto& ns : name_space) {
                // Get key iterator
                if (ns != "Default")
                    kiter = grib_keys_iterator_new(gh, key_iterator_filter_flags, const_cast<char*>(ns.c_str()));
                else
                    kiter = grib_keys_iterator_new(gh, key_iterator_filter_flags, nullptr);

                if (!kiter) {
                    GuiLog().error() << "Unable to create keys iterator for message no: " << grib_count << "\n";
                    return false;
                }

                // Loop for the keys in the current message until all the keys are found
                while (grib_keys_iterator_next(kiter)) {
                    const char* name = grib_keys_iterator_get_name(kiter);

                    if (!name) {
                        continue;
                    }

                    std::string cbuff = name;

                    // if this key is in our skiplist, then do not use it
                    auto i = std::find(key_skiplist.begin(), key_skiplist.end(), name);
                    if (i != key_skiplist.end())
                        continue;


                    // Add namespace prefix to the key name (e.g. mars.param)
                    if (ns != "Default") {
                        cbuff = ns + "." + cbuff;
                    }

                    // Get string value
                    vlen = MAX_VAL_LEN;
                    size_t len;
                    long longValue;
                    double doubleValue;
                    value[0] = '\0';

                    if (grib_get_native_type(gh, name, &keyType) == CODES_SUCCESS &&
                        grib_get_size(gh, name, &len) == CODES_SUCCESS &&
                        (keyType == CODES_TYPE_STRING || keyType == CODES_TYPE_LONG ||
                         keyType == CODES_TYPE_DOUBLE)) {
                        auto* item = new GribItem;
                        item_.push_back(item);
                        item->setName(cbuff);

                        if (keyType == CODES_TYPE_STRING || len == 1) {
                            grib_get_string(gh, name, value, &vlen);
                        }

                        if (keyType == CODES_TYPE_LONG && len == 1) {
                            if (codes_get_long(gh, name, &longValue) == CODES_SUCCESS) {
                                item->setValue(longValue, value);
                            }
                        }
                        else if (keyType == CODES_TYPE_DOUBLE && len == 1) {
                            if (codes_get_double(gh, name, &doubleValue) == CODES_SUCCESS) {
                                item->setValue(doubleValue, value);
                            }
                        }
                        else if (keyType == CODES_TYPE_STRING) {
                            item->setValue(value);
                        }
                        else {
                            svalue = "Array (" + std::to_string(len) + ")";
                            item->setValue(svalue);
                        }

                        auto it = keyMap_.find(keyType);
                        if (it != keyMap_.end()) {
                            item->setType(it->second);
                        }
                    }
                }

                grib_keys_iterator_delete(kiter);
            }

            fclose(fp);
            break;
        }

        if (gh)
            grib_handle_delete(gh);
    }

    return true;
}

//===================================================
//
//  GribTableDump
//
//===================================================

// msgCnt starts from 1!!!
bool GribTableDump::read(const std::string& fgrib, int msgCnt,
                         std::vector<std::string>& tablesMasterDir, std::vector<std::string>& tablesLocalDir)
{
    FILE* fp;
    grib_handle* gh = nullptr;
    int err = 0;
    int grib_count = 0;

    GuiLog().task() << "Generating table dump for message: " << msgCnt << GuiLog::methodKey() << "ecCodes C interface";

    // open grib file for reading
    fp = fopen(fgrib.c_str(), "rb");
    if (!fp) {
        GuiLog().error() << "Cannot open grib file: \n        " << fgrib << "\n";
        return false;
    }

    // Get messages form the file
    while (((gh = grib_handle_new_from_file(0, fp, &err)) != nullptr || err != CODES_SUCCESS) && grib_count < msgCnt) {
        grib_count++;
        if (grib_count == msgCnt) {
            if (!gh) {
                GuiLog().error() << "Unable to create grib handle for message no: " << grib_count;
                fclose(fp);
                return false;
            }

            const int MAX_CHAR_LEN = 1024;
            char charVal[MAX_CHAR_LEN];
            size_t vlen = 0;
            std::string masterDirPattern, localDirPattern;

            vlen = MAX_CHAR_LEN;
            if (MV_CODES_CHECK(grib_get_string(gh, "tablesMasterDir", charVal, &vlen), 0)) {
                masterDirPattern = std::string(charVal);
            }

            vlen = MAX_CHAR_LEN;
            if (MV_CODES_CHECK(grib_get_string(gh, "tablesLocalDir", charVal, &vlen), 0)) {
                localDirPattern = std::string(charVal);
            }

            vlen = MAX_CHAR_LEN;
            std::string centreStr;
            if (MV_CODES_CHECK(grib_get_string(gh, "centre", charVal, &vlen), 0)) {
                centreStr = std::string(charVal);
            }

            long centre = 0;
            MV_CODES_CHECK(grib_get_long(gh, "centre", &centre), 0);
            localDirPattern = metview::replace(localDirPattern, "[centre:l]", std::to_string(centre));
            localDirPattern = metview::replace(localDirPattern, "[centre:s]", centreStr);

            if (definitionsDir_.empty()) {
                if (char* defPath = codes_definition_path(nullptr)) {
                    std::string dStr(defPath);
                    Tokenizer parse(":");
                    parse(dStr, definitionsDir_);
                }
            }

            for (auto defDir : definitionsDir_) {
                tablesMasterDir.push_back(defDir + "/" + masterDirPattern);
                tablesLocalDir.push_back(defDir + "/" + localDirPattern);
            }

            grib_handle_delete(gh);
            fclose(fp);
            return true;
        }

        if (gh) {
            grib_handle_delete(gh);
        }
    }

    fclose(fp);
    return false;
}

//===================================================
//
//  GribValueDump
//
//===================================================

const int GribValueDump::maxNum_ = 7000000;

GribValueDump::~GribValueDump()
{
    clear();
}

void GribValueDump::clear()
{
    if (latitude_) {
        delete[] latitude_;
    }

    if (longitude_) {
        delete[] longitude_;
    }

    if (value_) {
        delete[] value_;
    }

    latitude_ = nullptr;
    longitude_ = nullptr;
    value_ = nullptr;
    num_ = 0;
    gridType_.clear();
}

void GribValueDump::readFailed(FILE* fp, grib_handle* gh)
{
    grib_handle_delete(gh);
    fclose(fp);
    clear();
}

bool GribValueDump::read(const std::string& fgrib, int msgCnt, std::string& errMsg, std::string& warnMsg)
{
    // Get grib dump for the given field
    grib_handle* gh = nullptr;

    int err = 0;
    int grib_count = 0;

    GuiLog().task() << "Generating value dump for message: " << msgCnt << GuiLog::methodKey() << "ecCodes C interface";

    // Clear the currently allocated data
    clear();
    assert(latitude_ == nullptr);
    assert(longitude_ == nullptr);
    assert(value_ == nullptr);
    assert(num_ == 0);

    // open grib file for reading
    FILE* fp = fopen(fgrib.c_str(), "rb");
    if (!fp) {
        errMsg = "Cannot open grib file: " + fgrib;
        GuiLog().error() << "Cannot open grib file: \n        " << fgrib << "\n";
        return false;
    }

    // Get messages form the file
    while (((gh = grib_handle_new_from_file(0, fp, &err)) != nullptr || err != CODES_SUCCESS) && grib_count < msgCnt) {
        grib_count++;

        // if(!gh)
        //{
        //	log->error("GribValueDump::read() ---> Unable to create grib handle\n");
        //	return;
        // }

        if (grib_count == msgCnt) {
            if (!gh) {
                errMsg = "Unable to create grib handle for message no: " + std::to_string(grib_count);
                GuiLog().error() << errMsg;
                fclose(fp);
                return false;
            }

            long dataNum = 0;
            size_t llvNum = 0;
            std::string llvName = "latLonValues";
            constexpr int triple = 3;
            constexpr int MAX_VAL_LEN = 1024;
            char cval[MAX_VAL_LEN];
            size_t vlen = MAX_VAL_LEN;

            // Grid type
            if (grib_get_string(gh, "gridType", cval, &vlen) == CODES_SUCCESS) {
                gridType_ = std::string(cval);
            }
            else {
                errMsg = "gridType is not defined";
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }

            if (gridType_ == "sh") {
                errMsg = "gridType=sh is not supported";
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }

            if (grib_get_long(gh, "numberOfDataPoints", &dataNum) != 0) {
                errMsg = "Failed to read numberOfDataPoints";
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }

            num_ = static_cast<int>(dataNum);
            if (num_ > maxNum_) {
                warnMsg = "Cannot generate values dump. Number of data values=" +
                          std::to_string(dataNum) + " is too large.";
                GuiLog().error() << warnMsg;
                readFailed(fp, gh);
                return false;
            }

            // LatLonValues
            grib_get_size(gh, llvName.c_str(), &llvNum);
            if (llvNum == 0) {
                errMsg = "Failed to read " + llvName + " size";
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            } else if (static_cast<int>(llvNum) != triple*num_) {
                errMsg = llvName + " num=" + std::to_string(llvNum) +
                         " differs to 3*numberOfDataPoints=" + std::to_string(num_);
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }

            std::unique_ptr<double[]> llv(new double[triple*dataNum]);
            if (grib_get_double_array(gh, llvName.c_str(), llv.get(), &llvNum) != 0) {
                errMsg = "Failed to read " + llvName;
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }


            latitude_ = new double[num_];
            longitude_ = new double[num_];
            value_ = new double[num_];

            std::size_t pos=0;
            for (std::size_t i=0; i < llvNum; i+=triple, pos++) {
                latitude_[pos] = llv[i];
                longitude_[pos] = llv[i+1];
                value_[pos] = llv[i+2];
            }

            llv.reset();

            auto res = std::minmax_element(value_, value_ + num_);
            auto minVal = *(res.first);
            auto maxVal = *(res.second);
//            MvLog().dbg() << MV_FN_INFO << "minVal=" << minVal << " maxvVal=" << maxVal;
            auto maxAbs = std::max(std::fabs(minVal), std::fabs(maxVal));
            decimalPlaces_ = 0;
            for (int i = 0; i > -10; i--) {
                if (maxAbs > std::pow(10, static_cast<double>(i))) {
                    decimalPlaces_ = -i;
                    break;
                }
            }

//            MvLog().dbg() << "decimalPlaces=" << decimalPlaces_;
        }

        if (gh)
            grib_handle_delete(gh);
    }

    fclose(fp);

    return true;
}

//=========================================
//
//  GribPlDump
//
//=========================================

void GribPlDump::clear()
{
    vals_.clear();
}

 // msgCnt starts from 1!!!
bool GribPlDump::read(const std::string& fgrib, int msgCnt)
{
    // Get grib dump for the given field
    grib_handle* gh = nullptr;

    int err = 0;
    int grib_count = 0;

    GuiLog().task() << "Generating PL dump for message: " << msgCnt << GuiLog::methodKey() << "ecCodes C interface";

    // Clear the currently allocated data
    clear();
    assert(vals_.size() == 0);

    std::string errMsg;
    std::string warnMsg;

    // open grib file for reading
    FILE* fp = fopen(fgrib.c_str(), "rb");
    if (!fp) {
        errMsg = "Cannot open grib file: " + fgrib;
        GuiLog().error() << "Cannot open grib file: \n        " << fgrib << "\n";
        return false;
    }

    // Get messages form the file
    while (((gh = grib_handle_new_from_file(0, fp, &err)) != nullptr || err != CODES_SUCCESS) && grib_count < msgCnt) {
        grib_count++;

        if (grib_count == msgCnt) {
            if (!gh) {
                errMsg = "Unable to create grib handle for message no: " + std::to_string(grib_count);
                GuiLog().error() << errMsg;
                fclose(fp);
                return false;
            }

            size_t num = 0;
            std::string name = "pl";

            grib_get_size(gh, name.c_str(), &num);
            if ( num == 0) {
                errMsg = "Failed to read " + name + " size";
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }

            std::unique_ptr<long[]> d(new long[num]);
            if (grib_get_long_array(gh, name.c_str(), d.get(), &num) != 0) {
                errMsg = "Failed to read " + name;
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }

            for (std::size_t i=0; i < num; i++) {
                vals_.emplace_back(d[i]);
            }

            long longVal;
            if (grib_get_long(gh, "N", &longVal) == CODES_SUCCESS) {
                N_ = static_cast<int>(longVal);
            }

            if (grib_get_long(gh, "isOctahedral", &longVal)  == CODES_SUCCESS) {
                octahedral_ = (longVal == 1);
            }

            const int MAX_VAL_LEN = 1024;
            char chValue[MAX_VAL_LEN];
            size_t vlen = MAX_VAL_LEN;
            if (grib_get_string(gh, "gridType", chValue, &vlen) == CODES_SUCCESS) {
                gridType_ = std::string(chValue);
            }

            grib_handle_delete(gh);
            fclose(fp);
            return true;

        }

        if (gh != nullptr) {
            grib_handle_delete(gh);
        }
    }

    fclose(fp);
    return false;
}

void GribPlDump::readFailed(FILE* fp, grib_handle* gh)
{
    grib_handle_delete(gh);
    fclose(fp);
    clear();
}

//=========================================
//
//  GribPvDump
//
//=========================================

void GribPvDump::clear()
{
    vals_.clear();
    a_.clear();
    b_.clear();
    ph_.clear();
    pf_.clear();
}

 // msgCnt starts from 1!!!
bool GribPvDump::read(const std::string& fgrib, int msgCnt)
{
    // Get grib dump for the given field
    grib_handle* gh = nullptr;

    int err = 0;
    int grib_count = 0;

    GuiLog().task() << "Generating PV dump for message: " << msgCnt << GuiLog::methodKey() << "ecCodes C interface";

    // Clear the currently allocated data
    clear();
    assert(vals_.size() == 0);

    std::string errMsg;
    std::string warnMsg;

    // open grib file for reading
    FILE* fp = fopen(fgrib.c_str(), "rb");
    if (!fp) {
        errMsg = "Cannot open grib file: " + fgrib;
        GuiLog().error() << "Cannot open grib file: \n        " << fgrib << "\n";
        return false;
    }

    // Get messages form the file
    while (((gh = grib_handle_new_from_file(0, fp, &err)) != nullptr || err != CODES_SUCCESS) && grib_count < msgCnt) {
        grib_count++;

        if (grib_count == msgCnt) {
            if (!gh) {
                errMsg = "Unable to create grib handle for message no: " + std::to_string(grib_count);
                GuiLog().error() << errMsg;
                fclose(fp);
                return false;
            }

            size_t num = 0;
            std::string name = "pv";

            grib_get_size(gh, name.c_str(), &num);
            if ( num == 0) {
                errMsg = "Failed to read " + name + " size";
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }

            std::cout << "PV size=" << num << std::endl;

            std::unique_ptr<double[]> d(new double[num]);
            if (grib_get_double_array(gh, name.c_str(), d.get(), &num) != 0) {
                errMsg = "Failed to read " + name;
                GuiLog().error() << errMsg;
                readFailed(fp, gh);
                return false;
            }

            grib_handle_delete(gh);
            fclose(fp);

            for (std::size_t i=0; i < num; i++) {
                vals_.emplace_back(d[i]);
            }

            if (vals_.size() % 2 == 0) {
                compute();
                return true;
            } else {
                clear();
                return false;
            }
        }

        if (gh != nullptr) {
            grib_handle_delete(gh);
        }
    }

    fclose(fp);
    return false;
}

void GribPvDump::compute()
{
    const float pScale = 0.01; // Pa -> hPa

    for(std::size_t i=0; i < vals_.size()/2; i++) {
        a_.emplace_back(vals_[i]);
    }

    for(std::size_t i=a_.size(); i < vals_.size(); i++) {
        b_.emplace_back(vals_[i]);
    }

    for (std::size_t i=0; i < a_.size(); i++) {
        ph_.emplace_back((a_[i] + b_[i]*psRef_)*pScale);
    }

    pf_.emplace_back(0.);
    for (std::size_t i=1; i < ph_.size(); i++) {
        pf_.emplace_back((ph_[i-1] + ph_[i])/2);
    }
}

void GribPvDump::readFailed(FILE* fp, grib_handle* gh)
{
    grib_handle_delete(gh);
    fclose(fp);
    clear();
}

int GribPvDump::levelNum() const
{
    int n = static_cast<int>(a_.size());
    return (n>0)?(n-1):0;
}

//=========================================
//
//  GribMetaData
//
//=========================================

GribMetaData::GribMetaData() :
    MvMessageMetaData(GribType),
    hasMultiMessage_(false)
{
    std::string cmd;

    GRIB_DEFINITION_PATH_ENV = "";
    const char* grb_def_path = getenv("GRIB_DEFINITION_PATH");
    if (grb_def_path) {
        GRIB_DEFINITION_PATH_ENV = std::string(grb_def_path);
    }

    // Define the grib dump program name
#ifdef ECCODES_UI
    char* gdexe = getenv("CODES_UI_GRIB_DUMP");
#else
    char* gdexe = getenv("METVIEW_GRIB_DUMP");
#endif

    if (gdexe == 0) {
        GRIB_DUMP_EXE = "grib_dump";
    }
    else {
        GRIB_DUMP_EXE = gdexe;
    }

    grib_multi_support_on(0);
}

GribMetaData::~GribMetaData()
{
    clear();
}

void GribMetaData::clear()
{
    hasMultiMessage_ = false;

    MvMessageMetaData::clear();
}

void GribMetaData::setFilter(const std::vector<off_t>& offset, const std::vector<int>& len)
{
    filterOffset_ = offset;
    filterLen_ = len;

    filterCnt_.clear();
    if (filterOffset_.size() > 0) {
        filterCnt_ = std::vector<int>(filterOffset_.size(), -1);
    }

    if (offset.size() != 0 &&
        offset.size() == len.size()) {
        filterEnabled_ = true;
        messageNum_ = filterOffset_.size();
    }
}

void GribMetaData::updateFilterCnt(const std::vector<off_t>& offset, const std::vector<int>& cnt)
{
    filterOffset_ = offset;
    filterCnt_ = cnt;
}

void GribMetaData::setFileName(std::string fname)
{
    clear();

    fileName_ = fname;
    // totalMessageNum_=computeTotalMessageNum();
    // if(messageNum_ == 0)
    //    messageNum_=totalMessageNum_;
}

int GribMetaData::computeTotalMessageNum()
{
    FILE* fp;
    fp = fopen(fileName_.c_str(), "rb");
    if (!fp) {
        // log->error("GribMetaData::readMessages() ---> Cannot open grib file: \n        " + fileName_ + "\n");
        return 0;
    }

    int res = 0;
    if (grib_count_in_file(0, fp, &res) != CODES_SUCCESS)
        res = 0;

    fclose(fp);
    return res;
}

void GribMetaData::setTotalMessageNum(int num)
{
    totalMessageNum_ = num;
    if (!filterEnabled_) {
        messageNum_ = totalMessageNum_;
    }
}

void GribMetaData::getKeyProfileForFirstMessage(MvKeyProfile* prof)
{
    prof->clearKeyData();

    FILE* fp;
    grib_handle* gh = nullptr;
    int err = 0;
    std::string cbuff;

    // open grib file for reading
    fp = fopen(fileName_.c_str(), "r");
    if (!fp) {
        // GuiLog().error() << "GribMetaData::readMessages() ---> Cannot open grib file: \n        " << fileName_ << "\n";
        return;
    }

    while ((gh = grib_handle_new_from_file(0, fp, &err)) != nullptr || err != CODES_SUCCESS) {
        if (!gh) {
            // GuiLog().error() <<  "GribMetaData::readMessages() --->  Unable to create grib handle for message count: " << grib_count+1 << std::endl;
        }

        readMessage(prof, gh);

        if (gh)
            grib_handle_delete(gh);

        return;
    }
}

#if 0
void GribMetaData::readMessages(MvKeyProfile *prof)
{
	/*std::vector<char*> name_space;
	name_space.push_back(0);
	name_space.push_back("geography");
    name_space.push_back("ls");==CODES_SUCCESS
	name_space.push_back("mars");
	name_space.push_back("parameter");
	name_space.push_back("time");
	name_space.push_back("vertical");*/

  	FILE* fp;
  	grib_handle* gh=nullptr;
 	int err=0;
 	int grib_count=0;
	long longValue;
	std::string cbuff;

    GuiLog().task() << "Generating grib key list for all the messages" <<
                       GuiLog::methodKey() << "ecCodes C interface";
	messageNum_=0;

	//open grib file for reading
  	fp = fopen(fileName_.c_str(),"r");
  	if(!fp) 
	{
        GuiLog().error() << "GribMetaData::readMessages() ---> Cannot open grib file: \n        " << fileName_ << "\n";
        return;
  	}

	//For filtered datasets
	if(filterEnabled_==true)
	{					
		//At the first scan we need to find the message cnt for each filtered message.
		//The goal is to fill up the filterCnt_ vector.
		if(firstScan_)
		{
			//long currentOffset=0;
			int msgCnt=0;
            while((gh = grib_handle_new_from_file(0,fp,&err)) != nullptr || err !=CODES_SUCCESS)
	 		{
                if(!gh)
				{      				
                    GuiLog().error() <<  "GribMetaData::readMessages() --->  Unable to create grib handle for message count: " << grib_count+1;
                }
				
				msgCnt++;

				if(gh)
				{  
                    if(grib_get_long(gh,"offset",&longValue) ==CODES_SUCCESS)
					{
						for(unsigned int i=0; i < filterOffset_.size(); i++)
						{
							off_t offset_diff = longValue - filterOffset_[i];
							if(ABS(offset_diff) < 120)
							{
								filterCnt_[i] = msgCnt;
								break;
							}
						}
					}
					else
					{						
                        GuiLog().error() <<  "GribMetaData::readMessages() --->  Cannot get offset for message count: " << grib_count+1;
                        grib_handle_delete(gh);
						return;
					}

					//currentOffset+=longValue;
					grib_handle_delete(gh);
				}	
			}
		}
		
		//Read the filtered messages
		for(unsigned int i=0; i < filterOffset_.size(); i++)
		{
			grib_count++;

			fseek(fp,filterOffset_[i],SEEK_SET);
	
			int err;
			gh = grib_handle_new_from_file(0,fp,&err);
	
			if(!gh) 
			{		
                GuiLog().error() <<  "GribMetaData::readMessages() --->  Unable to create grib handle for offset: " << filterOffset_[i];
				//return;
			}
				
			readMessage(prof,gh);
	
            broadcast(&MvMessageMetaDataObserver::messageScanStepChanged,grib_count);
						
			if(gh)
				grib_handle_delete(gh);
		}	
	}	
	else
	{	  
		//Get messages form the file
        while((gh = grib_handle_new_from_file(0,fp,&err)) != nullptr || err !=CODES_SUCCESS)
	 	{
            if(!gh)
			{      		
                GuiLog().error() <<  "GribMetaData::readMessages() --->  Unable to create grib handle for message count: " << grib_count+1 << std::endl;
            }
		
			grib_count++;

			readMessage(prof,gh);

            broadcast(&MvMessageMetaDataObserver::messageScanStepChanged,grib_count);

			if(gh) 
				grib_handle_delete(gh);
		}

		if(firstScan_)
		{
			if(totalMessageNum_ != grib_count)
			{
				if(totalMessageNum_ != 0)
				  	hasMultiMessage_=true;
			  	
				totalMessageNum_=grib_count;			  	
			}
		}
	}
	
	fclose(fp);
	
	messageNum_=grib_count;
	firstScan_=false;


	//------------------------------
	// Format key "count"
	//------------------------------

	char c[20];
	int n,c_format_id;
	const char c_format[4][5]={"%02d","%03d","%04d","%05d"};

	int num=messageNum_;
	if(num < 100)
	{
		c_format_id=0;
	}
	else if(num < 1000)
	{
		c_format_id=1;		
	}
	else if(num < 10000)
	{
		c_format_id=2;	
	}	
	else
	{
		c_format_id=3;	
	}

	MvKey* key=prof->key("count");
	if(key)
	{
		for(unsigned int i=0; i< key->value().size(); i++)
		{
			std::string s=key->value()[i];
			if(s != "")
			{ 
				std::stringstream sst(s);
				sst >> n;
				sprintf(c,c_format[c_format_id],n);
				s=c;
				key->setValue(i,s);
			}
		}
	
	}

	key=prof->key("MV_Index");
	if(key)
	{
		for(unsigned int i=0; i< key->value().size(); i++)
		{
			sprintf(c,c_format[c_format_id],i+1);
			std::string s(c);
			key->setValue(i,s);
		}
	
	}
}
#endif


void GribMetaData::getKeyList(int count, std::string nameSpace, MvKeyProfile* prof)
{
    std::list<std::string> keys;
    getKeyList(count, nameSpace, keys);
    for (auto& key : keys) {
        prof->addKey(new MvKey(key, key));
    }
}


void GribMetaData::getKeyList(int count, std::string nameSpace, std::list<std::string>& keys)
{
    std::vector<char*> nameSpaceL;

    if (nameSpace == "Default") {
        nameSpaceL.push_back(0);
    }
    else {
        char* nsC = new char[nameSpace.size() + 1];
        std::copy(nameSpace.begin(), nameSpace.end(), nsC);
        nsC[nameSpace.size()] = '\0';
        nameSpaceL.push_back(nsC);
    }

    // list of skipped keys - these can be very expensive to compute; we can
    // always put them back in if someone really really needs them...
    // to be considered: minimum, maximum
    std::vector<std::string> key_skiplist = {"distinctLatitudes", "distinctLongitudes", "distinctLatitudes"};

    // Other keys which define constant values and are not really useful for users
    key_skiplist.push_back("7777");
    key_skiplist.push_back("x");


    FILE* fp;
    grib_handle* gh = nullptr;
    grib_keys_iterator* kiter = nullptr;
    // const int MAX_KEY_LEN=255;
    // const int MAX_VAL_LEN=1024;
    unsigned long key_iterator_filter_flags = GRIB_KEYS_ITERATOR_ALL_KEYS;

    int err = 0;
    int grib_count = 0;
    int keyType;

    // char value[MAX_VAL_LEN];
    // size_t vlen=MAX_VAL_LEN;
    std::string cbuff;

    GuiLog().task() << "Generating grib key list for message: " << count << GuiLog::methodKey() << "ecCodes C interface";

    // open grib file for reading
    fp = fopen(fileName_.c_str(), "rb");
    if (!fp) {
        GuiLog().error() << "GribMetaData::getKeyList() ---> Cannot open grib file: \n        " << fileName_ << "\n";
        return;
    }

    // Get messages form the file
    while ((gh = grib_handle_new_from_file(0, fp, &err)) != nullptr || err != CODES_SUCCESS) {
        grib_count++;

        /*if(!gh)
        {
                log->error("GribMetaData::getKeyList() ---> Unable to create grib handle\n");
                return;
            }*/

        if (grib_count == count) {
            // std::cout << "-- GRIB N. " << grib_count << "---/n";

            if (!gh) {
                GuiLog().error() << "GribMetaData::getKeyList() ---> Unable to create grib handle for message no: " << grib_count << "\n";

                for (auto& it : nameSpaceL) {
                    if (it != 0)
                        delete[] it;
                }

                return;
            }

            for (unsigned int ns = 0; ns < nameSpaceL.size(); ns++) {
                // Get key iterator
                kiter = grib_keys_iterator_new(gh, key_iterator_filter_flags, nameSpaceL[ns]);
                if (!kiter) {
                    GuiLog().error() << "GribMetaData::getKeyList() ---> Unable to create keys iterator for message no: " << grib_count << "\n";

                    for (auto& it : nameSpaceL) {
                        if (it != 0)
                            delete[] it;
                    }

                    return;
                }

                // Loop for the keys in the current message until all the keys are found
                while (grib_keys_iterator_next(kiter)) {
                    const char* name = grib_keys_iterator_get_name(kiter);
                    if (!name) {
                        continue;
                    }

                    std::string cbuff = name;

                    // if this key is in our skiplist, then do not use it
                    auto i = std::find(key_skiplist.begin(), key_skiplist.end(), name);
                    if (i != key_skiplist.end())
                        continue;

                    // Add namespace prefix to the key name (e.g. mars.param)
                    if (nameSpaceL[ns] != 0) {
                        cbuff = std::string(nameSpaceL[ns]) + "." + cbuff;
                    }

                    if (grib_get_native_type(gh, cbuff.c_str(), &keyType) == CODES_SUCCESS &&
                        (keyType == CODES_TYPE_STRING || keyType == CODES_TYPE_LONG ||
                         keyType == CODES_TYPE_DOUBLE)) {
                        keys.push_back(cbuff);
                    }
                }

                grib_keys_iterator_delete(kiter);
            }

            grib_handle_delete(gh);
            break;
        }

        grib_handle_delete(gh);
    }

    keys.sort();

    fclose(fp);

    // This has to be called before calling return in this method!!
    for (auto& it : nameSpaceL) {
        if (it != 0)
            delete[] it;
    }
}

long GribMetaData::gribapiVersionNumber()
{
    return grib_get_api_version();
}

void GribMetaData::readMessage(MvKeyProfile* prof, grib_handle* gh)
{
    if (!gh) {
        for (unsigned int i = 0; i < prof->size(); i++) {
            prof->at(i)->addValue("N/A");
        }
        return;
    }

    const int MAX_VAL_LEN = 1024;
    long longValue;
    char value[MAX_VAL_LEN];
    std::string svalue;
    size_t vlen = MAX_VAL_LEN;

    for (unsigned int i = 0; i < prof->size(); i++) {
        size_t len;
        int keyType;
        const std::string& name = prof->at(i)->name();
        bool foundValue = false;

        if (grib_get_native_type(gh, name.c_str(), &keyType) == CODES_SUCCESS &&
            grib_get_size(gh, name.c_str(), &len) == CODES_SUCCESS &&
            (keyType == CODES_TYPE_STRING || keyType == CODES_TYPE_LONG ||
             keyType == CODES_TYPE_DOUBLE)) {
            if (keyType == CODES_TYPE_STRING || len == 1) {
                if (prof->at(i)->readIntAsString() == false &&
                    keyType == CODES_TYPE_LONG) {
                    if (grib_get_long(gh, name.c_str(), &longValue) == CODES_SUCCESS) {
                        std::stringstream s;
                        s << longValue;
                        prof->at(i)->addValue(s.str().c_str());
                        foundValue = true;
                    }
                }
                else {
                    vlen = MAX_VAL_LEN;
                    if (grib_get_string(gh, name.c_str(), value, &vlen) == CODES_SUCCESS) {
                        prof->at(i)->addValue(value);
                        foundValue = true;
                    }
                }
            }
            else if ((strcmp(name.c_str(), "paramId") == 0 || strcmp(name.c_str(), "parameter.paramId") == 0) &&
                     keyType == CODES_TYPE_LONG) {
                if (prof->at(i)->readIntAsString() == false) {
                    if (grib_get_long(gh, name.c_str(), &longValue) == CODES_SUCCESS) {
                        std::stringstream s;
                        s << longValue;
                        prof->at(i)->addValue(s.str().c_str());
                        foundValue = true;
                    }
                }
                else {
                    vlen = MAX_VAL_LEN;
                    if (grib_get_string(gh, name.c_str(), value, &vlen) == CODES_SUCCESS) {
                        prof->at(i)->addValue(value);
                        foundValue = true;
                    }
                }
            }
            else {
                std::stringstream s;
                s << len;
                svalue = "Array (" + s.str() + ")";
                prof->at(i)->addValue(svalue);
                foundValue = true;
            }
        }


        if (!foundValue) {
            prof->at(i)->addValue("N/A");
        }
    }
}
