#include "graph_load.h"

#include <yandex/maps/wiki/common/yt.h>
#include <mapreduce/yt/interface/client.h>
#include <maps/libs/log8/include/log8.h>
#include <util/stream/file.h>

#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

namespace maps::wiki::traffic_analyzer {

namespace {

const fs::path GRAPH_DIR_YT = "//home/maps/graph";
const fs::path GRAPH_DIR_LOCAL = "/var/spool/yandex/maps/graph";

const std::string DATA_FILE = "data.mms.2";
const std::string EDGES_RTREE_FILE = "edges_rtree.mms.2";
const std::string PERSISTENT_INDEX_FILE = "edges_persistent_index.mms.1";

const std::string YT_PROXY = "hahn";


fs::path graphFileLocalPath(std::string version, std::string graphFile)
{
    return GRAPH_DIR_LOCAL / std::move(version) / std::move(graphFile);
}

fs::path graphFileYtPath(std::string version, std::string graphFile)
{
    return GRAPH_DIR_YT / std::move(version) / std::move(graphFile);
}

bool graphExistsLocally(const std::string& version)
{
    return fs::exists(graphFileLocalPath(version, DATA_FILE)) &&
           fs::exists(graphFileLocalPath(version, EDGES_RTREE_FILE)) &&
           fs::exists(graphFileLocalPath(version, PERSISTENT_INDEX_FILE));
}

/*
    There are a bunch of ways to create temporary file. The most reliable is
    std::tmpfile, because it is guaranteed, that unique temporary file is
    created and is deleted after fclose on its FILE*.

    Unfortunately in order to work with graph mms files and corresponding
    mms::Holder2 wrapper, we must know filename of file.

    The suggested workaround is to create tmp file using mkstemp + use RAII
    wrapper of its descriptor and filename.

    Why use 'mkstemp'? Because unique tmp filename generation and file creation
    are performed atomically. Manual generation of unique filename
    (via boost::unique_path or std::tmpnam) and its creation is
    potential security flaw.
*/
class TmpFile : boost::noncopyable
{
public:
    TmpFile(int descriptor, std::string name) :
        descriptor_(descriptor), name_(std::move(name))
    {}

    ~TmpFile()
    {
        close(descriptor_);
        unlink(name_.c_str());
    }

    const std::string& filename() const { return name_; }

private:
    int descriptor_;
    std::string name_;
};

std::unique_ptr<TmpFile> createTmpFile()
{
    char tmpNameTemplate[] = "/tmp/traffic_analyzer.XXXXXX";

    int fileDescriptor = mkstemp(tmpNameTemplate);
    REQUIRE(fileDescriptor != -1,
        "Cannot create temporary file. Errno: " << errno);

    return std::make_unique<TmpFile>(fileDescriptor, tmpNameTemplate);
}

std::unique_ptr<TmpFile> loadGraphFileFromYtToTmp(
    const std::string& version, const std::string graphFile)
{
    auto ytGraphFile = graphFileYtPath(version, graphFile);

    auto ytClient = common::yt::createYtClient(TString(YT_PROXY));
    auto ytFileReader = ytClient->CreateFileReader(TString(ytGraphFile.string()));

    auto localTmpFile = createTmpFile();
    TFixedBufferFileOutput localTmpFileOut(TString(localTmpFile->filename()));

    ytFileReader->ReadAll(localTmpFileOut);

    return localTmpFile;
}

} // namespace anonymous

GraphMmapped loadGraphFromLocal(const std::string& version)
{
    return GraphMmapped(
        graphFileLocalPath(version, DATA_FILE).string(),
        graphFileLocalPath(version, EDGES_RTREE_FILE).string(),
        graphFileLocalPath(version, PERSISTENT_INDEX_FILE).string()
    );
}

GraphMmapped loadGraphFromYT(const std::string& version)
{
    auto tmpDataFile       = loadGraphFileFromYtToTmp(version, DATA_FILE);
    auto tmpEdgesRtreeFile = loadGraphFileFromYtToTmp(version, EDGES_RTREE_FILE);
    auto tmpPersIndexFile  = loadGraphFileFromYtToTmp(version, PERSISTENT_INDEX_FILE);

    /* It is ok to close and unlink temporary file after the file was mmapped.
       In this case the actual removing will occur after we finish
       working with mmapped memory.
    */
    return GraphMmapped(
        tmpDataFile->filename(),
        tmpEdgesRtreeFile->filename(),
        tmpPersIndexFile->filename()
    );
}

GraphMmapped loadGraph(const std::string& version)
{
    if (graphExistsLocally(version)) {
        INFO() << "Loading graph from local machine";
        return loadGraphFromLocal(version);
    } else {
        INFO() << "Loading graph from YT";
        return loadGraphFromYT(version);
    }
}

} // namespace maps::wiki::traffic_analyzer
