/***************************** 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 "MvMiscelaneous.h"
#include "MvTmpFile.h"
#include "MvWindef.h"

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

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

#include "Tokenizer.h"

#include <algorithm>
#include <fstream>
#include <cerrno>
#include <cstring>

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

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

#include <math.h>
#include <ctype.h>

namespace metview
{
std::string iconDirFile(const std::string& fName)
{
    std::string resPath;
#ifndef ECCODES_UI
    static const char* shareDir = getenv("METVIEW_DIR_SHARE");
    if (shareDir)
        resPath = std::string(shareDir) + "/icons_mv5/" + fName;
#endif
    return resPath;
}

std::string etcDirFile(const std::string& fName)
{
    std::string resPath;
#ifdef ECCODES_UI
    resPath = CodesDirHandler::instance()->shareDirFile(fName);
#else
    static const char* shareDir = getenv("METVIEW_DIR_SHARE");
    resPath                    = (shareDir) ? (std::string(shareDir) + "/etc/" + fName) : std::string("");
#endif
    return resPath;
}

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

std::string preferenceDirFile(const std::string& fName)
{
    std::string resPath;
#ifdef ECCODES_UI
    resPath = CodesDirHandler::instance()->confDirFile(fName);
#else
    static const char* userDir = getenv("METVIEW_USER_DIRECTORY");
    if (userDir)
        resPath = std::string(userDir) + "/System/Preferences/" + fName;
#endif
    return resPath;
}

std::string magicsStylesEcmwfDirFile(const std::string& fName)
{
    std::string resPath;
#ifndef ECCODES_UI
    static const char* magDir = getenv("MAGPLUS_HOME");
    if (magDir)
        resPath = std::string(magDir) + "/share/magics/styles/ecmwf/" + fName;
#endif
    return resPath;
}

std::string magicsStylesDirFile(const std::string& fName)
{
    std::string resPath;
#ifndef ECCODES_UI
    static const char* magDir = getenv("MAGPLUS_HOME");
    if (magDir)
        resPath = std::string(magDir) + "/share/magics/styles/" + fName;
#endif
    return resPath;
}

std::string stylePreviewDirFile(const std::string& fName)
{
    std::string resPath;
#ifndef ECCODES_UI
    static const char* shareDir = getenv("METVIEW_DIR_SHARE");
    if (shareDir)
        resPath = std::string(shareDir) + "/eccharts/style_previews/" + fName;
#endif
    return resPath;
}

std::string ecchartsDirFile(const std::string& fName)
{
    std::string resPath;
#ifndef ECCODES_UI
    static const char* shareDir = getenv("METVIEW_DIR_SHARE");
    if (shareDir)
        resPath = std::string(shareDir) + "/eccharts/" + fName;
#endif
    return resPath;
}

std::string mvlMacroDirFile(const std::string& fName)
{
    std::string resPath;
#ifndef ECCODES_UI
    static const char* shareDir = getenv("METVIEW_DIR_SHARE");
    if (shareDir)
        resPath = std::string(shareDir) + "/app-defaults/CommonMacroFuncs/" + fName;
#endif
    return resPath;
}


std::string webBrowser()
{
    std::string browser;
#ifndef ECCODES_UI
    static const char* browserCh = getenv("MV_BROWSER_NAME");
    if (browserCh)
        browser = std::string(browserCh);
#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;
    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=" + toString<float>(area[1]) +
                     " E=" + toString<float>(area[3]);
            return false;
        }
    }

    if (area[0] > area[2]) {
        errStr = "S should be smaller than N! S=" + toString<float>(area[0]) +
                 " N=" + toString<float>(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=" + toString<float>(grid[0]);
        return false;
    }
    if (grid[1] < 0.) {
        errStr = "The N-S grid increment must be greater than 0! The specified value=" + toString<float>(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(toString<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(toString<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();
}


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;
}

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("");

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

    return stnid;
}



bool isNumber(const std::string& v)
{
    for (size_t i = 0; i < v.size(); i++) {
        if (isdigit(v[i]) == 0)
            return false;
    }
    return true;
}

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

std::string toBold(float v)
{
    std::string s;
    s = "<b>" + metview::toString(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 string();

    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 string();
    }

    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');
    }

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

    return std::string(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 {
        path = (const char *) req("PATH");
    }
    return path;
}

#endif

}  //namespace metview
