// Copyright (c), ETH Zurich and UNC Chapel Hill.
// All rights reserved.
//
// 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 ETH Zurich and UNC Chapel Hill 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 HOLDERS 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.

#include "colmap/util/string.h"

#include <locale>

#include <gtest/gtest.h>

namespace colmap {
namespace {

#define TEST_STRING_INPLACE(Func, str, ref_str) \
  {                                             \
    std::string str_inplace = str;              \
    Func(&str_inplace);                         \
    EXPECT_EQ(str_inplace, ref_str);            \
  }

TEST(StringPrintf, Nominal) {
  EXPECT_EQ(StringPrintf("%s", "test"), "test");
  EXPECT_EQ(StringPrintf("%d", 1), "1");
  EXPECT_EQ(StringPrintf("%.3f", 1.234), "1.234");
  EXPECT_EQ(StringPrintf("test%s", "test"), "testtest");
  EXPECT_EQ(StringPrintf("test%d", 1), "test1");
  EXPECT_EQ(StringPrintf("test%.3f", 1.234), "test1.234");
  EXPECT_EQ(StringPrintf("%s%s", "test", "test"), "testtest");
  EXPECT_EQ(StringPrintf("%d%s", 1, "test"), "1test");
  EXPECT_EQ(StringPrintf("%.3f%s", 1.234, "test"), "1.234test");
}

TEST(StringReplace, Nominal) {
  EXPECT_EQ(StringReplace("test", "-", ""), "test");
  EXPECT_EQ(StringReplace("test", "t", "a"), "aesa");
  EXPECT_EQ(StringReplace("test", "t", "---"), "---es---");
  EXPECT_EQ(StringReplace("test", "", "a"), "test");
  EXPECT_EQ(StringReplace("test", "", ""), "test");
  EXPECT_EQ(StringReplace("ttt", "ttt", "+++"), "+++");
}

TEST(StringGetAfter, Nominal) {
  EXPECT_EQ(StringGetAfter("test", ""), "test");
  EXPECT_EQ(StringGetAfter("test", "notinit"), "");
  EXPECT_EQ(StringGetAfter("test", "e"), "st");
  EXPECT_EQ(StringGetAfter("test, multiple tests", "test"), "s");
  EXPECT_EQ(StringGetAfter("", ""), "");
  EXPECT_EQ(StringGetAfter("path/to/dataset/sub1/image.png", "sub1/"),
            "image.png");
}

TEST(StringSplit, Nominal) {
  const std::vector<std::string> list1 = StringSplit("1,2,3,4,5 , 6", ",");
  EXPECT_EQ(list1.size(), 6);
  EXPECT_EQ(list1[0], "1");
  EXPECT_EQ(list1[1], "2");
  EXPECT_EQ(list1[2], "3");
  EXPECT_EQ(list1[3], "4");
  EXPECT_EQ(list1[4], "5 ");
  EXPECT_EQ(list1[5], " 6");
  const std::vector<std::string> list2 = StringSplit("1,2,3,4,5 , 6", "");
  EXPECT_EQ(list2.size(), 1);
  EXPECT_EQ(list2[0], "1,2,3,4,5 , 6");
  const std::vector<std::string> list3 = StringSplit("1,,2,,3,4,5 , 6", ",");
  EXPECT_EQ(list3.size(), 6);
  EXPECT_EQ(list3[0], "1");
  EXPECT_EQ(list3[1], "2");
  EXPECT_EQ(list3[2], "3");
  EXPECT_EQ(list3[3], "4");
  EXPECT_EQ(list3[4], "5 ");
  EXPECT_EQ(list3[5], " 6");
  const std::vector<std::string> list4 = StringSplit("1,,2,,3,4,5 , 6", ",,");
  EXPECT_EQ(list4.size(), 6);
  EXPECT_EQ(list4[0], "1");
  EXPECT_EQ(list4[1], "2");
  EXPECT_EQ(list4[2], "3");
  EXPECT_EQ(list4[3], "4");
  EXPECT_EQ(list4[4], "5 ");
  EXPECT_EQ(list4[5], " 6");
  const std::vector<std::string> list5 = StringSplit("1,,2,,3,4,5 , 6", ", ");
  EXPECT_EQ(list5.size(), 6);
  EXPECT_EQ(list5[0], "1");
  EXPECT_EQ(list5[1], "2");
  EXPECT_EQ(list5[2], "3");
  EXPECT_EQ(list5[3], "4");
  EXPECT_EQ(list5[4], "5");
  EXPECT_EQ(list5[5], "6");
  const std::vector<std::string> list6 = StringSplit(",1,,2,,3,4,5 , 6 ", ", ");
  EXPECT_EQ(list6.size(), 8);
  EXPECT_EQ(list6[0], "");
  EXPECT_EQ(list6[1], "1");
  EXPECT_EQ(list6[2], "2");
  EXPECT_EQ(list6[3], "3");
  EXPECT_EQ(list6[4], "4");
  EXPECT_EQ(list6[5], "5");
  EXPECT_EQ(list6[6], "6");
  EXPECT_EQ(list6[7], "");
}

TEST(StringStartsWith, Nominal) {
  EXPECT_FALSE(StringStartsWith("", ""));
  EXPECT_FALSE(StringStartsWith("a", ""));
  EXPECT_FALSE(StringStartsWith("", "a"));
  EXPECT_TRUE(StringStartsWith("a", "a"));
  EXPECT_TRUE(StringStartsWith("aa", "a"));
  EXPECT_TRUE(StringStartsWith("aa", "aa"));
  EXPECT_TRUE(StringStartsWith("aaaaaaaaa", "aa"));
}

TEST(StringStrim, Nominal) {
  TEST_STRING_INPLACE(StringTrim, "", "");
  TEST_STRING_INPLACE(StringTrim, " ", "");
  TEST_STRING_INPLACE(StringTrim, "a", "a");
  TEST_STRING_INPLACE(StringTrim, " a", "a");
  TEST_STRING_INPLACE(StringTrim, "a ", "a");
  TEST_STRING_INPLACE(StringTrim, " a ", "a");
  TEST_STRING_INPLACE(StringTrim, "  a  ", "a");
  TEST_STRING_INPLACE(StringTrim, "aa  ", "aa");
  TEST_STRING_INPLACE(StringTrim, "a  a  ", "a  a");
  TEST_STRING_INPLACE(StringTrim, "a  a  a ", "a  a  a");
  TEST_STRING_INPLACE(StringTrim, "\n\r\t", "");
}

TEST(StringLeftString, Nominal) {
  TEST_STRING_INPLACE(StringLeftTrim, "", "");
  TEST_STRING_INPLACE(StringLeftTrim, " ", "");
  TEST_STRING_INPLACE(StringLeftTrim, "a", "a");
  TEST_STRING_INPLACE(StringLeftTrim, " a", "a");
  TEST_STRING_INPLACE(StringLeftTrim, "a ", "a ");
  TEST_STRING_INPLACE(StringLeftTrim, " a ", "a ");
  TEST_STRING_INPLACE(StringLeftTrim, "  a  ", "a  ");
  TEST_STRING_INPLACE(StringLeftTrim, "aa  ", "aa  ");
  TEST_STRING_INPLACE(StringLeftTrim, "a  a  ", "a  a  ");
  TEST_STRING_INPLACE(StringLeftTrim, "  a  a", "a  a");
  TEST_STRING_INPLACE(StringLeftTrim, "a  a  a ", "a  a  a ");
  TEST_STRING_INPLACE(StringTrim, "\n\r\ta", "a");
}

TEST(StringStrimRight, Nominal) {
  TEST_STRING_INPLACE(StringRightTrim, "", "");
  TEST_STRING_INPLACE(StringRightTrim, " ", "");
  TEST_STRING_INPLACE(StringRightTrim, "a", "a");
  TEST_STRING_INPLACE(StringRightTrim, " a", " a");
  TEST_STRING_INPLACE(StringRightTrim, "a ", "a");
  TEST_STRING_INPLACE(StringRightTrim, " a ", " a");
  TEST_STRING_INPLACE(StringRightTrim, "  a  ", "  a");
  TEST_STRING_INPLACE(StringRightTrim, "aa  ", "aa");
  TEST_STRING_INPLACE(StringRightTrim, "a  a  ", "a  a");
  TEST_STRING_INPLACE(StringRightTrim, "a  a  a ", "a  a  a");
  TEST_STRING_INPLACE(StringTrim, "a\n\r\t", "a");
}

TEST(StringToLower, Nominal) {
  TEST_STRING_INPLACE(StringToLower, "", "");
  TEST_STRING_INPLACE(StringToLower, " ", " ");
  TEST_STRING_INPLACE(StringToLower, "a", "a");
  TEST_STRING_INPLACE(StringToLower, " a", " a");
  TEST_STRING_INPLACE(StringToLower, "a ", "a ");
  TEST_STRING_INPLACE(StringToLower, " a ", " a ");
  TEST_STRING_INPLACE(StringToLower, "aa  ", "aa  ");
  TEST_STRING_INPLACE(StringToLower, "A", "a");
  TEST_STRING_INPLACE(StringToLower, " A", " a");
  TEST_STRING_INPLACE(StringToLower, "A ", "a ");
  TEST_STRING_INPLACE(StringToLower, " A ", " a ");
  TEST_STRING_INPLACE(StringToLower, "AA  ", "aa  ");
  TEST_STRING_INPLACE(StringToLower,
                      "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
                      "abcdefghijklmnopqrstuvwxyz");
  TEST_STRING_INPLACE(StringToLower,
                      "0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ",
                      "0123456789 abcdefghijklmnopqrstuvwxyz");
}

TEST(StringToUpper, Nominal) {
  TEST_STRING_INPLACE(StringToUpper, "", "");
  TEST_STRING_INPLACE(StringToUpper, " ", " ");
  TEST_STRING_INPLACE(StringToUpper, "A", "A");
  TEST_STRING_INPLACE(StringToUpper, " A", " A");
  TEST_STRING_INPLACE(StringToUpper, "A ", "A ");
  TEST_STRING_INPLACE(StringToUpper, " A ", " A ");
  TEST_STRING_INPLACE(StringToUpper, "AA  ", "AA  ");
  TEST_STRING_INPLACE(StringToUpper, "a", "A");
  TEST_STRING_INPLACE(StringToUpper, " a", " A");
  TEST_STRING_INPLACE(StringToUpper, "a ", "A ");
  TEST_STRING_INPLACE(StringToUpper, " a ", " A ");
  TEST_STRING_INPLACE(StringToUpper, "aa  ", "AA  ");
  TEST_STRING_INPLACE(StringToUpper,
                      "abcdefghijklmnopqrstuvwxyz",
                      "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  TEST_STRING_INPLACE(StringToUpper,
                      "0123456789 abcdefghijklmnopqrstuvwxyz",
                      "0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

TEST(StringContains, Nominal) {
  EXPECT_TRUE(StringContains("", ""));
  EXPECT_TRUE(StringContains("a", ""));
  EXPECT_TRUE(StringContains("a", "a"));
  EXPECT_TRUE(StringContains("ab", "a"));
  EXPECT_TRUE(StringContains("ab", "ab"));
  EXPECT_FALSE(StringContains("", "a"));
  EXPECT_FALSE(StringContains("ab", "c"));
}

TEST(ConversionBetweenPlatformAndUTF8, NonASCIIStringRoundtrip) {
  const std::unordered_map<int, std::string> kCodePageToUTF8Strings = {
      {// English
       {0, u8"Bundle Adjustment"},
       // Simplified Chinese
       {936, u8"光束法平差"},
       // Japanese
       {932, u8"バンドル調整"},
       // Korean
       {949, u8"번들조정"},
       // French
       {1252, u8"Ajustement de faisceau"},
       // German
       {1252, u8"Bündelanpassung"},
       // Russian
       {1251, u8"Байндл-адаптация"},
       // Greek
       {1253, u8"Προσαρμογή δεσμίδας"},
       // Turkish
       {1254, u8"Işın Demeti Ayarı"},
       // Hebrew
       {1255, u8"התאמת צרור"},
       // Arabic
       {1256, u8"ضبط الحزمة"},
       // Thai
       {874, u8"ปรับค่ากลุ่มภาพ"}}};

  for (const auto& [code_page, original] : kCodePageToUTF8Strings) {
#ifdef _WIN32
    // NOTE:
    // internal::UTF8ToCodePageWin and internal::CodePageToUTF8Win are internal
    // helpers used by the public API (UTF8ToPlatform / PlatformToUTF8) on
    // Windows. By explicitly specifying the code page, we can verify that the
    // internal logic correctly handles various legacy encodings independently
    // of the system ACP.
    const std::string platform_encoded =
        internal::UTF8ToCodePageWin(original, code_page);
    const std::string roundtrip =
        internal::CodePageToUTF8Win(platform_encoded, code_page);
#else
    const std::string platform_encoded = UTF8ToPlatform(original);
    const std::string roundtrip = PlatformToUTF8(platform_encoded);
#endif

    EXPECT_EQ(roundtrip, original);
  }
}

TEST(StringToDouble, Nominal) {
  EXPECT_DOUBLE_EQ(StringToDouble("0"), 0.0);
  EXPECT_DOUBLE_EQ(StringToDouble("0.5"), 0.5);
  EXPECT_DOUBLE_EQ(StringToDouble("-1.23"), -1.23);
  EXPECT_DOUBLE_EQ(StringToDouble("1.234e10"), 1.234e10);
  EXPECT_DOUBLE_EQ(StringToDouble("-5.67e-3"), -5.67e-3);
  EXPECT_DOUBLE_EQ(StringToDouble("100"), 100.0);
  EXPECT_DOUBLE_EQ(StringToDouble("0.12345678901234567"), 0.12345678901234567);
}

// Custom numpunct facet that uses comma as decimal separator, for testing
// locale independence without requiring a specific system locale.
struct CommaDecimalFacet : std::numpunct<char> {
 protected:
  char do_decimal_point() const override { return ','; }
};

TEST(StringToDouble, LocaleIndependence) {
  // Install a global locale that uses comma as decimal separator.
  const std::locale original_locale = std::locale::global(
      std::locale(std::locale::classic(), new CommaDecimalFacet));

  // Verify that the locale is used correctly.
  std::stringstream ss;
  ss << "1,23";
  double d;
  ss >> d;
  EXPECT_DOUBLE_EQ(d, 1.23);

  // StringToDouble must still parse dot-separated decimals correctly.
  EXPECT_DOUBLE_EQ(StringToDouble("0.5"), 0.5);
  EXPECT_DOUBLE_EQ(StringToDouble("0.1"), 0.1);
  EXPECT_DOUBLE_EQ(StringToDouble("0.2"), 0.2);
  EXPECT_DOUBLE_EQ(StringToDouble("0.3"), 0.3);
  EXPECT_DOUBLE_EQ(StringToDouble("-1.23e5"), -1.23e5);

  // Restore original locale.
  std::locale::global(original_locale);
}

}  // namespace
}  // namespace colmap
