/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "system-precomp.h"  // Precompiled headers
//
#include <mrpt/core/exceptions.h>  // for MRPT_END, MRPT_START, e
#include <mrpt/core/format.h>
#include <mrpt/system/CDirectoryExplorer.h>
#include <mrpt/system/filesystem.h>
#include <mrpt/system/os.h>  // for sprintf

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

#if STD_FS_IS_EXPERIMENTAL
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#include <filesystem>
namespace fs = std::filesystem;
#endif

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
//
#include <conio.h>
#include <direct.h>
#include <io.h>
#include <process.h>
#include <sys/utime.h>
#include <tlhelp32.h>
#else
#include <sys/time.h>
#include <termios.h>
#include <unistd.h>
#include <utime.h>

#include <cerrno>
#include <ctime>
#endif

#include <sys/stat.h>
#include <sys/types.h>

#if !defined(_MSC_VER)
#define _access access
#define _rmdir  rmdir
#ifndef _stat  // It seems MinGW already defines this.
#define _stat stat
#endif
#endif

#if defined(WIN32)
#include <codecvt>
#include <locale>
#include <string>
#endif

using namespace mrpt;
using namespace mrpt::system;
using namespace std;

#if defined(WIN32)
std::string ws2s(const std::wstring& wstr)
{
  using convert_typeX = std::codecvt_utf8<wchar_t>;
  std::wstring_convert<convert_typeX, wchar_t> converterX;
  return converterX.to_bytes(wstr);
}
#endif

// This is required since in Windows, the native representation of path ->
// string is std::wstring, not std::string:
std::string p2s(const fs::path& p)
{
#if defined(WIN32)
  return ws2s(p);
#else
  return p;
#endif
}

string mrpt::system::extractFileName(const string& filePath)
{
  return p2s(fs::path(filePath).stem());
}

string mrpt::system::extractFileDirectory(const string& filePath)
{
  return p2s(fs::path(filePath).parent_path());
}

string mrpt::system::extractFileExtension(const string& filePath, bool ignore_gz)
{
  if (filePath.size() < 2) return string("");

  size_t i_end = filePath.size() - 1;

  int i = (int)(i_end);
  while (i > 0)
  {
    if (filePath[i] == '.')
    {
      string the_ext = filePath.substr(i + 1, i_end - i);
      if (!ignore_gz || the_ext != "gz")
        return the_ext;
      else
      {
        i_end = --i;
      }
    }
    else
      i--;
  }
  // No extension:
  return string("");
}

bool mrpt::system::fileExists(const string& path)
{
  return 0 == _access(path.c_str(), 0x00);  // 0x00 = Check for existence only!
}

bool mrpt::system::directoryExists(const std::string& _path)
{
  std::string path = _path;

  // Remove the trailing "/" or "\\":
  if (!path.empty() && (*path.rbegin() == '/' || *path.rbegin() == '\\'))
    path = path.substr(0, path.size() - 1);

  // Verify it's a directory:
  struct _stat buf
  {
  };
  if (0 != _stat(path.c_str(), &buf)) return false;

#ifdef _WIN32
  return 0 != (buf.st_mode & _S_IFDIR);
#else
  return S_ISDIR(buf.st_mode);
#endif
}

bool mrpt::system::createDirectory(const string& dirName) { return fs::create_directory(dirName); }

bool mrpt::system::deleteFile(const string& fileName) { return 0 == remove(fileName.c_str()); }

void mrpt::system::deleteFiles(const string& s)
{
  MRPT_START
  size_t len = s.size() + 20;
  std::vector<char> aux(len);
#ifdef _WIN32
  os::sprintf(&aux[0], len, "del %s", &s[0]);
  for (char* c = &aux[0]; *c; c++)
    if (*c == '/') *c = '\\';
  os::strcat(&aux[0], len, " /Q");
#else
  os::sprintf(&aux[0], len, "rm %s", &s[0]);
#endif

  int res = ::system(&aux[0]);
  if (res)
  {
    fprintf(stderr, "[mrpt::system::deleteFiles] Warning: error invoking: `%s`\n", &aux[0]);
  }
  MRPT_END
}

/*---------------------------------------------------------------
          deleteFilesInDirectory
---------------------------------------------------------------*/
bool mrpt::system::deleteFilesInDirectory(const string& path, bool deleteDirectoryAsWell)
{
  if (!directoryExists(path)) return false;

  CDirectoryExplorer::TFileInfoList lstFiles;
  CDirectoryExplorer::explore(path, FILE_ATTRIB_ARCHIVE | FILE_ATTRIB_DIRECTORY, lstFiles);

  for (auto& lstFile : lstFiles)
  {
    if (lstFile.isDir)
    {
      if (lstFile.name != "." && lstFile.name != "..")
      {
        if (!mrpt::system::deleteFilesInDirectory(lstFile.wholePath, true)) return false;
      }
    }
    else
    {
      if (!mrpt::system::deleteFile(lstFile.wholePath)) return false;
    }
  }

  // Finally, delete the directory itselt.
  if (deleteDirectoryAsWell)
    return 0 == _rmdir(path.c_str());
  else
    return true;
}

std::string mrpt::system::getcwd() { return p2s(fs::current_path()); }

/*---------------------------------------------------------------
          getTempFileName
  ---------------------------------------------------------------*/
std::string mrpt::system::getTempFileName()
{
#ifdef _WIN32
  FILETIME tt;
  GetSystemTimeAsFileTime(&tt);
  const UINT uniq = static_cast<UINT>(tt.dwLowDateTime);
  char TMP_PATH[MAX_PATH];
  char tmpPath[MAX_PATH];
  GetTempPathA(MAX_PATH, tmpPath);
  GetTempFileNameA(tmpPath, "mrpt", uniq, TMP_PATH);
  return std::string(TMP_PATH);
#else
  char tmp[] = "/tmp/mrpt_tempXXXXXX";
  int fd;
  fd = mkstemp(tmp);
  ASSERT_(fd >= 0);
  close(fd);
  return std::string(tmp);
#endif
}

/** Renames a file - If the target path is different and the filesystem allows
 * it, it will be moved to the new location.
 * \return false on any error. In that case, if a pointer to a receiver string
 * is passed in error_msg, a description of the error is saved there.
 */
bool mrpt::system::renameFile(
    const string& oldFileName, const string& newFileName, std::string* error_msg)
{
  bool ret_err = 0 == rename(oldFileName.c_str(), newFileName.c_str());

  if (error_msg)
  {
    if (ret_err)
      *error_msg = strerror(errno);
    else
      *error_msg = "";
  }

  return ret_err;
}

/*---------------------------------------------------------------
      fileNameStripInvalidChars
---------------------------------------------------------------*/
std::string mrpt::system::fileNameStripInvalidChars(
    const std::string& filename, const char replacement_to_invalid_chars)
{
  const char forbid[] = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'};
  const unsigned int nForbid = sizeof(forbid) / sizeof(forbid[0]);

  string ret(filename);
  for (char& c : ret)
  {
    bool invalid = (c < 32);

    for (unsigned int i = 0; !invalid && i < nForbid; i++)
      if (c == forbid[i]) invalid = true;

    if (invalid) c = replacement_to_invalid_chars;
  }
  return ret;
}

/*---------------------------------------------------------------
      getFileSize
---------------------------------------------------------------*/
uint64_t mrpt::system::getFileSize(const std::string& fileName)
{
  return fs::file_size(fs::path(fileName));
}

/** Replace the filename extension by another one.  */
std::string mrpt::system::fileNameChangeExtension(
    const std::string& filePath, const std::string& newExtension)
{
  return p2s(fs::path(filePath).replace_extension(fs::path(newExtension)));
}

bool mrpt::system::copyFile(
    const std::string& sourceFile, const std::string& targetFile, std::string* outErrStr)
{
  std::error_code ec;
  bool ok = fs::copy_file(
      fs::path(sourceFile), fs::path(targetFile), fs::copy_options::overwrite_existing, ec);

  if (ok) return true;

  if (outErrStr) *outErrStr = ec.message();

  return false;
}

std::string mrpt::system::filePathSeparatorsToNative(const std::string& filePath)
{
  std::string ret = filePath;
  const size_t N = ret.size();
  for (size_t i = 0; i < N; i++)
  {
#ifdef _WIN32
    if (ret[i] == '/') ret[i] = '\\';
#else
    if (ret[i] == '\\') ret[i] = '/';
#endif
  }
  return p2s(fs::path(ret).native());
}

mrpt::Clock::time_point mrpt::system::getFileModificationTime(const std::string& filename)
{
  struct stat fS
  {
  };
  if (0 != stat(filename.c_str(), &fS))
    THROW_EXCEPTION_FMT("Could not access modification time of file '%s'", filename.c_str());
  else
    return mrpt::Clock::fromDouble(static_cast<double>(fS.st_mtime));
}

#include <mrpt/version.h>
// Read docs in .h
std::string mrpt::system::getShareMRPTDir()
{
  using std::string;
  using std::vector;

  static vector<string> sPaths;
  static string sDetectedPath;
  static bool is_first = true;
  if (is_first)
  {
    is_first = false;

    // Source dir:
    sPaths.push_back(string(MRPT_CMAKE_SOURCE_DIR) + string("/share/mrpt/"));
    // Install dir:
    sPaths.push_back(string(MRPT_CMAKE_INSTALL_PREFIX) + string("/share/mrpt/"));

    // Program path & ".." & "../..":
    char buf[2048];
    bool sBufOk = false;
#ifdef _WIN32
    sBufOk = (0 != GetModuleFileNameA(NULL, buf, sizeof(buf)));
#endif
#ifdef MRPT_OS_LINUX
    ssize_t nRead = readlink("/proc/self/exe", buf, sizeof(buf));
    if (nRead >= 0) buf[nRead] = '\0';
    sBufOk = (-1 != nRead);
#endif

    if (sBufOk)
    {
      string sBuf = string(buf);
      std::replace(sBuf.begin(), sBuf.end(), '\\', '/');
      sBuf = extractFileDirectory(sBuf);
      sPaths.push_back(sBuf + string("share/mrpt/"));
      sPaths.push_back(sBuf + string("../share/mrpt/"));
      sPaths.push_back(sBuf + string("../../share/mrpt/"));
    }

    for (const auto& e : sPaths)
      if (directoryExists(e))
      {
        sDetectedPath = e;
        break;
      }
  }
  return sDetectedPath;
}

std::string mrpt::system::toAbsolutePath(const std::string& path, bool resolveToCanonical)
{
  if (resolveToCanonical) return p2s(fs::canonical(fs::path(path)));
  return p2s(fs::absolute(fs::path(path)));
}

std::string mrpt::system::pathJoin(const std::vector<std::string>& paths)
{
  fs::path p;
  for (const auto& d : paths) p.append(d);

  return p2s(p);
}
