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

 Copyright 2015 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 <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>

#include <cerrno>
#include <fstream>
#include <iostream>
#include <set>

#include "fstream_mars_fix.h"

#include "Path.h"
#include "MvMiscellaneous.h"
#include "MvLog.h"


Path::Path(const std::string& s) :
    path_(s)
{
}

Path::~Path() = default;

void Path::rename(const Path& other) const
{
    ::movefile(path_.c_str(), other.path_.c_str());
}

void Path::copy(const Path& other) const
{
    ::mars_copyfile(path_.c_str(), other.path_.c_str());
}


// Path::copyData
// We sometimes need this because the MARS copyfile() function, when given
// a symlink as its source, will just copy the link; but we often want to
// copy the real file behind it, which is what copyData does.

bool Path::copyData(const Path& other) const
{
    // we will use the MARS copydata() function, but it only works
    // if the destintation file does not exist, so we have to remove it first;
    // note the very slight possibility for problems here - if the dest file
    // was created with marstmp() and we delete it, then perhaps a simultaneous
    // process will create a temp file with the same name...

    if (unlink(other.path_.c_str())) {
        std::cout << "Cannot unlink " << other.path_.c_str() << std::endl;
        return false;
    }

    return (::copydata(path_.c_str(), other.path_.c_str()) == 0);
}

void Path::remove() const
{
    ::deletefile(path_.c_str());
}


bool Path::exists() const
{
    if (::access(path_.c_str(), F_OK) != 0) {
        return false;  //-- file does not exist
    }

    //-- file exists, but if it is an empty dot file, we have a problem...

    const char* basnam = mbasename(path_.c_str());
    if (*basnam == '.') {  //-- if an empty dot file?
        struct stat info;

        if (::stat(path_.c_str(), &info) == 0 && info.st_size == 0) {
            std::string newnam = path_ + std::string(".empty");

            ::rename(path_.c_str(), newnam.c_str());

            std::cout << ">>>\n"
                      << ">>> Empty dot file: " << path_.c_str() << " renamed <<<\n"
                      << ">>>" << std::endl;

            //	  Log::error("startup") << "Empty dot file "
            //	                        << path_.c_str()
            //				<< ", trying to fix it..."
            //				<< std::endl;
            return false;  //-- now it is missing...
        }
    }

    return true;  //-- file  exits
}


void Path::mkdir() const
{
    ::mkdirp(path_.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
}

std::string Path::name() const
{
    return mbasename(path_.c_str());
}

Path Path::directory() const
{
    return std::string(mdirname(path_.c_str()));
}

Path Path::dot() const
{
    return directory().add(std::string(".") + name());
}

MvRequest Path::loadRequest() const
{
    return MvRequest(read_request_file(path_.c_str()), false);
}

void Path::saveRequest(const MvRequest& r) const
{
    FILE* f = fopen(path_.c_str(), "w");
    if (f == nullptr) {
        // Log::error(0) << "Cannot open " << path.c_str() << Log::syserr << std::endl;
        return;
    }

    save_all_requests(f, r);
    fclose(f);
}

bool Path::isSymLink() const
{
    struct stat s
    {
    };
    if (lstat(path_.c_str(), &s) == 0) {
        return S_ISLNK(s.st_mode) == 1;
    }
    return false;
}

void Path::symlink(const Path& other) const
{
    // Warning: reverse order
    ::symlink(other.path_.c_str(), path_.c_str());
#if 0
    MvLog().info() << MV_FN_INFO << "ret=" << ret << " errno=" << errno;
    MvLog().info() << "src=" << str();
    MvLog().info() << "target=" << other.str();
#endif
}

void Path::changeSymlink(const Path& other) const
{
    struct stat s
    {
    };
    if (lstat(path_.c_str(), &s) == 0) {
        if (S_ISLNK(s.st_mode) == 1) {
            int ret = ::unlink(path_.c_str());
            if (ret == 0) {
                ::symlink(other.path_.c_str(), path_.c_str());
#if 0
                MvLog().info() << MV_FN_INFO << "ret=" << ret << " errno=" << errno;
                MvLog().info() << "src=" << str();
                MvLog().info() << "target=" << other.str();
#endif
            }
        }
    }
}


std::string Path::symlinkTarget() const
{
    char buf[1024];
    std::string s;
    ssize_t count = readlink(path_.c_str(), buf, sizeof(buf));
    if (count > 0) {
        buf[count] = '\0';
        s = std::string(buf);
    }
    return s;
}

std::set<std::string> Path::files() const
{
    static char* myTmpDirLink = nullptr;  //-- link name in metview/Metview/
    if (!myTmpDirLink)
        myTmpDirLink = getenv("MV_TMPDIR_LINK");  //-- "MvTemporaryCache_$$"

    std::set<std::string> result;
    DIR* dir = opendir(path_.c_str());
    if (!dir) {
        std::cout << "error " << errno << std::endl;
        return result;
    }

    dirent* d;

    while ((d = readdir(dir))) {
        if (d->d_name[0] == '.')
            continue;

        //-----
        //---- In user's $HOME/metview/Metview folder there may be other valid TEMPORARY
        //---  folder links, created by other Metview sessions by this same user.
        //--   Inserting such a file (link) into 'result' would later break the link
        //-    by relinking it into the cache directory of _this_ session, thus skip it!
        //
        if (strncmp(d->d_name, myTmpDirLink, 17) == 0  //- start as "MvTemporaryCache_"
            && strcmp(d->d_name, myTmpDirLink) != 0)   //- but is not for this session!
        {
            // std::cout << ">>> NOT ours! Skipping folder " << d->d_name << std::std::endl;
            continue;  //-- temporary folder link of another Metview session
        }

        result.insert(d->d_name);
    }
    closedir(dir);
    return result;
}

std::set<std::string> Path::directories() const
{
    static char* myTmpDirLink = nullptr;  //-- link name in metview/Metview/
    if (!myTmpDirLink)
        myTmpDirLink = getenv("MV_TMPDIR_LINK");  //-- "MvTemporaryCache_$$"

    std::set<std::string> result;
    DIR* dir = opendir(path_.c_str());
    if (!dir)
        return result;

    dirent* d;

    while ((d = readdir(dir))) {
        if (d->d_name[0] == '.')
            continue;

        //-----
        //---- In user's $HOME/metview/Metview folder there may be other valid TEMPORARY
        //---  folder links, created by other Metview sessions by this same user.
        //--   Inserting such a file (link) into 'result' would later break the link
        //-    by relinking it into the cache directory of _this_ session, thus skip it!
        //
        if (strncmp(d->d_name, myTmpDirLink, 17) == 0  //- start as "MvTemporaryCache_"
            && strcmp(d->d_name, myTmpDirLink) != 0)   //- but is not for this session!
        {
            // std::cout << ">>> NOT ours! Skipping folder " << d->d_name << std::endl;
            continue;  //-- temporary folder link of another Metview session
        }

        struct stat ds;
        std::string dp = path_ + "/" + std::string(d->d_name);
        if (stat(dp.c_str(), &ds) || !S_ISDIR(ds.st_mode)) {
            continue;
        }

        result.insert(d->d_name);
    }
    closedir(dir);
    return result;
}


std::string Path::loadText() const
{
    std::ifstream in(path_.c_str());

    static std::string s;  // Keep it here so it keeps its size
    // s.clear();
    s = "";

    char c;
    while (in.get(c)) {
        // s.push_back(c);
        s += c;
    }


    return s;
}

void Path::saveText(const std::string& s) const
{
    std::ofstream out(path_.c_str());
    out << s;
}

Path Path::add(const std::string& name) const
{
    if (path_ != "/")
        return Path(path_ + "/" + name);
    else
        return Path("/" + name);
}

const std::string& Path::str() const
{
    return path_;
}

std::string Path::relativePath(const Path& other) const
{
    return ::relpath(path_.c_str(), other.path_.c_str());
}

void Path::touch() const
{
    struct stat info
    {
    };
    if (::stat(path_.c_str(), &info) == 0 && S_ISDIR(info.st_mode)) {
        Path dummy = add("...");
        dummy.touch();
        dummy.remove();
        return;
    }

    std::cout << "Touch " << *this << std::endl;
    FILE* f = fopen(path_.c_str(), "a+");
    if (f)
        fclose(f);
}

void Path::print(std::ostream& s) const
{
    s << path_;
}

time_t Path::lastModified(bool symLink) const
{
    struct stat info
    {
    };
    int ret = (!symLink) ? (::stat(path_.c_str(), &info)) : (::lstat(path_.c_str(), &info));
    if (ret == 0) {
        return info.st_mtime;
    }
    return 0;
}

off_t Path::sizeInBytes() const
{
    struct stat info
    {
    };
    if (::stat(path_.c_str(), &info) == 0)
        return info.st_size;
    else
        return 0;
}

void Path::makeWritableByUser() const
{
    struct stat info
    {
    };
    if (::stat(path_.c_str(), &info) == 0)
        if (!(info.st_mode & S_IWUSR))
            chmod(path_.c_str(), info.st_mode | S_IWUSR);
}

std::string Path::permissions(bool symLink) const
{
    struct stat info
    {
    };
    int ret = (!symLink) ? (::stat(path_.c_str(), &info)) : (::lstat(path_.c_str(), &info));
    if (ret == 0) {
        std::string str((info.st_mode & S_IRUSR) ? "r" : "-");
        str += ((info.st_mode & S_IWUSR) ? "w" : "-");
        str += ((info.st_mode & S_IXUSR) ? "x" : "-");
        str += ((info.st_mode & S_IRGRP) ? "r" : "-");
        str += ((info.st_mode & S_IWGRP) ? "w" : "-");
        str += ((info.st_mode & S_IXGRP) ? "x" : "-");
        str += ((info.st_mode & S_IROTH) ? "r" : "-");
        str += ((info.st_mode & S_IWOTH) ? "w" : "-");
        str += ((info.st_mode & S_IXOTH) ? "x" : "-");

        return str;
    }

    return "---------";
}

std::string Path::owner(bool symLink) const
{
    struct stat info
    {
    };
    int ret = (!symLink) ? (::stat(path_.c_str(), &info)) : (::lstat(path_.c_str(), &info));
    if (ret == 0) {
        struct passwd* pw = getpwuid(info.st_uid);
        if (pw) {
            std::string str(pw->pw_name);
            return str;
        }
    }

    return "???";
}

std::string Path::group(bool symLink) const
{
    struct stat info
    {
    };
    int ret = (!symLink) ? (::stat(path_.c_str(), &info)) : (::lstat(path_.c_str(), &info));
    if (ret == 0) {
        struct group* gg = getgrgid(info.st_gid);
        if (gg) {
            std::string str(gg->gr_name);
            return str;
        }
    }

    return "???";
}

bool Path::locked() const
{
    return faccess(path_.c_str(), W_OK);
}


void Path::nameAndSuffix(std::string& namePart, std::string& suffixPart) const
{
    size_t pos;
    namePart.clear();
    suffixPart.clear();

    namePart = name();
    if ((pos = namePart.find_last_of(".")) != std::string::npos) {
        if (pos > 0 && pos < namePart.size() - 1) {
            suffixPart = namePart.substr(pos + 1);
            namePart = namePart.substr(0, pos);
        }
    }
}

std::string Path::uniqueNameInDir() const
{
    std::string resName(name());
    int i = 0;
    const int imax = 100000;
    std::set<std::string> fileLst = directory().files();

    if (fileLst.find(resName) == fileLst.end())
        return resName;

    std::string fName;
    std::string fSuffix;
    nameAndSuffix(fName, fSuffix);

    // For macro and python we want to keep the suffix
    if (fSuffix == "mv" || fSuffix == "py") {
        do {
            resName = fName + "_" + std::to_string(++i) + "." + fSuffix;
        } while (fileLst.find(resName) != fileLst.end() && i < imax);
    }
    else {
        do {
            resName = fName + " " + std::to_string(++i);
        } while (fileLst.find(resName) != fileLst.end() && i < imax);
    }

    return resName;
}
