//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2019 James D. Mitchell
//
// 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/>.
//

// This file contains stuff for creating congruence over FroidurePinBase objects
// or over FpSemigroup objects.

#include "libsemigroups/cong.hpp"

#include <memory>  // for shared_ptr

#include "libsemigroups/cong-pair.hpp"  // for KnuthBendixCongruenceByPairs
#include "libsemigroups/debug.hpp"      // for LIBSEMIGROUPS_ASSERT
#include "libsemigroups/exception.hpp"  // for LIBSEMIGROUPS_EXCEPTION
#include "libsemigroups/fpsemi.hpp"     // for FpSemigroup
#include "libsemigroups/froidure-pin-base.hpp"  // for FroidurePinBase
#include "libsemigroups/kambites.hpp"           // for Kambites
#include "libsemigroups/knuth-bendix.hpp"       // for KnuthBendix
#include "libsemigroups/todd-coxeter.hpp"       // for ToddCoxeter

namespace libsemigroups {

  using ToddCoxeter      = congruence::ToddCoxeter;
  using KnuthBendix      = congruence::KnuthBendix;
  using Kambites         = congruence::Kambites;
  using class_index_type = CongruenceInterface::class_index_type;
  using options          = ToddCoxeter::options;

  //////////////////////////////////////////////////////////////////////////
  // Congruence - constructors - public
  //////////////////////////////////////////////////////////////////////////

  Congruence::Congruence(congruence_kind type, options::runners p)
      : CongruenceInterface(type), _race() {
    if (p == options::runners::standard) {
      _race.add_runner(std::make_shared<ToddCoxeter>(type));
      if (type == congruence_kind::twosided) {
        _race.add_runner(std::make_shared<KnuthBendix>());
      }
    }
  }

  Congruence::Congruence(congruence_kind                  type,
                         std::shared_ptr<FroidurePinBase> S)
      : Congruence(type, options::runners::none) {
    auto tc = std::make_shared<ToddCoxeter>(type, S);
    tc->froidure_pin_policy(
        congruence::ToddCoxeter::options::froidure_pin::use_relations);
    _race.add_runner(tc);

    tc = std::make_shared<ToddCoxeter>(type, S);
    tc->froidure_pin_policy(
        congruence::ToddCoxeter::options::froidure_pin::use_cayley_graph);
    _race.add_runner(tc);
    set_number_of_generators(S->number_of_generators());
    set_parent_froidure_pin(S);
  }

  Congruence::Congruence(congruence_kind type, FpSemigroup& S)
      : Congruence(type, options::runners::none) {
    set_number_of_generators(S.alphabet().size());
    LIBSEMIGROUPS_ASSERT(!has_parent_froidure_pin());
    set_parent_froidure_pin(S);
    if (type == congruence_kind::twosided && S.has_kambites()) {
      _race.add_runner(std::make_shared<Kambites>(*S.kambites()));
    }
    _race.max_threads(POSITIVE_INFINITY);
    if (S.has_todd_coxeter()) {
      // Method 1: use only the relations used to define S and genpairs to
      // run Todd-Coxeter. This runs whether or not we have computed a data
      // structure for S.
      // TODO(later) change the next line to S, instead of S.todd_coxeter!
      _race.add_runner(std::make_shared<ToddCoxeter>(type, *S.todd_coxeter()));

      if (S.todd_coxeter()->finished()) {
        // Method 2: use the Cayley graph of S and genpairs to run
        // Todd-Coxeter. If the policy here is use_relations, then this is
        // the same as Method 1. Note that the froidure_pin
        // must be finite in this case, and it must be possible for the
        // Froidure-Pin algorithm to complete in this case because
        // Todd-Coxeter did.
        _race.add_runner(std::make_shared<ToddCoxeter>(
            type,
            S.todd_coxeter()->froidure_pin(),
            congruence::ToddCoxeter::options::froidure_pin::use_cayley_graph));

        // Return here since we know that we can definitely complete at
        // this point.
        return;
      }
    }
    if (S.has_knuth_bendix()) {
      if (S.knuth_bendix()->finished()) {
        // TODO(later) remove the if-condition, make it so that if the
        // ToddCoxeter's below are killed then so too is the enumeration of
        // S.knuth_bendix().froidure_pin()
        if (S.knuth_bendix()->froidure_pin()->finished()) {
          // Method 3: Note that the
          // S.knuth_bendix().froidure_pin() must be finite
          // in this case, because otherwise it would not return true from
          // FroidurePin::finished. This is similar to Method 2.
          _race.add_runner(std::make_shared<ToddCoxeter>(
              type,
              S.knuth_bendix()->froidure_pin(),
              congruence::ToddCoxeter::options::froidure_pin::
                  use_cayley_graph));

          // Method 4: unlike with Method 2, this is not necessarily the same
          // as running Method 1, because the relations in S.knuth_bendix()
          // are likely not the same as those in S.todd_coxeter().
          // TODO(later)
          // - uncomment the next line
          // - check if the relations are really the same as those in
          //   S.todd_coxeter(), if it exists. This is probably too
          //   expensive!
          // - use the active rules of KnuthBendix in the ToddCoxeter
          // constructor, currently uses the relations in S.knuth_bendix()
          // which are guaranteed to equal those in S.todd_coxeter()!
          // _race.add_runner(new ToddCoxeter(type, S.knuth_bendix()));

          // Return here since we know that we can definitely complete at this
          // point.
          return;
        }
      }

      if (type == congruence_kind::twosided) {
        // Method 6 (KBFP)
        // S.knuth_bendix() must be copied because maybe we will add more
        // generating pairs.
        _race.add_runner(
            std::make_shared<congruence::KnuthBendix>(*S.knuth_bendix()));
      }

      // Method 7 (KBP)
      // This can return non-trivial classes when other methods cannot, for
      // example, when there are no generating pairs.
      KnuthBendixCongruenceByPairs* kbp
          = new KnuthBendixCongruenceByPairs(kind(), S.knuth_bendix());
      _race.add_runner(std::shared_ptr<KnuthBendixCongruenceByPairs>(kbp));
    }

    LIBSEMIGROUPS_ASSERT(!_race.empty());
  }

  ////////////////////////////////////////////////////////////////////////////
  // CongruenceInterface - non-pure virtual member functions - public
  ////////////////////////////////////////////////////////////////////////////

  bool Congruence::contains(word_type const& lhs, word_type const& rhs) {
    tril r = const_contains(lhs, rhs);
    if (r != tril::unknown) {
      return r == tril::TRUE;
    }
    run_until([this, &lhs, &rhs]() -> bool {
      return const_contains(lhs, rhs) != tril::unknown;
    });
    return const_contains(lhs, rhs) == tril::TRUE;
  }

  tril Congruence::const_contains(word_type const& lhs,
                                  word_type const& rhs) const {
    validate_word(lhs);
    validate_word(rhs);
    if (lhs == rhs) {
      return tril::TRUE;
    }
    for (auto runner : _race) {
      tril r = static_cast<CongruenceInterface*>(runner.get())
                   ->const_contains(lhs, rhs);
      if (r != tril::unknown) {
        return r;
      }
    }
    return tril::unknown;
  }

  //////////////////////////////////////////////////////////////////////////
  // CongruenceInterface - pure virtual member functions - private
  //////////////////////////////////////////////////////////////////////////

  word_type Congruence::class_index_to_word_impl(class_index_type i) {
    // TODO(later) it ought to be possible to answer this question without
    // getting the winner(). Since, for example, if we can run KnuthBendix,
    // then start enumerating its FroidurePin, then we can find the word of any
    // (sufficiently small) class index without any of the runners in the race
    // winning.
    if (_race.winner() == nullptr) {
      LIBSEMIGROUPS_EXCEPTION(
          "cannot determine the word corresponding to class index %llu",
          static_cast<uint64_t>(i));
    }
    return static_cast<CongruenceInterface*>(_race.winner().get())
        ->class_index_to_word(i);
  }

  size_t Congruence::number_of_classes_impl() {
    run();  // to ensure the state is correctly set.
    if (_race.winner() == nullptr) {
      // The next line is not testable, this is just a sanity check in case
      // all threads in the _race throw, and so there is no winner.
      LIBSEMIGROUPS_EXCEPTION("cannot determine the number of classes");
    }
    return static_cast<CongruenceInterface*>(_race.winner().get())
        ->number_of_classes();
  }

  std::shared_ptr<FroidurePinBase> Congruence::quotient_impl() {
    if (_race.winner() == nullptr) {
      // The next line is not testable, this is just a sanity check in case
      // all threads in the _race throw, and so there is no winner.
      LIBSEMIGROUPS_EXCEPTION("cannot determine the quotient FroidurePin");
    }
    return static_cast<CongruenceInterface*>(_race.winner().get())
        ->quotient_froidure_pin();
  }

  class_index_type Congruence::word_to_class_index_impl(word_type const& word) {
    // TODO(later) it ought to be possible to answer this question without
    // getting the winner(). Since, for example, if we can run KnuthBendix,
    // then start enumerating its FroidurePin, then we can find the class index
    // of any (sufficiently small) word without any of the runners in the race
    // winning.
    if (_race.winner() == nullptr) {
      LIBSEMIGROUPS_EXCEPTION("cannot determine the class index of word %s",
                              detail::to_string(word).c_str());
    }
    LIBSEMIGROUPS_ASSERT(static_cast<CongruenceInterface*>(_race.winner().get())
                             ->word_to_class_index(word)
                         != UNDEFINED);
    return static_cast<CongruenceInterface*>(_race.winner().get())
        ->word_to_class_index(word);
  }

  //////////////////////////////////////////////////////////////////////////
  // CongruenceInterface - non-pure virtual member functions - private
  //////////////////////////////////////////////////////////////////////////

  void Congruence::add_pair_impl(word_type const& u, word_type const& v) {
    for (auto runner : _race) {
      static_cast<CongruenceInterface*>(runner.get())->add_pair(u, v);
    }
  }

  std::shared_ptr<CongruenceInterface::non_trivial_classes_type const>
  Congruence::non_trivial_classes_impl() {
    run();  // required so that state is correctly set.
    auto winner = static_cast<CongruenceInterface*>(_race.winner().get());
    if (winner == nullptr) {
      // The next line is not testable, this is just a sanity check in case
      // all threads in the _race throw, and so there is no winner.
      LIBSEMIGROUPS_EXCEPTION("cannot determine the non-trivial classes!");
    }
    try {
      return winner->non_trivial_classes();
    } catch (LibsemigroupsException const& e) {
      // Special case don't currently know a better way of doing this
      if (has_parent_fpsemigroup()) {
        // The following static_pointer_cast is probably a bad idea, but at
        // present it is only possible to construct a Congruence over an
        // FpSemigroup, and so it is currently guaranteed that
        // parent_fpsemigroup is a shared_ptr<FpSemigroup>.
        auto ptr = std::static_pointer_cast<FpSemigroup>(parent_fpsemigroup());
        if (ptr->has_knuth_bendix()) {
          KnuthBendixCongruenceByPairs kbp(kind(), ptr->knuth_bendix());
          for (auto it = cbegin_generating_pairs();
               it != cend_generating_pairs();
               ++it) {
            kbp.add_pair(it->first, it->second);
          }
          return kbp.non_trivial_classes();
        }
      }
      throw e;
    }
  }

  bool Congruence::is_quotient_obviously_infinite_impl() {
    for (auto runner : _race) {
      if (static_cast<CongruenceInterface*>(runner.get())
              ->is_quotient_obviously_infinite()) {
        return true;
      }
    }
    return false;
  }

  bool Congruence::is_quotient_obviously_finite_impl() {
    for (auto runner : _race) {
      if (static_cast<CongruenceInterface*>(runner.get())
              ->is_quotient_obviously_finite()) {
        return true;
      }
    }
    return false;  // Returns false if _race is empty
  }

  void Congruence::set_number_of_generators_impl(size_t n) {
    for (auto runner : _race) {
      static_cast<CongruenceInterface*>(runner.get())
          ->set_number_of_generators(n);
    }
  }
}  // namespace libsemigroups
