// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2020 Finn Smith
//
// 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 "catch.hpp"      // for REQUIRE
#include "test-main.hpp"  // FOR LIBSEMIGROUPS_TEST_CASE

#include "libsemigroups/konieczny.hpp"  // for Konieczny
#include "libsemigroups/transf.hpp"     // for Transf<>

namespace libsemigroups {

  constexpr bool REPORT = false;

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "031",
                          "transformations",
                          "[quick][transf]") {
    using Transf         = LeastTransf<5>;
    auto              rg = ReportGuard(REPORT);
    Konieczny<Transf> S({Transf({1, 0, 2, 3, 4}),
                         Transf({1, 2, 3, 4, 0}),
                         Transf({0, 0, 2, 3, 4})});
    S.run();
    REQUIRE(S.size() == 3125);

    size_t sum = 0;
    std::for_each(
        S.cbegin_D_classes(),
        S.cend_D_classes(),
        [&sum, &S](Konieczny<Transf>::DClass const& x) {
          sum += S.D_class_of_element(x.rep()).number_of_idempotents();
        });
    REQUIRE(sum == 196);
    REQUIRE(S.number_of_idempotents() == 196);
    REQUIRE(std::vector<Transf>(S.cbegin_generators(), S.cend_generators())
            == std::vector<Transf>({Transf({1, 0, 2, 3, 4}),
                                    Transf({1, 2, 3, 4, 0}),
                                    Transf({0, 0, 2, 3, 4})}));
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "032",
                          "transformations - JDM favourite example",
                          "[quick][no-valgrind][transf]") {
    using Transf         = LeastTransf<8>;
    auto              rg = ReportGuard(REPORT);
    Konieczny<Transf> S({Transf({1, 7, 2, 6, 0, 4, 1, 5}),
                         Transf({2, 4, 6, 1, 4, 5, 2, 7}),
                         Transf({3, 0, 7, 2, 4, 6, 2, 4}),
                         Transf({3, 2, 3, 4, 5, 3, 0, 1}),
                         Transf({4, 3, 7, 7, 4, 5, 0, 4}),
                         Transf({5, 6, 3, 0, 3, 0, 5, 1}),
                         Transf({6, 0, 1, 1, 1, 6, 3, 4}),
                         Transf({7, 7, 4, 0, 6, 4, 1, 7})});
    REQUIRE(S.size() == 597369);
    size_t sum = 0;
    std::for_each(
        S.cbegin_D_classes(),
        S.cend_D_classes(),
        [&sum, &S](Konieczny<Transf>::DClass const& x) {
          sum += S.D_class_of_element(x.rep()).number_of_idempotents();
        });
    REQUIRE(sum == 8194);
    REQUIRE(S.number_of_idempotents() == 8194);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "033",
                          "transformations - large example",
                          "[quick][no-valgrind][transf]") {
    auto                        rg   = ReportGuard(REPORT);
    std::vector<Transf<>> const gens = {Transf<>({2, 1, 0, 4, 2, 1, 1, 8, 0}),
                                        Transf<>({1, 7, 6, 2, 5, 1, 1, 4, 3}),
                                        Transf<>({1, 0, 7, 2, 1, 3, 1, 3, 7}),
                                        Transf<>({0, 3, 8, 1, 2, 8, 1, 7, 0}),
                                        Transf<>({0, 0, 0, 2, 7, 7, 5, 5, 3})};
    Konieczny<Transf<>>         S(gens);

    for (auto x : gens) {
      REQUIRE(S.contains(x));
    }

    REQUIRE(S.current_size() < 15000);
    REQUIRE(S.current_number_of_regular_elements() < 10000);
    REQUIRE(S.current_number_of_idempotents() < 500);
    REQUIRE(S.current_number_of_D_classes() < 2000);
    REQUIRE(S.current_number_of_L_classes() < 4000);
    REQUIRE(S.current_number_of_R_classes() < 6500);

    REQUIRE(S.size() == 232511);
    REQUIRE(S.current_number_of_D_classes() == 2122);
    REQUIRE(S.current_number_of_L_classes() == 8450);
    REQUIRE(S.current_number_of_R_classes() == 14706);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "034",
                          "transformations - large example with stop",
                          "[quick][no-valgrind][transf]") {
    auto                rg = ReportGuard(REPORT);
    Konieczny<Transf<>> S({Transf<>({2, 1, 0, 4, 2, 1, 1, 8, 0}),
                           Transf<>({1, 7, 6, 2, 5, 1, 1, 4, 3}),
                           Transf<>({1, 0, 7, 2, 1, 3, 1, 3, 7}),
                           Transf<>({0, 3, 8, 1, 2, 8, 1, 7, 0}),
                           Transf<>({0, 0, 0, 2, 7, 7, 5, 5, 3})});
    S.run_for(std::chrono::milliseconds(100));
    REQUIRE(S.size() == 232511);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "035",
                          "transformations - large example with run_until",
                          "[quick][no-valgrind][transf]") {
    auto                rg = ReportGuard(REPORT);
    Konieczny<Transf<>> S({Transf<>({2, 1, 0, 4, 2, 1, 1, 8, 0}),
                           Transf<>({1, 7, 6, 2, 5, 1, 1, 4, 3}),
                           Transf<>({1, 0, 7, 2, 1, 3, 1, 3, 7}),
                           Transf<>({0, 3, 8, 1, 2, 8, 1, 7, 0}),
                           Transf<>({0, 0, 0, 2, 7, 7, 5, 5, 3})});
    S.run_until([&S]() -> bool {
      return S.cend_D_classes() - S.cbegin_D_classes() > 20;
    });

    size_t number_of_classes1 = S.cend_D_classes() - S.cbegin_D_classes();
    REQUIRE(number_of_classes1 >= 20);
    S.run();
    size_t number_of_classes2 = S.cend_D_classes() - S.cbegin_D_classes();
    REQUIRE(S.size() == 232511);
    REQUIRE(number_of_classes1 < number_of_classes2);
    REQUIRE(number_of_classes2 == 2122);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "036",
                          "transformations - large example with stop in Action",
                          "[quick][no-valgrind][transf]") {
    auto                rg = ReportGuard(REPORT);
    Konieczny<Transf<>> S({Transf<>({2, 1, 0, 4, 2, 1, 1, 8, 0}),
                           Transf<>({1, 7, 6, 2, 5, 1, 1, 4, 3}),
                           Transf<>({1, 0, 7, 2, 1, 3, 1, 3, 7}),
                           Transf<>({0, 3, 8, 1, 2, 8, 1, 7, 0}),
                           Transf<>({0, 0, 0, 2, 7, 7, 5, 5, 3})});
    S.run_for(std::chrono::milliseconds(5));
    S.run_for(std::chrono::milliseconds(5));
    S.run_for(std::chrono::milliseconds(5));
    S.run_for(std::chrono::milliseconds(100));
    S.run_for(std::chrono::milliseconds(100));
    S.run();
    S.run_for(std::chrono::milliseconds(100));
    S.run_for(std::chrono::milliseconds(100));
    REQUIRE(S.size() == 232511);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny", "037", "exceptions", "[quick][transf]") {
    auto rg          = ReportGuard(REPORT);
    using value_type = typename Transf<>::value_type;
    std::vector<value_type> v(65, 0);
    std::iota(v.begin(), v.end(), 0);
    REQUIRE_THROWS_AS(Konieczny<Transf<>>({Transf<>(v)}),
                      LibsemigroupsException);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "038",
                          "transformations: contains",
                          "[quick][transf]") {
    auto                rg = ReportGuard(REPORT);
    Konieczny<Transf<>> S({Transf<>({1, 0, 2, 3, 4}),
                           Transf<>({1, 2, 3, 4, 0}),
                           Transf<>({0, 0, 2, 3, 4})});
    REQUIRE(S.contains(Transf<>({1, 0, 2, 3, 4})));
    REQUIRE(S.contains(Transf<>({1, 2, 3, 4, 0})));
    REQUIRE(S.contains(Transf<>({0, 0, 2, 3, 4})));
    REQUIRE(!S.contains(Transf<>({1, 0, 2, 3, 4, 5})));
    REQUIRE(!S.contains(Transf<>({1, 2, 3, 4, 0, 5})));
    REQUIRE(!S.contains(Transf<>({0, 0, 2, 3, 4, 1})));

    REQUIRE_THROWS_AS(S.D_class_of_element(Transf<>({1, 0, 2, 3, 4, 5})),
                      LibsemigroupsException);
    REQUIRE_THROWS_AS(S.D_class_of_element(Transf<>({1, 2, 3, 4, 0, 5})),
                      LibsemigroupsException);
    REQUIRE_THROWS_AS(S.D_class_of_element(Transf<>({0, 0, 2, 3, 4, 1})),
                      LibsemigroupsException);

    Konieczny<Transf<>> T(
        {Transf<>({1, 0, 3, 4, 2}), Transf<>({0, 0, 2, 3, 4})});
    REQUIRE(T.contains(Transf<>({1, 0, 2, 3, 4})));
    REQUIRE(T.contains(Transf<>({0, 0, 2, 3, 4})));
    REQUIRE(!T.contains(Transf<>({1, 2, 3, 4, 0})));
    REQUIRE(!T.contains(Transf<>({1, 2, 3, 0, 4})));
    REQUIRE(!T.contains(Transf<>({1, 2, 3, 4, 0, 5})));
    REQUIRE(!T.contains(Transf<>({0, 2, 3, 4, 1})));

    REQUIRE_THROWS_AS(T.D_class_of_element(Transf<>({1, 2, 3, 4, 0})),
                      LibsemigroupsException);
    REQUIRE_THROWS_AS(T.D_class_of_element(Transf<>({1, 2, 3, 4, 0, 5})),
                      LibsemigroupsException);
    REQUIRE_THROWS_AS(T.D_class_of_element(Transf<>({0, 2, 3, 4, 1})),
                      LibsemigroupsException);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "039",
                          "transformations Hall monoid 5",
                          "[extreme][transf]") {
    auto rg      = ReportGuard();
    using Transf = LeastTransf<31>;
    Konieczny<Transf> K;
    K.add_generator(
        Transf({0, 16, 1, 17, 2,  18, 3,  19, 4,  20, 5,  21, 6,  22, 7, 23,
                8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15}));
    K.add_generator(
        Transf({0, 1, 2,  3,  4,  5,  6,  7,  16, 17, 18, 19, 20, 21, 22, 23,
                8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30}));
    K.add_generator(
        Transf({0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, 30,
                0, 17, 8, 25, 4, 21, 12, 29, 2, 19, 10, 27, 6, 23, 14}));
    K.add_generator(Transf({0, 0, 0, 16, 0, 8, 4, 28, 2, 2, 2, 18, 2, 10, 6, 30,
                            1, 1, 1, 17, 1, 9, 5, 29, 3, 3, 3, 19, 3, 11, 7}));
    K.add_generator(Transf({0, 0, 0, 16, 0, 8, 0, 24, 0, 4, 0, 20, 0, 12, 2, 30,
                            1, 1, 1, 17, 1, 9, 1, 25, 1, 5, 1, 21, 1, 13, 3}));
    K.add_generator(Transf({0, 0, 0, 16, 0, 8, 0, 24, 0, 0, 4, 20, 2, 10, 6, 30,
                            1, 1, 1, 17, 1, 9, 1, 25, 1, 1, 5, 21, 3, 11, 7}));
    K.add_generator(
        Transf({0, 0, 0, 16, 0, 8,  0, 24, 0, 4, 0, 20, 0, 12, 0, 28,
                0, 2, 0, 18, 0, 10, 0, 26, 0, 6, 0, 22, 0, 14, 1}));
    K.add_generator(Transf({0, 0, 0, 16, 0, 8, 0, 24, 0, 4, 0, 20, 0, 12, 0, 28,
                            0, 0, 2, 18, 0, 8, 2, 26, 0, 4, 2, 22, 1, 13, 3}));
    K.add_generator(
        Transf({0, 0, 0, 16, 0, 8,  0, 24, 0, 0, 4, 20, 0, 8,  4, 28,
                0, 0, 0, 16, 2, 10, 2, 26, 0, 1, 4, 21, 2, 11, 6}));
    K.add_generator(Transf({0, 0, 0, 16, 0, 8, 0, 24, 0, 0, 4, 20, 0, 8, 4, 28,
                            0, 0, 0, 16, 0, 8, 2, 26, 0, 1, 4, 21, 0, 9, 6}));
    K.add_generator(Transf({0, 0, 0, 16, 0, 8, 0, 24, 0, 0, 0, 16, 0, 8, 4, 28,
                            0, 0, 0, 16, 0, 8, 2, 26, 0, 1, 0, 17, 0, 9, 6}));
    K.add_generator(
        Transf({0, 0, 0, 16, 0, 8,  0, 24, 0, 0, 4, 20, 0, 8,  4, 28,
                0, 0, 0, 16, 2, 10, 2, 26, 1, 1, 5, 21, 3, 11, 7}));
    REQUIRE(K.size() == 23191071);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "040",
                          "transformations - destructor coverage",
                          "[quick][transf][no-valgrind]") {
    auto rg      = ReportGuard(REPORT);
    using Transf = LeastTransf<9>;
    Konieczny<Transf> S({Transf({2, 1, 0, 4, 2, 1, 1, 8, 0}),
                         Transf({1, 7, 6, 2, 5, 1, 1, 4, 3}),
                         Transf({1, 0, 7, 2, 1, 3, 1, 3, 7}),
                         Transf({0, 3, 8, 1, 2, 8, 1, 7, 0}),
                         Transf({0, 0, 0, 2, 7, 7, 5, 5, 3})});
    S.run_until(
        [&S]() -> bool { return S.current_number_of_regular_D_classes() > 2; });

    // if these fail, this test won't get the coverage hoped for
    REQUIRE(S.current_number_of_regular_D_classes() < 5);
    REQUIRE(S.current_number_of_D_classes() - S.number_of_regular_D_classes()
            < 2117);
    // now all of the destructor should run
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "041",
                          "current_number_D_classes",
                          "[quick][transf][no-valgrind]") {
    auto rg      = ReportGuard(REPORT);
    using Transf = LeastTransf<9>;
    Konieczny<Transf> S({Transf({2, 1, 0, 4, 2, 1, 1, 8, 0}),
                         Transf({1, 7, 6, 2, 5, 1, 1, 4, 3}),
                         Transf({1, 0, 7, 2, 1, 3, 1, 3, 7}),
                         Transf({0, 3, 8, 1, 2, 8, 1, 7, 0}),
                         Transf({0, 0, 0, 2, 7, 7, 5, 5, 3})});
    REQUIRE(S.current_number_of_regular_D_classes() == 0);
    REQUIRE(S.current_number_of_D_classes() == 0);
    S.run_until(
        [&S]() -> bool { return S.current_number_of_regular_D_classes() > 2; });

    S.run();
    REQUIRE(S.current_number_of_regular_D_classes() == 5);
    REQUIRE(S.current_number_of_D_classes() - S.number_of_regular_D_classes()
            == 2117);
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "042",
                          "add_generator",
                          "[quick][transf][no-valgrind]") {
    auto rg      = ReportGuard(REPORT);
    using Transf = LeastTransf<16>;

    Konieczny<Transf> S({Transf({1, 0, 2, 3, 4})});
    S.add_generator(Transf({1, 2, 3, 4, 0}));
    S.add_generator(Transf({0, 0, 2, 3, 4}));

    REQUIRE(S.generator(0) == Transf({1, 0, 2, 3, 4}));
    REQUIRE(S.generator(1) == Transf({1, 2, 3, 4, 0}));
    REQUIRE(S.generator(2) == Transf({0, 0, 2, 3, 4}));
    REQUIRE(S.number_of_generators() == 3);
    REQUIRE(S.degree() == Degree<Transf>()(Transf({1, 0, 2, 3, 4})));
  }

  LIBSEMIGROUPS_TEST_CASE("Konieczny",
                          "043",
                          "add_generator",
                          "[quick][transf][no-valgrind]") {
    auto rg      = ReportGuard(REPORT);
    using Transf = LeastTransf<16>;

    Konieczny<Transf> S;
    S.add_generator(Transf({1, 2, 3, 4, 0}));
    S.add_generator(Transf({0, 0, 2, 3, 4}));

    REQUIRE(S.degree() == Degree<Transf>()(Transf({1, 2, 3, 4, 0})));
    REQUIRE(S.number_of_generators() == 2);
  }

}  // namespace libsemigroups
