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

#ifndef LIBSEMIGROUPS_SRC_SEMIGROUPS_H_
#define LIBSEMIGROUPS_SRC_SEMIGROUPS_H_

#include <algorithm>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>

#include "elements.h"
#include "libsemigroups-debug.h"
#include "recvec.h"
#include "report.h"

#if (defined(__GNUC__) && __GNUC__ < 5 \
     && !(defined(__clang__) || defined(__INTEL_COMPILER)))
#pragma message( \
    "GCC version >=5.0 is recommended, some features may not work correctly")
#endif

//! Namespace for everything in the libsemigroups library.
namespace libsemigroups {

  class RWSE;

  // This object is used for printing information during a computation.  The
  // reason it is global is that we must be able to report from different
  // threads running concurrently.
  extern Reporter glob_reporter;

  //! Type for the index of a generator of a semigroup.
  typedef size_t letter_t;

  //! Type for a word over the generators of a semigroup.
  typedef std::vector<letter_t> word_t;

  //! Type for a pair of word_t (a *relation*) of a semigroup.
  typedef std::pair<word_t, word_t> relation_t;

  //! Class for semigroups generated by instances of Element.
  //!
  //! Semigroups are defined by a generating set, and the main method here is
  //! Semigroup::enumerate, which implements the
  //! [Froidure-Pin Algorithm](https://www.irif.fr/~jep/PDF/Rio.pdf).
  //! When the enumeration of the semigroup is complete, the size, the left and
  //! right Cayley graphs are determined, and a confluent terminating
  //! presentation for the semigroup is known.
  class Semigroup {
    typedef RecVec<bool> flags_t;

    // Type used for indexing elements in a Semigroup, use this when not
    // specifically referring to a position in _elements. It should be possible
    // to change this type and everything will just work, provided the size of
    // the semigroup is less than the maximum value of this type of integer.
    typedef size_t index_t;

    // The elements of a semigroup are stored in _elements, but because of the
    // way add_generators/closure work, it might not be the case that all the
    // words of a given length are contiguous in _elements. Hence we require a
    // means of finding the next element of a given length. In
    // _enumerate_order, the first K_1 values are element_index_t's
    // equal to the positions in _elements of the words of length 1,
    // the next K_2 values are the element_index_t's equal to the positions in
    // _elements of the words of length 2, and so on.
    //
    // This typedef is used to distinguish variables that refer to positions in
    // _elements (element_index_t) from those that refer to positions in
    // _enumerate_order (enumerate_index_t).
    typedef index_t enumerate_index_t;

   public:
    //! Type for the position of an element in an instance of Semigroup. The
    //! size of the semigroup being enumerated must be at most
    //! std::numeric_limits<element_index_t>::max()
    typedef index_t element_index_t;

    //! Type for a left or right Cayley graph of a semigroup.
    typedef RecVec<element_index_t> cayley_graph_t;

   public:
    //! Deleted.
    //!
    //! The Semigroup class does not support an assignment contructor to avoid
    //! accidental copying. An object in Semigroup may use many gigabytes of
    //! memory and might be extremely expensive to copy. A copy constructor is
    //! provided in case such a copy should it be required anyway.
    Semigroup& operator=(Semigroup const& semigroup) = delete;

    //! Construct from generators.
    //!
    //! This is the default constructor for a semigroup generated by \p gens.
    //! The generators \p gens must all be of the same derived subclass of the
    //! Element base class. Additionally, \p gens must satisfy the following:
    //!
    //! 1. there must be at least one generator
    //! 2. the generators must have equal degree Element::degree
    //!
    //! if either of these points is not satisfied, then an asssertion failure
    //! will occur.
    //!
    //! There can be duplicate generators and although they do not count as
    //! distinct elements, they do count as distinct generators. In other words,
    //! the generators of the semigroup are precisely (a copy of) \p gens in the
    //! same order they occur in \p gens.
    //!
    //! The generators \p gens are copied by the constructor, and so it is the
    //! responsibility of the caller to delete \p gens.
    explicit Semigroup(std::vector<Element const*> const* gens);

    //! Construct from generators.
    //!
    //! This constructor is for convenience only, and it simply
    //! reinterpret_cast's the argument from std::vector<Element*> const* to
    //! std::vector<Element const*> const*.
    explicit Semigroup(std::vector<Element*> const* gens)
        : Semigroup(
              reinterpret_cast<std::vector<Element const*> const*>(gens)) {}

    //! Construct from generators.
    //!
    //! This constructor is for convenience only, and it simply const_casts the
    //! argument from std::vector<Element*>* to std::vector<Element*> const*.
    explicit Semigroup(std::vector<Element*>* gens)
        : Semigroup(const_cast<std::vector<Element*> const*>(gens)) {}

    //! Construct from generators.
    //!
    //! This constructor is for convenience only, and it simply
    //! const_casts the argument from std::vector<Element const*>* to
    //! std::vector<Element const*> const*.
    explicit Semigroup(std::vector<Element const*>* gens)
        : Semigroup(const_cast<std::vector<Element const*> const*>(gens)) {}

    //! Construct from generators.
    //!
    //! This constructor is for convenience only, see Semigroup::Semigroup.
    explicit Semigroup(std::vector<Element const*> const& gens);

    //! Construct from generators.
    //!
    //! This constructor is for convenience, and it simply reinterpret_casts
    //! its argument to <std::vector<Element const*> const&.
    explicit Semigroup(std::vector<Element*> const& gens)
        : Semigroup(
              reinterpret_cast<std::vector<Element const*> const&>(gens)) {}

    //! Construct from generators.
    //!
    //! This constructor is for convenience, and it simply static_casts its
    //! argument to std::vector<Element*>.
    explicit Semigroup(std::initializer_list<Element*> gens)
        : Semigroup(static_cast<std::vector<Element*>>(gens)) {}

    //! Copy constructor.
    //!
    //! Constructs a new Semigroup which is an exact copy of \p copy. No
    //! enumeration is triggered for either \p copy or of the newly constructed
    //! semigroup.
    Semigroup(const Semigroup& copy);

   private:
    // Partial copy.
    // \p copy a semigroup
    // \p coll a collection of additional generators
    //
    // This is a constructor for a semigroup generated by the generators of the
    // Semigroup copy and the (possibly) additional generators coll.
    //
    // The relevant parts of the data structure of copy are copied and
    // \c this will be corrupt unless add_generators or closure is called
    // subsequently. This is why this method is private.
    //
    // The same effect can be obtained by copying copy using the copy
    // constructor and then calling add_generators or closure. However,
    // this constructor avoids copying those parts of the data structure of
    // copy that add_generators invalidates anyway. If copy has not been
    // enumerated at all, then these two routes for adding more generators are
    // equivalent.
    Semigroup(Semigroup const& copy, std::vector<Element const*> const* coll);

   public:
    //! A default destructor.
    ~Semigroup();

    //! Returns the position in the semigroup corresponding to the element
    //! represented by the word \p w.
    //!
    //! The parameter \p w must consist of non-negative integers less than
    //! Semigroup::nrgens. This method returns the position in \c this of the
    //! element obtained by evaluating \p w. This is equivalent to finding the
    //! product \c x of the generators Semigroup::gens(\c w[i]) and then
    //! calling Semigroup::position with argument \c x.
    //!
    //! \sa Semigroup::word_to_element.
    element_index_t word_to_pos(word_t const& w) const;

    //! Returns a pointer to the element of \c this represented by the word
    //! \p w.
    //!
    //! The parameter \p w must consist of non-negative integers less than
    //! Semigroup::nrgens. This method returns a pointer to the element of
    //! \c this obtained by evaluating \p w. This is equivalent to finding the
    //! product \c x of the generators Semigroup::gens(\c w[i]).
    //!
    //! \sa Semigroup::word_to_pos.
    Element* word_to_element(word_t const& w) const;

    //! Returns the maximum length of a word in the generators so far computed.
    //!
    //! Every elements of the semigroup can be expressed as a product of the
    //! generators. The elements of the semigroup are enumerated in the
    //! short-lex order induced by the order of the generators (as passed to
    //! Semigroup::Semigroup).  This method returns the length of the longest
    //! word in the generators that has so far been enumerated.
    size_t current_max_word_length() const {
      if (is_done()) {
        return _lenindex.size() - 2;
      } else if (_nr > _lenindex.back()) {
        return _lenindex.size();
      } else {
        return _lenindex.size() - 1;
      }
    }

    //! Returns the degree of any (and all) Element's in the semigroup.
    size_t degree() const {
      return _degree;
    }

    //! Returns the number of generators of the semigroup.
    size_t nrgens() const {
      return _gens.size();
    }

    //! Return a pointer to the generator with index \p pos.
    Element const* gens(letter_t pos) const {
      LIBSEMIGROUPS_ASSERT(pos < _gens.size());
      return _gens[pos];
    }

    //! Returns \c true if the semigroup is fully enumerated and \c false if
    //! not.
    //!
    //! The semigroup is fully enumerated when the product of every element
    //! by every generator is known.
    bool is_done() const {
      return (_pos >= _nr);
    }

    //! Returns  \c true if no elements other than the generators have
    //! been enumerated so far and \c false otherwise.
    bool is_begun() const {
      LIBSEMIGROUPS_ASSERT(_lenindex.size() > 1);
      return (_pos >= _lenindex[1]);
    }

    //! Returns the position of the element \p x in the semigroup if it is
    //! already known to belong to the semigroup.
    //!
    //! This method finds the position of the element \p x in the semigroup if
    //! it is already known to belong to the semigroup, and
    //! libsemigroups::Semigroup::UNDEFINED if not. If the semigroup is
    //! not fully enumerated, then this method may return
    //! libsemigroups::Semigroup::UNDEFINED when \p x is in the semigroup,
    //! but not this is not yet known.
    //!
    //! \sa Semigroup::position and Semigroup::sorted_position.
    element_index_t current_position(Element const* x) const {
      if (x->degree() != _degree) {
        return UNDEFINED;
      }

      auto it = _map.find(x);
      return (it == _map.end() ? UNDEFINED : it->second);
    }

    //! Returns the number of elements in the semigroup that have been
    //! enumerated so far.
    //!
    //! This is only the actual size of the semigroup if the semigroup is fully
    //! enumerated.
    size_t current_size() const {
      return _elements.size();
    }

    //! Returns the number of relations in the presentation for the semigroup
    //! that have been found so far.
    //!
    //! This is only the actual number of relations in a presentation defining
    //! the semigroup if the semigroup is fully enumerated.
    size_t current_nrrules() const {
      return _nrrules;
    }

    //! Returns the position of the prefix of the element \c x in position
    //! \p pos (of the semigroup) of length one less than the length of \c x.
    //!
    //! The parameter \p pos must be a valid position of an already enumerated
    //! element of the semigroup, this is asserted in the method.
    element_index_t prefix(element_index_t pos) const {
      LIBSEMIGROUPS_ASSERT(pos < _nr);
      return _prefix[pos];
    }

    //! Returns the position of the suffix of the element \c x in position
    //! \p pos (of the semigroup) of length one less than the length of \c x.
    //!
    //! The parameter \p pos must be a valid position of an already enumerated
    //! element of the semigroup, this is asserted in the method.
    element_index_t suffix(element_index_t pos) const {
      LIBSEMIGROUPS_ASSERT(pos < _nr);
      return _suffix[pos];
    }

    //! Returns the first letter of the element in position \p pos.
    //!
    //! This method returns the first letter of the element in position \p pos
    //! of the semigroup, which is the index of the generator corresponding to
    //! the first letter of the element.
    //!
    //! Note that Semigroup::gens[Semigroup::first_letter(\c pos)] is only
    //! equal to Semigroup::at(Semigroup::first_letter(\c pos)) if there are no
    //! duplicate generators.
    //!
    //! The parameter \p pos must be a valid position of an already enumerated
    //! element of the semigroup, this is asserted in the method.
    letter_t first_letter(element_index_t pos) const {
      LIBSEMIGROUPS_ASSERT(pos < _nr);
      return _first[pos];
    }

    //! Returns the last letter of the element in position \p pos.
    //!
    //! This method returns the final letter of the element in position \p pos
    //! of the semigroup, which is the index of the generator corresponding to
    //! the first letter of the element.
    //!
    //! Note that Semigroup::gens[Semigroup::final_letter(\c pos)] is only
    //! equal to Semigroup::at(Semigroup::final_letter(\c pos)) if there are no
    //! duplicate generators.
    //!
    //! The parameter \p pos must be a valid position of an already enumerated
    //! element of the semigroup, this is asserted in the method.
    letter_t final_letter(element_index_t pos) const {
      LIBSEMIGROUPS_ASSERT(pos < _nr);
      return _final[pos];
    }

    //! Returns the current value of the batch size. This is the minimum
    //! number of elements enumerated in any call to Semigroup::enumerate.
    size_t batch_size() const {
      return _batch_size;
    }

    //! Returns the length of the element in position \c pos of the semigroup.
    //!
    //! The parameter \p pos must be a valid position of an already enumerated
    //! element of the semigroup, this is asserted in the method. This method
    //! causes no enumeration of the semigroup.
    size_t length_const(element_index_t pos) const {
      LIBSEMIGROUPS_ASSERT(pos < _nr);
      return _length[pos];
    }

    //! Returns the length of the element in position \c pos of the semigroup.
    //!
    //! The parameter \p pos must be a valid position of an element of the
    //! semigroup, this is asserted in the method.
    size_t length_non_const(element_index_t pos) {
      if (pos >= _nr) {
        enumerate();
      }
      return length_const(pos);
    }

    //! Returns the position in \c this of the product of \c this->at(i) and
    //! \c this->at(j) by following a path in the Cayley graph.
    //!
    //! This method asserts that the values \p i and \p j are valid, in that
    //! they are less than Semigroup::current_size.  This method returns the
    //! position Semigroup::element_index_t in the semigroup of the product of
    //! \c this->at(i) and \c this->at(j) elements by following the path in the
    //! right or left Cayley graph from \p i to \p j, whichever is shorter.
    element_index_t product_by_reduction(element_index_t i,
                                         element_index_t j) const;

    //! Returns the position in \c this of the product of \c this->at(i) and
    //! \c this->at(j).
    //!
    //! This method asserts that the parameters \p i and \p j are less than
    //! Semigroup::current_size, and it either:
    //!
    //! * follows the path in the right or left Cayley graph from \p i to \p j,
    //!   whichever is shorter using Semigroup::product_by_reduction; or
    //!
    //! * multiplies the elements in postions \p i and \p j together;
    //!
    //! whichever is better. The method used is determined by comparing
    //! Element::complexity and the Semigroup::length_const of \p i and \p j.
    //!
    //! For example, if the Element::complexity of the multiplication is linear
    //! and \c this is a semigroup of transformations of degree 20, and the
    //! shortest paths in the left and right Cayley graphs from \p i to \p j
    //! are of length 100 and 1131, then it better to just multiply the
    //! transformations together.
    element_index_t fast_product(element_index_t i, element_index_t j) const;

    //! Returns the position in \c this of the generator with index \p i
    //!
    //! This method asserts that the value of \p i is valid.  In many cases \p
    //! letter_to_pos(i) will equal \p i, examples of when this will not be the
    //! case are:
    //!
    //! * there are duplicate generators;
    //!
    //! * Semigroup::add_generators was called after the semigroup was already
    //! partially enumerated.
    element_index_t letter_to_pos(letter_t i) const {
      LIBSEMIGROUPS_ASSERT(i < _nrgens);
      return _letter_to_pos[i];
    }

    //! Returns the total number of idempotents in the semigroup.
    //!
    //! This method involves fully enumerating the semigroup, if it is not
    //! already fully enumerated.  The value of the positions, and number, of
    //! idempotents is stored after they are first computed.
    size_t nridempotents();

    //! Returns \c true if the element in position \p pos is an idempotent
    //! and \c false if it is not.
    //!
    //! This method involves fully enumerating the semigroup, if it is not
    //! already fully enumerated.
    bool is_idempotent(element_index_t pos);

    //! Returns  the total number of relations in the presentation defining the
    //! semigroup.
    //!
    //! \sa Semigroup::next_relation.
    size_t nrrules() {
      enumerate();
      return _nrrules;
    }

    //! Set a new value for the batch size.
    //!
    //! The *batch size* is the number of new elements to be found by any call
    //! to Semigroup::enumerate. A call to enumerate returns between 0 and
    //! approximately the batch size.
    //!
    //! The default value of the batch size is 8192.
    //!
    //! This is used by, for example, Semigroup::position so that it is
    //! possible to find the position of an element without fully enumerating
    //! the semigroup.
    // FIXME Make _batch_size mutable and this const
    void set_batch_size(size_t batch_size) {
      _batch_size = batch_size;
    }

    //! Requests that the capacity (i.e. number of elements) of the semigroup
    //! be at least enough to contain n elements.
    //!
    //! The parameter \p n is also used to initialise certain data members, if
    //! you know a good upper bound for the size of your semigroup, then it is
    //! a good idea to call this method with that upper bound as an argument,
    //! this can significantly improve the performance of the
    //! Semigroup::enumerate method, and consequently every other method too.
    void reserve(size_t n);

    //! Returns the size of the semigroup.
    size_t size() {
      enumerate();
      return _elements.size();
    }

    //! Returns \c true if \p x is an element of \c this and \c false if it is
    //! not.
    //!
    //! This method can be used to check if the element \p x is an element of
    //! the semigroup. The semigroup is enumerated in batches until \p x is
    //! found or the semigroup is fully enumerated but \p x was not found (see
    //! Semigroup::set_batch_size).
    bool test_membership(Element const* x) {
      return (position(x) != UNDEFINED);
    }

    //! Returns the position of \p x in \c this, or Semigroup::UNDEFINED if \p
    //! x is not an element of \c this.
    //!
    //! This method can be used to find the Semigroup::element_index_t position
    //! of
    //! the
    //! element \p x if it belongs to the semigroup. The semigroup is
    //! enumerated in batches until \p x is found or the semigroup is fully
    //! enumerated but \p x was not found (see Semigroup::set_batch_size).
    element_index_t position(Element const* x);

    //! Returns the position of \p x in the sorted array of elements of the
    //! semigroup, or Semigroup::UNDEFINED if \p x is not an element of \c
    //! this.
    element_index_t sorted_position(Element const* x);

    //! Returns the position of \c this->at(pos) in the sorted array of
    //! elements of the semigroup, or Semigroup::UNDEFINED if \p pos is greater
    //! than the size of the semigroup.
    element_index_t position_to_sorted_position(element_index_t pos);

    //! Returns  the element of the semigroup in position \p pos, or a
    //! \c nullptr if there is no such element.
    //!
    //! This method attempts to enumerate the semigroup until at least
    //! \c pos + 1 elements have been found. If \p pos is greater than
    //! Semigroup::size, then this method returns \c nullptr.
    Element const* at(element_index_t pos);

    //! Returns the element of the semigroup in position \p pos.
    //!
    //! This method performs no checks on its argument, and performs no
    //! enumeration of the semigroup.
    Element const* operator[](element_index_t pos) const {
      return _elements[pos];
    }

    //! Returns the element of the semigroup in position \p pos of the sorted
    //! array of elements, or \c nullptr in \p pos is not valid (i.e. too big).
    //!
    //! This method fully enumerates the semigroup.
    Element const* sorted_at(element_index_t pos);

    //! Returns the index of the product of the element in position \p i with
    //! the generator with index \p j.
    //!
    //! This method fully enumerates the semigroup.
    inline element_index_t right(element_index_t i, letter_t j) {
      enumerate();
      return _right.get(i, j);
    }

    //! Returns a copy of the right Cayley graph of the semigroup.
    //!
    //! This method fully enumerates the semigroup.
    cayley_graph_t* right_cayley_graph_copy() {
      enumerate();
      return new cayley_graph_t(_right);
    }

    //! Returns the index of the product of the generator with index \p j and
    //! the element in position \p i.
    //!
    //! This method fully enumerates the semigroup.
    inline element_index_t left(element_index_t i, letter_t j) {
      enumerate();
      return _left.get(i, j);
    }

    //! Returns a copy of the left Cayley graph of the semigroup.
    //!
    //! This method fully enumerates the semigroup.
    cayley_graph_t* left_cayley_graph_copy() {
      enumerate();
      return new cayley_graph_t(_left);
    }

    //! Changes \p word in-place to contain a minimal word with respect to the
    //! short-lex ordering in the generators equal to the \p pos element of
    //! the semigroup.
    //!
    //! If \p pos is less than the size of this semigroup, then this method
    //! changes its first parameter \p word in-place by first clearing it and
    //! then to contain a minimal factorization of the element in position \p
    //! pos of the semigroup with respect to the generators of the semigroup.
    //! This method enumerates the semigroup until at least the \p pos element
    //! is known. If \p pos is greater than the size of the semigroup, then
    //! nothing happens and word is not modified, in particular not cleared.
    void minimal_factorisation(word_t& word, element_index_t pos);

    //! Returns a pointer to a minimal libsemigroups::word_t which evaluates to
    //! the Element in position \p pos of \c this.
    //!
    //! This is the same as the two-argument method for
    //! Semigroup::minimal_factorisation, but it returns a pointer to the
    //! factorisation instead of modifying an argument in-place.
    word_t* minimal_factorisation(element_index_t pos);

    //! Returns a pointer to a minimal libsemigroups::word_t which evaluates to
    //! \p x.
    //!
    //! This is the same as the method taking a Semigroup::element_index_t, but
    //! it factorises an Element instead of using the position of an element.
    word_t* minimal_factorisation(Element const* x);

    //! Changes \p word in-place to contain a word in the generators equal to
    //! the \p pos element of the semigroup.
    //!
    //! The key difference between this method and
    //! Semigroup::minimal_factorisation(word_t& word, element_index_t pos), is
    //! that the resulting factorisation may not be minimal.
    void factorisation(word_t& word, element_index_t pos) {
      minimal_factorisation(word, pos);
    }

    //! Returns a pointer to a libsemigroups::word_t which evaluates to
    //! the Element in position \p pos of \c this.
    //!
    //! The key difference between this method and
    //! Semigroup::minimal_factorisation(element_index_t pos), is that the
    //! resulting
    //! factorisation may not be minimal.
    word_t* factorisation(element_index_t pos) {
      return minimal_factorisation(pos);
    }

    //! Returns a pointer to a libsemigroups::word_t which evaluates to
    //!
    //! The key difference between this method and
    //! Semigroup::minimal_factorisation(Element const* x), is that the
    //! resulting factorisation may not be minimal.
    word_t* factorisation(Element const* x);

    //! This method resets Semigroup::next_relation so that when it is next
    //! called the resulting relation is the first one.
    //!
    //! After a call to this function, the next call to
    //! Semigroup::next_relation will return the first relation of the
    //! presentation defining the semigroup.
    void reset_next_relation() {
      _relation_pos = UNDEFINED;
      _relation_gen = 0;
    }

    //! This method changes \p relation in-place to contain the next relation
    //! of the presentation defining \c this.
    //!
    //! This method changes \p relation in-place so that one of the following
    //! holds:
    //!
    //! * \p relation is a vector consisting of a libsemigroups::letter_t and a
    //! libsemigroups::letter_t such that
    //! Semigroup::gens(\c relation[\c 0]) ==
    //! Semigroup::gens(\c relation[\c 1]), i.e. if the
    //! semigroup was defined with duplicate generators;
    //!
    //! * \p relation is a vector consisting of a
    //! libsemigroups::element_index_t, libsemigroups::letter_t, and
    //! libsemigroups::element_index_t such that
    //! \code{.cpp}
    //!   this[relation[0]] * Semigroup::gens(relation[1]) == this[relation[2]]
    //! \endcode
    //!
    //! * \p relation is empty if there are no more relations.
    //!
    //! Semigroup::next_relation is guaranteed to output all relations of
    //! length 2 before any relations of length 3. If called repeatedly after
    //! Semigroup::reset_next_relation, and until relation is empty, the values
    //! placed in \p relation correspond to a length-reducing confluent
    //! rewriting system that defines the semigroup.
    //!
    //! This method can be used in conjunction with Semigroup::factorisation to
    //! obtain a presentation defining the semigroup.
    //!
    //! \sa Semigroup::reset_next_relation.
    void next_relation(word_t& relation);

    //! Enumerate the semigroup until \p limit elements are found or \p killed
    //! is \c true.
    //!
    //! This is the main method of the Semigroup class, where the
    //! Froidure-Pin Algorithm is implemented.
    //!
    //! If the semigroup is already fully enumerated, or the number of elements
    //! previously enumerated exceeds \p limit, then calling this method does
    //! nothing. Otherwise, enumerate attempts to find at least the maximum of
    //! \p limit and Semigroup::batch_size elements of the semigroup. If \p
    //! killed is set to \c true (usually by another process), then the
    //! enumeration is terminated as soon as possible.  It is possible to
    //! resume enumeration at some later point after any call to this method,
    //! even if it has been killed.
    //!
    //! If the semigroup is fully enumerated, then it knows its left and right
    //! Cayley graphs, and a minimal factorisation of every element (in terms of
    //! its generating set).  All of the elements are stored in memory until the
    //! object is destroyed.
    //!
    //! The parameter \p limit defaults to Semigroup::LIMIT_MAX.
    void enumerate(std::atomic<bool>& killed, size_t limit = LIMIT_MAX);

    //! Enumerate the semigroup until \p limit elements are found.
    //!
    //! See Semigroup::enumerate(std::atomic<bool>& killed, size_t limit) for
    //! more details.
    void enumerate(size_t limit = LIMIT_MAX) {
      std::atomic<bool> killed(false);
      enumerate(killed, limit);
    }

    //! Add copies of the generators \p coll to the generators of \c this.
    //!
    //! This method can be used to add new generators to the existing semigroup
    //! in such a way that any previously enumerated data is preserved and not
    //! recomputed, or copied. This can be faster than recomputing the semigroup
    //! generated by the old generators and the new generators in the parameter
    //! \p coll.
    //!
    //! This method changes the semigroup in-place, thereby invalidating
    //! possibly previously known data about the semigroup, such as the left or
    //! right Cayley graphs, number of idempotents, and so on.
    //!
    //! Every generator in \p coll is added regardless of whether or not it is
    //! already a generator or element of the semigroup (it may belong to the
    //! semigroup but just not be known to belong). If \p coll is empty, then
    //! the semigroup is left unchanged. The order the generators is added is
    //! also the order they occur in the parameter \p coll.
    //!
    //! The semigroup is returned in a state where all of the previously
    //! enumerated elements which had been multiplied by all of the old
    //! generators, have now been multiplied by all of the old and new
    //! generators. This means that after this method is called the semigroup
    //! might contain many more elements than before (whether it is fully
    //! enumerating or not).  It can also be the case that the new generators
    //! are the only new elements, unlike, say, in the case of non-trivial
    //! groups.
    //!
    //! The elements the argument \p coll are copied into the semigroup, and
    //! should be deleted by the caller.
    void add_generators(std::vector<Element const*> const* coll);

    //! Add copies of the generators \p coll to the generators of \c this.
    //!
    //! See Semigroup::add_generators for more details.
    void add_generators(std::vector<Element*> const* coll) {
      add_generators(
          reinterpret_cast<std::vector<Element const*> const*>(coll));
    }

    //! Add copies of the generators \p coll to the generators of \c this.
    //!
    //! See Semigroup::add_generators for more details.
    void add_generators(std::vector<Element const*> const& coll);

    //! Add copies of the generators \p coll to the generators of \c this.
    //!
    //! See Semigroup::add_generators for more details.
    void add_generators(std::vector<Element*> const& coll) {
      add_generators(
          reinterpret_cast<std::vector<Element const*> const&>(coll));
    }

    //! Add copies of the generators \p coll to the generators of \c this.
    //!
    //! See Semigroup::add_generators for more details.
    void add_generators(std::initializer_list<Element*> coll) {
      add_generators(static_cast<std::vector<Element*>>(coll));
    }

    //! Returns a new semigroup generated by \c this and \p coll.
    //!
    //! This method is equivalent to copying \c this using
    //! Semigroup::Semigroup(const Semigroup& copy) and then calling
    //! Semigroup::add_generators on the copy, but this method avoids copying
    //! the parts of \c this that are immediately invalidated by
    //! Semigroup::add_generators.
    //!
    //! The elements the argument \p coll are copied into the semigroup, and
    //! should be deleted by the caller.
    Semigroup*
    copy_add_generators(std::vector<Element const*> const* coll) const;

    //! Returns a new semigroup generated by \c this and \p coll.
    //!
    //! See Semigroup::copy_add_generators for more details.
    Semigroup* copy_add_generators(std::vector<Element*> const* coll) const {
      return copy_add_generators(
          reinterpret_cast<std::vector<Element const*> const*>(coll));
    }

    //! Add copies of the non-redundant generators in \p coll to the generators
    //! of \c this.
    //!
    //! This method can be used to add new generators to an existing semigroup
    //! in such a way that any previously enumerated data is preserved and not
    //! recomputed, or copied. This can be faster than recomputing the semigroup
    //! generated by the old generators and the new in \p coll.
    //!
    //! This method differs from Semigroup::add_generators in that it tries to
    //! add the new generators one by one, and only adds those generators that
    //! are not products of existing generators (including any new generators
    //! from \p coll that were added before). The generators are added in the
    //! order they occur in \p coll.
    //!
    //! This method changes the semigroup in-place, thereby
    //! invalidating possibly previously known data about the semigroup, such as
    //! the left or right Cayley graphs, or number of idempotents, for example.
    //!
    //! The elements the parameter \p coll are copied into the semigroup, and
    //! should be deleted by the caller.
    void closure(std::vector<Element const*> const* coll);

    //! Add copies of the non-redundant generators in \p coll to the
    //! generators of \c this.
    //!
    //! See Semigroup::closure for more details.
    void closure(std::vector<Element const*> const& coll);

    //! Add copies of the non-redundant generators in \p coll to the
    //! generators of \c this.
    //!
    //! See Semigroup::closure for more details.
    void closure(std::vector<Element*> const& coll) {
      closure(reinterpret_cast<std::vector<Element const*> const&>(coll));
    }

    //! Add copies of the non-redundant generators in \p coll to the
    //! generators of \c this.
    //!
    //! See Semigroup::closure for more details.
    void closure(std::initializer_list<Element*> coll) {
      closure(static_cast<std::vector<Element*>>(coll));
    }

    //! Returns a new semigroup generated by \c this and copies of the
    //! non-redundant elements of \p coll.
    //!
    //! This method is equivalent to copying \c this and then calling
    //! Semigroup::closure on the copy with \p coll, but this method avoids
    //! copying the parts of \c this that are immediately invalidated by
    //! Semigroup::closure.
    //!
    //! The elements the argument \p coll are copied into the semigroup, and
    //! should be deleted by the caller.
    Semigroup* copy_closure(std::vector<Element const*> const* coll);

    //! Returns a new semigroup generated by \c this and copies of the
    //! non-redundant elements of \p coll.
    //!
    //! See Semigroup::copy_closure for more details.
    Semigroup* copy_closure(std::vector<Element*> const* gens) {
      return copy_closure(
          reinterpret_cast<std::vector<Element const*> const*>(gens));
    }

    //! This variable is used to indicate that a value is undefined, such as,
    //! for example, the position of an element that does not belong to a
    //! semigroup.
    static index_t const UNDEFINED;

    //! This variable is used to indicate the maximum possible limit that can
    //! be used with Semigroup::enumerate.
    static index_t const LIMIT_MAX;

    //! Turn reporting on or off.
    //
    //!  If \p val is true, then some methods for a Semigroup object may
    //! report information about the progress of the computation.
    void set_report(bool val) const {
      glob_reporter.set_report(val);
    }

    //! Set the maximum number of threads that any method of an instance of
    //! Semigroup can use.
    //!
    //! This method sets the maximum number of threads to be used by any method
    //! of a Semigroup object. The number of threads is limited to the maximum
    //! of 1 and the minimum of \p nr_threads and the number of threads
    //! supported by the hardware.
    void set_max_threads(size_t nr_threads) {
      unsigned int n
          = static_cast<unsigned int>(nr_threads == 0 ? 1 : nr_threads);
      _max_threads = std::min(n, std::thread::hardware_concurrency());
    }

   private:
    template <typename T, class C> class iterator_base {
     public:
      typedef typename std::vector<Element const*>::size_type  size_type;
      typedef typename std::vector<T>::difference_type         difference_type;
      typedef typename std::vector<Element const*>::value_type value_type;
      typedef typename std::vector<Element const*>::const_reference reference;
      typedef typename std::vector<Element const*>::const_pointer   pointer;
      typedef std::random_access_iterator_tag iterator_category;

      explicit iterator_base(typename std::vector<T>::const_iterator it_vec)
          : _it_vec(it_vec) {}

      iterator_base(iterator_base const& that) : iterator_base(that._it_vec) {}

      iterator_base& operator=(iterator_base const& that) {
        _it_vec = that._it_vec;
        return *this;
      }

      virtual ~iterator_base() {}

      bool operator==(iterator_base const& that) const {
        return _it_vec == that._it_vec;
      }

      bool operator!=(iterator_base const& that) const {
        return _it_vec != that._it_vec;
      }

      bool operator<(iterator_base const& that) const {
        return _it_vec < that._it_vec;
      }

      bool operator>(iterator_base const& that) const {
        return _it_vec > that._it_vec;
      }

      bool operator<=(iterator_base const& that) const {
        return operator<(that) || operator==(that);
      }

      bool operator>=(iterator_base const& that) const {
        return operator>(that) || operator==(that);
      }

      // postfix
      iterator_base operator++(int) {
        iterator_base  tmp(*this);
        iterator_base::operator++();
        return tmp;
      }

      iterator_base operator--(int) {
        iterator_base  tmp(*this);
        iterator_base::operator--();
        return tmp;
      }

      iterator_base operator+(size_type val) const {
        iterator_base out(*this);
        return out += val;
      }

      friend iterator_base operator+(size_type val, iterator_base const& it) {
        return it + val;
      }

      iterator_base operator-(size_type val) const {
        iterator_base out(*this);
        return out -= val;
      }

      reference operator[](size_type pos) const {
        return *(*this + pos);
      }

      iterator_base& operator++() {  // prefix
        ++_it_vec;
        return *this;
      }

      iterator_base& operator--() {
        --_it_vec;
        return *this;
      }

      iterator_base& operator+=(size_type val) {
        _it_vec += val;
        return *this;
      }

      iterator_base& operator-=(size_type val) {
        _it_vec -= val;
        return *this;
      }

      difference_type operator-(iterator_base that) const {
        return _it_vec - that._it_vec;
      }

      reference operator*() const {
        return _methods.indirection(_it_vec);
      }

      pointer operator->() const {
        return _methods.addressof(_it_vec);
      }

     protected:
      typename std::vector<T>::const_iterator _it_vec;
      static C const                          _methods;
    };  // iterator_base definition ends

    struct IteratorMethods {
      IteratorMethods() {}
      typename std::vector<Element const*>::const_reference indirection(
          typename std::vector<Element const*>::const_iterator it) const {
        return *it;
      }
      typename std::vector<Element const*>::const_pointer
      addressof(typename std::vector<Element const*>::const_iterator it) const {
        return &(*it);
      }
    };

    struct IteratorMethodsPairFirst {
      IteratorMethodsPairFirst() {}
      typename std::vector<Element const*>::const_reference indirection(
          typename std::vector<std::pair<Element const*, element_index_t>>::
              const_iterator it) const {
        return (*it).first;
      }

      typename std::vector<Element const*>::const_pointer
      addressof(typename std::vector<
                std::pair<Element const*, element_index_t>>::const_iterator it)
          const {
        return &((*it).first);
      }
    };

    typedef iterator_base<Element const*, IteratorMethods> const_iterator;
    typedef iterator_base<std::pair<Element const*, element_index_t>,
                          IteratorMethodsPairFirst>
                                                  const_iterator_pair_first;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
    typedef std::reverse_iterator<const_iterator_pair_first>
        const_reverse_iterator_pair_first;

    typedef const_iterator_pair_first         const_iterator_sorted;
    typedef const_iterator_pair_first         const_iterator_idempotents;
    typedef const_reverse_iterator_pair_first const_reverse_iterator_sorted;
    typedef const_reverse_iterator_pair_first
        const_reverse_iterator_idempotents;

   public:
    //! Returns a const iterator pointing to the first element of the
    //! semigroup.
    //!
    //! This method does not perform any enumeration of the semigroup, the
    //! iterator returned may be invalidated by any call to a non-const method
    //! of
    //! the Semigroup class.
    const_iterator cbegin() const {
      return const_iterator(_elements.cbegin());
    }

    //! Returns a const iterator pointing to the first element of the
    //! semigroup.
    //!
    //! This method does not perform any enumeration of the semigroup, the
    //! iterator returned may be invalidated by any call to a non-const method
    //! of
    //! the Semigroup class.
    const_iterator begin() const {
      return cbegin();
    }

    //! Returns a const iterator pointing to one past the last currently known
    //! element of the semigroup.
    //!
    //! This method does not perform any enumeration of the semigroup, the
    //! iterator returned may be invalidated by any call to a non-const method
    //! of
    //! the Semigroup class.
    const_iterator cend() const {
      return const_iterator(_elements.cend());
    }

    //! Returns a const iterator pointing to one past the last currently known
    //! element of the semigroup.
    //!
    //! This method does not perform any enumeration of the semigroup, the
    //! iterator returned may be invalidated by any call to a non-const method
    //! of
    //! the Semigroup class.
    const_iterator end() const {
      return cend();
    }

    //! Returns a const reverse iterator pointing to the last currently known
    //! element of the semigroup.
    //!
    //! This method does not perform any enumeration of the semigroup, the
    //! iterator returned may be invalidated by any call to a non-const method
    //! of
    //! the Semigroup class.
    const_reverse_iterator crbegin() const {
      return const_reverse_iterator(cend());
    }

    //! Returns a const reverse iterator pointing to one before the first
    //! element of the semigroup.
    //!
    //! This method does not perform any enumeration of the semigroup, the
    //! iterator returned may be invalidated by any call to a non-const method
    //! of
    //! the Semigroup class.
    const_reverse_iterator crend() const {
      return const_reverse_iterator(cbegin());
    }

    //! Returns a const iterator pointing to the first element of the semigroup
    //! when the elements are sorted by Element::operator<.
    //!
    //! This method fully enumerates the semigroup, the returned iterator
    //! returned may be invalidated by any call to a non-const method of the
    //! Semigroup class.
    const_iterator_sorted cbegin_sorted() {
      init_sorted();
      return const_iterator_pair_first(_sorted.cbegin());
    }

    //! Returns a const iterator pointing to one past the last element of the
    //! semigroup when the elements are sorted by Element::operator<.
    //!
    //! This method fully enumerates the semigroup, the returned iterator
    //! returned may be invalidated by any call to a non-const method of the
    //! Semigroup class.
    const_iterator_sorted cend_sorted() {
      init_sorted();
      return const_iterator_pair_first(_sorted.cend());
    }

    //! Returns a const iterator pointing to the last element of the semigroup
    //! when the elements are sorted by Element::operator<.
    //!
    //! This method fully enumerates the semigroup, the returned iterator
    //! returned may be invalidated by any call to a non-const method of the
    //! Semigroup class.
    const_reverse_iterator_sorted crbegin_sorted() {
      init_sorted();
      return const_reverse_iterator_pair_first(cend_sorted());
    }

    //! Returns a const iterator pointing to one before the first element of
    //! the semigroup when the elements are sorted by Element::operator<.
    //!
    //! This method fully enumerates the semigroup, the returned iterator
    //! returned may be invalidated by any call to a non-const method of the
    //! Semigroup class.
    const_reverse_iterator_sorted crend_sorted() {
      init_sorted();
      return const_reverse_iterator_pair_first(cbegin_sorted());
    }

    //! Returns a const iterator pointing at the first idempotent in the
    //! semigroup.
    //!
    //! If the returned iterator is incremented, then it points to the second
    //! idempotent in the semigroup (if it exists), and every subsequent
    //! increment points to the next idempotent.
    //!
    //! This method involves fully enumerating the semigroup, if it is not
    //! already fully enumerated.
    const_iterator_idempotents cbegin_idempotents() {
      init_idempotents();
      return const_iterator_pair_first(_idempotents.cbegin());
    }

    //! Returns a const iterator referring to past the end of the last
    //! idempotent in the semigroup.
    //!
    //! This method involves fully enumerating the semigroup, if it is not
    //! already fully enumerated.
    const_iterator_idempotents cend_idempotents() {
      init_idempotents();
      return const_iterator_pair_first(_idempotents.cend());
    }

   private:
    // Expand the data structures in the semigroup with space for nr elements
    void inline expand(index_t nr) {
      _left.add_rows(nr);
      _reduced.add_rows(nr);
      _right.add_rows(nr);
    }

    // Check if an element is the identity, x should be in the position pos
    // of _elements.
    void inline is_one(Element const* x, element_index_t pos) {
      if (!_found_one && *x == *_id) {
        _pos_one   = pos;
        _found_one = true;
      }
    }

    void copy_gens();

    void inline closure_update(element_index_t i,
                               letter_t        j,
                               letter_t        b,
                               element_index_t s,
                               index_t         old_nr,
                               size_t const&   thread_id);

    // Initialise the data member _sorted. We store a list of pairs consisting
    // of an Element* and element_index_t which is sorted on the first entry
    // using the operator< of the Element class. The second component is then
    // inverted (as a permutation) so that we can then find the position of an
    // element in the sorted list of elements.
    void init_sorted();

    typedef std::pair<Element const*, element_index_t> idempotent_value_t;

    // Find the idempotents and store their pointers and positions in a
    // std::pair of type idempotent_value_t.
    void init_idempotents();

    // Find the idempotents in the range [first, last) and store
    // the corresponding std::pair of type idempotent_value_t in the 4th
    // parameter. The parameter threshold is the point, calculated in
    // init_idempotents, at which it is better to simply multiply elements
    // rather than trace in the left/right Cayley graph.
    void idempotents(enumerate_index_t const          first,
                     enumerate_index_t const          last,
                     enumerate_index_t const          threshold,
                     std::vector<idempotent_value_t>& idempotents);

    size_t                                     _batch_size;
    element_index_t                            _degree;
    std::vector<std::pair<letter_t, letter_t>> _duplicate_gens;
    std::vector<Element const*>                _elements;
    std::vector<element_index_t>               _enumerate_order;
    std::vector<letter_t>                      _final;
    std::vector<letter_t>                      _first;
    bool                                       _found_one;
    std::vector<Element const*>                _gens;
    Element const*                             _id;
    std::vector<idempotent_value_t>            _idempotents;
    bool                                       _idempotents_found;
    std::vector<uint8_t>                       _is_idempotent;
    cayley_graph_t                             _left;
    std::vector<index_t>                       _length;
    std::vector<enumerate_index_t>             _lenindex;
    std::vector<element_index_t>               _letter_to_pos;
    std::unordered_map<Element const*,
                       element_index_t,
                       Element::Hash,
                       Element::Equal>
                                                            _map;
    size_t                                                  _max_threads;
    std::mutex                                              _mtx;
    index_t                                                 _nr;
    letter_t                                                _nrgens;
    size_t                                                  _nrrules;
    enumerate_index_t                                       _pos;
    element_index_t                                         _pos_one;
    std::vector<element_index_t>                            _prefix;
    flags_t                                                 _reduced;
    letter_t                                                _relation_gen;
    enumerate_index_t                                       _relation_pos;
    cayley_graph_t                                          _right;
    std::vector<std::pair<Element const*, element_index_t>> _sorted;
    std::vector<element_index_t>                            _suffix;
    Element*                                                _tmp_product;
    size_t                                                  _wordlen;

    static std::vector<element_index_t> _tmp_inverter;
    static std::vector<bool>            _old_new;
#ifdef LIBSEMIGROUPS_STATS
    size_t _nr_products;
#endif
  };

  //! This is just for backwards compatibility and should disappear at some
  //! point.
  typedef Semigroup::cayley_graph_t cayley_graph_t;

  template <typename T, typename C>
  C const Semigroup::iterator_base<T, C>::_methods;
}  // namespace libsemigroups

#endif  // LIBSEMIGROUPS_SRC_SEMIGROUPS_H_
