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

#include <algorithm>
#include <sstream>
#include <string>
#include <vector>

#include <cm/filesystem>

#include "testCommon.h"

namespace {

namespace fs = cm::filesystem;

void checkResult(bool success)
{
  if (!success) {
    std::cout << " => failed";
  }
  std::cout << std::endl;
}

bool testConstructors()
{
  std::cout << "testConstructors()";

  bool result = true;

  {
    fs::path p;
    if (p != fs::path()) {
      result = false;
    }
  }
  {
    fs::path p1("/a/b/c");
    fs::path p2("/a/b/c");
    if (p1 != p2) {
      result = false;
    }
    if (p1.string() != p2.string()) {
      result = false;
    }
    if (p1.string() != "/a/b/c") {
      result = false;
    }
  }
  {
    std::string s("/a/b/c");
    fs::path p1(s);
    fs::path p2(s.begin(), s.end());
    if (p1 != p2) {
      result = false;
    }
    if (p1.string() != s || p2.string() != s) {
      result = false;
    }
#if CM_FILESYSTEM_SOURCE_TRAITS_ITERATOR
    std::string s2(s);
    s2 += '\0';
    fs::path p3(s2.begin());
    if (p1 != p3 || p3.string() != s) {
      result = false;
    }
#endif
  }
  {
    std::wstring s(L"/a/b/c");
    fs::path p1(s);
    fs::path p2(s.begin(), s.end());
    if (p1 != p2) {
      result = false;
    }
    if (p1.wstring() != s || p2.wstring() != s) {
      result = false;
    }
#if CM_FILESYSTEM_SOURCE_TRAITS_ITERATOR
    std::wstring s2(s);
    s2 += L'\0';
    fs::path p3(s2.begin());
    if (p1 != p3 || p3.wstring() != s) {
      result = false;
    }
#endif
  }
  {
    std::string s("/a/b/c");
    fs::path::string_type ws;
    for (auto c : s) {
      ws += fs::path::value_type(c);
    }
    fs::path p1(ws);
    fs::path p2(ws.begin(), ws.end());
    if (p1 != p2) {
      result = false;
    }
    if (p1.native() != ws || p2.native() != ws) {
      result = false;
    }
#if CM_FILESYSTEM_SOURCE_TRAITS_ITERATOR
    fs::path::string_type ws2(ws);
    ws2 += fs::path::value_type('\0');
    fs::path p3(ws2.begin());
    if (p1 != p3 || p3.native() != ws) {
      result = false;
    }
#endif
  }

  checkResult(result);

  return result;
}

bool testConcatenation()
{
  std::cout << "testConcatenation()";

  bool result = true;

  {
    fs::path p("/a/b");
    p /= "c";
    if (!(p.string() == "/a/b/c" || p.string() == "/a/b\\c")) {
      result = false;
    }
    p += "d";
    if (!(p.string() == "/a/b/cd" || p.string() == "/a/b\\cd")) {
      result = false;
    }
    fs::path p2("x/y");
    p /= p2;
    if (!(p.string() == "/a/b/cd/x/y" || p.string() == "/a/b\\cd\\x/y")) {
      result = false;
    }
    p = p / p2;
    if (!(p.string() == "/a/b/cd/x/y/x/y" ||
          p.string() == "/a/b\\cd\\x/y\\x/y")) {
      result = false;
    }
  }
  {
    fs::path p("a");
    p /= "";
    if (!(p.string() == "a/" || p.string() == "a\\")) {
      result = false;
    }
    p /= "/b";
    if (p.string() != "/b") {
      result = false;
    }
  }
#if defined(_WIN32)
  {
    fs::path p("a");
    p /= "c:/b";
    if (p.string() != "c:/b") {
      result = false;
    }
    p = fs::path("a") / "c:";
    if (p.string() != "c:") {
      result = false;
    }
    p = fs::path("c:") / "";
    if (p.string() != "c:") {
      result = false;
    }
    p = fs::path("c:a") / "/b";
    if (p.string() != "c:/b") {
      result = false;
    }
    p = fs::path("c:a") / "c:b";
    if (p.string() != "c:a\\b") {
      result = false;
    }
    p = fs::path("//host") / "b";
    if (p.string() != "//host\\b") {
      result = false;
    }
    p = fs::path("//host/") / "b";
    if (p.string() != "//host/b") {
      result = false;
    }
  }
#endif

  checkResult(result);

  return result;
}

bool testModifiers()
{
  std::cout << "testModifiers()";

  bool result = true;

  {
    std::string s("a///b/");
    fs::path p(s);
    std::replace(
      s.begin(), s.end(), '/',
      static_cast<std::string::value_type>(fs::path::preferred_separator));
    p.make_preferred();
    if (p.string() != s) {
      result = false;
    }
  }
  {
    fs::path p("a/b/c.e.f");
    p.remove_filename();
    if (p.string() != "a/b/") {
      result = false;
    }
    p.remove_filename();
    if (p.string() != "a/b/") {
      result = false;
    }
  }
  {
    fs::path p("a/b/c.e.f");
    p.replace_filename("x.y");
    if (p.string() != "a/b/x.y") {
      result = false;
    }
  }
  {
    fs::path p("a/b/c.e.f");
    p.replace_extension(".x");
    if (p.string() != "a/b/c.e.x") {
      result = false;
    }
    p.replace_extension(".y");
    if (p.string() != "a/b/c.e.y") {
      result = false;
    }
    p.replace_extension();
    if (p.string() != "a/b/c.e") {
      result = false;
    }
    p = "/a/b";
    p.replace_extension(".x");
    if (p.string() != "/a/b.x") {
      result = false;
    }
    p = "/a/b/";
    p.replace_extension(".x");
    if (p.string() != "/a/b/.x") {
      result = false;
    }
  }

  checkResult(result);

  return result;
}

bool testObservers()
{
  std::cout << "testObservers()";

  bool result = true;

  {
    std::string s("a/b/c");
    fs::path p(s);
    fs::path::string_type st;
    for (auto c : s) {
      st += static_cast<fs::path::value_type>(c);
    }
    if (p.native() != st || static_cast<fs::path::string_type>(p) != st ||
        p.c_str() != st) {
      result = false;
    }
  }
  {
    std::string s("a//b//c");
    std::wstring ws(L"a//b//c");
    fs::path p(s);
    if (p.string() != s || p.wstring() != ws) {
      result = false;
    }
  }
  {
    std::string s("a/b/c");
    std::wstring ws;
    for (auto c : s) {
      ws += static_cast<std::wstring::value_type>(c);
    }
    std::string ns(s);
    std::replace(
      ns.begin(), ns.end(), '/',
      static_cast<std::string::value_type>(fs::path::preferred_separator));
    fs::path p(ns);
    if (p.generic_string() != s || p.generic_wstring() != ws) {
      result = false;
    }
  }

  checkResult(result);

  return result;
}

bool testCompare()
{
  std::cout << "testCompare()";

  bool result = true;

  {
    std::string s("a/b/c");
    fs::path p1(s);
    fs::path p2(s);
    if (p1.compare(p2) != 0) {
      result = false;
    }
    p2 = "a/b";
    if (p1.compare(p2) <= 0) {
      result = false;
    }
    p2 = "a/d";
    if (p1.compare(p2) >= 0) {
      result = false;
    }
    p2 = "a/b/d";
    if (p1.compare(p2) >= 0) {
      result = false;
    }
    p2 = "a/b/a";
    if (p1.compare(p2) <= 0) {
      result = false;
    }
    p2 = "a/b/c/d";
    if (p1.compare(p2) >= 0) {
      result = false;
    }
    p1 = "a";
    p2 = "b";
    if (p1.compare(p2) == 0) {
      result = false;
    }
  }
  {
    // LWG 3096 (https://cplusplus.github.io/LWG/issue3096)
    // fs::path p1("/a/");
    // fs::path p2("/a/.");
    // if (p1.compare(p2) != 0) {
    //   result = false;
    // }
  }

  checkResult(result);

  return result;
}

bool testGeneration()
{
  std::cout << "testGeneration()";

  bool result = true;

  {
    fs::path p("a/./b/..");
    if (p.lexically_normal().generic_string() != "a/") {
      result = false;
    }
    p = "a/.///b/../";
    if (p.lexically_normal().generic_string() != "a/") {
      result = false;
    }
  }
#if defined(_WIN32)
  {
    fs::path p("//host/./b/..");
    if (p.lexically_normal().string() != "\\\\host\\") {
      result = false;
    }
    p = "//host/.///b/../";
    if (p.lexically_normal().string() != "\\\\host\\") {
      result = false;
    }
    p = "c://a/.///b/../";
    if (p.lexically_normal().string() != "c:\\a\\") {
      result = false;
    }
  }
#endif

  {
    if (fs::path("/a//d").lexically_relative("/a/b/c") != "../../d") {
      result = false;
    }
    if (fs::path("/a//b///c").lexically_relative("/a/d") != "../b/c") {
      result = false;
    }
    if (fs::path("a/b/c").lexically_relative("a") != "b/c") {
      result = false;
    }
    if (fs::path("a/b/c").lexically_relative("a/b/c/x/y") != "../..") {
      result = false;
    }
    if (fs::path("a/b/c").lexically_relative("a/b/c") != ".") {
      result = false;
    }
    if (fs::path("a/b").lexically_relative("c/d") != "../../a/b") {
      result = false;
    }
  }
  {
#if defined(_WIN32)
    if (fs::path("/a/d").lexically_relative("e/d/c") != "/a/d") {
      result = false;
    }
    if (!fs::path("c:/a/d").lexically_relative("e/d/c").empty()) {
      result = false;
    }
#else
    if (!fs::path("/a/d").lexically_relative("e/d/c").empty()) {
      result = false;
    }
#endif
  }
  {
#if defined(_WIN32)
    if (fs::path("c:/a/d").lexically_proximate("e/d/c") != "c:/a/d") {
      result = false;
    }
#else
    if (fs::path("/a/d").lexically_proximate("e/d/c") != "/a/d") {
      result = false;
    }
#endif
    if (fs::path("/a/d").lexically_proximate("/a/b/c") != "../../d") {
      result = false;
    }
  }
  // LWG 3070
  {
#if defined(_WIN32)
    if (!fs::path("/a:/b:").lexically_relative("/a:/c:").empty()) {
      result = false;
    }
    if (fs::path("c:/a/b").lexically_relative("c:/a/d") != "../b") {
      result = false;
    }
    if (!fs::path("c:/a/b:").lexically_relative("c:/a/d").empty()) {
      result = false;
    }
    if (!fs::path("c:/a/b").lexically_relative("c:/a/d:").empty()) {
      result = false;
    }
#else
    if (fs::path("/a:/b:").lexically_relative("/a:/c:") != "../b:") {
      result = false;
    }
#endif
  }
  // LWG 3096
  {
    if (fs::path("/a").lexically_relative("/a/.") != ".") {
      result = false;
    }
    if (fs::path("/a").lexically_relative("/a/") != ".") {
      result = false;
    }
    if (fs::path("a/b/c").lexically_relative("a/b/c") != ".") {
      result = false;
    }
    if (fs::path("a/b/c").lexically_relative("a/b/c/") != ".") {
      result = false;
    }
    if (fs::path("a/b/c").lexically_relative("a/b/c/.") != ".") {
      result = false;
    }
    if (fs::path("a/b/c/").lexically_relative("a/b/c") != ".") {
      result = false;
    }
    if (fs::path("a/b/c/.").lexically_relative("a/b/c") != ".") {
      result = false;
    }
    if (fs::path("a/b/c/.").lexically_relative("a/b/c/") != ".") {
      result = false;
    }
  }

  checkResult(result);

  return result;
}

bool testDecomposition()
{
  std::cout << "testDecomposition()";

  bool result = true;

  {
    if (!fs::path("/a/b").root_name().empty()) {
      result = false;
    }
#if defined(_WIN32)
    if (fs::path("c:/a/b").root_name() != "c:") {
      result = false;
    }
    if (fs::path("c:a/b").root_name() != "c:") {
      result = false;
    }
    if (fs::path("c:").root_name() != "c:") {
      result = false;
    }
    if (fs::path("//host/b").root_name() != "//host") {
      result = false;
    }
    if (fs::path("//host").root_name() != "//host") {
      result = false;
    }
#endif
  }
  {
    if (!fs::path("a/b").root_directory().empty()) {
      result = false;
    }
    if (fs::path("/a/b").root_directory() != "/") {
      result = false;
    }
#if defined(_WIN32)
    if (!fs::path("c:a/b").root_directory().empty()) {
      result = false;
    }
    if (fs::path("/a/b").root_directory() != "/") {
      result = false;
    }
    if (fs::path("c:/a/b").root_directory() != "/") {
      result = false;
    }
    if (fs::path("//host/b").root_directory() != "/") {
      result = false;
    }
#endif
  }
  {
    if (!fs::path("a/b").root_path().empty()) {
      result = false;
    }
    if (fs::path("/a/b").root_path() != "/") {
      result = false;
    }
#if defined(_WIN32)
    if (fs::path("c:a/b").root_path() != "c:") {
      result = false;
    }
    if (fs::path("/a/b").root_path() != "/") {
      result = false;
    }
    if (fs::path("c:/a/b").root_path() != "c:/") {
      result = false;
    }
    if (fs::path("//host/b").root_path() != "//host/") {
      result = false;
    }
#endif
  }
  {
    if (!fs::path("/").relative_path().empty()) {
      result = false;
    }
    if (fs::path("a/b").relative_path() != "a/b") {
      result = false;
    }
    if (fs::path("/a/b").relative_path() != "a/b") {
      result = false;
    }
#if defined(_WIN32)
    if (fs::path("c:a/b").relative_path() != "a/b") {
      result = false;
    }
    if (fs::path("/a/b").relative_path() != "a/b") {
      result = false;
    }
    if (fs::path("c:/a/b").relative_path() != "a/b") {
      result = false;
    }
    if (fs::path("//host/b").relative_path() != "b") {
      result = false;
    }
#endif
  }
  {
    if (fs::path("/a/b").parent_path() != "/a") {
      result = false;
    }
    if (fs::path("/a/b/").parent_path() != "/a/b") {
      result = false;
    }
    if (fs::path("/a/b/.").parent_path() != "/a/b") {
      result = false;
    }
    if (fs::path("/").parent_path() != "/") {
      result = false;
    }
#if defined(_WIN32)
    if (fs::path("c:/a/b").parent_path() != "c:/a") {
      result = false;
    }
    if (fs::path("c:a").parent_path() != "c:") {
      result = false;
    }
    if (fs::path("c:/").parent_path() != "c:/") {
      result = false;
    }
    if (fs::path("c:").parent_path() != "c:") {
      result = false;
    }
    if (fs::path("//host/").parent_path() != "//host/") {
      result = false;
    }
    if (fs::path("//host").parent_path() != "//host") {
      result = false;
    }
#endif
  }
  {
    if (fs::path("/a/b.txt").filename() != "b.txt") {
      result = false;
    }
    if (fs::path("/a/.b").filename() != ".b") {
      result = false;
    }
    if (fs::path("/foo/bar/").filename() != "") {
      result = false;
    }
    if (fs::path("/foo/.").filename() != ".") {
      result = false;
    }
    if (fs::path("/foo/..").filename() != "..") {
      result = false;
    }
    if (fs::path(".").filename() != ".") {
      result = false;
    }
    if (fs::path("..").filename() != "..") {
      result = false;
    }
    if (!fs::path("/").filename().empty()) {
      result = false;
    }
#if defined(_WIN32)
    if (fs::path("c:a").filename() != "a") {
      result = false;
    }
    if (fs::path("c:/a").filename() != "a") {
      result = false;
    }
    if (!fs::path("c:").filename().empty()) {
      result = false;
    }
    if (!fs::path("c:/").filename().empty()) {
      result = false;
    }
    if (!fs::path("//host").filename().empty()) {
      result = false;
    }
#endif
  }
  {
    if (fs::path("/a/b.txt").stem() != "b") {
      result = false;
    }
    if (fs::path("/a/b.c.txt").stem() != "b.c") {
      result = false;
    }
    if (fs::path("/a/.b").stem() != ".b") {
      result = false;
    }
    if (fs::path("/a/b").stem() != "b") {
      result = false;
    }
    if (fs::path("/a/b/.").stem() != ".") {
      result = false;
    }
    if (fs::path("/a/b/..").stem() != "..") {
      result = false;
    }
    if (!fs::path("/a/b/").stem().empty()) {
      result = false;
    }
#if defined(_WIN32)
    if (!fs::path("c:/a/b/").stem().empty()) {
      result = false;
    }
    if (!fs::path("c:/").stem().empty()) {
      result = false;
    }
    if (!fs::path("c:").stem().empty()) {
      result = false;
    }
    if (!fs::path("//host/").stem().empty()) {
      result = false;
    }
    if (!fs::path("//host").stem().empty()) {
      result = false;
    }
#endif
  }
  {
    if (fs::path("/a/b.txt").extension() != ".txt") {
      result = false;
    }
    if (fs::path("/a/b.").extension() != ".") {
      result = false;
    }
    if (!fs::path("/a/b").extension().empty()) {
      result = false;
    }
    if (fs::path("/a/b.txt/b.cc").extension() != ".cc") {
      result = false;
    }
    if (fs::path("/a/b.txt/b.").extension() != ".") {
      result = false;
    }
    if (!fs::path("/a/b.txt/b").extension().empty()) {
      result = false;
    }
    if (!fs::path("/a/.").extension().empty()) {
      result = false;
    }
    if (!fs::path("/a/..").extension().empty()) {
      result = false;
    }
    if (!fs::path("/a/.hidden").extension().empty()) {
      result = false;
    }
    if (fs::path("/a/..b").extension() != ".b") {
      result = false;
    }
  }

  checkResult(result);

  return result;
}

bool testQueries()
{
  std::cout << "testQueries()";

  bool result = true;

  {
    if (fs::path("/a/b").has_root_name()) {
      result = false;
    }
    fs::path p("/a/b");
    if (!p.has_root_directory() || !p.has_root_path()) {
      result = false;
    }
    if (!fs::path("/a/b").has_root_path() || fs::path("a/b").has_root_path()) {
      result = false;
    }
    if (!fs::path("/a/b").has_relative_path() ||
        fs::path("/").has_relative_path()) {
      result = false;
    }
    if (!fs::path("/a/b").has_parent_path() ||
        !fs::path("/").has_parent_path() || fs::path("a").has_parent_path()) {
      result = false;
    }
    if (!fs::path("/a/b").has_filename() || !fs::path("a.b").has_filename() ||
        fs::path("/a/").has_filename() || fs::path("/").has_filename()) {
      result = false;
    }
    if (!fs::path("/a/b").has_stem() || !fs::path("a.b").has_stem() ||
        !fs::path("/.a").has_stem() || fs::path("/a/").has_stem() ||
        fs::path("/").has_stem()) {
      result = false;
    }
    if (!fs::path("/a/b.c").has_extension() ||
        !fs::path("a.b").has_extension() || fs::path("/.a").has_extension() ||
        fs::path("/a/").has_extension() || fs::path("/").has_extension()) {
      result = false;
    }
#if defined(_WIN32)
    p = "c:/a/b";
    if (!fs::path("c:/a/b").has_root_name() || !p.has_root_directory() ||
        !p.has_root_path()) {
      result = false;
    }
    p = "c:a/b";
    if (!p.has_root_name() || p.has_root_directory() || !p.has_root_path()) {
      result = false;
    }
    p = "//host/b";
    if (!p.has_root_name() || !p.has_root_directory() || !p.has_root_path()) {
      result = false;
    }
    p = "//host";
    if (!p.has_root_name() || p.has_root_directory() || !p.has_root_path()) {
      result = false;
    }
    if (!fs::path("c:/a/b").has_relative_path() ||
        !fs::path("c:a/b").has_relative_path() ||
        !fs::path("//host/b").has_relative_path()) {
      result = false;
    }
    if (!fs::path("c:/a/b").has_parent_path() ||
        !fs::path("c:/").has_parent_path() ||
        !fs::path("c:").has_parent_path() ||
        !fs::path("//host/").has_parent_path() ||
        !fs::path("//host").has_parent_path()) {
      result = false;
    }
#endif
  }
  {
#if defined(_WIN32)
    fs::path p("c:/a");
#else
    fs::path p("/a");
#endif
    if (!p.is_absolute() || p.is_relative()) {
      result = false;
    }
    p = "a/b";
    if (p.is_absolute() || !p.is_relative()) {
      result = false;
    }
#if defined(_WIN32)
    p = "c:/a/b";
    if (!p.is_absolute() || p.is_relative()) {
      result = false;
    }
    p = "//host/b";
    if (!p.is_absolute() || p.is_relative()) {
      result = false;
    }
    p = "/a";
    if (p.is_absolute() || !p.is_relative()) {
      result = false;
    }
    p = "c:a";
    if (p.is_absolute() || !p.is_relative()) {
      result = false;
    }
#endif
  }

  checkResult(result);

  return result;
}

bool testIterators()
{
  std::cout << "testIterators()";

  bool result = true;

  {
    fs::path p("/a/b/");
#if defined(_WIN32)
    std::vector<fs::path::string_type> ref{ L"/", L"a", L"b", L"" };
#else
    std::vector<fs::path::string_type> ref{ "/", "a", "b", "" };
#endif
    std::vector<fs::path::string_type> res;
    for (auto i = p.begin(), e = p.end(); i != e; ++i) {
      res.push_back(*i);
    }
    if (res != ref) {
      result = false;
    }
    res.clear();
    for (auto const& e : p) {
      res.push_back(e);
    }
    if (res != ref) {
      result = false;
    }
  }
  {
    fs::path p("/a/b/");
#if defined(_WIN32)
    std::vector<fs::path::string_type> ref{ L"", L"b", L"a", L"/" };
#else
    std::vector<fs::path::string_type> ref{ "", "b", "a", "/" };
#endif
    std::vector<fs::path::string_type> res;
    auto i = p.end(), b = p.begin();
    do {
      res.push_back(*--i);
    } while (i != b);
    if (res != ref) {
      result = false;
    }
  }

  checkResult(result);

  return result;
}

bool testNonMemberFunctions()
{
  std::cout << "testNonMemberFunctions()";

  bool result = true;

  {
    fs::path p1("/a/b/");
    fs::path p2("/c/d");
    fs::swap(p1, p2);
    if (p1.string() != "/c/d" || p2.string() != "/a/b/")
      result = false;
  }
  {
    auto h1 = fs::hash_value(fs::path("/a//b//"));
    auto h2 = fs::hash_value(fs::path("/a/b/"));
    if (h1 != h2)
      result = false;
  }
  {
    fs::path p1("/a/b/");
    fs::path p2("/c/d");
    if (p1 == p2)
      result = false;
    p1 = "/a//b//";
    p2 = "/a/b/";
    if (p1 != p2)
      result = false;
  }
  {
    fs::path p = "/a";
    p = p / "b" / "c";
    if (p.generic_string() != "/a/b/c") {
      result = false;
    }
    fs::path::string_type ref;
    ref += fs::path::value_type('/');
    ref += fs::path::value_type('a');
    ref += fs::path::preferred_separator;
    ref += fs::path::value_type('b');
    ref += fs::path::preferred_separator;
    ref += fs::path::value_type('c');
    if (p.native() != ref) {
      result = false;
    }
  }
  {
    fs::path p("/a b\\c/");
    std::ostringstream oss;
    oss << p;
    if (oss.str() != "\"/a b\\\\c/\"") {
      result = false;
    }
    std::istringstream iss(oss.str());
    fs::path p2;
    iss >> p2;
    if (p2 != p) {
      result = false;
    }
  }

  checkResult(result);

  return result;
}
}

int testCMFilesystemPath(int /*unused*/, char* /*unused*/[])
{
  return runTests({ testConstructors, testConcatenation, testModifiers,
                    testObservers, testCompare, testGeneration,
                    testDecomposition, testQueries, testIterators,
                    testNonMemberFunctions },
                  false);
}
