//
//  FilesystemUtils.cpp
//  eagle0_server
//
//  Created by Dan Crosby on 11/5/17.
//  Copyright © 2017 none. All rights reserved.
//

#include "FilesystemUtils.hpp"

#include <pwd.h>
#include <unistd.h>

#include <cstdio>
#include <filesystem>
#include <fstream>
#include <mutex>

#include "tools/cpp/runfiles/runfiles.h"

using bazel::tools::cpp::runfiles::Runfiles;
using std::string;
using std::vector;
namespace fs = std::filesystem;

static string rLocation;

auto rloc(const string& execPath) -> string {
    string error;
    const std::unique_ptr<Runfiles> runfiles(Runfiles::Create(execPath, &error));

    if (runfiles == nullptr) {
        printf("Error! %s\n", error.c_str());
        abort();
        // error handling
    }

    return runfiles->Rlocation("_main");
}

void FilesystemUtils::SetExecPath(const string& execPath) { rLocation = rloc(execPath); }

auto FilesystemUtils::FilesInDirectory(const string& directoryPath, const string& extension)
        -> vector<string> {
    vector<string> paths;

    for (const fs::directory_entry& entry : fs::directory_iterator(directoryPath)) {
        if (extension.empty() || (entry.path().filename().extension() == extension)) {
            paths.push_back(string(entry.path().filename()));
        }
    }

    return paths;
}

auto FilesystemUtils::FileExistsAtPath(const string& path) -> bool { return fs::exists(path); }

auto FilesystemUtils::StaticEagle0FilesDirectory() -> string { return "/usr/local/share/eagle0/"; }

auto FilesystemUtils::StaticShardokFilesDirectory() -> string {
    return rLocation + "/src/main/resources/net/eagle0/shardok/";
}

auto FilesystemUtils::MapFilesDirectory() -> string {
    return StaticShardokFilesDirectory() + "maps/";
}

void FilesystemUtils::MakeDirectoryIfNecessary(const string& directoryPath) {
    if (fs::create_directories(directoryPath))
        printf("Directory %s created\n", directoryPath.c_str());
    else
        printf("No new directory created for %s\n", directoryPath.c_str());
}

auto FilesystemUtils::SaveFilesDirectory() -> string {
    static string* _directoryPath;
    static std::once_flag _homedir_once;

    std::call_once(_homedir_once, []() {
        const char* homedir;  // NOLINT
        if ((homedir = getenv("HOME")) == nullptr) { homedir = getpwuid(geteuid())->pw_dir; }

        _directoryPath = new string(string(homedir) + "/eagle0/shardok/save/");
        MakeDirectoryIfNecessary(*_directoryPath);
    });

    return *_directoryPath;
}

auto FilesystemUtils::CacheFilesDirectory() -> string {
    static string* _directoryPath;
    static std::once_flag _homedir_once;

    std::call_once(_homedir_once, []() {
        const char* homedir;  // NOLINT
        if ((homedir = getenv("HOME")) == nullptr) { homedir = getpwuid(geteuid())->pw_dir; }

        _directoryPath = new string(string(homedir) + "/eagle0/shardok/cache/");
        MakeDirectoryIfNecessary(*_directoryPath);
    });

    return *_directoryPath;
}

auto FilesystemUtils::GameRequestsDirectory() -> string {
    static string* _directoryPath;
    static std::once_flag _homedir_once;

    std::call_once(_homedir_once, []() {
        const char* homedir;  // NOLINT
        if ((homedir = getenv("HOME")) == nullptr) { homedir = getpwuid(geteuid())->pw_dir; }

        _directoryPath = new string(string(homedir) + "/eagle0/shardok/game_requests/");
        MakeDirectoryIfNecessary(*_directoryPath);
    });

    return *_directoryPath;
}

auto FilesystemUtils::AtomicallySaveToPath(const string& path, const byte_vector& data) -> bool {
    static auto* io_mutex = new std::mutex;
    std::lock_guard<std::mutex> lk(*io_mutex);
    const string tempPath = path + ".TEMP";

    std::ofstream ostr(tempPath.c_str(), std::ofstream::out | std::ofstream::trunc);
    ostr << data;
    ostr.close();

    if (ostr.good()) {
        const int err = rename(tempPath.c_str(), path.c_str());
        if (err == -1) {
            printf("Failed to move file to %s! Errno %d\n", path.c_str(), errno);
            return false;
        }
    } else {
        printf("Failed writing to %s!\n", tempPath.c_str());
        return false;
    }

    return true;
}

auto FilesystemUtils::LoadFromPath(const string& path) -> byte_vector {
    std::ifstream inputFileStream(path, std::ios::binary | std::ios::ate);
    const std::streamsize size = inputFileStream.tellg();
    inputFileStream.seekg(0, std::ios::beg);

    auto bv = byte_vector(size);
    inputFileStream.read((char*)bv.data(), size);

    return bv;
}
