/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "eckit/filesystem/marsfs/MarsFSPath.h"
#include "eckit/filesystem/BasePathNameT.h"
#include "eckit/filesystem/marsfs/MarsFSClient.h"
#include "eckit/io/Length.h"
#include "eckit/io/MarsFSHandle.h"
#include "eckit/io/PartFileHandle.h"
#include "eckit/io/FileHandle.h"
#include "eckit/io/cluster/ClusterDisks.h"
#include "eckit/io/cluster/ClusterNodes.h"
#include "eckit/io/cluster/NodeInfo.h"
#include "eckit/thread/Mutex.h"
#include "eckit/utils/MD5.h"

#include "eckit/os/BackTrace.h"
#include "eckit/config/LibEcKit.h"



namespace eckit {

//----------------------------------------------------------------------------------------------------------------------

MarsFSPath::MarsFSPath(const std::string& path, bool tildeIsUserHome) {
    ASSERT(!tildeIsUserHome);

    std::string p = path;
    // node_ = NodeInfo::thisNode().node();

    ASSERT(p.find("marsfs:") == 0);
    { p = p.substr(7); }

    ASSERT(p.find("//") == 0);
    {
        p        = p.substr(2);
        size_t n = p.find("/");
        ASSERT(n != std::string::npos);

        node_ = p.substr(0, n);
        p     = p.substr(n);
    }
    path_ = p;

    PANIC(path_.find("marsfs:") != std::string::npos);
    PANIC(node_.find("marsfs:") != std::string::npos);
}

MarsFSPath::MarsFSPath(const std::string& node, const std::string& path) : node_(node), path_(path) {
    PANIC(node_.find("marsfs:") != std::string::npos);
    PANIC(path_.find("marsfs:") != std::string::npos);
}

MarsFSPath::MarsFSPath(const MarsFSPath& other) : node_(other.node_), path_(other.path_) {
    PANIC(node_.find("marsfs:") != std::string::npos);
    PANIC(path_.find("marsfs:") != std::string::npos);
}

MarsFSPath& MarsFSPath::operator=(const MarsFSPath& other) {
    node_ = other.node_;
    path_ = other.path_;
    PANIC(node_.find("marsfs:") != std::string::npos);
    PANIC(path_.find("marsfs:") != std::string::npos);
    return *this;
}

MarsFSPath::MarsFSPath(Stream& s) {
    s >> node_;
    s >> path_;
}

MarsFSPath::~MarsFSPath() {}


void MarsFSPath::print(std::ostream& s) const {
    s << "marsfs://" << node_ << path_;
}

void operator<<(Stream& s, MarsFSPath const& path) {
    s << path.node_;
    s << path.path_;
}

bool MarsFSPath::isLocal() const {
    LOG_DEBUG_LIB(LibEcKit) << "MarsFSPath::isLocal: " << (node_ == NodeInfo::thisNode().node()) << std::endl;
    return node_ == NodeInfo::thisNode().node();
}

MarsFSPath::operator std::string() const {
    return std::string("marsfs://") + node_ + path_;
    // return path_;
}


MarsFSPath MarsFSPath::dirName() const {
    return MarsFSPath(node(), MarsFSClient(*this).dirName(path_));
}

MarsFSPath MarsFSPath::fullName() const {
    return MarsFSPath(node(), MarsFSClient(*this).fullName(path_));
}

MarsFSPath MarsFSPath::baseName(bool ext) const {
    return MarsFSPath(node(), MarsFSClient(*this).baseName(path_, ext));
}

void MarsFSPath::touch() const {
    MarsFSClient(*this).touch(path_);
}

Length MarsFSPath::size() const {
    return MarsFSClient(*this).size(path_);
}

bool MarsFSPath::sameAs(const MarsFSPath& other) const {
    if (node() != other.node())
        return false;
    return MarsFSClient(*this).sameAs(path_, other.path_);
}

MarsFSPath MarsFSPath::unique(const MarsFSPath& path) {
    return MarsFSPath(path.node(), MarsFSClient(path).unique(path.path_));
}

void MarsFSPath::reserve(const Length&) const {
    NOTIMP;
}

void MarsFSPath::mkdir(short mode) const {
    MarsFSClient(*this).mkdir(path_, mode);
}

void MarsFSPath::chmod(const FileMode& mode) const  {
    NOTIMP;
}

void MarsFSPath::fileSystemSize(FileSystemSize& fs) const {
    MarsFSClient(*this).fileSystemSize(path_, fs);
}

time_t MarsFSPath::created() const {
    return MarsFSClient(*this).created(path_);
}

bool MarsFSPath::isDir() const {
    throw NotImplemented(Here());
}

bool MarsFSPath::isLink() const {
    throw NotImplemented(Here());
}

time_t MarsFSPath::lastAccess() const {
    return MarsFSClient(*this).lastAccess(path_);
}

time_t MarsFSPath::lastModified() const {
    return MarsFSClient(*this).lastModified(path_);
}

bool MarsFSPath::exists() const {
    return MarsFSClient(*this).exists(path_);
}

bool MarsFSPath::available() const {
    return ClusterNodes::available("marsfs", node_);
}

void MarsFSPath::unlink(bool) const {
    MarsFSClient(*this).unlink(path_);
}

void MarsFSPath::rmdir(bool) const {
    MarsFSClient(*this).rmdir(path_);
}

void MarsFSPath::syncParentDirectory() const {
    MarsFSClient(*this).syncParentDirectory(path_);
}

const char* MarsFSPath::localPath() const {
    throw SeriousBug(std::string("Attempting to access ") + std::string(*this) + " locally");
}


void MarsFSPath::children(std::vector<MarsFSPath>& files, std::vector<MarsFSPath>& dirs) const {
    std::vector<std::string> f;
    std::vector<std::string> d;

    MarsFSClient(*this).children(path_, f, d);

    files.clear();
    for (std::vector<std::string>::iterator j = f.begin(); j != f.end(); ++j)
        files.push_back(MarsFSPath(node(), *j));

    dirs.clear();
    for (std::vector<std::string>::iterator j = d.begin(); j != d.end(); ++j)
        dirs.push_back(MarsFSPath(node(), *j));
}

void MarsFSPath::match(const MarsFSPath& path, std::vector<MarsFSPath>& result, bool recurse) {
    std::vector<std::string> r;
    MarsFSClient(path).match(path.path_, r, recurse);
    result.clear();
    for (std::vector<std::string>::iterator j = r.begin(); j != r.end(); ++j)
        result.push_back(MarsFSPath(path.node(), *j));
}

void MarsFSPath::link(const MarsFSPath& from, const MarsFSPath& to) {
    ASSERT(from.node_ == to.node_);
    MarsFSClient(from).link(from.path_, to.path_);
}

void MarsFSPath::rename(const MarsFSPath& from, const MarsFSPath& to) {
    ASSERT(from.node_ == to.node_);
    MarsFSClient(from).rename(from.path_, to.path_);
}

DataHandle* MarsFSPath::fileHandle(bool overwrite) const {
    LOG_DEBUG_LIB(LibEcKit) << "MarsFSPath::fileHandle() path_: " << path_ << std::endl;
    if(isLocal()) {
        return new FileHandle(path_, overwrite);
    }
    else {
        return new MarsFSHandle(*this, overwrite);
    }
}

DataHandle* MarsFSPath::partHandle(const OffsetList& o, const LengthList& l) const {
    return new PartFileHandle(*this, o, l);
}

DataHandle* MarsFSPath::partHandle(const Offset& o, const Length& l) const {
    return new PartFileHandle(*this, o, l);
}

MarsFSPath MarsFSPath::mountPoint() const {
    return MarsFSPath(node_, MarsFSClient(*this).mountPoint(path_));
}


MarsFSPath MarsFSPath::realName() const {
    NOTIMP;
    // return MarsFSPath(node_, MarsFSClient(*this).realName(path_));
}

MarsFSPath MarsFSPath::orphanName() const {

    std::ostringstream os;
    os << mountPoint() << "/orphans/";

    const char* q = path_.c_str();
    while (*q) {
        if (*q == '/')
            os << '_';
        else
            os << *q;
        q++;
    }

    return os.str();
}

std::string MarsFSPath::clusterName() const {
    return std::string(*this);
}

std::string MarsFSPath::extension() const {
    NOTIMP;
}

void MarsFSPath::hash(MD5& md5) const {
    md5 << node_;
    md5 << path_;
}


BasePathName* MarsFSPath::checkClusterNode() const {
    try {
        std::string n = ClusterDisks::node(path_);
        ASSERT(n != NodeInfo::thisNode().node());  // TODO: code mo, if a remote file becomes local
        if (n != node_) {
            //            Log::warning() << *this << " is now on node [" << n << "]" << std::endl;
        }
        return new BasePathNameT<MarsFSPath>(MarsFSPath(n, path_));
    }
    catch (std::exception& e) {
        Log::error() << "** " << e.what() << " Caught in " << Here() << std::endl;
        Log::error() << "** Exception is handled" << std::endl;
        return new BasePathNameT<MarsFSPath>(MarsFSPath(node_, path_));
    }
}

//----------------------------------------------------------------------------------------------------------------------

}  // namespace eckit
