// Copyright 2018 Bloomberg Finance L.P
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef INCLUDED_FILEUTILS
#define INCLUDED_FILEUTILS

#include <functional>
#include <iostream>
#include <iterator>
#include <reccdefaults.h>
#include <set>
#include <string>
#include <sys/stat.h>
#include <thread>
#include <vector>
namespace recc {

struct FileUtils {
    /**
     * Return the 'struct stat' given an absolute file path
     */
    static struct stat getStat(const std::string &path,
                               const bool followSymlinks);

    static bool isRegularFileOrSymlink(const struct stat &s);
    static bool isExecutable(const struct stat &s);
    static bool isSymlink(const struct stat &s);

    /**
     * Given the path to a symlink, return a std::string with its contents.
     *
     * The path must be a path to a symlink that exists on disk. It can be
     * absolute or relative to the current directory.
     */
    static std::string getSymlinkContents(const std::string &path,
                                          const struct stat &statResult);

    /**
     * Returns true if "path" has "prefix" as a prefix.
     *
     * Before performing the check, a trailing slash is appended to prefix if
     * it doesn't have one since it is assumed that prefix is a directory.
     *
     * Note that this isn't just "is_subdirectory_of": /a/ is a prefix of
     * /a/../b/.
     */
    static bool hasPathPrefix(const std::string &path,
                              const std::string &prefix);

    static bool hasPathPrefixes(const std::string &path,
                                const std::set<std::string> &prefixes);

    /**
     * Return the current working directory.
     *
     * If the current working directory cannot be determined (if the user does
     * not have permission to read the current directory, for example), return
     * the empty std::string and log a warning.
     */
    static std::string getCurrentWorkingDirectory();

    /**
     * Return the number of levels of parent directory needed to follow the
     * given path.
     *
     * For example, "a/b/c.txt" has zero parent directory levels,
     * "a/../../b.txt" has one, and "../.." has two.
     */
    static int parentDirectoryLevels(const std::string &path);

    /**
     * Return a std::string containing the last N segments of the given path,
     * without a trailing slash.
     *
     * If the given path doesn't have that many segments, throws an exception.
     */
    static std::string lastNSegments(const std::string &path, const int n);

    /**
     * Determine if the path is absolute.
     * Replace with std::filesystem if compiling with C++17.
     */
    static bool isAbsolutePath(const std::string &path);

    /**
     * Replace the start of the path with it's remote mapping if one is
     * available in PREFIX_REPLACEMENT_MAP.
     */
    static std::string
    resolvePathPrefixFromLocalToRemote(const std::string &path);

    /**
     * Replace the start of the path with it's local mapping if one is
     * available in PREFIX_REPLACEMENT_MAP.
     */
    static std::string
    resolvePathPrefixFromRemoteToLocal(const std::string &path);

    static std::vector<std::string> parseDirectories(std::string &path);

    /**
     * This helper modifies the given path to make it suitable for running
     * remotely. The path is replaced if matching a option in RECC_PREFIX_MAP,
     * and then made relative to the working directory
     *
     * Calling this function also normalizes the path by default, but this can
     * be disabled by by setting the corresponding param to false.
     */
    static std::string modifyPathForRemote(const std::string &path,
                                           const std::string &workingDirectory,
                                           bool normalizePath = true);

    /**
     * Make the given path relative to the given working directory.
     *
     * If the given working directory is empty, or if the given path is already
     * relative or has nothing to do with the working directory, the path will
     * be returned unmodified.
     */
    static std::string makePathRelative(const std::string &path,
                                        const std::string &cwd);

    /**
     * This method will call makePathRelative on the path if the path has
     * the workingDirectory in it's prefix and RECC_NO_PATH_REWRITE is not
     * enabled.
     */
    static std::string
    rewritePathToRelative(const std::string &path,
                          const std::string &workingDirectory);

    /**
     * This method will resolve any local paths prefixes to remote ones inside
     * a new copy of a dependency file ready to be uploaded.
     *
     * If a RECC_PREFIX_MAP is defined a dependency file complied
     * locally will contain file paths that don't match the remote build
     * environment as they have been generated with a different directory
     * structure.
     *
     * This method returns a path to the original file if there are no
     * changes to make. If there are changes a new file is created in the
     * specified directory containing the modified contents.
     */
    static const std::string resolvePathsInsideDependencyFileForRemote(
        const std::string &path, const std::string &modifiedUploadsDirectory);

    /**
     * This method will resolve any remote prefix paths to local ones inside a
     * downloaded dependency file.
     *
     * If a RECC_PREFIX_MAP is defined a dependency file complied
     * remotely will contain file paths that don't match the local build
     * environment as they have been generated with a different directory
     * structure.
     *
     * This method modifies the contents of a downloaded dependency file
     * using the prefix map to turn any remote specific file paths into their
     * local counterpart.
     */
    static void
    resolvePathsInsideDependencyFileForLocal(const std::string &path);

    /**
     * Return the target path for a symlink.
     */
    static std::string resolveSymlink(const std::string &path);

    /*
     * Strip the path string to show just the filename by removing any content
     * found before the last "/" in the string (including the "/").
     */
    static std::string stripDirectory(const std::string &path);

    /**
     * Remove the content following the last "." in the path string (including
     * the ".") and add the new suffix string to the end.
     */
    static std::string replaceSuffix(const std::string &path,
                                     const std::string &suffix);
};

} // namespace recc

#endif
