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

#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cmath>
#include <cstring>
#include <fstream>
#include "fstream_mars_fix.h"

#include <cstdlib>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>

#ifndef METVIEW_ON_WINDOWS
#include <unistd.h>
#endif

#include "MvTmpFile.h"
#include "MvWindef.h"

#ifdef ECCODES_UI
#include "CodesDirHandler.h"
#endif

#ifdef METVIEW
#include "mars.h"
#endif

#include "Tokenizer.h"

namespace metview
{
#ifndef ECCODES_UI
const std::string& metviewRootDir()
{
    static std::string rDir;
    if (rDir.empty()) {
        auto ch = getenv("METVIEW_USER_DIRECTORY");
        rDir = (ch) ? std::string(ch) : "";
    }
    return rDir;
}

const std::string& metviewShareDir()
{
    static std::string shDir;
    if (shDir.empty()) {
        auto ch = getenv("METVIEW_DIR_SHARE");
        shDir = (ch) ? std::string(ch) : "";
    }
    return shDir;
}

const std::string& metviewUserDir()
{
    static std::string uDir;
    if (uDir.empty()) {
        auto ch = getenv("METVIEW_USER_DIRECTORY");
        uDir = (ch) ? std::string(ch) : "";
    }
    return uDir;
}

const std::string& magicsHomeDir()
{
    static std::string mpDir;
    if (mpDir.empty()) {
        auto ch = getenv("MAGPLUS_HOME");
        mpDir = (ch) ? std::string(ch) : "";
    }
    return mpDir;
}
#endif

#ifndef ECCODES_UI
std::string iconDirFile(const std::string& fName)
{
    return metviewShareDir() + "/icons_mv5/" + fName;
}
#else
std::string iconDirFile(const std::string&)
{
    return {};
}
#endif

#ifndef ECCODES_UI
std::string iconDirFile(const std::string& style, const std::string& fName)
{
    return metviewShareDir() + "/icons_mv5/" + style + "/" + fName;
}
#else
std::string iconDirFile(const std::string&, const std::string&)
{
    return {};
}
#endif

std::string etcDirFile(const std::string& fName)
{
#ifdef ECCODES_UI
    return CodesDirHandler::instance()->shareDirFile(fName);
#else
    return metviewShareDir() + "/etc/" + fName;
#endif
}

std::string appDefDirFile(const std::string& fName)
{
#ifdef ECCODES_UI
    return CodesDirHandler::instance()->shareDirFile(fName);
#else
    static std::string linkDir;
    if (linkDir.empty()) {
        auto ch = getenv("METVIEW_LINK_DIR");
        linkDir = (ch) ? std::string(ch) : "";
    }
    return linkDir + "/" + fName;
#endif
}

std::string preferenceDirFile(const std::string& fName)
{
#ifdef ECCODES_UI
    return CodesDirHandler::instance()->confDirFile(fName);
#else
    return metviewUserDir() + "/System/Preferences/" + fName;
#endif
}

#ifndef ECCODES_UI
std::string systemFeatureItemsFile(const std::string& fName)
{
    return metviewShareDir() + "/icons_mv5/meteo/" + fName;
}

const std::string& extraFeatureItemsImageDir()
{
    static std::string resPath;
    if (resPath.empty()) {
        if (auto ch = getenv("METVIEW_EXTRA_FEATURE_SYMBOLS_DIR")) {
            resPath = std::string(ch) + "/images";
        }
    }
    return resPath;
}

const std::string& extraFeatureItemsLibraryDir()
{
    static std::string resPath;
    if (resPath.empty()) {
        if (auto ch = getenv("METVIEW_EXTRA_FEATURE_SYMBOLS_DIR")) {
            resPath = std::string(ch) + "/icons";
        }
    }
    return resPath;
}

const std::string& localFeatureItemsImageDir()
{
    static std::string p = metviewUserDir() + "/System/Symbols/images";
    return p;
}

const std::string& localFeatureItemsLibraryDir()
{
    static std::string p = metviewUserDir() + "/System/Symbols/icons";
    return p;
}

std::string magicsStylesEcmwfDirFile(const std::string& fName)
{
    return magicsHomeDir() + "/share/magics/styles/ecmwf/" + fName;
}

std::string magicsStylesDirFile(const std::string& fName)
{
    return magicsHomeDir() + "/share/magics/styles/" + fName;
}

std::string stylePreviewDirFile(const std::string& fName)
{
    return metviewShareDir() + "/eccharts/style_previews/" + fName;
}

std::string ecchartsDirFile(const std::string& fName)
{
    return metviewShareDir() + "/eccharts/" + fName;
}

std::string mvlMacroDirFile(const std::string& fName)
{
    return metviewShareDir() + "/app-defaults/CommonMacroFuncs/" + fName;
}
#endif

std::string qtResourceDirFile(const std::string& fName)
{
#ifdef ECCODES_UI
    return CodesDirHandler::instance()->qtResourceDirFile(fName);
#else
    static std::string linkDir;
    if (linkDir.empty()) {
        auto ch = getenv("METVIEW_LINK_DIR");
        linkDir = (ch) ? std::string(ch) : "";
    }
    return linkDir + "/images/" + fName;
#endif
}

std::string webBrowser()
{
    static std::string browser;
#ifndef ECCODES_UI
    if (browser.empty()) {
        auto ch = getenv("MV_BROWSER_NAME");
        browser = (ch) ? std::string(ch) : "";
    }
#endif
    return browser;
}

bool openInBrowser(const std::string& url, std::string& errStr)
{
    std::string browser = webBrowser();
    if (browser.empty()) {
        errStr = "No web browser is defined! Please specify it via env variable MV_BROWSER_NAME";
        return false;
    }

    std::string cmd = browser + " " + url + "&";

    int ret = system(cmd.c_str());
    if (ret != 0)
        return false;

        // This code caused the app to hang
#if 0
    std::stringstream out;
    std::stringstream err;
    metview::shellCommand(cmd, out, err);
    if (!err.str().empty()) {
        errStr = err.str();
        return false;
    }
#endif
    return true;
}

// Call a shell command
// Returns the result and an error message
void shellCommand(const std::string& command, std::stringstream& out, std::stringstream& err)
{
    FILE* in = nullptr;
    char cbuf[512];

    // Create a temporary file
    MvTmpFile ftmp;
    std::string cmd = command + " 2>" + ftmp.path();

    if (!(in = popen(cmd.c_str(), "r")))
        return;

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

    pclose(in);

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

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

    fclose(in);
}

bool createWorkDir(const std::string& prefix, std::string& tmpPath, std::string& errTxt)
{
#ifndef METVIEW_ON_WINDOWS
    std::string tmpRoot;

    char* mvtmp = getenv("METVIEW_TMPDIR");
    if (mvtmp == nullptr) {
        errTxt = "No env variable METVIEW_TMPDIR is not defined!";
        return false;
    }
    else {
        tmpRoot = std::string(mvtmp);
    }

    time_t sec = time(nullptr);
    pid_t pid = getpid();

    std::stringstream out;
    out << tmpRoot << "/" + prefix + "_" << sec << "_" << pid;
    tmpPath = out.str();

    if (mkdir(tmpPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) != 0) {
        errTxt = "Could not genarate work directory: " + tmpPath;
        return false;
    }

    return true;
#else
    return false;
#endif
}

bool checkGrid(const std::vector<std::string>& areaStr, const std::vector<std::string>& gridStr,
               std::vector<std::string>& numXy, std::string& errStr)
{
    if (areaStr.size() != 4)
        return false;

    if (gridStr.size() != 2)
        return false;


    std::vector<float> area;  // S/W/N/E
    for (std::size_t i = 0; i < 4; i++) {
        area.push_back(fromString<float>(areaStr[i]));
    }

    if (area[1] > area[3]) {
        if (area[3] < 0)
            area[3] += 360.;
        else {
            errStr = "W should be smaller than E! W=" + std::to_string(area[1]) +
                     " E=" + std::to_string(area[3]);
            return false;
        }
    }

    if (area[0] > area[2]) {
        errStr = "S should be smaller than N! S=" + std::to_string(area[0]) +
                 " N=" + std::to_string(area[2]);
        return false;
    }

    std::vector<float> grid;
    for (unsigned int i = 0; i < 2; i++) {
        grid.push_back(fromString<float>(gridStr[i]));
    }

    if (grid[0] < 0.) {
        errStr = "The W-E grid increment must be greater than 0! The specified value=" + std::to_string(grid[0]);
        return false;
    }
    if (grid[1] < 0.) {
        errStr = "The N-S grid increment must be greater than 0! The specified value=" + std::to_string(grid[1]);
        return false;
    }

    long c0 = roundl(1.E5 * area[0]);
    long c1 = roundl(1.E5 * area[1]);
    long c2 = roundl(1.E5 * area[2]);
    long c3 = roundl(1.E5 * area[3]);
    long dx = roundl(1.E5 * grid[0]);
    long dy = roundl(1.E5 * grid[1]);

    if ((c3 - c1) % dx != 0) {
        errStr = "W-E grid resolution= " + gridStr[0] + " does not match the area!";
        return false;
    }
    numXy.push_back(std::to_string(static_cast<long>((c3 - c1) / dx)));

    if ((c2 - c0) % dy != 0) {
        errStr = "N-S grid resolution= " + gridStr[1] + " does not match the area!";
        return false;
    }
    numXy.push_back(std::to_string(static_cast<long>((c2 - c0) / dy)));
    return true;
}

std::string simplified(const std::string& str)
{
    std::size_t pos1 = str.find_first_not_of(" ");
    std::size_t pos2 = str.find_last_not_of(" ");

    if (pos1 != std::string::npos && pos2 != std::string::npos && pos2 >= pos1) {
        return str.substr(pos1, pos2 - pos1 + 1);
    }
    return {};
}


std::string replace(const std::string& data, const std::string& oldStr, const std::string& newStr)
{
    std::string res = data;
    size_t pos = res.find(oldStr);
    while (pos != std::string::npos) {
        res.replace(pos, oldStr.size(), newStr);
        pos = res.find(oldStr, pos + newStr.size());
    }
    return res;
}

bool startsWith(const std::string& str, const char& startCh, bool allowWhiteSpace)
{
    static std::string ws = "\n\t ";
    if (allowWhiteSpace) {
        auto pos = str.find_first_not_of(ws);
        if (pos != std::string::npos) {
            return str[pos] == startCh;
        }
    }
    else if (str.length() > 0) {
        return str[0] == startCh;
    }
    return false;
}

std::string stationIdForWritingToFile(const std::string& in)
{
    static std::string space(" ");
    static std::string spaceReplacement("\\32\\");
    static std::string tab("\t");
    static std::string tabReplacement("\\9\\");
    if (in.empty())
        return "?";
    else {
        std::string strToWrite = metview::replace(in, space, spaceReplacement);
        strToWrite = metview::replace(strToWrite, tab, tabReplacement);
        return strToWrite;
    }
}

std::string stationIdFromFile(const std::string& in)
{
    static std::string space(" ");
    static std::string spaceReplacement("\\32\\");
    static std::string tab("\t");
    static std::string tabReplacement("\\9\\");

    if (in == "?")
        return {""};

    std::string stnid = metview::replace(in, spaceReplacement, space);
    stnid = metview::replace(stnid, tabReplacement, tab);

    return stnid;
}


bool isNumber(const std::string& v)
{
    for (char i : v) {
        if (isdigit(i) == 0)
            return false;
    }
    return true;
}

std::string toBold(int v)
{
    std::string s;
    s = "<b>" + std::to_string(v) + "</b>";
    return s;
}

std::string toBold(float v)
{
    std::string s;
    s = "<b>" + std::to_string(v) + "</b>";
    return s;
}

std::string toBold(const std::string& v)
{
    std::string s;
    s = "<b>" + v + "</b>";
    return s;
}

std::string beautify(const std::string& name)
{
    std::string result = name;
    bool up = true;

    for (int it = 0; it < static_cast<int>(name.length()); ++it) {
        char j = result[it];
        if (j == '_' || j == ' ') {
            up = true;
            result[it] = ' ';
        }
        else {
            result[it] = up ? toupper(j) : tolower(j);
            up = false;
        }
    }

    return result;
}


bool isForceMarsLogSet()
{
    const char* marslog = getenv("METVIEW_MARS_LOG");
    if (marslog && strcmp(marslog, "1") == 0)
        return true;

    return false;
}


double truncate(double d, int decimals)
{
    double p = pow(10, decimals);
    return round(d * p) / p;
}

#ifdef METVIEW
void writeFileToLogInfo(const std::string& fileName, int maxLineNum)
{
    writeFileToLog(LOG_INFO, fileName, maxLineNum);
}

void writeFileToLogWarn(const std::string& fileName, int maxLineNum)
{
    writeFileToLog(LOG_WARN, fileName, maxLineNum);
}

void writeFileToLogErr(const std::string& fileName, int maxLineNum)
{
    writeFileToLog(LOG_EROR, fileName, maxLineNum);
}

void writeFileToLog(int logId, const std::string& fileName, int maxLineNum)
{
    if (maxLineNum == -1) {
        std::ifstream in(fileName.c_str());
        std::string line;
        while (getline(in, line)) {
            marslog(logId, "%s", line.c_str());
        }
    }
    else {
        std::string err;
        std::string txt = getLastLines(fileName, maxLineNum, err);
        std::istringstream in(txt);
        std::string line;
        while (getline(in, line)) {
            marslog(logId, "%s", line.c_str());
        }
    }
}
#endif


std::string getLastLines(const std::string& fileName, int lastLineNum, std::string& error_msg)
{
    if (lastLineNum <= 0)
        return {};

    std::ifstream source(fileName.c_str(), std::ios_base::in);
    if (!source) {
        error_msg = "File::get_last_n_lines: Could not open file " + fileName;
        error_msg += " (";
        error_msg += strerror(errno);
        error_msg += ")";
        return {};
    }

    size_t const granularity = 100 * lastLineNum;
    source.seekg(0, std::ios_base::end);
    size_t fileSize = static_cast<size_t>(source.tellg());
    std::vector<char> buffer;
    int newlineCount = 0;
    while (source && buffer.size() != fileSize && newlineCount < lastLineNum) {
        buffer.resize(std::min(buffer.size() + granularity, fileSize));
        source.seekg(-static_cast<std::streamoff>(buffer.size()), std::ios_base::end);
        source.read(&(buffer.front()), buffer.size());
        newlineCount = std::count(buffer.begin(), buffer.end(), '\n');
    }

    auto start = buffer.begin();
    while (newlineCount > lastLineNum) {
        start = std::find(start, buffer.end(), '\n') + 1;
        --newlineCount;
    }

    return {start, buffer.end()};
}

#ifdef METVIEW

std::string pathFromFieldsetWrittenToDisk(const MvRequest& req)
{
    std::string path;
    if ((const char*)req("FIELDSET_FROM_FILTER") &&
        (int)req("FIELDSET_FROM_FILTER") == 1) {
        fieldset* fs = request_to_fieldset(req);
        fieldset* z = copy_fieldset(fs, fs->count, true);
        save_fieldset(z);
        request* finalreq = fieldset_to_request(z);
        path = get_value(finalreq, "PATH", 0);
    }
    else {
        if (const char* ch = (const char*)req("PATH")) {
            path = std::string(ch);
        }
    }
    return path;
}

#endif

std::string merge(const std::vector<std::string>& v, const std::string& sep)
{
    std::string res;
    for (size_t i = 0; i < v.size(); i++) {
        if (i > 0)
            res += sep;
        res += v[i];
    }
    return res;
}

std::string toMacroList(const std::vector<std::string>& v)
{
    std::string res;
    for (size_t i = 0; i < v.size(); i++) {
        if (i > 0)
            res += ",";
        res += v[i];
    }

    if (v.size() >= 1)
        res = "[" + res + "]";

    return res;
}


std::string stepToMacro(const std::vector<std::string>& v)
{
    std::string res;

    bool useQuote = false;
    for (auto s : v) {
        toLower(s);
        if (s.find("-") != std::string::npos || s == "to" || s == "by") {
            useQuote = true;
            break;
        }
    }

    for (size_t i = 0; i < v.size(); i++) {
        if (i > 0)
            res += ",";

        if (useQuote) {
            res += "\'" + v[i] + "\'";
        }
        else {
            res += v[i];
        }
    }

    if (v.size() >= 1)
        res = "[" + res + "]";

    return res;
}

bool is_locale_numeric_set()
{
    static bool *st = nullptr;
    if (st == nullptr) {
        auto ch = getenv("LC_NUMERIC");
        std::string localeStr = (ch) ? std::string(ch) : "";
        st = new bool;
        *st = (localeStr == "C");
    }
    return *st;
}

}  // namespace metview
