//
// Copyright 2024 Eugene Gershnik
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://github.com/gershnik/sys_string/blob/master/LICENSE
//
// _🪦🐮🐌_

#include <sys_string/grapheme_view.h>
#include <sys_string/utf_view.h>
#include <sys_string/sys_string.h>

#include <doctest/doctest.h>

#include <vector>
#include <string>
#include <string_view>
#include <forward_list>
#include <ostream>

using namespace sysstr;
using namespace std::literals;

#define FILINE __FILE__, __LINE__

#if __cpp_lib_ranges >= 202106L

static_assert(std::invocable<const sysstr::graphemes_func &, std::string>);

#endif

namespace {

    template<std::ranges::forward_range Range>
    void check_graphemes_range(Range && range, const std::initializer_list<std::u32string_view> & expected,
                               const char * file, int line)
    {
        std::vector<std::u32string> result;
        #if __cpp_lib_ranges >= 202202L
            std::ranges::transform(std::forward<Range>(range) | graphemes, std::back_inserter(result), [](auto range) {
                return std::u32string(range.begin(), range.end());
            });
        #elif __cpp_lib_ranges >= 202106L
            std::ranges::transform(graphemes(std::forward<Range>(range)), std::back_inserter(result), [](auto range) {
                return std::u32string(range.begin(), range.end());
            });
        #else
            for (auto res:  grapheme_view(range)) {
                result.push_back(std::u32string(res.begin(), res.end()));
            }
        #endif
        INFO("source: ", std::string(file), std::string(":"), line);
        bool res = std::ranges::equal(result, expected);
        CHECK(res);
        
        
    }
    
    template<std::ranges::forward_range Range>
    void check_graphemes_reverse_range(Range && range, const std::initializer_list<std::u32string_view> & expected,
                                       const char * file, int line)
    {
        std::vector<std::u32string> rexpected;

        #if !defined(_LIBCPP_VERSION) || SYS_STRING_LIBCPP_AT_LEAST(160000)
            for(auto & ex: expected | std::views::reverse)
                rexpected.emplace_back(ex.rend().base(), ex.rbegin().base());
        #else
            for(size_t i = expected.size(); i != 0; --i)
                rexpected.push_back(std::u32string(*(expected.begin() + (i - 1))));
        #endif

        std::vector<std::u32string> result;
        #if __cpp_lib_ranges >= 202106L
            auto gr = graphemes(std::forward<Range>(range));
            std::ranges::transform(std::ranges::subrange(gr.rbegin(), gr.rend()), std::back_inserter(result), [](auto range) {
                return std::u32string(range.begin(), range.end());
            });
        #else
            auto gr = grapheme_view(range);
            for (auto it = gr.rbegin(); it != gr.rend(); ++it) {
                auto res = *it;
                result.push_back(std::u32string(res.begin(), res.end()));
            }
        #endif
        INFO("source: ", std::string(file), std::string(":"), line);
        bool res = std::ranges::equal(result, rexpected);
        CHECK(res);
    }
    
    template<std::ranges::forward_range Range>
    void check_graphemes_iter(const Range & range, const std::vector<std::u32string> & expected, 
                              const char * file, int line)
    {
        util::grapheme_iterator<std::ranges::iterator_t<const Range>, 
                                std::ranges::sentinel_t<const Range>,
                                util::iter_direction::forward> it(
                                    std::ranges::begin(range),
                                    std::ranges::end(range)
                                );
        std::vector<std::u32string> result;
        #if __cpp_lib_ranges >= 202106L
            std::ranges::transform(it, std::default_sentinel, std::back_inserter(result), [](auto range) {
                return std::u32string(range.begin(), range.end());
            });
        #else
            for (; it != std::default_sentinel; ++it) {
                auto res = *it;
                result.push_back(std::u32string(res.begin(), res.end()));
            }
        #endif
        INFO("source: ", std::string(file), std::string(":"), line);
        bool res = std::ranges::equal(result, expected);
        CHECK(res);
    }
    
    template<std::ranges::forward_range Range>
    void check_graphemes_reverse_iter(const Range & range, const std::vector<std::u32string> & expected,
                                      const char * file, int line)
    {
        util::grapheme_iterator<ranges::reverse_iterator_t<const Range>,
                                ranges::reverse_sentinel_t<const Range>,
                                util::iter_direction::backward> rit(
                                    std::ranges::rbegin(range),
                                    std::ranges::rend(range)
                                );
        std::vector<std::u32string> result;
        #if __cpp_lib_ranges >= 202106L
            std::ranges::transform(rit, std::default_sentinel, std::back_inserter(result), [](auto range) {
                return std::u32string(range.begin(), range.end());
            });
        #else
            for (; rit != std::default_sentinel; ++rit) {
                auto res = *rit;
                result.push_back(std::u32string(res.begin(), res.end()));
            }
        #endif
        INFO("source: ", std::string(file), std::string(":"), line);

        #if !defined(_LIBCPP_VERSION) || SYS_STRING_LIBCPP_AT_LEAST(160000)
            bool res = std::ranges::equal(result, expected | std::views::reverse);
        #else
            std::vector<std::u32string> rexpected;
            for(size_t i = expected.size(); i != 0; --i)
                rexpected.emplace_back(std::u32string(*(expected.begin() + (i - 1))));
            bool res = std::ranges::equal(result, rexpected);
        #endif

        
        CHECK(res);
    }

#if defined(__GNUC__)
    [[gnu::noinline]]
#elif defined(__MSVC__)
    [[msvc::noinline]]
#endif
    void check_graphemes(std::u32string_view src, const std::initializer_list<std::u32string_view> & expected, 
                         const char * file, int line)
    {
        check_graphemes_range(src, expected, file, line);
        check_graphemes_reverse_range(src, expected, file, line);
    }
    
}

TEST_SUITE("grapheme") {

TEST_CASE("boundary") {
    check_graphemes(U""sv, {}, FILINE);
    check_graphemes(std::u32string_view(U"\0\0", 2), {std::u32string(U"\0", 1), std::u32string(U"\0", 1)}, FILINE);
}

TEST_CASE("generated") {

#if SYS_STRING_USE_ICU 
    #if U_ICU_VERSION_MAJOR_NUM < 76
        #include "test_grapheme_data_15.h"
    #elif U_ICU_VERSION_MAJOR_NUM < 78
        #include "test_grapheme_data_16.h"
    #else
        #include "test_grapheme_data.h"
    #endif
#else
    #include "test_grapheme_data.h"
#endif
}

TEST_CASE("iterators") {

    check_graphemes_iter(""s, {}, FILINE);
    check_graphemes_reverse_iter(""s, {}, FILINE);
    check_graphemes_iter("ab"s, {U"a", U"b"}, FILINE);
    check_graphemes_reverse_iter("ab"s, {U"a", U"b"}, FILINE);
}

TEST_CASE("ranges") {

    check_graphemes_range("ab"s, {U"a", U"b"}, FILINE);
    check_graphemes_reverse_range("ab"s, {U"a", U"b"}, FILINE);
    check_graphemes_range(u"ab"s, {U"a", U"b"}, FILINE);
    check_graphemes_reverse_range(u"ab"s, {U"a", U"b"}, FILINE);
    check_graphemes_range(U"ab"s, {U"a", U"b"}, FILINE);
    check_graphemes_reverse_range(U"ab"s, {U"a", U"b"}, FILINE);
    check_graphemes_range(as_utf32("ab"s), {U"a", U"b"}, FILINE);
    check_graphemes_reverse_range(as_utf32("ab"s), {U"a", U"b"}, FILINE);

    check_graphemes_range(std::forward_list<char>{'a', 'b', 'c'}, {U"a", U"b", U"c"}, FILINE);
}

#if __cpp_lib_ranges >= 202106L

TEST_CASE("view interface") {

    auto view = graphemes("abc"s);

    CHECK(view);
    CHECK(!view.empty());
    CHECK(sys_string(view.front()) == S("a"));
    CHECK(sys_string(view.back()) == S("c"));

    CHECK(view.begin() == view.cbegin());
    static_assert(std::is_same_v<decltype(view.end()), std::default_sentinel_t>);
    static_assert(std::is_same_v<decltype(view.cend()), std::default_sentinel_t>);
    CHECK(view.rbegin() == view.crbegin());
    static_assert(std::is_same_v<decltype(view.rend()), std::default_sentinel_t>);
    static_assert(std::is_same_v<decltype(view.crend()), std::default_sentinel_t>);

    auto empty_view = graphemes(""s);
    CHECK(!empty_view);
    CHECK(empty_view.empty());

    auto s = S("xyz");
    auto access = sys_string::char_access(s);
    CHECK(sys_string(graphemes(access).front()) == S("x"));
}

#endif

}
