/*
 * SPDX-FileCopyrightText: 2017-2017 CSSlayer <wengxt@gmail.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include <cstddef>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <fcitx-utils/log.h>
#include "libime/core/segmentgraph.h"
#include "libime/pinyin/pinyincorrectionprofile.h"
#include "libime/pinyin/pinyinencoder.h"
#include "libime/pinyin/shuangpinprofile.h"

using namespace libime;

void dfs(const SegmentGraph &segs,
         const std::vector<std::string> &expectedMatch = {}) {
    FCITX_ASSERT(segs.checkGraph());

    bool checked = false;
    auto callback = [&checked,
                     &expectedMatch](const SegmentGraphBase &segs,
                                     const std::vector<size_t> &path) {
        size_t start = 0;
        std::vector<std::string> result;
        for (auto end : path) {
            result.push_back(std::string(segs.segment(start, end)));
            start = end;
        }
        if (!checked && !expectedMatch.empty()) {
            if (result == expectedMatch) {
                checked = true;
            }
        }
        FCITX_INFO() << result;
        return true;
    };

    segs.dfs(callback);
    FCITX_ASSERT(checked || expectedMatch.empty())
        << segs.data() << " " << expectedMatch;
}

void check(std::string py, PinyinFuzzyFlags flags,
           const std::vector<std::string> &expectedMatch) {
    dfs(PinyinEncoder::parseUserPinyin(std::move(py), flags), expectedMatch);
}

int main() {
    check("wa'nan'''", PinyinFuzzyFlag::None, {"wa", "'", "nan", "'''"});
    check("lvenu", PinyinFuzzyFlag::None, {"lve", "nu"});
    check("woaizuguotiananmen", PinyinFuzzyFlag::None,
          {"wo", "ai", "zu", "guo", "tian", "an", "men"});
    check("wanan", PinyinFuzzyFlag::None, {"wan", "an"});
    check("biiiiiilp", PinyinFuzzyFlag::None, {"bi", "iiiiilp"});
    check("zhm", PinyinFuzzyFlag::None, {"zh", "m"});
    check("zzhzhhzhzh", PinyinFuzzyFlag::None,
          {"z", "zh", "zh", "h", "zh", "zh"});
    check("shuou", PinyinFuzzyFlag::None, {"shu", "ou"});
    check("tanan", PinyinFuzzyFlag::None, {"tan", "an"});
    check("lven", PinyinFuzzyFlag::None, {"lv", "en"});
    check("ananananana", PinyinFuzzyFlag::None,
          {"an", "an", "an", "an", "an", "a"});
    check("wa'nan", PinyinFuzzyFlag::None, {"wa", "'", "nan"});
    check("xian", PinyinFuzzyFlag::None, {"xian"});
    check("xian", PinyinFuzzyFlag::Inner, {"xi", "an"});
    check("xi'an", PinyinFuzzyFlag::Inner, {"xi", "'", "an"});
    check("kuai", PinyinFuzzyFlag::None, {"kuai"});
    check("kuai", PinyinFuzzyFlag::Inner, {"ku", "ai"});
    check("jiaou", PinyinFuzzyFlag::Inner, {"jia", "ou"});
    check("jin'an", PinyinFuzzyFlag::Inner, {"jin", "'", "an"});
    check("qie", PinyinFuzzyFlag::Inner, {"qie"});
    check("qie", PinyinFuzzyFlag::InnerShort, {"qi", "e"});
    check("qi'e", PinyinFuzzyFlag::Inner, {"qi", "'", "e"});
    check("nng", PinyinFuzzyFlag::InnerShort, {"n", "ng"});

    FCITX_ASSERT(PinyinEncoder::stringToSyllables(
                     "niagn", PinyinFuzzyFlags{PinyinFuzzyFlag::L_N,
                                               PinyinFuzzyFlag::IAN_IANG,
                                               PinyinFuzzyFlag::CommonTypo}) ==
                 MatchedPinyinSyllables{
                     {PinyinInitial::N,
                      {{PinyinFinal::IANG, true}, {PinyinFinal::IAN, true}}},
                     {PinyinInitial::L,
                      {{PinyinFinal::IANG, true}, {PinyinFinal::IAN, true}}}});
    FCITX_ASSERT(
        PinyinEncoder::stringToSyllablesWithFuzzyFlags(
            "niagn", nullptr,
            PinyinFuzzyFlags{PinyinFuzzyFlag::L_N, PinyinFuzzyFlag::IAN_IANG,
                             PinyinFuzzyFlag::CommonTypo}) ==
        MatchedPinyinSyllablesWithFuzzyFlags{
            {PinyinInitial::N,
             {{PinyinFinal::IANG,
               PinyinFuzzyFlags{PinyinFuzzyFlag::CommonTypo}},
              {PinyinFinal::IAN, PinyinFuzzyFlags{PinyinFuzzyFlag::CommonTypo,
                                                  PinyinFuzzyFlag::IAN_IANG}}}},
            {PinyinInitial::L,
             {{PinyinFinal::IANG, PinyinFuzzyFlags{PinyinFuzzyFlag::CommonTypo,
                                                   PinyinFuzzyFlag::L_N}},
              {PinyinFinal::IAN, PinyinFuzzyFlags{PinyinFuzzyFlag::CommonTypo,
                                                  PinyinFuzzyFlag::IAN_IANG,
                                                  PinyinFuzzyFlag::L_N}}}}});

    FCITX_ASSERT(PinyinEncoder::stringToSyllables(
                     "n", PinyinFuzzyFlags{PinyinFuzzyFlag::L_N,
                                           PinyinFuzzyFlag::IAN_IANG,
                                           PinyinFuzzyFlag::CommonTypo}) ==
                 MatchedPinyinSyllables{
                     {PinyinInitial::N, {{PinyinFinal::Invalid, false}}},
                     {PinyinInitial::L, {{PinyinFinal::Invalid, true}}}});

    FCITX_ASSERT(PinyinEncoder::stringToSyllables(
                     "r", PinyinFuzzyFlags{PinyinFuzzyFlag::L_R}) ==
                 MatchedPinyinSyllables{
                     {PinyinInitial::R, {{PinyinFinal::Invalid, false}}},
                     {PinyinInitial::L, {{PinyinFinal::Invalid, true}}}});

    FCITX_ASSERT(PinyinEncoder::stringToSyllables(
                     "cuagn", {PinyinFuzzyFlag::C_CH, PinyinFuzzyFlag::UAN_UANG,
                               PinyinFuzzyFlag::CommonTypo}) ==
                 MatchedPinyinSyllables{
                     {PinyinInitial::C, {{PinyinFinal::UAN, true}}},
                     {PinyinInitial::CH,
                      {{PinyinFinal::UAN, true}, {PinyinFinal::UANG, true}}}});

    FCITX_ASSERT(PinyinEncoder::stringToSyllables(
                     "e", PinyinFuzzyFlags{PinyinFuzzyFlag::PartialFinal}) ==
                 MatchedPinyinSyllables{{PinyinInitial::Zero,
                                         {{PinyinFinal::E, false},
                                          {PinyinFinal::EI, true},
                                          {PinyinFinal::EN, true},
                                          {PinyinFinal::ENG, true},
                                          {PinyinFinal::ER, true}}}});
    FCITX_ASSERT(PinyinEncoder::stringToSyllables(
                     "din", PinyinFuzzyFlags{PinyinFuzzyFlag::CommonTypo}) ==
                 MatchedPinyinSyllables{
                     {PinyinInitial::D,
                      {{PinyinFinal::ING, true}, {PinyinFinal::IN, false}}}});

    for (const auto &syl : PinyinEncoder::stringToSyllables(
             "e", PinyinFuzzyFlags{PinyinFuzzyFlag::PartialFinal})) {
        for (auto f : syl.second) {
            FCITX_INFO() << PinyinSyllable(syl.first, f.first).toString();
        }
    }
    {
        // xiang o n
        check("xian", PinyinFuzzyFlag::None, {"xian"});

        // xian gong
        check("xiangong", PinyinFuzzyFlag::None, {"xian", "gong"});

        // xiang o n
        check("xiangon", PinyinFuzzyFlag::None, {"xiang", "o", "n"});

        // yan d
        check("yand", PinyinFuzzyFlag::None, {"yan", "d"});
        // hua c o
        check("huaco", PinyinFuzzyFlag::None, {"hua", "c", "o"});
        // hua c o
        check("xion", PinyinFuzzyFlag::None, {"xi", "o", "n"});
        check("xiana", PinyinFuzzyFlag::None, {"xian", "a"});

        check("Nihao", PinyinFuzzyFlag::None, {"Ni", "hao"});
    }

    {
        auto graph = PinyinEncoder::parseUserPinyin("", PinyinFuzzyFlag::None);
        {
            auto graph2 =
                PinyinEncoder::parseUserPinyin("z", PinyinFuzzyFlag::None);
            graph.merge(graph2);
        }
        dfs(graph, {"z"});
        {
            auto graph2 =
                PinyinEncoder::parseUserPinyin("zn", PinyinFuzzyFlag::None);
            graph.merge(graph2);
        }
        dfs(graph, {"z", "n"});
        {
            auto graph2 =
                PinyinEncoder::parseUserPinyin("z", PinyinFuzzyFlag::None);
            graph.merge(graph2);
        }
        dfs(graph, {"z"});
    }
    {
        auto result =
            PinyinEncoder::stringToSyllables("z", PinyinFuzzyFlag::None);
        for (const auto &p : result) {
            for (auto f : p.second) {
                FCITX_INFO() << PinyinEncoder::initialToString(p.first)
                             << PinyinEncoder::finalToString(f.first);
            }
        }
    }

    bool hasException = false;
    try {
        PinyinEncoder::encodeFullPinyin("lue");
    } catch (const std::invalid_argument &) {
        hasException = true;
    }
    FCITX_ASSERT(hasException);

    {
        auto result = PinyinEncoder::encodeFullPinyinWithFlags(
            "lue", PinyinFuzzyFlag::VE_UE);
        FCITX_ASSERT(PinyinEncoder::decodeFullPinyin(result) == "lve");
    }
    {
        auto result = PinyinEncoder::encodeOneUserPinyin("nihao");
        FCITX_ASSERT(PinyinEncoder::decodeFullPinyin(result) == "ni'hao");
    }
    {
        auto result = PinyinEncoder::encodeOneUserPinyin("xian");
        FCITX_ASSERT(PinyinEncoder::decodeFullPinyin(result) == "xian");
    }
    {
        auto result = PinyinEncoder::encodeOneUserPinyin("xi'an");
        FCITX_ASSERT(PinyinEncoder::decodeFullPinyin(result) == "xi'an");
    }
    {
        auto result = PinyinEncoder::encodeOneUserPinyin("nh");
        FCITX_ASSERT(PinyinEncoder::decodeFullPinyin(result) == "n'h");
    }
    {
        auto result = PinyinEncoder::encodeOneUserPinyin("nfi");
        FCITX_INFO() << PinyinEncoder::decodeFullPinyin(result);
    }
    {
        auto result = PinyinEncoder::initialFinalToPinyinString(
            libime::PinyinInitial::N, libime::PinyinFinal::VE);
        FCITX_ASSERT(result == "nüe");
    }
    {
        auto result = PinyinEncoder::initialFinalToPinyinString(
            libime::PinyinInitial::L, libime::PinyinFinal::V);
        FCITX_ASSERT(result == "lü");
    }

    check("zhunipingan", PinyinFuzzyFlag::Inner, {"zhu", "ni", "ping", "an"});
    check("zhunipingan", PinyinFuzzyFlag::Inner, {"zhu", "ni", "pin", "gan"});
    check("zhuna", PinyinFuzzyFlag::Inner, {"zhu", "na"});
    check("zhuna", PinyinFuzzyFlag::Inner, {"zhun", "a"});

    check("sangeren", PinyinFuzzyFlag::Inner, {"san", "ge", "ren"});

    check("jiao", PinyinFuzzyFlag::Inner, {"jiao"});
    check("jiao", PinyinFuzzyFlag::Inner, {"ji", "ao"});
    check("jiao", PinyinFuzzyFlag::Inner, {"jia", "o"});

    {
        PinyinCorrectionProfile profile(BuiltinPinyinCorrectionProfile::Qwerty);
        auto graph = PinyinEncoder::parseUserPinyin(
            "zhyi", &profile, PinyinFuzzyFlag::Correction);
        dfs(graph, {"zhyi"});

        graph = PinyinEncoder::parseUserPinyin("xianguaoyige", &profile,
                                               PinyinFuzzyFlag::Correction);
        dfs(graph, {"xiang", "uao", "yi", "ge"});
        dfs(graph, {"xian", "gu", "ao", "yi", "ge"});

        graph = PinyinEncoder::parseUserPinyin("suang", &profile,
                                               PinyinFuzzyFlag::Correction);
        dfs(graph, {"suan", "g"});
    }

    {
        ShuangpinProfile sp(ShuangpinBuiltinProfile::Xiaohe);
        PinyinCorrectionProfile profile(BuiltinPinyinCorrectionProfile::Qwerty);

        auto graph = PinyinEncoder::parseUserShuangpin(
            "xnqiee", sp, PinyinFuzzyFlag::Correction);
        dfs(graph, {"xn", "qi", "ee"});
        graph = PinyinEncoder::parseUserShuangpin("znwiee", sp,
                                                  PinyinFuzzyFlag::Correction);
        dfs(graph, {"z", "nw", "ie", "e"});

        graph = PinyinEncoder::parseUserShuangpin("wokeyityxxboli", sp,
                                                  PinyinFuzzyFlag::Correction);
        dfs(graph, {"wo", "ke", "yi", "ty", "xx", "bo", "li"});
        graph = PinyinEncoder::parseUserShuangpin("wokeyityxzboli", sp,
                                                  PinyinFuzzyFlag::Correction);
        dfs(graph, {"wo", "ke", "yi", "ty", "x", "z", "bo", "li"});
    }

    return 0;
}
