// Copyright (c) 2019, Open Source Robotics Foundation, Inc.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the copyright holder nor the names of its
//      contributors may be used to endorse or promote products derived from
//      this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

// This file is originally from:
// https://github.com/ros/pluginlib/blob/1a4de29fa55173e9b897ca8ff57ebc88c047e0b3/pluginlib/include/pluginlib/impl/filesystem_helper.hpp

/*! \file filesystem_helper.hpp
 * \brief Cross-platform filesystem helper functions and additional emulation of [std::filesystem](https://en.cppreference.com/w/cpp/filesystem).
 *
 * Note: Once std::filesystem is supported on all ROS2 platforms, this class
 * can be deprecated/removed in favor of the built-in functionality.
 */

#ifndef RCPPUTILS__FILESYSTEM_HELPER_HPP_
#define RCPPUTILS__FILESYSTEM_HELPER_HPP_

#include <cstdint>
#include <filesystem>
#include <string>
#include <vector>

#include "rcpputils/visibility_control.hpp"

namespace rcpputils
{
namespace fs
{

#ifdef _WIN32
#  define RCPPUTILS_IMPL_OS_DIRSEP '\\'
#else
#  define RCPPUTILS_IMPL_OS_DIRSEP '/'
#endif

/**
 * \def kPreferredSeparator
 *
 * A definition for this platforms string path separator
 */
static constexpr const char kPreferredSeparator = RCPPUTILS_IMPL_OS_DIRSEP;

#undef RCPPUTILS_IMPL_OS_DIRSEP

// TODO(ahcorde): Remove deprecated class on the next release.
#if !defined(_WIN32)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#else  // !defined(_WIN32)
# pragma warning(push)
# pragma warning(disable: 4996)
#endif
/**
 * \brief Drop-in replacement for [std::filesystem::path](https://en.cppreference.com/w/cpp/filesystem/path).
 *
 * It must conform to the same standard described and cannot include methods that are not
 * incorporated there.
 */
class [[deprecated("use std::filesystem instead of rcpputils::path")]] path
{
public:
  /**
    * \brief Constructs an empty path.
    */
  RCPPUTILS_PUBLIC
  path() = default;

  /**
   * \brief Conversion constructor from a std::string path.
   *
   * \param[in] p A string path split by the platform's string path separator.
   */
  RCPPUTILS_PUBLIC
  path(const std::string & p);  // NOLINT(runtime/explicit): this is a conversion constructor

  /**
    * \brief Copy constructor.
    */
  RCPPUTILS_PUBLIC path(const path & p) = default;

  /**
   * \brief Copy assignment operator.
   *
   * \return Reference to the copied path.
   */
  RCPPUTILS_PUBLIC path & operator=(const path &) = default;

  /**
   * \brief Get the path delimited using this system's path separator.
   *
   * \return The path as a string
   */
  RCPPUTILS_PUBLIC std::string string() const;

  /**
   * \brief Check if this path exists.
   *
   * \return True if the path exists, false otherwise.
   */
  RCPPUTILS_PUBLIC bool exists() const;

  /**
   * \brief Check if the path exists and it is a directory.
   *
   * \return True if the path is an existing directory, false otherwise.
   */
  RCPPUTILS_PUBLIC bool is_directory() const noexcept;

  /**
   * \brief Check if the path is a regular file.
   *
   * \return True if the file is an existing regular file, false otherwise.
   */
  RCPPUTILS_PUBLIC bool is_regular_file() const noexcept;

  /**
  * \brief Return the size of the file in bytes.
  *
  * \return size of file in bytes
  * \throws std::system_error
  */
  RCPPUTILS_PUBLIC uint64_t file_size() const;

  /**
  * \brief Check if the path is empty.
  *
  * \return True if the path is empty, false otherwise.
  */
  RCPPUTILS_PUBLIC bool empty() const;

  /**
  * \brief Check if the path is an absolute path.
  *
  * \return True if the path is absolute, false otherwise.
  */
  RCPPUTILS_PUBLIC bool is_absolute() const;

  /**
  * \brief Const iterator to first element of this path.
  *
  * \return A const iterator to the first element.
  */
  RCPPUTILS_PUBLIC std::vector<std::string>::const_iterator cbegin() const;

  /**
  * Const iterator to one past the last element of this path.
  *
  * return A const iterator to one past the last element of the path.
  */
  RCPPUTILS_PUBLIC std::vector<std::string>::const_iterator cend() const;

  /**
  * \brief Get the parent directory of this path.
  *
  * \return A path to the parent directory.
  */
  RCPPUTILS_PUBLIC path parent_path() const;

  /**
  * \brief Get the last element in this path.
  *
  * If this path points to a directory, it will return the directory name.
  *
  * \return The last element in this path
  */
  RCPPUTILS_PUBLIC path filename() const;

  /**
  * \brief Get a relative path to the component including and following the last '.'.
  *
  * \return The string extension
  */
  RCPPUTILS_PUBLIC path extension() const;

  /**
  * \brief Concatenate a path and a string into a single path.
  *
  * \param[in] other the string compnoent to concatenate
  * \return The combined path of this and other.
  */
  RCPPUTILS_PUBLIC path operator/(const std::string & other) const;

  /**
  * \brief Append a string component to this path.
  *
  * \param[in] other the string component to append
  * \return *this
  */
  RCPPUTILS_PUBLIC path & operator/=(const std::string & other);

  /**
  * \brief Concatenate two paths together.
  *
  * \param[in] other the path to append
  * \return The combined path.
  */
  RCPPUTILS_PUBLIC path operator/(const path & other) const;

  /**
  * \brief Append a string component to this path.
  *
  * \param[in] other the string component to append
  * \return *this
  */
  RCPPUTILS_PUBLIC path & operator/=(const path & other);

private:
  std::string path_;
  std::vector<std::string> path_as_vector_;
};

/**
 * \brief Check if the path is a regular file.
 *
 * \param[in] p The path to check
 * \return True if the path exists, false otherwise.
 */
[[deprecated("Please use std::filesystem::is_regular_file(..) instead")]]
RCPPUTILS_PUBLIC bool is_regular_file(const path & p) noexcept;

/**
 * \brief Check if the path is a directory.
 *
 * \param[in] p The path to check
 * \return True if the path is an existing directory, false otherwise.
 */
[[deprecated("Please use std::filesystem::is_directory(..) instead")]]
RCPPUTILS_PUBLIC bool is_directory(const path & p) noexcept;

/**
 * \brief Get the file size of the path.
 *
 * \param[in] p The path to get the file size of.
 * \return The file size in bytes.
 *
 * \throws std::sytem_error
 */
[[deprecated("Please use std::filesystem::file_size(..) instead")]]
RCPPUTILS_PUBLIC uint64_t file_size(const path & p);

/**
 * \brief Check if a path exists.
 *
 * \param[in] path_to_check The path to check.
 * \return True if the path exists, false otherwise.
 */
[[deprecated("Please use std::filesystem::exists(..) instead")]]
RCPPUTILS_PUBLIC bool exists(const path & path_to_check);


/**
 * \brief Get a path to a location in the temporary directory, if it's available.
 *
 * This does not create any directories.
 * On Windows, this uses "GetTempPathA"
 * On non-Windows, this prefers the environment variable TMPDIR, falling back to /tmp
 *
 * \return A path to a directory for storing temporary files and directories.
 */
[[deprecated("Please use std::filesystem::temp_directory_path() instead")]]
RCPPUTILS_PUBLIC path temp_directory_path();

/**
 * \brief Construct a uniquely named temporary directory, in "parent", with format base_nameXXXXXX
 *
 * The output, if successful, is guaranteed to be a newly-created directory.
 * The underlying implementation keeps generating paths until one that does not exist is found.
 * This guarantees that there will be no existing files in the returned directory.
 *
 * \param[in] base_name User-specified portion of the created directory
 * \param[in] parent_path The parent path of the directory that will be created
 * \return A path to a newly-created directory with base_name and a 6-character unique suffix
 *
 * \throws std::system_error If any OS APIs do not succeed.
 */
[[deprecated("Please use rcpputils::fs::create_temporary_directory(..) instead")]]
RCPPUTILS_PUBLIC path create_temp_directory(
  const std::string & base_name,
  const path & parent_path = path(std::filesystem::temp_directory_path().generic_string()));

/// \brief Construct a uniquely named temporary directory, in "parent", with format base_nameXXXXXX
/// The output, if successful, is guaranteed to be a newly-created directory.
/// The underlying implementation keeps generating paths until one that does not exist is found or
/// until the number of iterations exceeded the maximum tries.
/// This guarantees that there will be no existing files in the returned directory.
/// \param[in] base_name User-specified portion of the created directory.
/// \param[in] parent_path The parent path of the directory that will be created.
/// \param[in] max_tries The maximum number of tries to find a unique directory (default 1000)
/// \return A path to a newly created directory with base_name and a 6-character unique suffix.
/// \throws std::invalid_argument If base_name contain directory-separator defined as
/// std::filesystem::path::preferred_separator.
/// \throws std::system_error If any OS APIs do not succeed.
/// \throws std::runtime_error If the number of the iterations exceeds the maximum tries and
/// a unique directory is not found.
RCPPUTILS_PUBLIC std::filesystem::path create_temporary_directory(
  const std::string & base_name,
  const std::filesystem::path & parent_path = std::filesystem::temp_directory_path(),
  size_t max_tries = 1000);

/**
 * \brief Return current working directory.
 *
 * \return The current working directory.
 *
 * \throws std::system_error
 */
[[deprecated("Please use std::filesystem::current_path(..) instead")]]
RCPPUTILS_PUBLIC path current_path();

/**
 * \brief Create a directory with the given path p.
 *
 * This builds directories recursively and will skip directories if they are already created.
 * \param[in] p The path at which to create the directory.
 * \return Return true if the directory already exists or is created, false otherwise.
 */
[[deprecated("Please use std::filesystem::create_directories(..) instead")]]
RCPPUTILS_PUBLIC bool create_directories(const path & p);

/**
 * \brief Remove the file or directory at the path p.
 *
 * \param[in] p The path of the object to remove.
 * \return true if the file exists and it was successfully removed, false otherwise.
 */
[[deprecated("Please use std::filesystem::remove(..) instead")]]
RCPPUTILS_PUBLIC bool remove(const path & p);

/**
 * \brief Remove the directory at the path p and its content.
 *
 * Additionally to \sa remove, remove_all removes a directory and its containing files.
 *
 * \param[in] p The path of the directory to remove.
 * \return true if the directory exists and it was successfully removed, false otherwise.
 */
[[deprecated("Please use std::filesystem::remove_all(..) instead")]]
RCPPUTILS_PUBLIC bool remove_all(const path & p);

/**
 * \brief Remove extension(s) from a path.
 *
 * An extension is defined as text starting from the end of a path to the first period (.) character.
 *
 * \param[in] file_path The file path string.
 * \param[in] n_times The number of extensions to remove if there are multiple extensions.
 * \return The path object.
 */
RCPPUTILS_PUBLIC path remove_extension(const path & file_path, int n_times = 1);

/**
 * \brief Compare two paths for equality.
 *
 * \return True if both paths are equal as strings.
 */
[[deprecated("This operator will be remove with the deprecated path class")]]
RCPPUTILS_PUBLIC bool operator==(const path & a, const path & b);
[[deprecated("This operator will be remove with the deprecated path class")]]
RCPPUTILS_PUBLIC bool operator!=(const path & a, const path & b);

/**
* \brief Convert the path to a string for ostream usage, such as in logging or string formatting.
*
* \param[in] os The stream to send the path string to
* \param[in] p The path to stringify
* \return The ostream, for chaining
*/
[[deprecated("This operator will be remove with the deprecated path class")]]
RCPPUTILS_PUBLIC std::ostream & operator<<(std::ostream & os, const path & p);

// remove warning suppression
#if !defined(_WIN32)
# pragma GCC diagnostic pop
#else  // !defined(_WIN32)
# pragma warning(pop)
#endif
}  // namespace fs
}  // namespace rcpputils

#endif  // RCPPUTILS__FILESYSTEM_HELPER_HPP_
