#include <yandex/maps/wiki/common/natural_sort.h>
#include <maps/libs/common/include/exception.h>

#include <unicode/coll.h>
#include <unicode/regex.h>
#include <unicode/unistr.h>

#include <string>

namespace maps::wiki::common {

namespace {

icu::RegexPattern compilePattern()
{
    UErrorCode status = U_ZERO_ERROR;
    std::unique_ptr<icu::RegexPattern> pattern(
        icu::RegexPattern::compile("([0-9]+)|([^0-9]+)", 0, status));
    REQUIRE(U_SUCCESS(status), "bad regex: " << u_errorName(status));
    return *pattern;
}

const icu::RegexPattern REGEX = compilePattern();

std::unique_ptr<icu::Collator> createCollator()
{
    UErrorCode status = U_ZERO_ERROR;
    std::unique_ptr<icu::Collator> collator(icu::Collator::createInstance(icu::Locale::getUS(), status));
    REQUIRE(U_SUCCESS(status), "libicu Collator creation failed: " << u_errorName(status));
    collator->setStrength(icu::Collator::PRIMARY);
    return collator;
}

const std::unique_ptr<icu::Collator> COLLATOR = createCollator();

icu::UnicodeString pad(icu::UnicodeString s, size_t len)
{
    s.padLeading(len, '0');
    return s;
}

bool compare(
    const icu::UnicodeString& l1,
    const icu::UnicodeString& l2,
    const icu::UnicodeString& r1,
    const icu::UnicodeString& r2)
{
    if (!l2.isEmpty() || !r2.isEmpty()) {
        UErrorCode status = U_ZERO_ERROR;
        return COLLATOR->compare(l2, r2, status) == UCOL_LESS;
    }
    if (l1.isEmpty() && !r1.isEmpty()) {
        return true;
    }
    if (!l1.isEmpty() && r1.isEmpty()) {
        return false;
    }
    if (!l1.isEmpty() && !r1.isEmpty()) {
        size_t padLength = std::max(l1.length(), r1.length()) + 1;
        return pad(l1, padLength) < pad(r1, padLength);
    }
    return false;
}

} // namespace

bool natural_sort::operator()(const std::string& lhs, const std::string& rhs) const
{
    auto lhsUnicodeStr = icu::UnicodeString::fromUTF8(lhs);
    auto rhsUnicodeStr = icu::UnicodeString::fromUTF8(rhs);

    UErrorCode status = U_ZERO_ERROR;
    std::unique_ptr<icu::RegexMatcher> lhsMatcher(REGEX.matcher(lhsUnicodeStr, status));
    REQUIRE(U_SUCCESS(status), "Failed to create matcher for string: " << lhs);

    std::unique_ptr<icu::RegexMatcher> rhsMatcher(REGEX.matcher(rhsUnicodeStr, status));
    REQUIRE(U_SUCCESS(status), "Failed to create matcher for string: " << rhs);

    bool lhsMatchResult = false;
    bool rhsMatchResult = false;

    while (true) {
        lhsMatchResult = lhsMatcher->find();
        rhsMatchResult = rhsMatcher->find();

        if (!lhsMatchResult || !rhsMatchResult) {
            break;
        }

        auto lhsGroup1 = lhsMatcher->group(1, status);
        REQUIRE(U_SUCCESS(status), "Failed to get group 1 from string: " << lhs);

        auto lhsGroup2 = lhsMatcher->group(2, status);
        REQUIRE(U_SUCCESS(status), "Failed to get group 2 from string: " << lhs);

        auto rhsGroup1 = rhsMatcher->group(1, status);
        REQUIRE(U_SUCCESS(status), "Failed to get group 1 from string: " << rhs);

        auto rhsGroup2 = rhsMatcher->group(2, status);
        REQUIRE(U_SUCCESS(status), "Failed to get group 2 from string: " << rhs);

        if (lhsGroup1 == rhsGroup1 && lhsGroup2 == rhsGroup2) {
            continue;
        }

        return compare(lhsGroup1, lhsGroup2, rhsGroup1, rhsGroup2);
    }

    return !lhsMatchResult && rhsMatchResult;
}

} // namespace maps::wiki::common
