/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */

#include "cmConfigure.h" // IWYU pragma: keep

#include "cmCMakePath.h"

#include <string>

#if defined(_WIN32)
#  include <cstdlib>
#endif

#include <cm/filesystem>
#include <cm/string_view>

#if defined(_WIN32)
#  include <cmext/string_view>

#  include "cmStringAlgorithms.h"
#endif

cmCMakePath& cmCMakePath::ReplaceWideExtension(cm::string_view extension)
{
  auto file = this->Path.filename().string();
  if (!file.empty() && file != "." && file != "..") {
    auto pos = file.find('.', file[0] == '.' ? 1 : 0);
    if (pos != std::string::npos) {
      file.erase(pos);
    }
  }
  if (!extension.empty()) {
    if (extension[0] != '.') {
      file += '.';
    }
    file.append(std::string(extension));
  }
  this->Path.replace_filename(file);
  return *this;
}

cmCMakePath cmCMakePath::GetWideExtension() const
{
  auto file = this->Path.filename().string();
  if (file.empty() || file == "." || file == "..") {
    return cmCMakePath{};
  }

  auto pos = file.find('.', file[0] == '.' ? 1 : 0);
  if (pos != std::string::npos) {
    return cm::string_view(file.data() + pos, file.length() - pos);
  }

  return cmCMakePath{};
}

cmCMakePath cmCMakePath::GetNarrowStem() const
{
  auto stem = this->Path.stem().string();
  if (stem.empty() || stem == "." || stem == "..") {
    return stem;
  }

  auto pos = stem.find('.', stem[0] == '.' ? 1 : 0);
  if (pos != std::string::npos) {
    return stem.substr(0, pos);
  }
  return stem;
}

cmCMakePath cmCMakePath::Absolute(const cm::filesystem::path& base) const
{
  if (this->Path.is_relative()) {
    auto path = base;
    path /= this->Path;
    // filesystem::path::operator/= use preferred_separator ('\' on Windows)
    // so converts back to '/'
    return path.generic_string();
  }
  return *this;
}

bool cmCMakePath::IsPrefix(const cmCMakePath& path) const
{
  auto prefix_it = this->Path.begin();
  auto prefix_end = this->Path.end();
  auto path_it = path.Path.begin();
  auto path_end = path.Path.end();

  while (prefix_it != prefix_end && path_it != path_end &&
         *prefix_it == *path_it) {
    ++prefix_it;
    ++path_it;
  }
  return (prefix_it == prefix_end) ||
    (prefix_it->empty() && path_it != path_end);
}

std::string cmCMakePath::FormatPath(std::string path, format fmt)
{
#if defined(_WIN32)
  if (fmt == auto_format || fmt == native_format) {
    auto prefix = path.substr(0, 4);
    for (auto& c : prefix) {
      if (c == '\\') {
        c = '/';
      }
    }
    // remove Windows long filename marker
    if (prefix == "//?/"_s) {
      path.erase(0, 4);
    }
    if (cmHasPrefix(path, "UNC/"_s) || cmHasPrefix(path, "UNC\\"_s)) {
      path.erase(0, 2);
      path[0] = '/';
    }
  }
#else
  static_cast<void>(fmt);
#endif
  return path;
}

void cmCMakePath::GetNativePath(std::string& path) const
{
  cm::filesystem::path tmp(this->Path);
  tmp.make_preferred();

  path = tmp.string();
}
void cmCMakePath::GetNativePath(std::wstring& path) const
{
  cm::filesystem::path tmp(this->Path);
  tmp.make_preferred();

  path = tmp.wstring();

#if defined(_WIN32)
  // Windows long filename
  static std::wstring UNC(L"\\\\?\\UNC");
  static std::wstring PREFIX(L"\\\\?\\");

  if (this->IsAbsolute() && path.length() > _MAX_PATH - 12) {
    if (this->HasRootName() && path[0] == L'\\') {
      path = UNC + path.substr(1);
    } else {
      path = PREFIX + path;
    }
  }
#endif
}
