//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2021 James D. Mitchell + Maria Tsalakou
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include <cstddef>   // for size_t
#include <iostream>  // for to_string
#include <string>    // for to_string
#include <unordered_set>

#include "bench-main.hpp"  // for CATCH_CONFIG_ENABLE_BENCHMARKING
#include "catch.hpp"       // for TEST_CASE, BENCHMARK, REQUIRE

#include "libsemigroups/kambites.hpp"      // for Kambites
#include "libsemigroups/knuth-bendix.hpp"  // for KnuthBendix
#include "libsemigroups/knuth-bendix.hpp"  // for random_string
#include "libsemigroups/siso.hpp"          // for cbegin_sislo

// TODO:
// - include number of recursive calls to wp-prefix.

namespace libsemigroups {
  using detail::MultiStringView;
  using detail::power_string;
  using detail::random_string;
  using detail::random_strings;
  using fpsemigroup::Kambites;

  namespace {
    std::string zip(std::vector<std::string> const& x,
                    std::vector<std::string> const& y) {
      LIBSEMIGROUPS_ASSERT(x.size() == y.size());
      std::string result = "";
      for (size_t i = 0; i < x.size(); ++i) {
        result += x[i];
        result += y[i];
      }
      return result;
    }

    // Returns {u_1, u_2, ..., u_{exp}} where u_i are chosen with uniform
    // distribution in {s, t}
    std::vector<std::string> random_sequence(std::string const& s,
                                             std::string const& t,
                                             size_t             exp) {
      static std::random_device              rd;
      static std::mt19937                    generator(rd());
      static std::uniform_int_distribution<> distribution(0, 1);
      std::vector<std::string>               result;
      while (exp > 0) {
        switch (distribution(generator)) {
          case 0: {
            result.push_back(s);
            break;
          }
          case 1: {
            result.push_back(t);
            break;
          }
        }
        exp--;
      }
      return result;
    }

  }  // namespace

  ////////////////////////////////////////////////////////////////////////
  // Benchmark checking C(4) or higher - Example A.1
  ////////////////////////////////////////////////////////////////////////

  std::pair<std::string, std::string> example1(size_t N) {
    std::string lhs, rhs;
    for (size_t b = 1; b <= N; ++b) {
      lhs += "a" + std::string(b, 'b');
    }
    for (size_t b = N + 1; b <= 2 * N; ++b) {
      rhs += "a" + std::string(b, 'b');
    }
    return std::make_pair(lhs, rhs);
  }

  template <typename T>
  void c4_ex_A1() {
    for (size_t N = 100; N <= 1000; N += 25) {
      size_t M;
      {
        std::string lhs, rhs;
        std::tie(lhs, rhs) = example1(N);
        M                  = lhs.size() + rhs.size();
      }
      BENCHMARK_ADVANCED("Length of relation words = " + std::to_string(M))
      (Catch::Benchmark::Chronometer meter) {
        std::string lhs, rhs;
        std::tie(lhs, rhs) = example1(N);
        Kambites<T> k;
        k.set_alphabet("ab");
        k.add_rule(lhs, rhs);
        meter.measure([&k]() { REQUIRE(k.small_overlap_class() >= 4); });
      };  // NOLINT(readability/braces)
    }
  }

  TEST_CASE("Example A1: determining C(4) (std::string)", "[quick][000]", ) {
    c4_ex_A1<std::string>();
  }

  TEST_CASE("Example A1: determining C(4) (MultiStringView)",
            "[quick][001]", ) {
    c4_ex_A1<MultiStringView>();
  }

  TEST_CASE("Example A1: KnuthBendix", "[quick][002]", ) {
    auto rg = ReportGuard(false);
    for (size_t N = 100; N <= 1000; N += 25) {
      size_t const M = N * (2 * N + 3);
      std::string  lhs, rhs;
      std::tie(lhs, rhs) = example1(N);
      BENCHMARK("Length of relation words = " + std::to_string(M)) {
        fpsemigroup::KnuthBendix k;
        k.set_alphabet("ab");
        k.add_rule(lhs, rhs);
        k.run();
        return k.confluent();
      };  // NOLINT(readability/braces)
    }
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark checking C(4) or higher - Example A.2
  ////////////////////////////////////////////////////////////////////////

  // <a, b, c | a(bc) ^ k a = a (cb) ^ l a>

  template <typename T>
  void c4_ex_A2() {
    for (size_t m = 5000; m < 500000; m += 20000) {
      BENCHMARK_ADVANCED("Length of relation words = "
                         + std::to_string(4 * m + 4))
      (Catch::Benchmark::Chronometer meter) {
        std::string lhs = "a" + power_string("bc", m) + "a";
        std::string rhs = "a" + power_string("cb", m) + "a";
        meter.measure([&lhs, &rhs]() {
          Kambites<T> k;
          k.set_alphabet("abc");
          k.add_rule(lhs, rhs);
          REQUIRE(k.small_overlap_class() >= 4);
        });
      };  // NOLINT(readability/braces)
    }
  }

  TEST_CASE("Example A2: determining C(4) (std::string)", "[quick][003]", ) {
    c4_ex_A2<std::string>();
  }

  TEST_CASE("Example A2: determining C(4) (MultiStringView)",
            "[quick][004]", ) {
    c4_ex_A2<MultiStringView>();
  }

  TEST_CASE("Example A2: KnuthBendix", "[quick][005]", ) {
    auto rg = ReportGuard(false);
    for (size_t m = 5000; m < 500000; m += 20000) {
      std::string lhs = "a" + power_string("bc", m) + "a";
      std::string rhs = "a" + power_string("cb", m) + "a";
      BENCHMARK("Length of relation words = " + std::to_string(4 * m + 4)) {
        fpsemigroup::KnuthBendix k;
        k.set_alphabet("abc");
        k.add_rule(lhs, rhs);
        k.run();
        return k.confluent();
      };
    }
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark checking small overlap condition - Example A.3
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void c4_ex_A3(size_t first, size_t last, size_t step) {
    auto   rg                                  = ReportGuard(false);
    size_t number_c4                           = 0;
    size_t number_c123                         = 0;
    size_t number_confluent                    = 0;
    size_t number_confluent_after_1_second     = 0;
    size_t number_not_confluent_after_1_second = 0;

    for (size_t m = first; m < last; m += step) {
      auto   rules  = random_strings(std::string("abcdefgh"), 8, 0, m);
      size_t length = std::accumulate(
          rules.cbegin(),
          rules.cend(),
          size_t(0),
          [](size_t val, std::string const& s) { return val + s.size(); });
      bool counted = false;  // each benchmark is invoked multiple times, but we
                             // only want to count once!
      BENCHMARK("(Kambites) length = " + std::to_string(length)) {
        Kambites<T> k;
        k.set_alphabet("abcdefgh");
        k.add_rule(rules[0], rules[1]);
        k.add_rule(rules[2], rules[3]);
        k.add_rule(rules[4], rules[5]);
        k.add_rule(rules[6], rules[7]);
        if (k.small_overlap_class() >= 4 && !counted) {
          number_c4++;
        } else if (!counted) {
          number_c123++;
        }
        counted = true;
        return k.small_overlap_class();
      };
      fpsemigroup::KnuthBendix k;
      k.set_alphabet("abcdefgh");
      k.add_rule(rules[0], rules[1]);
      k.add_rule(rules[2], rules[3]);
      k.add_rule(rules[4], rules[5]);
      k.add_rule(rules[6], rules[7]);
      if (k.confluent()) {
        std::cout << std::endl << "Presentation is confluent!" << std::endl;
        number_confluent++;
        BENCHMARK("(KnuthBendix) length = " + std::to_string(length)) {
          fpsemigroup::KnuthBendix k;
          k.set_alphabet("abcdefgh");
          k.add_rule(rules[0], rules[1]);
          k.add_rule(rules[2], rules[3]);
          k.add_rule(rules[4], rules[5]);
          k.add_rule(rules[6], rules[7]);
          return k.confluent();
        };
      } else {
        k.run_for(std::chrono::seconds(1));
        if (k.confluent()) {
          std::cout << std::endl
                    << "Presentation is confluent (after running KnuthBendix "
                       "for ~1 second)!"
                    << std::endl;
          number_confluent_after_1_second++;
          BENCHMARK("(KnuthBendix) length = " + std::to_string(length)) {
            fpsemigroup::KnuthBendix k;
            k.set_alphabet("abcdefgh");
            k.add_rule(rules[0], rules[1]);
            k.add_rule(rules[2], rules[3]);
            k.add_rule(rules[4], rules[5]);
            k.add_rule(rules[6], rules[7]);
            k.run_for(std::chrono::seconds(1));
            return k.confluent();
          };
        } else {
          std::cout
              << std::endl
              << "Presentation is not confluent (after running KnuthBendix "
                 "for ~1 second)!"
              << std::endl;
          number_not_confluent_after_1_second++;
        }
      }
    }
    std::cout << "\n Number of C(4) presentations = " << number_c4 << std::endl;
    std::cout << "Number of C(1,2,3) presentations = " << number_c123
              << std::endl;
    std::cout << "Number of confluent presentations = " << number_confluent
              << std::endl;
    std::cout << "Number confluent after 1 second of KnuthBendix = "
              << number_confluent_after_1_second << std::endl;
    std::cout << "Number non-confluent after 1 second of KnuthBendix = "
              << number_not_confluent_after_1_second << std::endl;
  }

  TEST_CASE("Example A3: determining C(4) (std::string)", "[quick][006]", ) {
    c4_ex_A3<std::string>(3000, 150000, 3675);
  }

  TEST_CASE("Example A3: determining C(4) (MultiStringView)",
            "[quick][007]", ) {
    c4_ex_A3<std::string>(3000, 150000, 3675);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark checking small overlap condition - Example A.5
  ////////////////////////////////////////////////////////////////////////
  // <a, b, c, d | a(bc) ^ m a = a (cb) ^ m  a,  a(bc) ^ m a = b d ^ m b >

  template <typename T>
  void c4_ex_A5() {
    for (size_t m = 5000; m < 250000; m += 10000) {
      BENCHMARK_ADVANCED("Length of relation words = "
                         + std::to_string(7 * m + 8))
      (Catch::Benchmark::Chronometer meter) {
        std::string lhs1 = "a" + power_string("bc", m) + "a";
        std::string rhs1 = "a" + power_string("cb", m) + "a";
        std::string lhs2 = "b" + power_string("d", m) + "b";
        std::string rhs2 = "a" + power_string("bc", m) + "a";
        meter.measure([&lhs1, &rhs1, &lhs2, &rhs2]() {
          Kambites<T> k;
          k.set_alphabet("abcd");
          k.add_rule(lhs1, rhs1);
          k.add_rule(lhs2, rhs2);
          REQUIRE(k.small_overlap_class() >= 4);
        });
      };  // NOLINT(readability/braces)
    }
  }

  TEST_CASE("Example A5: determining C(4) (std::string)", "[quick][008]", ) {
    c4_ex_A5<std::string>();
  }

  TEST_CASE("Example A5: determining C(4) (MultiStringView)",
            "[quick][009]", ) {
    c4_ex_A5<MultiStringView>();
  }

  TEST_CASE("Example A5: determining C(4) (KnuthBendix)", "[quick][009]", ) {
    for (size_t m = 5000; m < 250000; m += 10000) {
      std::string lhs1 = "a" + power_string("bc", m) + "a";
      std::string rhs1 = "a" + power_string("cb", m) + "a";
      std::string lhs2 = "b" + power_string("d", m) + "b";
      std::string rhs2 = "a" + power_string("bc", m) + "a";
      BENCHMARK_ADVANCED("Length = " + std::to_string(7 * m + 8)) {
        fpsemigroup::KnuthBendix k;
        k.set_alphabet("abcd");
        k.add_rule(lhs1, rhs1);
        k.add_rule(lhs2, rhs2);
        REQUIRE(k.confluent());
        return k.confluent();
      };
    }
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark wp-prefix - Example A.1
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void equal_to_ex_A1(size_t m) {
    for (size_t N = 100; N <= 400; N += 8) {
      std::string lhs, rhs;
      std::tie(lhs, rhs) = example1(m);
      Kambites<T> k;
      k.set_alphabet("ab");
      k.add_rule(lhs, rhs);
      REQUIRE(k.small_overlap_class() >= 4);
      auto random = random_strings("ab", N, 0, 4 * N + 4);
      auto u      = zip(random_sequence(lhs, rhs, N), random);
      auto v      = zip(random_sequence(lhs, rhs, N), random);
      REQUIRE(k.small_overlap_class() >= 4);
      BENCHMARK("length = " + std::to_string(u.size() + v.size())) {
        REQUIRE(k.equal_to(u, v));
      };
    }
  }

  TEST_CASE("Example A1_10: equal_to (std::string)", "[quick][010]", ) {
    equal_to_ex_A1<std::string>(10);
  }

  TEST_CASE("Example A1_10: equal_to (MultiStringView)", "[quick][011]", ) {
    equal_to_ex_A1<MultiStringView>(10);
  }

  TEST_CASE("Example A1_10: equal_to (KnuthBendix)", "[quick][012]", ) {
    for (size_t N = 100; N <= 400; N += 8) {
      std::string lhs, rhs;
      std::tie(lhs, rhs) = example1(10);
      fpsemigroup::KnuthBendix k;
      k.set_alphabet("ab");
      k.add_rule(lhs, rhs);
      REQUIRE(k.confluent());
      auto random = random_strings("ab", N, 0, 4 * N + 4);
      auto u      = zip(random_sequence(lhs, rhs, N), random);
      auto v      = zip(random_sequence(lhs, rhs, N), random);
      BENCHMARK("length = " + std::to_string(u.size() + v.size())) {
        REQUIRE(k.equal_to(u, v));
      };
    }
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark wp-prefix - Example A.2
  ////////////////////////////////////////////////////////////////////////

  // w_0 lhs w_1 lhs w_2 ... w_n = w_0 rhs w_1 rhs w_2 ... w_n
  template <typename T>
  void equal_to_ex_A2(size_t m) {
    for (size_t N = 100; N <= 400; N += 8) {
      std::string lhs = "a" + power_string("bc", m) + "a";
      std::string rhs = "a" + power_string("cb", m) + "a";
      Kambites<T> k;
      k.set_alphabet("abc");
      k.add_rule(lhs, rhs);
      REQUIRE(k.small_overlap_class() >= 4);
      auto random = random_strings("abc", N, 0, 4 * N + 4);
      auto u      = zip(random_sequence(lhs, rhs, N), random);
      auto v      = zip(random_sequence(lhs, rhs, N), random);
      BENCHMARK("length = " + std::to_string(u.size() + v.size())) {
        REQUIRE(k.equal_to(u, v));
      };
    }
  }

  TEST_CASE("Example A2_13: equal_to (std::string)", "[quick][013]", ) {
    equal_to_ex_A2<std::string>(13);
  }

  TEST_CASE("Example A2_13: equal_to (MultiStringView)", "[quick][014]", ) {
    equal_to_ex_A2<MultiStringView>(13);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark wp-prefix - Example A.3
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void equal_to_ex_A3(size_t m, size_t first, size_t last, size_t step) {
  start:
    auto        rules = random_strings(std::string("abcdefgh"), 8, 0, m);
    Kambites<T> k;
    k.set_alphabet("abcdefgh");
    k.add_rule(rules[0], rules[1]);
    k.add_rule(rules[2], rules[3]);
    k.add_rule(rules[4], rules[5]);
    k.add_rule(rules[6], rules[7]);
    if (k.small_overlap_class() < 4) {
      goto start;
    }

    for (size_t N = first; N <= last; N += step) {
      auto random = random_strings("abcdefgh", N, 0, 4 * N + 4);
      auto u      = zip(random_sequence(rules[4], rules[5], N), random);
      auto v      = zip(random_sequence(rules[4], rules[5], N), random);
      BENCHMARK("length = " + std::to_string(u.size() + v.size())) {
        REQUIRE(k.equal_to(u, v));
      };
    }
  }

  TEST_CASE("Example A3_300: equal_to (std::string)", "[quick][015]", ) {
    equal_to_ex_A3<std::string>(300, 100, 180, 2);
    // This runs superslow with the same params as the next test
  }

  TEST_CASE("Example A3_300: equal_to (MultiStringView)", "[quick][016]", ) {
    equal_to_ex_A3<MultiStringView>(300, 100, 400, 8);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark wp-prefix - Example A.4
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void equal_to_ex_A4() {
    Kambites<T> k;
    k.set_alphabet("abcde");
    k.add_rule("bceac", "aeebbc");
    k.add_rule("aeebbc", "dabcd");
    k.small_overlap_class();
    std::string w1 = "bceac";
    std::string w2 = "dabcd";
    std::string w3 = "aeebbc";

    for (size_t N = 1000; N < 14000; N += 500) {
      auto random = random_strings("abcde", N, 0, 12);
      auto u      = zip(random_sequence(w1, w2, N), random);
      auto v      = zip(random_sequence(w3, w1, N), random);
      BENCHMARK("length = " + std::to_string(u.size() + v.size())) {
        REQUIRE(k.equal_to(u, v));
      };
    }
  }

  TEST_CASE("Example A4: equal_to (std::string)", "[quick][017]", ) {
    equal_to_ex_A4<std::string>();
  }

  TEST_CASE("Example A4: equal_to (MultiStringView)", "[quick][018]", ) {
    equal_to_ex_A4<MultiStringView>();
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark wp-prefix - Example A.5
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void equal_to_ex_A5(size_t m, size_t first, size_t last, size_t step) {
    std::string lhs1 = "a" + power_string("bc", m) + "a";
    std::string rhs1 = "a" + power_string("cb", m) + "a";
    std::string lhs2 = "b" + power_string("d", m) + "b";
    std::string rhs2 = "a" + power_string("bc", m) + "a";
    Kambites<T> k;
    k.set_alphabet("abcd");
    k.add_rule(lhs1, rhs1);
    k.add_rule(lhs2, rhs2);
    for (size_t N = first; N <= last; N += step) {
      auto random = random_strings("abcdefgh", N, 0, 4 * N + 4);
      auto u      = zip(random_sequence(rhs1, rhs2, N), random);
      auto v      = zip(random_sequence(lhs1, lhs2, N), random);
      BENCHMARK("length = " + std::to_string(u.size() + v.size())) {
        REQUIRE(k.equal_to(u, v));
      };
    }
  }

  TEST_CASE("Example A5_254: equal_to (std::string)", "[quick][019]", ) {
    equal_to_ex_A5<std::string>(254, 10, 160, 4);
  }

  TEST_CASE("Example A5_254: equal_to (MultiStringView)", "[quick][020]", ) {
    equal_to_ex_A5<MultiStringView>(254, 10, 310, 8);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark normal_form - Example A.1
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void normal_form_ex_A1(size_t m, size_t first, size_t last, size_t step) {
    for (size_t N = first; N < last; N += step) {
      std::string lhs, rhs;
      std::tie(lhs, rhs) = example1(m);
      Kambites<T> k;
      k.set_alphabet("ab");
      k.add_rule(lhs, rhs);
      REQUIRE(k.small_overlap_class() >= 4);
      auto random = random_strings("ab", N, 0, 4 * N + 4);
      auto w      = zip(random_sequence(lhs, rhs, N), random);
      BENCHMARK("length = " + std::to_string(w.size())) {
        REQUIRE_NOTHROW(k.normal_form(w));
      };
    }
  }

  TEST_CASE("Example A1_159: normal_form (std::string)", "[quick][021]", ) {
    normal_form_ex_A1<std::string>(159, 1, 12, 1);
  }

  TEST_CASE("Example A1_159: normal_form checking (MultiStringView)",
            "[quick][022]", ) {
    normal_form_ex_A1<MultiStringView>(159, 1, 40, 1);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark normal_form - Example A.2
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void normal_form_ex_A2(size_t m) {
    for (size_t N = 18; N <= 116; N += 2) {
      std::string lhs = "a" + power_string("bc", m) + "a";
      std::string rhs = "a" + power_string("cb", m) + "a";
      Kambites<T> k;
      k.set_alphabet("abc");
      k.add_rule(lhs, rhs);
      REQUIRE(k.small_overlap_class() >= 4);
      auto random = random_strings("abc", N, 0, 4 * N + 4);
      auto w      = zip(random_sequence(lhs, rhs, N), random);
      BENCHMARK("length = " + std::to_string(w.size())) {
        REQUIRE_NOTHROW(k.normal_form(w));
      };
    }
  }

  TEST_CASE("Example A2_104: normal_form checking (std::string)",
            "[quick][023]", ) {
    normal_form_ex_A2<std::string>(104);
  }

  TEST_CASE("Example A2_104: normal_form checking (MultiStringView)",
            "[quick][024]", ) {
    normal_form_ex_A2<MultiStringView>(104);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark normal_form - Example A.3
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void normal_form_ex_A3(size_t m) {
  start:
    auto        rules = random_strings(std::string("abcdefgh"), 8, 0, m);
    Kambites<T> k;
    k.set_alphabet("abcdefgh");
    k.add_rule(rules[0], rules[1]);
    k.add_rule(rules[2], rules[3]);
    k.add_rule(rules[4], rules[5]);
    k.add_rule(rules[6], rules[7]);
    if (k.small_overlap_class() < 4) {
      goto start;
    }

    for (size_t N = 20; N < 220; N += 5) {
      auto random = random_strings("abcdefgh", N, 0, 4 * N + 4);
      auto w      = zip(random_sequence(rules[0], rules[7], N), random);
      BENCHMARK("length = " + std::to_string(w.size())) {
        REQUIRE_NOTHROW(k.normal_form(w));
      };
    }
  }

  TEST_CASE("Example A3_14: normal_form checking (std::string)",
            "[quick][025]", ) {
    normal_form_ex_A3<std::string>(14);
  }

  TEST_CASE("Example A3_14: normal_form checking (MultiStringView)",
            "[quick][026]", ) {
    normal_form_ex_A3<MultiStringView>(14);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark normal_form - Example A.4
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void normal_form_ex_A4() {
    Kambites<T> k;
    k.set_alphabet("abcde");
    k.add_rule("bceac", "aeebbc");
    k.add_rule("aeebbc", "dabcd");
    k.small_overlap_class();
    std::string w1 = "bceac";
    std::string w2 = "dabcd";
    std::string w3 = "aeebbc";

    for (size_t N = 50; N < 750; N += 18) {
      auto random = random_strings("abcde", N, 0, 12);
      auto w      = zip(random_sequence(w3, w1, N), random);
      BENCHMARK("length = " + std::to_string(w.size())) {
        REQUIRE_NOTHROW(k.normal_form(w));
      };
    }
  }

  TEST_CASE("Example A4: normal_form (std::string)", "[quick][027]", ) {
    normal_form_ex_A4<std::string>();
  }

  TEST_CASE("Example A4: normal_form (MultiStringView)", "[quick][028]", ) {
    normal_form_ex_A4<MultiStringView>();
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark normal_form - Example A.5
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void normal_form_ex_A5(size_t m) {
    std::string lhs1 = "a" + power_string("bc", m) + "a";
    std::string rhs1 = "a" + power_string("cb", m) + "a";
    std::string lhs2 = "b" + power_string("d", m) + "b";
    std::string rhs2 = "a" + power_string("bc", m) + "a";
    Kambites<T> k;
    k.set_alphabet("abcd");
    k.add_rule(lhs1, rhs1);
    k.add_rule(lhs2, rhs2);
    for (size_t N = 18; N <= 310; N += 8) {
      auto random = random_strings("abcdefgh", N, 0, 4 * N + 4);
      auto w      = zip(random_sequence(rhs1, rhs2, N), random);
      BENCHMARK("length = " + std::to_string(w.size())) {
        REQUIRE_NOTHROW(k.normal_form(w));
      };
    }
  }

  TEST_CASE("Example A5_42: normal_form (std::string)", "[quick][029]", ) {
    normal_form_ex_A5<std::string>(42);
  }

  TEST_CASE("Example A5_42: normal_form (MultiStringView)", "[quick][030]", ) {
    normal_form_ex_A5<MultiStringView>(42);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark number_of_normal_forms - Example A.1
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void number_of_normal_forms_ex_A1(size_t m) {
    auto        rg = ReportGuard(false);
    std::string lhs, rhs;
    std::tie(lhs, rhs) = example1(m);
    BENCHMARK("Enumerate all normal forms length 0 to 18") {
      Kambites<T> k;
      k.set_alphabet("ab");
      k.add_rule(lhs, rhs);
      REQUIRE(k.number_of_normal_forms(0, 18) == 262142);
    };
  }

  TEST_CASE("Example A1_4: number_of_normal_forms (std::string)",
            "[quick][031]", ) {
    number_of_normal_forms_ex_A1<std::string>(4);
  }

  TEST_CASE("Example A1_4: number_of_normal_forms (MultiStringView)",
            "[quick][032]", ) {
    number_of_normal_forms_ex_A1<MultiStringView>(4);
  }

  ////////////////////////////////////////////////////////////////////////
  // Benchmark number_of_normal_forms - Example A.2
  ////////////////////////////////////////////////////////////////////////

  template <typename T>
  void number_of_normal_forms_ex_A2(size_t m) {
    std::array<size_t, 5> values = {0, 29381, 29516, 29523, 29523};
    auto                  rg     = ReportGuard(false);
    std::string           lhs    = "a" + power_string("bc", m) + "a";
    std::string           rhs    = "a" + power_string("cb", m) + "a";
    BENCHMARK("Enumerate normal forms length 0 to 10, m = "
              + std::to_string(m)) {
      Kambites<T> k;
      k.set_alphabet("abc");
      k.add_rule(lhs, rhs);
      REQUIRE(k.number_of_normal_forms(0, 10) == values[m]);
    };
  }  // NOLINT(readability/braces)

  TEST_CASE("Example A2_1_2_3_4: number_of_normal_forms (std::string)",
            "[quick][033]", ) {
    for (size_t i = 1; i <= 4; ++i) {
      number_of_normal_forms_ex_A2<std::string>(i);
    }
  }

  TEST_CASE("Example A2_1_2_3_4: number_of_normal_forms (MultiStringView)",
            "[quick][034]", ) {
    for (size_t i = 1; i <= 4; ++i) {
      number_of_normal_forms_ex_A2<MultiStringView>(i);
    }
  }

  std::string swap_a_and_b(std::string const& w) {
    std::string result;
    for (auto l : w) {
      if (l == 'a') {
        result += "b";
      } else {
        result += "a";
      }
    }
    return result;
  }

  void normal_form_2_letter_1_relation_c4_monoids(size_t min,
                                                  size_t max,
                                                  size_t nr_words,
                                                  size_t length_words) {
    auto first
        = cbegin_sislo("ab", std::string(min, 'a'), std::string(max, 'a'));
    auto last
        = cbegin_sislo("ab", std::string(min, 'a'), std::string(max, 'a'));
    size_t N = std::distance(
        first, cend_sislo("ab", std::string(min, 'a'), std::string(max, 'a')));
    std::advance(last, N - 1);

    auto llast = last;
    ++llast;
    std::unordered_set<std::string>        set;
    std::vector<Kambites<MultiStringView>> kk;

    for (auto it1 = first; it1 != last; ++it1) {
      auto it2 = it1;
      ++it2;
      for (; it2 != llast; ++it2) {
        Kambites<MultiStringView> k;
        k.set_alphabet("ab");
        k.add_rule(*it1, *it2);
        auto tmp = *it1 + "#" + *it2;
        if (set.insert(tmp).second) {
          if (k.small_overlap_class() >= 4) {
            auto u = swap_a_and_b(*it1);
            auto v = swap_a_and_b(*it2);
            if (shortlex_compare(u, v)) {
              set.insert(u + "#" + v);
            } else {
              set.insert(v + "#" + u);
            }
            std::cout << *it1 << " = " << *it2 << std::endl;
            kk.push_back(k);
          }
        }
      }
    }
    auto   w = random_strings("ab", nr_words, length_words, length_words + 1);
    size_t n = 0;
    for (auto& k : kk) {
      n += 1;
      BENCHMARK("n = " + std::to_string(n)) {
        std::string u;
        for (size_t i = 0; i < nr_words; ++i) {
          u += k.normal_form(w[i]);
        }
        return u;
      };
    }
  }

  TEST_CASE("All 2-letter 1-relation C4 monoids 10 random words of length 10)",
            "[035]", ) {
    normal_form_2_letter_1_relation_c4_monoids(7, 11, 10, 10);
  }

  TEST_CASE(
      "All 2-letter 1-relation C4 monoids 100 random words of length 100)",
      "[036]", ) {
    normal_form_2_letter_1_relation_c4_monoids(7, 11, 100, 100);
  }

  /*std::array<std::string, 5> swap_a_b_c(std::string const& w) {
    static std::array<std::string, 5> perms
        = {"bac", "acb", "cba", "bca", "cab"};
    std::array<std::string, 5> result;
    size_t                     count = 0;
    for (auto const& p : perms) {
      std::string ww;
      for (auto l : w) {
        if (l == 'a') {
          ww += p[0];
        } else if (l == 'b') {
          ww += p[1];
        } else {
          ww += p[2];
        }
      }
      result[count] = ww;
      count++;
    }
    return result;
  }

  TEST_CASE("Kambites",
                          "073",
                          "(fpsemi) 3-generated 1-relation C(4)-semigroups",
                          "[extreme][kambites][fpsemigroup][fpsemi]") {
    auto   first = cbegin_sislo("abc", "a", std::string(8, 'a'));
    auto   last  = cbegin_sislo("abc", "a", std::string(8, 'a'));
    size_t N
        = std::distance(first, cend_sislo("abc", "a", std::string(8, 'a')));
    REQUIRE(N == 3279);
    std::advance(last, N - 1);

    size_t total_c4 = 0;
    size_t total    = 0;
    auto   llast    = last;
    ++llast;
    std::unordered_set<std::string>        set;
    std::vector<Kambites<MultiStringView>> kk;

    for (auto it1 = first; it1 != last; ++it1) {
      auto it2 = it1;
      ++it2;
      for (; it2 != llast; ++it2) {
        total++;
        Kambites<std::string> k;
        k.set_alphabet("abc");
        k.add_rule(*it1, *it2);
        if (k.small_overlap_class() >= 4) {
          auto tmp = *it1 + "#" + *it2;
          if (set.insert(tmp).second) {
            auto u = swap_a_b_c(*it1);
            auto v = swap_a_b_c(*it2);
            for (size_t i = 0; i < 5; ++i) {
              if (shortlex_compare(u[i], v[i])) {
                set.insert(u[i] + "#" + v[i]);
              } else {
                set.insert(v[i] + "#" + u[i]);
              }
            }
            std::cout << *it1 << " = " << *it2 << std::endl;
            total_c4++;
          }
        }
      }
    }
    REQUIRE(total_c4 == 307511);
    REQUIRE(total == 5374281);
    auto   w = random_strings("abc", 10, 10, 11);
    size_t n = 0;
    for (auto& k : kk) {
      n += 1;
      BENCHMARK("n = " + std::to_string(n)) {
        std::string u;
        for (size_t i = 0; i < 10; ++i) {
          u += k.normal_form(w[i]);
        }
        return u;
      };
    }
  }*/

  void random_benchmarks(std::string const& alphabet,
                         size_t             number,
                         size_t             min,
                         size_t             max,
                         size_t             sample_size) {
    std::cout << "Alphabet            = " << alphabet << std::endl;
    std::cout << "Number of relations = " << number << std::endl;
    std::cout << "Minimum length      = " << min << std::endl;
    std::cout << "Maximum length      = " << max << std::endl;
    std::cout << "Sample size         = " << sample_size << std::endl;

    std::vector<std::vector<std::string>> sample;
    for (size_t i = 0; i < sample_size; ++i) {
      sample.push_back(random_strings(alphabet, 2 * number, min, max));
    }

    size_t number_confluent = 0;
    size_t number_c4        = 0;
    size_t number_both      = 0;

    for (size_t i = 0; i < sample_size; ++i) {
      bool confluent = false;
      bool c4        = false;
      BENCHMARK_ADVANCED("(KnuthBendix) n = " + std::to_string(i))
      (Catch::Benchmark::Chronometer meter) {
        fpsemigroup::KnuthBendix k;
        k.set_alphabet(alphabet);
        for (size_t j = 0; j < 2 * number; j += 2) {
          k.add_rule(sample[i][j], sample[i][j + 1]);
        }
        meter.measure([&k, &confluent]() {
          bool result = k.confluent();
          if (result) {
            confluent = true;
          }
          return result;
        });
      };  // NOLINT(readability/braces)
      BENCHMARK_ADVANCED("(Kambites) n = " + std::to_string(i))
      (Catch::Benchmark::Chronometer meter) {
        Kambites<std::string> k;
        k.set_alphabet(alphabet);
        for (size_t j = 0; j < 2 * number; j += 2) {
          k.add_rule(sample[i][j], sample[i][j + 1]);
        }
        meter.measure([&k, &c4]() {
          size_t result = k.small_overlap_class();
          if (result >= 4) {
            c4 = true;
          }
          return result;
        });
      };  // NOLINT(readability/braces)
      number_c4 += c4;
      number_confluent += confluent;
      number_both += (c4 && confluent);
    }
    std::cout << "\n Number confluent = " << number_confluent << std::endl;
    std::cout << "Number C(4)      = " << number_c4 << std::endl;
    std::cout << "Number both      = " << number_both << std::endl;
  }

  TEST_CASE("Random benchmarks 2-generator", "[037]") {
    size_t sample_size = 100;
    for (size_t nr_rels = 1; nr_rels < 200; nr_rels += 4) {
      for (size_t min = 50; min < 13000; min *= 2) {
        std::cout << std::string(72, '#') << std::endl;
        random_benchmarks("ab", nr_rels, min, 2 * min, sample_size);
        std::cout << std::string(72, '#') << std::endl;
      }
    }
  }
}  // namespace libsemigroups
