/***************************************************************************
    copyright           : (C) 2007 by Lukas Lalinsky
    email               : lukas@oxygene.sk
 ***************************************************************************/

/***************************************************************************
 *   This library is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License version   *
 *   2.1 as published by the Free Software Foundation.                     *
 *                                                                         *
 *   This library is distributed in the hope that it will be useful, but   *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
 *   02110-1301  USA                                                       *
 *                                                                         *
 *   Alternatively, this file is available under the Mozilla Public        *
 *   License Version 1.1.  You may obtain a copy of the License at         *
 *   http://www.mozilla.org/MPL/                                           *
 ***************************************************************************/

#include <cstring>

#include "tstring.h"
#include "tutils.h"
#include <cppunit/extensions/HelperMacros.h>

using namespace std;
using namespace TagLib;

class TestString : public CppUnit::TestFixture
{
  CPPUNIT_TEST_SUITE(TestString);
  CPPUNIT_TEST(testString);
  CPPUNIT_TEST(testRfind);
  CPPUNIT_TEST(testUTF16Encode);
  CPPUNIT_TEST(testUTF16Decode);
  CPPUNIT_TEST(testUTF16DecodeInvalidBOM);
  CPPUNIT_TEST(testUTF16DecodeEmptyWithBOM);
  CPPUNIT_TEST(testSurrogatePair);
  CPPUNIT_TEST(testAppendCharDetach);
  CPPUNIT_TEST(testAppendStringDetach);
  CPPUNIT_TEST(testToInt);
  CPPUNIT_TEST(testFromInt);
  CPPUNIT_TEST(testSubstr);
  CPPUNIT_TEST(testNewline);
  CPPUNIT_TEST(testUpper);
  CPPUNIT_TEST(testEncodeNonLatin1);
  CPPUNIT_TEST(testEncodeEmpty);
  CPPUNIT_TEST(testEncodeNonBMP);
  CPPUNIT_TEST(testIterator);
  CPPUNIT_TEST(testInvalidUTF8);
  CPPUNIT_TEST_SUITE_END();

public:

  void testString()
  {
    String s = "taglib string";
    ByteVector v = "taglib string";
    CPPUNIT_ASSERT(v == s.data(String::Latin1));

    char str[] = "taglib string";
    CPPUNIT_ASSERT(strcmp(s.toCString(), str) == 0);
    CPPUNIT_ASSERT(s == "taglib string");
    CPPUNIT_ASSERT(s != "taglib STRING");
    CPPUNIT_ASSERT(s != "taglib");
    CPPUNIT_ASSERT(s != "taglib string taglib");
    CPPUNIT_ASSERT(s == L"taglib string");
    CPPUNIT_ASSERT(s != L"taglib STRING");
    CPPUNIT_ASSERT(s != L"taglib");
    CPPUNIT_ASSERT(s != L"taglib string taglib");

    s.clear();
    CPPUNIT_ASSERT(s.isEmpty());

    String unicode("José Carlos", String::UTF8);
    CPPUNIT_ASSERT(strcmp(unicode.toCString(), "Jos\xe9 Carlos") == 0);

    String latin = "Jos\xe9 Carlos";
    CPPUNIT_ASSERT(strcmp(latin.toCString(true), "José Carlos") == 0);

    String c = "1";
    CPPUNIT_ASSERT(c == L"1");

    c = L'\u4E00';
    CPPUNIT_ASSERT(c == L"\u4E00");

    String unicode2(unicode.to8Bit(true), String::UTF8);
    CPPUNIT_ASSERT(unicode == unicode2);

    String unicode3(L"\u65E5\u672C\u8A9E");
    CPPUNIT_ASSERT(*(unicode3.toCWString() + 1) == L'\u672C');

    constexpr wchar_t wcSystemOrder[] = {L'\u65E5', L'\u672C', L'\u8A9E', 0};
    constexpr wchar_t wcSwappedOrder[] = {L'\uE565', L'\u2C67', L'\u9E8A', 0};
    const std::wstring wsSystemOrder = L"\u65e5\u672c\u8a9e";
    const std::wstring wsSwappedOrder = L"\ue565\u2c67\u9e8a";
    const bool isLe = Utils::systemByteOrder() == Utils::LittleEndian;

    String unicode4(isLe ? wcSwappedOrder : wcSystemOrder, String::UTF16BE);
    CPPUNIT_ASSERT(unicode4[1] == L'\u672c');

    String unicode5(isLe ? wcSystemOrder : wcSwappedOrder, String::UTF16LE);
    CPPUNIT_ASSERT(unicode5[1] == L'\u672c');

    String unicode6(isLe ? wsSwappedOrder : wsSystemOrder, String::UTF16BE);
    CPPUNIT_ASSERT(unicode6[1] == L'\u672c');

    String unicode7(isLe ? wsSystemOrder : wsSwappedOrder, String::UTF16LE);
    CPPUNIT_ASSERT(unicode7[1] == L'\u672c');

    CPPUNIT_ASSERT(String("  foo  ").stripWhiteSpace() == String("foo"));
    CPPUNIT_ASSERT(String("foo    ").stripWhiteSpace() == String("foo"));
    CPPUNIT_ASSERT(String("    foo").stripWhiteSpace() == String("foo"));
    CPPUNIT_ASSERT(String("foo").stripWhiteSpace() == String("foo"));
    CPPUNIT_ASSERT(String("f o o").stripWhiteSpace() == String("f o o"));
    CPPUNIT_ASSERT(String(" f o o ").stripWhiteSpace() == String("f o o"));

    CPPUNIT_ASSERT(memcmp(String("foo").data(String::Latin1).data(), "foo", 3) == 0);
    CPPUNIT_ASSERT(memcmp(String("f").data(String::Latin1).data(), "f", 1) == 0);
  }

  void testUTF16Encode()
  {
    String a("foo");
    ByteVector b("\0f\0o\0o", 6);
    ByteVector c("f\0o\0o\0", 6);
    ByteVector d("\377\376f\0o\0o\0", 8);
    CPPUNIT_ASSERT(a.data(String::UTF16BE) != a.data(String::UTF16LE));
    CPPUNIT_ASSERT(b == a.data(String::UTF16BE));
    CPPUNIT_ASSERT(c == a.data(String::UTF16LE));
    CPPUNIT_ASSERT_EQUAL(d, a.data(String::UTF16));
  }

  void testUTF16Decode()
  {
    String a("foo");
    ByteVector b("\0f\0o\0o", 6);
    ByteVector c("f\0o\0o\0", 6);
    ByteVector d("\377\376f\0o\0o\0", 8);
    CPPUNIT_ASSERT_EQUAL(a, String(b, String::UTF16BE));
    CPPUNIT_ASSERT_EQUAL(a, String(c, String::UTF16LE));
    CPPUNIT_ASSERT_EQUAL(a, String(d, String::UTF16));
  }

  // this test is expected to print "TagLib: String::prepare() -
  // Invalid UTF16 string." on the console 3 times
  void testUTF16DecodeInvalidBOM()
  {
    ByteVector b(" ", 1);
    ByteVector c("  ", 2);
    ByteVector d("  \0f\0o\0o", 8);
    CPPUNIT_ASSERT_EQUAL(String(), String(b, String::UTF16));
    CPPUNIT_ASSERT_EQUAL(String(), String(c, String::UTF16));
    CPPUNIT_ASSERT_EQUAL(String(), String(d, String::UTF16));
  }

  void testUTF16DecodeEmptyWithBOM()
  {
    ByteVector a("\377\376", 2);
    ByteVector b("\376\377", 2);
    CPPUNIT_ASSERT_EQUAL(String(), String(a, String::UTF16));
    CPPUNIT_ASSERT_EQUAL(String(), String(b, String::UTF16));
  }

  void testSurrogatePair()
  {
    // Make sure that a surrogate pair is converted into single UTF-8 char
    // and vice versa.

    const ByteVector v1("\xff\xfe\x42\xd8\xb7\xdf\xce\x91\x4b\x5c");
    const ByteVector v2("\xf0\xa0\xae\xb7\xe9\x87\x8e\xe5\xb1\x8b");

    const String s1(v1, String::UTF16);
    CPPUNIT_ASSERT_EQUAL(s1.data(String::UTF8), v2);

    const String s2(v2, String::UTF8);
    CPPUNIT_ASSERT_EQUAL(s2.data(String::UTF16), v1);

    const ByteVector v3("\xfe\xff\xd8\x01\x30\x42");
    CPPUNIT_ASSERT(String(v3, String::UTF16).data(String::UTF8).isEmpty());

    const ByteVector v4("\xfe\xff\x30\x42\xdc\x01");
    CPPUNIT_ASSERT(String(v4, String::UTF16).data(String::UTF8).isEmpty());

    const ByteVector v5("\xfe\xff\xdc\x01\xd8\x01");
    CPPUNIT_ASSERT(String(v5, String::UTF16).data(String::UTF8).isEmpty());
  }

  void testAppendStringDetach()
  {
    String a("a");
    String b = a;
    a += "b";
    CPPUNIT_ASSERT_EQUAL(String("ab"), a);
    CPPUNIT_ASSERT_EQUAL(String("a"), b);
  }

  void testAppendCharDetach()
  {
    String a("a");
    String b = a;
    a += 'b';
    CPPUNIT_ASSERT_EQUAL(String("ab"), a);
    CPPUNIT_ASSERT_EQUAL(String("a"), b);
  }

  void testRfind()
  {
    CPPUNIT_ASSERT_EQUAL(-1, String("foo.bar").rfind(".", 0));
    CPPUNIT_ASSERT_EQUAL(-1, String("foo.bar").rfind(".", 1));
    CPPUNIT_ASSERT_EQUAL(-1, String("foo.bar").rfind(".", 2));
    CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 3));
    CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 4));
    CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 5));
    CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 6));
    CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 7));
    CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind("."));
  }

  void testToInt()
  {
    bool ok;
    CPPUNIT_ASSERT_EQUAL(String("123").toInt(&ok), 123);
    CPPUNIT_ASSERT_EQUAL(ok, true);

    CPPUNIT_ASSERT_EQUAL(String("-123").toInt(&ok), -123);
    CPPUNIT_ASSERT_EQUAL(ok, true);

    CPPUNIT_ASSERT_EQUAL(String("abc").toInt(&ok), 0);
    CPPUNIT_ASSERT_EQUAL(ok, false);

    CPPUNIT_ASSERT_EQUAL(String("1x").toInt(&ok), 1);
    CPPUNIT_ASSERT_EQUAL(ok, false);

    CPPUNIT_ASSERT_EQUAL(String("").toInt(&ok), 0);
    CPPUNIT_ASSERT_EQUAL(ok, false);

    CPPUNIT_ASSERT_EQUAL(String("-").toInt(&ok), 0);
    CPPUNIT_ASSERT_EQUAL(ok, false);

    CPPUNIT_ASSERT_EQUAL(String("123").toInt(), 123);
    CPPUNIT_ASSERT_EQUAL(String("-123").toInt(), -123);
    CPPUNIT_ASSERT_EQUAL(String("123aa").toInt(), 123);
    CPPUNIT_ASSERT_EQUAL(String("-123aa").toInt(), -123);

    CPPUNIT_ASSERT_EQUAL(String("0000").toInt(), 0);
    CPPUNIT_ASSERT_EQUAL(String("0001").toInt(), 1);

    String("2147483648").toInt(&ok);
    CPPUNIT_ASSERT_EQUAL(ok, false);

    String("-2147483649").toInt(&ok);
    CPPUNIT_ASSERT_EQUAL(ok, false);
  }

  void testFromInt()
  {
    CPPUNIT_ASSERT_EQUAL(String::number(0), String("0"));
    CPPUNIT_ASSERT_EQUAL(String::number(12345678), String("12345678"));
    CPPUNIT_ASSERT_EQUAL(String::number(-12345678), String("-12345678"));
  }

  void testSubstr()
  {
    CPPUNIT_ASSERT_EQUAL(String("01"), String("0123456").substr(0, 2));
    CPPUNIT_ASSERT_EQUAL(String("12"), String("0123456").substr(1, 2));
    CPPUNIT_ASSERT_EQUAL(String("123456"), String("0123456").substr(1, 200));
    CPPUNIT_ASSERT_EQUAL(String("0123456"), String("0123456").substr(0, 7));
    CPPUNIT_ASSERT_EQUAL(String("0123456"), String("0123456").substr(0, 200));
  }

  void testNewline()
  {
    ByteVector cr("abc\x0dxyz", 7);
    ByteVector lf("abc\x0axyz", 7);
    ByteVector crlf("abc\x0d\x0axyz", 8);

    CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(7), String(cr).size());
    CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(7), String(lf).size());
    CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(8), String(crlf).size());

    CPPUNIT_ASSERT(L'\x0d' == String(cr)[3]);
    CPPUNIT_ASSERT(L'\x0a' == String(lf)[3]);
    CPPUNIT_ASSERT(L'\x0d' == String(crlf)[3]);
    CPPUNIT_ASSERT(L'\x0a' == String(crlf)[4]);
  }

  void testUpper()
  {
    String s1 = "tagLIB 012 strING";
    String s2 = s1.upper();
    CPPUNIT_ASSERT_EQUAL(String("tagLIB 012 strING"), s1);
    CPPUNIT_ASSERT_EQUAL(String("TAGLIB 012 STRING"), s2);
  }

  void testEncodeNonLatin1()
  {
    const String jpn(L"\u65E5\u672C\u8A9E");
    CPPUNIT_ASSERT_EQUAL(ByteVector("\xE5\x2C\x9E"), jpn.data(String::Latin1));
    CPPUNIT_ASSERT_EQUAL(ByteVector("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E"), jpn.data(String::UTF8));
    CPPUNIT_ASSERT_EQUAL(ByteVector("\xFF\xFE\xE5\x65\x2C\x67\x9E\x8A"), jpn.data(String::UTF16));
    CPPUNIT_ASSERT_EQUAL(ByteVector("\xE5\x65\x2C\x67\x9E\x8A"), jpn.data(String::UTF16LE));
    CPPUNIT_ASSERT_EQUAL(ByteVector("\x65\xE5\x67\x2C\x8A\x9E"), jpn.data(String::UTF16BE));
    CPPUNIT_ASSERT_EQUAL(std::string("\xE5\x2C\x9E"), jpn.to8Bit(false));
    CPPUNIT_ASSERT_EQUAL(std::string("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E"), jpn.to8Bit(true));
  }

  void testEncodeEmpty()
  {
    const String empty;
    CPPUNIT_ASSERT(empty.data(String::Latin1).isEmpty());
    CPPUNIT_ASSERT(empty.data(String::UTF8).isEmpty());
    CPPUNIT_ASSERT_EQUAL(ByteVector("\xFF\xFE"), empty.data(String::UTF16));
    CPPUNIT_ASSERT(empty.data(String::UTF16LE).isEmpty());
    CPPUNIT_ASSERT(empty.data(String::UTF16BE).isEmpty());
    CPPUNIT_ASSERT(empty.to8Bit(false).empty());
    CPPUNIT_ASSERT(empty.to8Bit(true).empty());
  }

  void testEncodeNonBMP()
  {
    const ByteVector a("\xFF\xFE\x3C\xD8\x50\xDD\x40\xD8\xF5\xDC\x3C\xD8\x00\xDE", 14);
    const ByteVector b("\xF0\x9F\x85\x90\xF0\xA0\x83\xB5\xF0\x9F\x88\x80");
    CPPUNIT_ASSERT_EQUAL(b, String(a, String::UTF16).data(String::UTF8));
  }

  void testIterator()
  {
    String s1 = "taglib string";
    String s2 = s1;

    String::Iterator it1 = s1.begin();
    String::Iterator it2 = s2.begin();

    CPPUNIT_ASSERT(L't' == *it1);
    CPPUNIT_ASSERT(L't' == *it2);

    std::advance(it1, 4);
    std::advance(it2, 4);
    *it2 = L'I';
    CPPUNIT_ASSERT(L'i' == *it1);
    CPPUNIT_ASSERT(L'I' == *it2);
  }

  void testInvalidUTF8()
  {
    CPPUNIT_ASSERT_EQUAL(String("/"), String(ByteVector("\x2F"), String::UTF8));
    CPPUNIT_ASSERT(String(ByteVector("\xC0\xAF"), String::UTF8).isEmpty());
    CPPUNIT_ASSERT(String(ByteVector("\xE0\x80\xAF"), String::UTF8).isEmpty());
    CPPUNIT_ASSERT(String(ByteVector("\xF0\x80\x80\xAF"), String::UTF8).isEmpty());

    CPPUNIT_ASSERT(String(ByteVector("\xF8\x80\x80\x80\x80"), String::UTF8).isEmpty());
    CPPUNIT_ASSERT(String(ByteVector("\xFC\x80\x80\x80\x80\x80"), String::UTF8).isEmpty());

    CPPUNIT_ASSERT(String(ByteVector("\xC2"), String::UTF8).isEmpty());
    CPPUNIT_ASSERT(String(ByteVector("\xE0\x80"), String::UTF8).isEmpty());
    CPPUNIT_ASSERT(String(ByteVector("\xF0\x80\x80"), String::UTF8).isEmpty());
    CPPUNIT_ASSERT(String(ByteVector("\xF8\x80\x80\x80"), String::UTF8).isEmpty());
    CPPUNIT_ASSERT(String(ByteVector("\xFC\x80\x80\x80\x80"), String::UTF8).isEmpty());

    CPPUNIT_ASSERT(String('\x80', String::UTF8).isEmpty());

    CPPUNIT_ASSERT(String(ByteVector("\xED\xA0\x80\xED\xB0\x80"), String::UTF8).isEmpty());
    CPPUNIT_ASSERT(String(ByteVector("\xED\xB0\x80\xED\xA0\x80"), String::UTF8).isEmpty());
  }

};

CPPUNIT_TEST_SUITE_REGISTRATION(TestString);
