//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2022 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 a declaration of a class for performing the "low-index
// congruence" algorithm for semigroups and monoids.
// TODO(Sims1):
// * implement joins (HopcroftKarp), meets (not sure), containment (find join
//   and check equality)?
// * generating pairs for congruences defined by "action digraph"?
// * is 2-sided congruence method. One approach would be to compute the kernel
//   of the associated homomorphism, which is the largest 2-sided congruence
//   contained in the right congruence. Not sure if this is a good approach.
// * generally fix reporting
// * be useful to have output when no congruences are found too (i.e. in
//   Heineken group example). Can't really think of a nice way of doing this at
//   present in 2022.
// * the stats() object returned by Sims1 seems to not be populated
//
// Notes:
// 1. In 2022, when first writing this file, JDM tried templating the word_type
//    used by the presentations in Sims1 (so that we could use StaticVector1
//    for example, using smaller integer types for letters, and the stack to
//    hold the words rather than the heap), but this didn't seem to give any
//    performance improvement, and so I backed out the changes.

#ifndef LIBSEMIGROUPS_SIMS1_HPP_
#define LIBSEMIGROUPS_SIMS1_HPP_

#include <chrono>      // for high_resolution_clock
#include <cstddef>     // for size_t
#include <cstdint>     // for uint64_t, uint32_t
#include <functional>  // for function
#include <iostream>    // for ostream
#include <iterator>    // for forward_iterator_tag
#include <mutex>       // for mutex
#include <string>      // for operator+, basic_string
#include <thread>      // for thread, yield
#include <utility>     // for move
#include <vector>      // for vector

#ifdef LIBSEMIGROUPS_ENABLE_STATS
#include <algorithm>  // for max
#endif

#include "config.hpp"             // for LIBSEMIGROUPS_ENABLE_STATS
#include "constants.hpp"          // for UNDEFINED
#include "debug.hpp"              // for LIBSEMIGROUPS_ASSERT
#include "deprecated.hpp"         // for LIBSEMIGROUPS_DEPRECATED
#include "digraph.hpp"            // for ActionDigraph
#include "exception.hpp"          // for LIBSEMIGROUPS_EXCEPTION
#include "felsch-digraph.hpp"     // for FelschDigraph
#include "froidure-pin.hpp"       // for FroidurePin
#include "make-froidure-pin.hpp"  // for make
#include "make-present.hpp"       // for make
#include "present.hpp"            // for Presentation, Presentati...
#include "report.hpp"             // for REPORT_DEFAULT, Reporter
#include "stl.hpp"                // for JoinThreads
#include "timer.hpp"              // for Timer
#include "transf.hpp"             // for Transf
#include "types.hpp"              // for word_type, congruence_kind

namespace libsemigroups {

#ifdef LIBSEMIGROUPS_ENABLE_STATS
  // This isn't inside Sims1 because it doesn't depend on the template args at
  // all.
  //! Defined in ``sims1.hpp``.
  //!
  //! On this page we describe the `Sims1Stats` struct. The purpose of this
  //! class is to collect some statistics related to `Sims1` class template.
  //!
  //! \sa \ref Sims1
  struct Sims1Stats {
    //! The maximum number of pending definitions.
    //!
    //! A *pending definition* is just an edge that will be defined at some
    //! point in the future in the ActionDigraph represented by a Sims1
    //! instance at any given moment.
    //!
    //! This member tracks the maximum number of such pending definitions that
    //! occur during the running of the algorithms in Sims1.
    uint64_t max_pending = 0;

    //! The total number of pending definitions.
    //!
    //! A *pending definition* is just an edge that will be defined at some
    //! point in the future in the ActionDigraph represented by a Sims1
    //! instance at any given moment.
    //!
    //! This member tracks the total number of pending definitions that
    //! occur during the running of the algorithms in Sims1. This is the same
    //! as the number of nodes in the search tree encounter during the running
    //! of Sims1.
    uint64_t total_pending = 0;

    //! Combine two Sims1Stats objects
    //!
    //! This function changes this Sims1Stats in-place so that \ref max_pending
    //! is the maximum of `this->max_pending` and `that.max_pending`; and
    //! \ref total_pending is the sum of `this->total_pending` and
    //! `that.total_pending`.
    Sims1Stats& operator+=(Sims1Stats const& that) {
      max_pending = std::max(max_pending, that.max_pending);
      total_pending += that.total_pending;
      return *this;
    }
  };
#endif

  //! No doc
  // This class allows us to use the same interface for settings for Sims1,
  // RepOrc, and MinimalRepOrc without duplicating the code.
  template <typename T>
  class Sims1Settings {
   private:
    Presentation<word_type> _extra;
    Presentation<word_type> _longs;
    size_t                  _num_threads;
    size_t                  _report_interval;
    Presentation<word_type> _shorts;
#ifdef LIBSEMIGROUPS_ENABLE_STATS
    mutable Sims1Stats _stats;

#endif

   public:
    //! No doc
    Sims1Settings();

    //! No doc
    template <typename S>
    Sims1Settings(Sims1Settings<S> const& that);

    //! Returns the settings object of *this.
    //!
    //! The settings object contains all the settings that are common to
    //! `Sims1`, `RepOrc`, and `MinimalRepOrc`, which are currently:
    //! * \ref short_rules
    //! * \ref long_rules
    //! * \ref report_interval
    //! * \ref number_of_threads
    //! * \ref extra
    //!
    //! The return value of this function can be used to initialise another
    //! `Sims1`, `RepOrc`, or `MinimalRepOrc` with these settings.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns A const reference to `Sims1Settings`.
    //!
    //! \exceptions
    //! \noexcept
    // So that we can access the settings from the derived class T.
    Sims1Settings const& settings() const noexcept {
      return *this;
    }

    //! Copy the settings from \p that into `this`.
    //!
    //! The settings object contains all the settings that are common to
    //! `Sims1`, `RepOrc`, and `MinimalRepOrc`, which are currently:
    //! * \ref short_rules
    //! * \ref long_rules
    //! * \ref report_interval
    //! * \ref number_of_threads
    //! * \ref extra
    //!
    //! The return value of this function can be used to initialise another
    //! `Sims1`, `RepOrc`, or `MinimalRepOrc` with these settings.
    //!
    //! \param that the object to copy the settings from.
    //!
    //! \returns A const reference to `this`.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    T& settings(Sims1Settings const& that) {
      *this = that;
      return static_cast<T&>(*this);
    }

    //! \anchor number_of_threads
    //! Set the number of threads.
    //!
    //! This function sets the number of threads to be used by `Sims1`.
    //!
    //! The default value is `1`.
    //!
    //! \param val the maximum number of threads to use.
    //!
    //! \returns A reference to \c this.
    //!
    //! \throws LibsemigroupsException if the argument \p val is 0.
    //!
    //! \warning If \p val exceeds `std::thread::hardware_concurrency()`, then
    //! this is likely to have a negative impact on the performance of the
    //! algorithms implemented by `Sims1`.
    T& number_of_threads(size_t val);

    //! Returns the current number of threads.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns
    //! A `size_t`.
    //!
    //! \exceptions
    //! \noexcept
    size_t number_of_threads() const noexcept {
      return _num_threads;
    }

    //! \anchor report_interval
    //! Set the report interval.
    //!
    //! Only report after \p val new congruences have been found.
    //!
    //! The default value is `1999`.
    //!
    //! \param val the new value for the report interval.
    //!
    //! \returns A reference to \c this.
    //!
    //! \exceptions
    //! \noexcept
    T& report_interval(size_t val) noexcept {
      _report_interval = val;
      return static_cast<T&>(*this);
    }

    //! Returns the current report interval.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns
    //! A `size_t`.
    //!
    //! \exceptions
    //! \noexcept
    size_t report_interval() const noexcept {
      return _report_interval;
    }

    //! Set the short rules.
    //!
    //! These are the rules used at every node in the depth first search
    //! conducted by `Sims1`.
    //!
    //! If the template parameter \p P is not `Presentation<word_type>`, then
    //! the parameter \p p is first converted to a value of type
    //! `Presentation<word_type>` and it is this converted value that is used.
    //!
    //! \tparam P A specific value of the class template `Presentation`, must be
    //! derived from `PresentationBase`.
    //! \param p the presentation.
    //!
    //! \returns A reference to \c this.
    //!
    //! \throws LibsemigroupsException
    //! if `make<Presentation<word_type>>(p)` throws
    //! \throws LibsemigroupsException if `p` is not valid
    //! \throws LibsemigroupsException
    //! if the alphabet of `p` is non-empty and not equal to that of
    //! \ref long_rules or \ref extra.
    //! \throws LibsemigroupsException if `p` has 0-generators and 0-relations.
    template <typename P>
    T& short_rules(P const& p);

    //! \anchor short_rules
    //! Returns a const reference to the current short rules.
    //!
    //! This function returns the defining presentation of a `Sims1`
    //! instance. The congruences computed by \ref cbegin and \ref cend are
    //! defined over the semigroup or monoid defined by this presentation.
    //!
    //! Note that it might not be the case that the value returned by this
    //! function and the presentation used to construct the object are the
    //! same. A `Sims1` object requires the generators of the defining
    //! presentation \f$\mathcal{P}\f$ to be \f$\{0, \ldots, n - 1\}\f$ where
    //! \f$n\f$ is the size of the alphabet of \f$\mathcal{P}\f$. Every
    //! occurrence of every generator \c a in the presentation \c p used to
    //! construct a `Sims1` instance is replaced by `p.index(a)`.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns
    //! A const reference to `Presentation<word_type>`.
    //!
    //! \exceptions
    //! \noexcept
    //!
    //! \warning
    //! If \ref split_at or \ref long_rule_length have been called, then some of
    //! the defining relations may have been moved from \ref short_rules to
    //! \ref long_rules.
    Presentation<word_type> const& short_rules() const noexcept {
      return _shorts;
    }

#ifndef DOXYGEN_SHOULD_SKIP_THIS
    LIBSEMIGROUPS_DEPRECATED
    Presentation<word_type> const& presentation() const noexcept {
      return short_rules();
    }
#endif

    //! Set the long rules.
    //!
    //! These are the rules used after a complete deterministic word graph
    //! compatible with \ref short_rules has been found by `Sims1`. If such
    //! a word graph is compatible with the long rules specified by this
    //! function, then this word graph is accepted, and if not it is not
    //! accepted.
    //!
    //! If the template parameter \p P is not `Presentation<word_type>`, then
    //! the parameter \p p is first converted to a value of type
    //! `Presentation<word_type>` and it is this converted value that is used.
    //!
    //! \tparam P A specific value of the class template `Presentation`, must
    //! be derived from `PresentationBase`.
    //! \param p the presentation.
    //!
    //! \returns A reference to \c this.
    //!
    //! \throws LibsemigroupsException if `make<Presentation<word_type>>(p)`
    //! throws
    //! \throws LibsemigroupsException if `p` is not valid
    //! \throws LibsemigroupsException
    //! if the alphabet of `p` is non-empty and not equal to that of
    //! \ref short_rules or \ref extra.
    template <typename P>
    T& long_rules(P const& p);

    //! \anchor long_rules
    //! Returns the current long rules.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns
    //! A const reference to `Presentation<word_type>`.
    //!
    //! \exceptions
    //! \noexcept
    Presentation<word_type> const& long_rules() const noexcept {
      return _longs;
    }

    //! \anchor extra
    //! Returns a const reference to the additional defining pairs.
    //!
    //! The congruences computed by a Sims1 instance always contain the
    //! relations of this presentation. In other words, the congruences
    //! computed by this instance are only taken among those that contains the
    //! pairs of elements of the underlying semigroup (defined by the
    //! presentation returned by \ref short_rules and \ref long_rules)
    //! represented by the relations of the presentation returned by `extra()`.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns
    //! A const reference to `Presentation<word_type>`.
    //!
    //! \exceptions
    //! \noexcept
    Presentation<word_type> const& extra() const noexcept {
      return _extra;
    }

    //! Set the extra rules.
    //!
    //! The congruences computed by a `Sims1` instance always contain the
    //! relations of this presentation. In other words, the congruences
    //! computed by this instance are only taken among those that contains the
    //! pairs of elements of the underlying semigroup (defined by the
    //! presentation returned by \ref short_rules and \ref long_rules)
    //! represented by the relations of the presentation returned by `extra()`.
    //!
    //! If the template parameter \p P is not `Presentation<word_type>`, then
    //! the parameter \p p is first converted to a value of type
    //! `Presentation<word_type>` and it is this converted value that is used.
    //!
    //! \tparam P A specific value of the class template `Presentation`, must
    //! be derived from `PresentationBase`.
    //! \param p the presentation.
    //!
    //! \returns A reference to \c this.
    //!
    //! \throws LibsemigroupsException if `make<Presentation<word_type>>(p)`
    //! throws
    //! \throws LibsemigroupsException if `p` is not valid
    //! \throws LibsemigroupsException
    //! if the alphabet of `p` is non-empty and not equal to that of
    //! \ref short_rules or \ref long_rules.
    template <typename P>
    T& extra(P const& p);

#ifdef LIBSEMIGROUPS_ENABLE_STATS
    //! Returns a const reference to the current stats object.
    //!
    //! The value returned by this function is a `Sims1Stats` object which
    //! contains some statistics related to the current `Sims1` instance and
    //! any part of the depth first search already conducted. If reporting is
    //! disabled, then no statistics are collected. FIXME
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns
    //! A const reference to `Sims1Stats`.
    //!
    //! \exceptions
    //! \noexcept
    Sims1Stats const& stats() const noexcept {
      return _stats;
    }

   protected:
    T const& stats(Sims1Stats const& stts) const {
      _stats = std::move(stts);
      return static_cast<T const&>(*this);
    }
#endif

   public:
    //! \anchor long_rule_length
    //! Define the long rule length.
    //!
    //! This function modifies \ref short_rules and \ref long_rules so that
    //! \ref short_rules only contains those rules whose length (sum of the
    //! lengths of the two sides of the rules) is less than \p val (if any) and
    //! \ref long_rules only contains those rules of length at least \p val (if
    //! any). The rules contained in the union of \ref short_rules and
    //! \ref long_rules is invariant under this function, but the distribution
    //! of the rules between \ref short_rules and \ref long_rules is not.
    //!
    //! The relative orders of the rules within \ref short_rules and
    //! \ref long_rules may not be preserved.
    //!
    //! \param val the value of the long rule length.
    //!
    //! \returns
    //! A const reference to `this`.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    T& long_rule_length(size_t val);

    //! \anchor split_at
    //! Split the rules in \ref short_rules and \ref long_rules.
    //!
    //! This function splits the relations in \ref short_rules and \ref
    //! long_rules so that \ref short_rules contains the first `2 * val` rules
    //! and \ref long_rules contains any remaining rules.
    //!
    //! The order of the relations is the same as the current order.
    //!
    //! \param val the relation to split at.
    //!
    //! \returns (None)
    //!
    //! \throws LibsemigroupsException if \p val is out of bounds, i.e. if it
    //! exceeds `(short_rules().rules.size() + long_rules().rules.size()) / 2`.
    // TODO(v3) this should return T& for compatibility with the other mem
    // functions
    void split_at(size_t val);

   protected:
    void validate_presentation(Presentation<word_type> const& arg,
                               Presentation<word_type> const& existing);
  };

  //! Defined in ``sims1.hpp``.
  //!
  //! On this page we describe the functionality relating to the small index
  //! congruence algorithm. The algorithm implemented by this class template
  //! is essentially the low index subgroup algorithm for finitely presented
  //! groups described in Section 5.6 of [Computation with Finitely Presented
  //! Groups](https://doi.org/10.1017/CBO9780511574702) by C. Sims. The low
  //! index subgroups algorithm was adapted for semigroups and monoids by J.
  //! D. Mitchell and M. Tsalakou.
  //!
  //! The purpose of this class is to provide the functions \ref cbegin, \ref
  //! cend, \ref for_each, and \ref find_if which permit iterating through the
  //! one-sided congruences of a semigroup or monoid defined by a presentation
  //! containing (a possibly empty) set of pairs and with at most a given
  //! number of classes. An iterator returned by \ref cbegin points at an
  //! ActionDigraph instance containing the action of the semigroup or monoid
  //! on the classes of a congruence.
  // TODO(v3) remove the template T here
  template <typename T>
  class Sims1 : public Sims1Settings<Sims1<T>> {
   public:
    //! Type for the nodes in the associated ActionDigraph objects.
    using node_type = T;

    //! Type for letters in the underlying presentation.
    using letter_type = typename word_type::value_type;

    //! The size_type of the associated ActionDigraph objects.
    using size_type = typename ActionDigraph<node_type>::size_type;

    // We use ActionDigraph, even though the iterators produced by this class
    // hold FelschDigraph's, none of the features of FelschDigraph are useful
    // for the output, only for the implementation
    //! The type of the associated ActionDigraph objects.
    using digraph_type = ActionDigraph<node_type>;

   private:
    congruence_kind _kind;

    using Sims1Settings<Sims1>::validate_presentation;

   public:
#ifndef DOXYGEN_SHOULD_SKIP_THIS
    template <typename W>
    LIBSEMIGROUPS_DEPRECATED Sims1(congruence_kind ck, Presentation<W> p)
        : Sims1(ck) {
      short_rules(p);
    }

    template <typename W>
    LIBSEMIGROUPS_DEPRECATED Sims1(congruence_kind ck,
                                   Presentation<W> p,
                                   Presentation<W> e)
        : Sims1(ck, p) {
      extra(e);
    }
#endif

    //! Construct from \ref congruence_kind.
    //!
    //! \param ck the handedness of the congruences (left or right)
    //!
    //! \throws LibsemigroupsException if \p ck is \ref
    //! congruence_kind::twosided
    //!
    //! \sa \ref cbegin and \ref cend.
    explicit Sims1(congruence_kind ck);

    //! Default constructor - deleted!
    Sims1() = delete;

    //! Default copy constructor.
    Sims1(Sims1 const&) = default;

    //! Default move constructor.
    Sims1(Sims1&&) = default;

    //! Default copy assignment operator.
    Sims1& operator=(Sims1 const&) = default;

    //! Default move assignment operator.
    Sims1& operator=(Sims1&&) = default;

    // No doc
    ~Sims1();

#ifndef DOXYGEN_SHOULD_SKIP_THIS
    template <typename P>
    Sims1& short_rules(P const& p) {
      if (_kind == congruence_kind::left) {
        P q(p);
        presentation::reverse(q);
        return Sims1Settings<Sims1>::short_rules(q);
      } else {
        return Sims1Settings<Sims1>::short_rules(p);
      }
    }

    template <typename P>
    Sims1& long_rules(P const& p) {
      if (_kind == congruence_kind::left) {
        P q(p);
        presentation::reverse(q);
        return Sims1Settings<Sims1>::long_rules(q);
      } else {
        return Sims1Settings<Sims1>::long_rules(p);
      }
    }

    template <typename P>
    Sims1& extra(P const& p) {
      if (_kind == congruence_kind::left) {
        P q(p);
        presentation::reverse(q);
        return Sims1Settings<Sims1>::extra(q);
      } else {
        return Sims1Settings<Sims1>::extra(p);
      }
    }
#endif

    using Sims1Settings<Sims1>::short_rules;
    using Sims1Settings<Sims1>::long_rules;
    using Sims1Settings<Sims1>::extra;
    using Sims1Settings<Sims1>::number_of_threads;
    using Sims1Settings<Sims1>::report_interval;
#ifdef LIBSEMIGROUPS_ENABLE_STATS
    using Sims1Settings<Sims1>::stats;
#endif

   private:
    struct PendingDef {
      PendingDef() = default;

      PendingDef(node_type   s,
                 letter_type g,
                 node_type   t,
                 size_type   e,
                 size_type   n) noexcept
          : source(s), generator(g), target(t), num_edges(e), num_nodes(n) {}
      node_type   source;
      letter_type generator;
      node_type   target;
      size_type   num_edges;  // Number of edges in the graph when *this was
                              // added to the stack
      size_type num_nodes;    // Number of nodes in the graph after the
                              // definition is made
    };

    // This class collects some common aspects of the iterator and
    // thread_iterator nested classes. The Mutex does nothing for <iterator>
    // and is an actual std::mutex for <thread_iterator>.
    class iterator_base {
     public:
      //! No doc
      using const_reference =
          typename std::vector<digraph_type>::const_reference;

      //! No doc
      using const_pointer = typename std::vector<digraph_type>::const_pointer;

     private:
      Presentation<word_type> _extra;
      Presentation<word_type> _longs;
      size_type               _max_num_classes;
      size_type               _min_target_node;

     protected:
      // short_rules is stored in _felsch_graph
      FelschDigraph<word_type, node_type> _felsch_graph;
      // This mutex does nothing for iterator, only does something for
      // thread_iterator
      std::mutex              _mtx;
      std::vector<PendingDef> _pending;

#ifdef LIBSEMIGROUPS_ENABLE_STATS
      Sims1Stats _stats;
#endif

      // Push initial PendingDef's into _pending, see tpp file for
      // explanation.
      void init(size_type n);

      // We could use the copy constructor, but there's no point in copying
      // anything except the FelschDigraph and so we only copy that.
      void copy_felsch_graph(iterator_base const& that) {
        _felsch_graph = that._felsch_graph;
      }

      // Try to make the definition represented by PendingDef, returns false
      // if it wasn't possible, and true if it was.
      //! No doc
      bool try_define(PendingDef const&);

      // Try to pop from _pending into the argument (reference), returns true
      // if successful and false if not.
      bool try_pop(PendingDef&);

     public:
      //! No doc
      iterator_base(Presentation<word_type> const& p,
                    Presentation<word_type> const& e,
                    Presentation<word_type> const& f,
                    size_type                      n);

      // None of the constructors are noexcept because the corresponding
      // constructors for Presentation aren't currently

      //! No doc
      iterator_base() = default;

      // Intentionally don't copy the mutex, it doesn't compile, wouldn't make
      // sense if the mutex was used here.
      //! No doc
      iterator_base(iterator_base const& that);

      // Intentionally don't copy the mutex, it doesn't compile, wouldn't make
      // sense if the mutex was used here.
      //! No doc
      iterator_base(iterator_base&& that);

      // Intentionally don't copy the mutex, it doesn't compile, wouldn't make
      // sense if the mutex was used here.
      //! No doc
      iterator_base& operator=(iterator_base const& that);

      // Intentionally don't copy the mutex, it doesn't compile, wouldn't make
      // sense if the mutex was used here.
      //! No doc
      iterator_base& operator=(iterator_base&& that);

      //! No doc
      virtual ~iterator_base() = default;

      //! No doc
      bool operator==(iterator_base const& that) const noexcept {
        return _felsch_graph == that._felsch_graph;
      }

      //! No doc
      bool operator!=(iterator_base const& that) const noexcept {
        return !(operator==(that));
      }

      //! No doc
      const_reference operator*() const noexcept {
        return _felsch_graph;
      }

      //! No doc
      const_pointer operator->() const noexcept {
        return &_felsch_graph;
      }

      //! No doc
      void swap(iterator_base& that) noexcept;

#ifdef LIBSEMIGROUPS_ENABLE_STATS
      //! No doc
      Sims1Stats const& stats() const noexcept {
        return _stats;
      }
#endif
    };

   public:
    //! The return type of \ref cbegin and \ref cend.
    //!
    //! This is a forward iterator values of this type are expensive to copy
    //! due to their internal state, and prefix increment should be preferred
    //! to postfix.
    class iterator : public iterator_base {
      using iterator_base::init;
      using iterator_base::try_define;
      using iterator_base::try_pop;

     public:
      //! No doc
      using const_pointer = typename iterator_base::const_pointer;
      //! No doc
      using const_reference = typename iterator_base::const_reference;

      //! No doc
      using size_type = typename std::vector<digraph_type>::size_type;
      //! No doc
      using difference_type =
          typename std::vector<digraph_type>::difference_type;
      //! No doc
      using pointer = typename std::vector<digraph_type>::pointer;
      //! No doc
      using reference = typename std::vector<digraph_type>::reference;
      //! No doc
      using value_type = digraph_type;
      //! No doc
      using iterator_category = std::forward_iterator_tag;

      //! No doc
      using iterator_base::iterator_base;

     private:
      // Only want Sims1 to be able to use this constructor.
      iterator(Presentation<word_type> const& p,
               Presentation<word_type> const& e,
               Presentation<word_type> const& f,
               size_type                      n);

      // So that we can use the constructor above.
      friend iterator Sims1::cbegin(Sims1::size_type) const;
      friend iterator Sims1::cend(Sims1::size_type) const;

     public:
      //! No doc
      ~iterator() = default;

      // prefix
      //! No doc
      iterator const& operator++();

      // postfix
      //! No doc
      iterator operator++(int) {
        iterator copy(*this);
        ++(*this);
        return copy;
      }

      using iterator_base::swap;
    };  // class iterator

    //! Returns a forward iterator pointing at the first congruence.
    //!
    //! Returns a forward iterator pointing to the ActionDigraph representing
    //! the first congruence described by Sims1 object with at most \p n
    //! classes.
    //!
    //! If incremented, the iterator will point to the next such congruence.
    //! The order which the congruences are returned in is implementation
    //! specific. Iterators of the type returned by this function are equal
    //! whenever they point to equal objects. The iterator is exhausted if and
    //! only if it points to an ActionDigraph with zero nodes.
    //!
    //! The meaning of the ActionDigraph pointed at by Sims1 iterators depends
    //! on whether the input is a monoid presentation (i.e.
    //! Presentation::contains_empty_word() returns \c true) or a semigroup
    //! presentation. If the input is a monoid presentation for a monoid
    //! \f$M\f$, then the ActionDigraph pointed to by an iterator of this type
    //! has precisely \p n nodes, and the right action of \f$M\f$ on the nodes
    //! of the digraph is isomorphic to the action of \f$M\f$ on the classes
    //! of a right congruence.
    //!
    //! If the input is a semigroup presentation for a semigroup \f$S\f$, then
    //! the ActionDigraph has \p n + 1 nodes, and the right action of \f$S\f$
    //! on the nodes \f$\{1, \ldots, n\}\f$ of the ActionDigraph is isomorphic
    //! to the action of \f$S\f$ on the classes of a right congruence. It'd
    //! probably be better in this case if node \f$0\f$ was not included in
    //! the output ActionDigraph, but it is required in the implementation of
    //! the low-index congruence algorithm, and to avoid unnecessary copies,
    //! we've left it in for the time being. \param n the maximum number of
    //! classes in a congruence.
    //!
    //! \returns
    //! An iterator \c it of type \c iterator pointing to an
    //! ActionDigraph with at most \p n nodes.
    //!
    //! \throws LibsemigroupsException if \p n is \c 0.
    //! \throws LibsemigroupsException if `short_rules()` has 0-generators and
    //! 0-relations (i.e. it has not been initialised).
    //!
    //! \warning
    //! Copying iterators of this type is expensive.  As a consequence, prefix
    //! incrementing \c ++it the returned  iterator \c it significantly
    //! cheaper than postfix incrementing \c it++.
    //!
    //! \sa
    //! \ref cend
    // TODO(Sims1) it'd be good to remove node 0 to avoid confusion. This seems
    // complicated however, and so isn't done at present.
    iterator cbegin(size_type n) const {
      if (n == 0) {
        LIBSEMIGROUPS_EXCEPTION("the argument (size_type) must be non-zero");
      } else if (short_rules().rules.empty()
                 && short_rules().alphabet().empty()) {
        LIBSEMIGROUPS_EXCEPTION(
            "the short_rules() must be defined before calling this function");
      }
      return iterator(short_rules(), extra(), long_rules(), n);
    }

    //! Returns a forward iterator pointing one beyond the last congruence.
    //!
    //! Returns a forward iterator pointing to the empty ActionDigraph.
    //! If incremented, the returned iterator remains valid and continues to
    //! point at the empty ActionDigraph.
    //!
    //! \param n the maximum number of classes in a congruence.
    //!
    //! \returns
    //! An iterator \c it of type \c iterator pointing to an
    //! ActionDigraph with at most \p 0 nodes.
    //!
    //! \throws LibsemigroupsException if \p n is \c 0.
    //! \throws LibsemigroupsException if `short_rules()` has 0-generators and
    //! 0-relations (i.e. it has not been initialised).
    //!
    //! \warning
    //! Copying iterators of this type is expensive.  As a consequence, prefix
    //! incrementing \c ++it the returned  iterator \c it significantly
    //! cheaper than postfix incrementing \c it++.
    //!
    //! \sa
    //! \ref cbegin
    iterator cend(size_type n) const {
      if (n == 0) {
        LIBSEMIGROUPS_EXCEPTION("the argument (size_type) must be non-zero");
      } else if (short_rules().rules.empty()
                 && short_rules().alphabet().empty()) {
        LIBSEMIGROUPS_EXCEPTION(
            "the short_rules() must be defined before calling this function");
      }
      return iterator(short_rules(), extra(), long_rules(), 0);
    }

    //! Returns the number of one-sided congruences with up to a given number
    //! of classes.
    //!
    //! This function is similar to `std::distance(begin(n), end(n))` and
    //! exists to:
    //! * provide some feedback on the progress of the computation if it runs
    //! for more than 1 second.
    //! * allow for the computation of `std::distance(begin(n), end(n))` to be
    //! performed using \ref number_of_threads in parallel.
    //!
    //! \param n the maximum number of congruence classes.
    //!
    //! \returns A value of type \c uint64_t.
    //!
    //! \throws LibsemigroupsException if \p n is \c 0.
    //! \throws LibsemigroupsException if `short_rules()` has 0-generators and
    //! 0-relations (i.e. it has not been initialised).
    // TODO(v3): this should be in the sims1 helper namespace
    uint64_t number_of_congruences(size_type n) const;

    //! Apply the function \p pred to every one-sided congruence with at
    //! most \p n classes
    //!
    //! This function is similar to `std::for_each(begin(n), end(n), pred)`
    //! and exists to:
    //! * provide some feedback on the progress of the computation if it runs
    //! for more than 1 second.
    //! * allow for the computation of `std::for_each(begin(n), end(n), pred)`
    //! to be performed using \ref number_of_threads in parallel.
    //!
    //! \param n the maximum number of congruence classes.
    //! \param pred the predicate applied to every congruence found.
    //!
    //! \returns (None)
    //!
    //! \throws LibsemigroupsException if \p n is \c 0.
    //! \throws LibsemigroupsException if `short_rules()` has 0-generators and
    //! 0-relations (i.e. it has not been initialised).
    // TODO(v3): this should be in the sims1 helper namespace
    void for_each(size_type                                n,
                  std::function<void(digraph_type const&)> pred) const;

    //! Apply the function \p pred to every one-sided congruence with at
    //! most \p n classes, until it returns \c true.
    //!
    //! This function is similar to `std::find_if(begin(n), end(n), pred)` and
    //! exists to:
    //! * provide some feedback on the progress of the computation if it runs
    //! for more than 1 second.
    //! * allow for the computation of `std::find_if(begin(n), end(n), pred)`
    //! to be performed using \ref number_of_threads in parallel.
    //!
    //! \param n the maximum number of congruence classes.
    //! \param pred the predicate applied to every congruence found.
    //!
    //! \returns The first congruence whose ActionDigraph for which \p pred
    //! returns \c true.
    //!
    //! \throws LibsemigroupsException if \p n is \c 0.
    //! \throws LibsemigroupsException if `short_rules()` has 0-generators and
    //! 0-relations (i.e. it has not been initialised).
    // TODO(v3): this should be in the sims1 helper namespace
    digraph_type find_if(size_type                                n,
                         std::function<bool(digraph_type const&)> pred) const;

   private:
    using time_point = std::chrono::high_resolution_clock::time_point;

    static void report_at_start(Presentation<word_type> const& shorts,
                                Presentation<word_type> const& longs,
                                size_t                         num_classes,
                                size_t                         num_threads);

    template <typename S>
    static void report_number_of_congruences(uint64_t    report_interval,
                                             time_point& start_time,
                                             time_point& last_report,
                                             S&          last_count,
                                             uint64_t    count_now,
                                             std::mutex& mtx);

    void report_stats() const;

    static void final_report_number_of_congruences(time_point& start_time,
                                                   uint64_t    count);

    class thread_iterator;
    class thread_runner;
  };

#ifdef LIBSEMIGROUPS_ENABLE_STATS
  inline std::ostream& operator<<(std::ostream& os, Sims1Stats const& stats);
#endif  // LIBSEMIGROUPS_ENABLE_STATS

  //! Defined in ``sims1.hpp``.
  //!
  //! This class is a helper for `Sims1` calling the `digraph` member function
  //! attempts to find a right congruence, represented as an ActionDigraph, of
  //! the semigroup or monoid defined by the presentation consisting of its
  //! \ref short_rules and \ref long_rules with the following properties:
  //! * the transformation semigroup defined by the ActionDigraph has size
  //! \ref
  //!   target_size;
  //! * the number of nodes in the ActionDigraph is at least \ref min_nodes
  //!   and at most \ref max_nodes.
  //!
  //! If no such ActionDigraph can be found, then an empty ActionDigraph is
  //! returned (with `0` nodes and `0` edges).
  class RepOrc : public Sims1Settings<RepOrc> {
   private:
    size_t _min;
    size_t _max;
    size_t _size;

   public:
    //! Default constructor.
    RepOrc() : _min(0), _max(0), _size(0) {}

    //! Construct from Sims1 or MinimalRepOrc.
    //!
    //! This constructor creates a new RepOrc instance with the same
    //! Sims1Settings as \p s but that is otherwise uninitialised.
    //!
    //! \tparam S the type of the argument \p s (which is derived from
    //! `Sims1Settings<S>`).
    //!
    //! \param s the Sims1 or MinimalRepOrc whose settings should be used.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    template <typename S>
    explicit RepOrc(Sims1Settings<S> const& s)
        : Sims1Settings<RepOrc>(s), _min(0), _max(0), _size(0) {}

    //! Set the minimum number of nodes.
    //!
    //! This function sets the minimal number of nodes in the ActionDigraph
    //! that we are seeking.
    //!
    //! \param val the minimum number of nodes
    //!
    //! \returns A reference to `this`.
    //!
    //! \exceptions
    //! \noexcept
    RepOrc& min_nodes(size_t val) noexcept {
      _min = val;
      return *this;
    }

    //! The current minimum number of nodes.
    //!
    //! This function returns the current value for the minimum number of
    //! nodes in the ActionDigraph that we are seeking.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns A value of type `size_t`.
    //!
    //! \exceptions
    //! \noexcept
    size_t min_nodes() const noexcept {
      return _min;
    }

    //! Set the maximum number of nodes.
    //!
    //! This function sets the maximum number of nodes in the ActionDigraph
    //! that we are seeking.
    //!
    //! \param val the maximum number of nodes
    //!
    //! \returns A reference to `this`.
    //!
    //! \exceptions
    //! \noexcept
    RepOrc& max_nodes(size_t val) noexcept {
      _max = val;
      return *this;
    }

    //! The current maximum number of nodes.
    //!
    //! This function returns the current value for the maximum number of
    //! nodes in the ActionDigraph that we are seeking.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns A value of type `size_t`.
    //!
    //! \exceptions
    //! \noexcept
    size_t max_nodes() const noexcept {
      return _max;
    }

    //! Set the target size.
    //!
    //! This function sets the target size, i.e. the desired size of the
    //! transformation semigroup corresponding to the ActionDigraph returned
    //! by the function \ref digraph.
    //!
    //! \param val the target size.
    //!
    //! \returns A reference to `this`.
    //!
    //! \exceptions
    //! \noexcept
    RepOrc& target_size(size_t val) noexcept {
      _size = val;
      return *this;
    }

    //! The current target size.
    //!
    //! This function returns the current value for the target size, i.e. the
    //! desired size of the transformation semigroup corresponding to the
    //! ActionDigraph returned by the function \ref digraph.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns A value of type `size_t`.
    //!
    //! \exceptions
    //! \noexcept
    size_t target_size() const noexcept {
      return _size;
    }

    //! Get the digraph.
    //!
    //! This function attempts to find a right congruence, represented as an
    //! ActionDigraph, of the semigroup or monoid defined by the presentation
    //! consisting of its \ref short_rules and \ref long_rules with the
    //! following properties:
    //! * the transformation semigroup defined by the ActionDigraph has size
    //! \ref target_size;
    //! * the number of nodes in the ActionDigraph is at least \ref min_nodes
    //!   and at most \ref max_nodes.
    //!
    //! If no such ActionDigraph can be found, then an empty ActionDigraph is
    //! returned (with `0` nodes and `0` edges).
    //!
    //! \warning The return value of this function is recomputed every time it
    //! is called.
    //!
    //! \warning If the return value of \ref number_of_threads is greater than
    //! \c 1, then the value returned by this function is non-deterministic,
    //! and may vary even for the same parameters.
    //!
    //! \tparam T the type of the nodes in the returned digraph.
    //! \param (None) this function has no parameters.
    //!
    //! \returns A value of type `ActionDigraph`.
    //!
    //! \exceptions \no_libsemigroups_except
    template <typename T = uint32_t>
    ActionDigraph<T> digraph() const;

    using Sims1Settings<RepOrc>::short_rules;
    using Sims1Settings<RepOrc>::long_rules;
  };

  //! Defined in ``sims1.hpp``.
  //!
  //! This class is a helper for `Sims1`, calling the `digraph` member
  //! function attempts to find a right congruence, represented as an
  //! ActionDigraph, with the minimum possible number of nodes such that the
  //! action of the semigroup or monoid defined by the presentation consisting
  //! of its \ref short_rules and \ref long_rules on the nodes of the
  //! ActionDigraph corresponds to a semigroup of size \ref target_size.
  //!
  //! If no such ActionDigraph can be found, then an empty ActionDigraph is
  //! returned (with `0` nodes and `0` edges).
  class MinimalRepOrc : public Sims1Settings<MinimalRepOrc> {
   private:
    size_t _size;

   public:
#ifdef LIBSEMIGROUPS_ENABLE_STATS
    using Sims1Settings<MinimalRepOrc>::stats;
#endif

    //! Default constructor
    MinimalRepOrc() = default;

    //! Set the target size.
    //!
    //! This function sets the target size, i.e. the desired size of the
    //! transformation semigroup corresponding to the ActionDigraph returned
    //! by the function \ref digraph.
    //!
    //! \param val the target size.
    //!
    //! \returns A reference to `this`.
    //!
    //! \exceptions
    //! \noexcept
    MinimalRepOrc& target_size(size_t val) noexcept {
      _size = val;
      return *this;
    }

    //! The current target size.
    //!
    //! This function returns the current value for the target size, i.e. the
    //! desired size of the transformation semigroup corresponding to the
    //! ActionDigraph returned by the function \ref digraph.
    //!
    //! \param (None) this function has no parameters.
    //!
    //! \returns A value of type `size_t`.
    //!
    //! \exceptions
    //! \noexcept
    size_t target_size() const noexcept {
      return _size;
    }

    //! Get the digraph.
    //!
    //! This function attempts to find a right congruence, represented as an
    //! ActionDigraph, with the minimum possible number of nodes such that the
    //! action of the semigroup or monoid defined by the presentation
    //! consisting of its \ref short_rules and \ref long_rules on the nodes of
    //! the ActionDigraph corresponds to a semigroup of size \ref target_size.
    //!
    //! If no such ActionDigraph can be found, then an empty ActionDigraph is
    //! returned (with `0` nodes and `0` edges).
    //!
    //! The algorithm implemented by this function repeatedly runs:
    //! \code
    //! RepOrc(*this)
    //!     .min_nodes(1)
    //!     .max_nodes(best)
    //!     .target_size(target_size())
    //!     .digraph();
    //! \endcode
    //! where `best` is initially \ref target_size, until the returned
    //! ActionDigraph is empty, and then the penultimate ActionDigraph is
    //! returned (if any).
    //!
    //! \warning The return value of this function is recomputed every time it
    //! is called.
    //!
    //! \warning If the return value of \ref number_of_threads is greater than
    //! \c 1, then the value returned by this function is non-deterministic,
    //! and may vary even for the same parameters.
    //!
    //! \tparam T the type of the nodes in the returned digraph.
    //! \param (None) this function has no parameters.
    //!
    //! \returns A value of type `ActionDigraph`.
    //!
    //! \exceptions \no_libsemigroups_except
    template <typename T = uint32_t>
    ActionDigraph<T> digraph() const;
  };

}  // namespace libsemigroups

#include "sims1.tpp"

#endif  // LIBSEMIGROUPS_SIMS1_HPP_
