//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2019 James D. Mitchell
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

// This file contains the implementation of the class KnuthBendixImpl, which is
// the main implementation for the class KnuthBendix.

#ifndef LIBSEMIGROUPS_SRC_KNUTH_BENDIX_IMPL_HPP_
#define LIBSEMIGROUPS_SRC_KNUTH_BENDIX_IMPL_HPP_

#include <algorithm>    // for max, min
#include <atomic>       // for atomic
#include <cinttypes>    // for int64_t
#include <cstddef>      // for size_t
#include <limits>       // for numeric_limits
#include <list>         // for list, list<>::iterator
#include <ostream>      // for string
#include <set>          // for set
#include <stack>        // for stack
#include <string>       // for operator!=, basic_strin...
#include <type_traits>  // for swap
#include <utility>      // for pair
#include <vector>       // for vector

#ifdef LIBSEMIGROUPS_VERBOSE
#include <unordered_set>  // for unordered_set
#endif

#include "libsemigroups/config.hpp"        // for LIBSEMIGROUPS_DEBUG
#include "libsemigroups/constants.hpp"     // for POSITIVE_INFINITY
#include "libsemigroups/debug.hpp"         // for LIBSEMIGROUPS_ASSERT
#include "libsemigroups/knuth-bendix.hpp"  // for KnuthBendix, KnuthBendi...
#include "libsemigroups/order.hpp"         // for shortlex_compare
#include "libsemigroups/report.hpp"        // for REPORT
#include "libsemigroups/string.hpp"  // for detail::is_suffix, maximum_comm...
#include "libsemigroups/timer.hpp"   // for detail::Timer
#include "libsemigroups/types.hpp"   // for word_type

namespace libsemigroups {
  namespace detail {
    class KBE;  // Forward declarations
  }
  namespace fpsemigroup {

    class KnuthBendix::KnuthBendixImpl {
      ////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - typedefs/aliases - private
      ////////////////////////////////////////////////////////////////////////

      using external_string_type = std::string;
      using internal_string_type = std::string;
      using external_char_type   = char;
      using internal_char_type   = char;

      ////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - nested subclasses - private
      ////////////////////////////////////////////////////////////////////////

      // Rule and RuleLookup classes
      class Rule {
       public:
        // Construct from KnuthBendix with new but empty internal_string_type's
        explicit Rule(KnuthBendixImpl const* kbimpl, int64_t id)
            : _kbimpl(kbimpl),
              _lhs(new internal_string_type()),
              _rhs(new internal_string_type()),
              _id(-1 * id) {
          LIBSEMIGROUPS_ASSERT(_id < 0);
        }

        // The Rule class does not support an assignment constructor to avoid
        // accidental copying.
        Rule& operator=(Rule const& copy) = delete;

        // The Rule class does not support a copy constructor to avoid
        // accidental copying.
        Rule(Rule const& copy) = delete;

        // Destructor, deletes pointers used to create the rule.
        ~Rule() {
          delete _lhs;
          delete _rhs;
        }

        // Returns the left hand side of the rule, which is guaranteed to be
        // greater than its right hand side according to the reduction ordering
        // of the KnuthBendix used to construct this.
        internal_string_type* lhs() const {
          return _lhs;
        }

        // Returns the right hand side of the rule, which is guaranteed to be
        // less than its left hand side according to the reduction ordering of
        // the KnuthBendix used to construct this.
        internal_string_type* rhs() const {
          return _rhs;
        }

        void rewrite() {
          LIBSEMIGROUPS_ASSERT(_id != 0);
          _kbimpl->internal_rewrite(_lhs);
          _kbimpl->internal_rewrite(_rhs);
          // reorder if necessary
          if (shortlex_compare(_lhs, _rhs)) {
            std::swap(_lhs, _rhs);
          }
        }

        void clear() {
          LIBSEMIGROUPS_ASSERT(_id != 0);
          _lhs->clear();
          _rhs->clear();
        }

        inline bool active() const {
          LIBSEMIGROUPS_ASSERT(_id != 0);
          return (_id > 0);
        }

        void deactivate() {
          LIBSEMIGROUPS_ASSERT(_id != 0);
          if (active()) {
            _id *= -1;
          }
        }

        void activate() {
          LIBSEMIGROUPS_ASSERT(_id != 0);
          if (!active()) {
            _id *= -1;
          }
        }

        void set_id(int64_t id) {
          LIBSEMIGROUPS_ASSERT(id > 0);
          LIBSEMIGROUPS_ASSERT(!active());
          _id = -1 * id;
        }

        int64_t id() const {
          LIBSEMIGROUPS_ASSERT(_id != 0);
          return _id;
        }

        KnuthBendixImpl const* _kbimpl;
        internal_string_type*  _lhs;
        internal_string_type*  _rhs;
        int64_t                _id;
      };  // struct Rule

      // Simple class wrapping a two iterators to an internal_string_type and a
      // Rule const*

      class RuleLookup {
       public:
        RuleLookup() : _rule(nullptr) {}

        explicit RuleLookup(Rule* rule)
            : _first(rule->lhs()->cbegin()),
              _last(rule->lhs()->cend()),
              _rule(rule) {}

        RuleLookup& operator()(internal_string_type::iterator const& first,
                               internal_string_type::iterator const& last) {
          _first = first;
          _last  = last;
          return *this;
        }

        Rule const* rule() const {
          return _rule;
        }

        // This implements reverse lex comparison of this and that, which
        // satisfies the requirement of std::set that equivalent items be
        // incomparable, so, for example bcbc and abcbc are considered
        // equivalent, but abcba and bcbc are not.
        bool operator<(RuleLookup const& that) const {
          auto it_this = _last - 1;
          auto it_that = that._last - 1;
          while (it_this > _first && it_that > that._first
                 && *it_this == *it_that) {
            --it_that;
            --it_this;
          }
          return *it_this < *it_that;
        }

       private:
        internal_string_type::const_iterator _first;
        internal_string_type::const_iterator _last;
        Rule const*                          _rule;
      };  // class RuleLookup

      // Overlap measures
      struct OverlapMeasure {
        virtual size_t operator()(Rule const*,
                                  Rule const*,
                                  internal_string_type::const_iterator const&)
            = 0;
        virtual ~OverlapMeasure() {}
      };

      struct ABC : OverlapMeasure {
        size_t
        operator()(Rule const*                                 AB,
                   Rule const*                                 BC,
                   internal_string_type::const_iterator const& it) override {
          LIBSEMIGROUPS_ASSERT(AB->active() && BC->active());
          LIBSEMIGROUPS_ASSERT(AB->lhs()->cbegin() <= it);
          LIBSEMIGROUPS_ASSERT(it < AB->lhs()->cend());
          // |A| + |BC|
          return (it - AB->lhs()->cbegin()) + BC->lhs()->size();
        }
      };

      struct AB_BC : OverlapMeasure {
        size_t
        operator()(Rule const*                                 AB,
                   Rule const*                                 BC,
                   internal_string_type::const_iterator const& it) override {
          LIBSEMIGROUPS_ASSERT(AB->active() && BC->active());
          LIBSEMIGROUPS_ASSERT(AB->lhs()->cbegin() <= it);
          LIBSEMIGROUPS_ASSERT(it < AB->lhs()->cend());
          (void) it;
          // |AB| + |BC|
          return AB->lhs()->size() + BC->lhs()->size();
        }
      };

      struct MAX_AB_BC : OverlapMeasure {
        size_t
        operator()(Rule const*                                 AB,
                   Rule const*                                 BC,
                   internal_string_type::const_iterator const& it) override {
          LIBSEMIGROUPS_ASSERT(AB->active() && BC->active());
          LIBSEMIGROUPS_ASSERT(AB->lhs()->cbegin() <= it);
          LIBSEMIGROUPS_ASSERT(it < AB->lhs()->cend());
          (void) it;
          // max(|AB|, |BC|)
          return std::max(AB->lhs()->size(), BC->lhs()->size());
        }
      };

      //////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - friend declarations - private
      //////////////////////////////////////////////////////////////////////////

      friend class Rule;                          // defined in this file
      friend class ::libsemigroups::detail::KBE;  // defined in detail::kbe.hpp

     public:
      //////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - constructors + destructor - public
      //////////////////////////////////////////////////////////////////////////

      explicit KnuthBendixImpl(KnuthBendix* kb)
          : _active_rules(),
            _confluent(false),
            _confluence_known(false),
            _inactive_rules(),
            _internal_is_same_as_external(false),
            _contains_empty_string(false),
            _kb(kb),
            _min_length_lhs_rule(std::numeric_limits<size_t>::max()),
            _overlap_measure(nullptr),
            _stack(),
            _tmp_word1(new internal_string_type()),
            _tmp_word2(new internal_string_type()),
            _total_rules(0) {
        _next_rule_it1 = _active_rules.end();  // null
        _next_rule_it2 = _active_rules.end();  // null
        this->set_overlap_policy(options::overlap::ABC);
#ifdef LIBSEMIGROUPS_VERBOSE
        _max_stack_depth        = 0;
        _max_word_length        = 0;
        _max_active_word_length = 0;
        _max_active_rules       = 0;
#endif
      }

      ~KnuthBendixImpl() {
        delete _overlap_measure;
        delete _tmp_word1;
        delete _tmp_word2;
        for (Rule const* rule : _active_rules) {
          delete const_cast<Rule*>(rule);
        }
        for (Rule* rule : _inactive_rules) {
          delete rule;
        }
        while (!_stack.empty()) {
          Rule* rule = _stack.top();
          _stack.pop();
          delete rule;
        }
      }

     private:
      //////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - converting ints <-> string/char - private
      //////////////////////////////////////////////////////////////////////////

      static size_t internal_char_to_uint(internal_char_type c) {
#ifdef LIBSEMIGROUPS_DEBUG
        LIBSEMIGROUPS_ASSERT(c >= 97);
        return static_cast<size_t>(c - 97);
#else
        return static_cast<size_t>(c - 1);
#endif
      }

      static internal_char_type uint_to_internal_char(size_t a) {
        LIBSEMIGROUPS_ASSERT(
            a <= size_t(std::numeric_limits<internal_char_type>::max()));
#ifdef LIBSEMIGROUPS_DEBUG
        LIBSEMIGROUPS_ASSERT(
            a <= size_t(std::numeric_limits<internal_char_type>::max() - 97));
        return static_cast<internal_char_type>(a + 97);
#else
        return static_cast<internal_char_type>(a + 1);
#endif
      }

      static internal_string_type uint_to_internal_string(size_t i) {
        LIBSEMIGROUPS_ASSERT(
            i <= size_t(std::numeric_limits<internal_char_type>::max()));
        return internal_string_type({uint_to_internal_char(i)});
      }

      static word_type internal_string_to_word(internal_string_type const& s) {
        word_type w;
        w.reserve(s.size());
        for (internal_char_type const& c : s) {
          w.push_back(internal_char_to_uint(c));
        }
        return w;
      }

      static internal_string_type*
      word_to_internal_string(word_type const& w, internal_string_type* ww) {
        ww->clear();
        for (size_t const& a : w) {
          (*ww) += uint_to_internal_char(a);
        }
        return ww;
      }

      static internal_string_type word_to_internal_string(word_type const& u) {
        internal_string_type v;
        v.reserve(u.size());
        for (size_t const& l : u) {
          v += uint_to_internal_char(l);
        }
        return v;
      }

      internal_char_type external_to_internal_char(external_char_type c) const {
        LIBSEMIGROUPS_ASSERT(!_internal_is_same_as_external);
        return uint_to_internal_char(_kb->char_to_uint(c));
      }

      external_char_type internal_to_external_char(internal_char_type a) const {
        LIBSEMIGROUPS_ASSERT(!_internal_is_same_as_external);
        return _kb->uint_to_char(internal_char_to_uint(a));
      }

      void external_to_internal_string(external_string_type& w) const {
        if (_internal_is_same_as_external) {
          return;
        }
        for (auto& a : w) {
          a = external_to_internal_char(a);
        }
      }

      void internal_to_external_string(internal_string_type& w) const {
        if (_internal_is_same_as_external) {
          return;
        }
        for (auto& a : w) {
          a = internal_to_external_char(a);
        }
      }

     public:
      //////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - methods for rules - public
      //////////////////////////////////////////////////////////////////////////

      void add_rule(std::string const& p, std::string const& q) {
        LIBSEMIGROUPS_ASSERT(p != q);
        auto pp = new external_string_type(p);
        auto qq = new external_string_type(q);
        external_to_internal_string(*pp);
        external_to_internal_string(*qq);
        push_stack(new_rule(pp, qq));
      }

      void add_rules(KnuthBendixImpl const* impl) {
        for (Rule const* rule : impl->_active_rules) {
          add_rule(new_rule(rule));
        }
      }

      std::vector<std::pair<std::string, std::string>> rules() const {
        std::vector<std::pair<external_string_type, external_string_type>>
            rules;
        rules.reserve(_active_rules.size());
        for (Rule const* rule : _active_rules) {
          internal_string_type lhs = internal_string_type(*rule->lhs());
          internal_string_type rhs = internal_string_type(*rule->rhs());
          internal_to_external_string(lhs);
          internal_to_external_string(rhs);
          rules.emplace_back(lhs, rhs);
        }
        std::sort(
            rules.begin(),
            rules.end(),
            [](std::pair<external_string_type, external_string_type> rule1,
               std::pair<external_string_type, external_string_type> rule2) {
              return shortlex_compare(rule1.first, rule2.first)
                     || (rule1.first == rule2.first
                         && shortlex_compare(rule1.second, rule2.second));
            });
        return rules;
      }

      size_t number_of_rules() const {
        return _active_rules.size();
      }

     private:
      //////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - methods for rules - private
      //////////////////////////////////////////////////////////////////////////

      Rule* new_rule() const {
        ++_total_rules;
        Rule* rule;
        if (!_inactive_rules.empty()) {
          rule = _inactive_rules.front();
          rule->clear();
          rule->set_id(_total_rules);
          _inactive_rules.erase(_inactive_rules.begin());
        } else {
          rule = new Rule(this, _total_rules);
        }
        LIBSEMIGROUPS_ASSERT(!rule->active());
        return rule;
      }

      Rule* new_rule(internal_string_type* lhs,
                     internal_string_type* rhs) const {
        Rule* rule = new_rule();
        delete rule->_lhs;
        delete rule->_rhs;
        if (shortlex_compare(rhs, lhs)) {
          rule->_lhs = lhs;
          rule->_rhs = rhs;
        } else {
          rule->_lhs = rhs;
          rule->_rhs = lhs;
        }
        return rule;
      }

      Rule* new_rule(Rule const* rule1) const {
        Rule* rule2 = new_rule();
        rule2->_lhs->append(*rule1->lhs());  // copies lhs
        rule2->_rhs->append(*rule1->rhs());  // copies rhs
        return rule2;
      }

      Rule* new_rule(internal_string_type::const_iterator begin_lhs,
                     internal_string_type::const_iterator end_lhs,
                     internal_string_type::const_iterator begin_rhs,
                     internal_string_type::const_iterator end_rhs) const {
        Rule* rule = new_rule();
        rule->_lhs->append(begin_lhs, end_lhs);
        rule->_rhs->append(begin_rhs, end_rhs);
        return rule;
      }

      void add_rule(Rule* rule) {
        LIBSEMIGROUPS_ASSERT(*rule->lhs() != *rule->rhs());
#ifdef LIBSEMIGROUPS_VERBOSE
        _max_word_length  = std::max(_max_word_length, rule->lhs()->size());
        _max_active_rules = std::max(_max_active_rules, _active_rules.size());
        _unique_lhs_rules.insert(*rule->lhs());
#endif
        LIBSEMIGROUPS_ASSERT(_set_rules.emplace(RuleLookup(rule)).second);
#ifndef LIBSEMIGROUPS_DEBUG
        _set_rules.emplace(RuleLookup(rule));
#endif
        rule->activate();
        _active_rules.push_back(rule);
        if (_next_rule_it1 == _active_rules.end()) {
          --_next_rule_it1;
        }
        if (_next_rule_it2 == _active_rules.end()) {
          --_next_rule_it2;
        }
        _confluence_known = false;
        if (rule->lhs()->size() < _min_length_lhs_rule) {
          // TODO(later) this is not valid when using non-length reducing
          // orderings (such as RECURSIVE)
          _min_length_lhs_rule = rule->lhs()->size();
        }
        if (!_contains_empty_string) {
          _contains_empty_string = rule->lhs()->empty() || rule->rhs()->empty();
        }
        LIBSEMIGROUPS_ASSERT(_set_rules.size() == _active_rules.size());
      }

      std::list<Rule const*>::iterator
      remove_rule(std::list<Rule const*>::iterator it) {
#ifdef LIBSEMIGROUPS_VERBOSE
        _unique_lhs_rules.erase(*((*it)->lhs()));
#endif
        Rule* rule = const_cast<Rule*>(*it);
        rule->deactivate();
        if (it != _next_rule_it1 && it != _next_rule_it2) {
          it = _active_rules.erase(it);
        } else if (it == _next_rule_it1 && it != _next_rule_it2) {
          _next_rule_it1 = _active_rules.erase(it);
          it             = _next_rule_it1;
        } else if (it != _next_rule_it1 && it == _next_rule_it2) {
          _next_rule_it2 = _active_rules.erase(it);
          it             = _next_rule_it2;
        } else {
          _next_rule_it1 = _active_rules.erase(it);
          _next_rule_it2 = _next_rule_it1;
          it             = _next_rule_it1;
        }
#ifdef LIBSEMIGROUPS_DEBUG
        LIBSEMIGROUPS_ASSERT(_set_rules.erase(RuleLookup(rule)));
#else
        _set_rules.erase(RuleLookup(rule));
#endif
        LIBSEMIGROUPS_ASSERT(_set_rules.size() == _active_rules.size());
        return it;
      }

     public:
      bool contains_empty_string() const noexcept {
        return _contains_empty_string;
      }

      //////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - other methods - public
      //////////////////////////////////////////////////////////////////////////

      external_string_type* rewrite(external_string_type* w) const {
        external_to_internal_string(*w);
        internal_rewrite(w);
        internal_to_external_string(*w);
        return w;
      }

      bool equal_to(external_string_type const& u,
                    external_string_type const& v) {
        if (u == v) {
          return true;
        }
        external_string_type uu = _kb->rewrite(u);
        external_string_type vv = _kb->rewrite(v);
        if (uu == vv) {
          return true;
        }
        knuth_bendix();
        external_to_internal_string(uu);
        external_to_internal_string(vv);
        internal_rewrite(&uu);
        internal_rewrite(&vv);
        return uu == vv;
      }

      void set_overlap_policy(options::overlap p) {
        if (p == _kb->_settings._overlap_policy
            && _overlap_measure != nullptr) {
          return;
        }
        delete _overlap_measure;
        switch (p) {
          case options::overlap::ABC:
            _overlap_measure = new ABC();
            break;
          case options::overlap::AB_BC:
            _overlap_measure = new AB_BC();
            break;
          case options::overlap::MAX_AB_BC:
            _overlap_measure = new MAX_AB_BC();
            break;
          default:
            LIBSEMIGROUPS_ASSERT(false);
        }
      }

      void set_internal_alphabet(std::string const& lphbt = "") {
        _internal_is_same_as_external = true;
        for (size_t i = 0; i < lphbt.size(); ++i) {
          if (uint_to_internal_char(i) != lphbt[i]) {
            _internal_is_same_as_external = false;
            return;
          }
        }
      }

     private:
      //////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - other methods - private
      //////////////////////////////////////////////////////////////////////////
      // REWRITE_FROM_LEFT from Sims, p67
      // Caution: this uses the assumption that rules are length reducing, if it
      // is not, then u might not have sufficient space!
      void internal_rewrite(internal_string_type* u) const {
        if (u->size() < _min_length_lhs_rule) {
          return;
        }
        internal_string_type::iterator const& v_begin = u->begin();
        internal_string_type::iterator        v_end
            = u->begin() + _min_length_lhs_rule - 1;
        internal_string_type::iterator        w_begin = v_end;
        internal_string_type::iterator const& w_end   = u->end();

        RuleLookup lookup;

        while (w_begin != w_end) {
          *v_end = *w_begin;
          ++v_end;
          ++w_begin;

          auto it = _set_rules.find(lookup(v_begin, v_end));
          if (it != _set_rules.end()) {
            Rule const* rule = (*it).rule();
            if (rule->lhs()->size() <= static_cast<size_t>(v_end - v_begin)) {
              LIBSEMIGROUPS_ASSERT(detail::is_suffix(
                  v_begin, v_end, rule->lhs()->cbegin(), rule->lhs()->cend()));
              v_end -= rule->lhs()->size();
              w_begin -= rule->rhs()->size();
              detail::string_replace(
                  w_begin, rule->rhs()->cbegin(), rule->rhs()->cend());
            }
          }
          while (w_begin != w_end
                 && _min_length_lhs_rule - 1
                        > static_cast<size_t>((v_end - v_begin))) {
            *v_end = *w_begin;
            ++v_end;
            ++w_begin;
          }
        }
        u->erase(v_end - u->cbegin());
      }

      // TEST_2 from Sims, p76
      void clear_stack() {
        while (!_stack.empty() && !_kb->stopped()) {
#ifdef LIBSEMIGROUPS_VERBOSE
          _max_stack_depth = std::max(_max_stack_depth, _stack.size());
#endif

          Rule* rule1 = _stack.top();
          _stack.pop();
          LIBSEMIGROUPS_ASSERT(!rule1->active());
          LIBSEMIGROUPS_ASSERT(*rule1->lhs() != *rule1->rhs());
          // Rewrite both sides and reorder if necessary . . .
          rule1->rewrite();

          if (*rule1->lhs() != *rule1->rhs()) {
            internal_string_type const* lhs = rule1->lhs();
            for (auto it = _active_rules.begin(); it != _active_rules.end();) {
              Rule* rule2 = const_cast<Rule*>(*it);
              if (rule2->lhs()->find(*lhs) != external_string_type::npos) {
                it = remove_rule(it);
                LIBSEMIGROUPS_ASSERT(*rule2->lhs() != *rule2->rhs());
                // rule2 is added to _inactive_rules by clear_stack
                _stack.emplace(rule2);
              } else {
                if (rule2->rhs()->find(*lhs) != external_string_type::npos) {
                  internal_rewrite(rule2->rhs());
                }
                ++it;
              }
            }
            add_rule(rule1);
            // rule1 is activated, we do this after removing rules that rule1
            // makes redundant to avoid failing to insert rule1 in _set_rules
          } else {
            _inactive_rules.push_back(rule1);
          }
          if (_kb->report()) {
            REPORT_DEFAULT(
                "active rules = %d, inactive rules = %d, rules defined = "
                "%d\n",
                _active_rules.size(),
                _inactive_rules.size(),
                _total_rules);
            REPORT_VERBOSE_DEFAULT("max stack depth        = %d\n"
                                   "max word length        = %d\n"
                                   "max active word length = %d\n"
                                   "max active rules       = %d\n"
                                   "number of unique lhs   = %d\n",
                                   _max_stack_depth,
                                   _max_word_length,
                                   max_active_word_length(),
                                   _max_active_rules,
                                   _unique_lhs_rules.size());
          }
        }
      }
      // FIXME(later) there is a possibly infinite loop here clear_stack ->
      // push_stack -> clear_stack and so on
      void push_stack(Rule* rule) {
        LIBSEMIGROUPS_ASSERT(!rule->active());
        if (*rule->lhs() != *rule->rhs()) {
          _stack.emplace(rule);
          clear_stack();
        } else {
          _inactive_rules.push_back(rule);
        }
      }

      // OVERLAP_2 from Sims, p77
      void overlap(Rule const* u, Rule const* v) {
        LIBSEMIGROUPS_ASSERT(u->active() && v->active());
        auto limit
            = u->lhs()->cend() - std::min(u->lhs()->size(), v->lhs()->size());
        int64_t u_id = u->id();
        int64_t v_id = v->id();
        for (auto it = u->lhs()->cend() - 1;
             it > limit && u_id == u->id() && v_id == v->id() && !_kb->stopped()
             && (_kb->_settings._max_overlap == POSITIVE_INFINITY
                 || (*_overlap_measure)(u, v, it)
                        <= _kb->_settings._max_overlap);
             --it) {
          // Check if B = [it, u->lhs()->cend()) is a prefix of v->lhs()
          if (detail::is_prefix(
                  v->lhs()->cbegin(), v->lhs()->cend(), it, u->lhs()->cend())) {
            // u = P_i = AB -> Q_i and v = P_j = BC -> Q_j
            // This version of new_rule does not reorder
            Rule* rule = new_rule(u->lhs()->cbegin(),
                                  it,
                                  u->rhs()->cbegin(),
                                  u->rhs()->cend());  // rule = A -> Q_i
            rule->_lhs->append(*v->rhs());            // rule = AQ_j -> Q_i
            rule->_rhs->append(v->lhs()->cbegin() + (u->lhs()->cend() - it),
                               v->lhs()->cend());  // rule = AQ_j -> Q_iC
            // rule is reordered during rewriting in clear_stack
            push_stack(rule);
            // It can be that the iterator `it` is invalidated by the call to
            // push_stack (i.e. if `u` is deactivated, then rewritten, actually
            // changed, and reactivated) and that is the reason for the checks
            // in the for-loop above. If this is the case, then we should stop
            // considering the overlaps of u and v here, and note that they will
            // be considered later, because when the rule `u` is reactivated it
            // is added to the end of the active rules list.
          }
        }
      }

     public:
      //////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - main methods - public
      //////////////////////////////////////////////////////////////////////////

      bool confluent_known() const noexcept {
        return _confluence_known;
      }

      bool confluent() const {
        if (!_stack.empty()) {
          return false;
        }
        if (!_confluence_known && (!_kb->running() || !_kb->stopped())) {
          LIBSEMIGROUPS_ASSERT(_stack.empty());
          _confluent        = true;
          _confluence_known = true;
          internal_string_type word1;
          internal_string_type word2;
          size_t               seen = 0;

          for (auto it1 = _active_rules.cbegin();
               it1 != _active_rules.cend()
               && (!_kb->running() || !_kb->stopped());
               ++it1) {
            Rule const* rule1 = *it1;
            // Seems to be much faster to do this in reverse.
            for (auto it2 = _active_rules.crbegin();
                 it2 != _active_rules.crend()
                 && (!_kb->running() || !_kb->stopped());
                 ++it2) {
              seen++;
              Rule const* rule2 = *it2;
              for (auto it = rule1->lhs()->cend() - 1;
                   it >= rule1->lhs()->cbegin()
                   && (!_kb->running() || !_kb->stopped());
                   --it) {
                // Find longest common prefix of suffix B of rule1.lhs() defined
                // by it and R = rule2.lhs()
                auto prefix
                    = detail::maximum_common_prefix(it,
                                                    rule1->lhs()->cend(),
                                                    rule2->lhs()->cbegin(),
                                                    rule2->lhs()->cend());
                if (prefix.first == rule1->lhs()->cend()
                    || prefix.second == rule2->lhs()->cend()) {
                  word1.clear();
                  word1.append(rule1->lhs()->cbegin(), it);          // A
                  word1.append(*rule2->rhs());                       // S
                  word1.append(prefix.first, rule1->lhs()->cend());  // D

                  word2.clear();
                  word2.append(*rule1->rhs());                        // Q
                  word2.append(prefix.second, rule2->lhs()->cend());  // E

                  if (word1 != word2) {
                    internal_rewrite(&word1);
                    internal_rewrite(&word2);
                    if (word1 != word2) {
                      _confluent = false;
                      return _confluent;
                    }
                  }
                }
              }
            }
            if (_kb->report()) {
              REPORT_DEFAULT("checked %llu pairs of overlaps out of %llu\n",
                             uint64_t(seen),
                             uint64_t(_active_rules.size())
                                 * uint64_t(_active_rules.size()));
            }
          }
          if (_kb->running() && _kb->stopped()) {
            _confluence_known = false;
          }
        }
        return _confluent;
      }

      // KBS_2 from Sims, p77-78
      bool knuth_bendix() {
        detail::Timer timer;
        if (_stack.empty() && confluent() && !_kb->stopped()) {
          // _stack can be non-empty if non-reduced rules were used to define
          // the KnuthBendix.  If _stack is non-empty, then it means that the
          // rules in _active_rules might not define the system.
          REPORT_DEFAULT("the system is confluent already\n");
          return true;
        } else if (_active_rules.size() >= _kb->_settings._max_rules) {
          REPORT_DEFAULT("too many rules\n");
          return false;
        }
        // Reduce the rules
        _next_rule_it1 = _active_rules.begin();
        while (_next_rule_it1 != _active_rules.end() && !_kb->stopped()) {
          // Copy *_next_rule_it1 and push_stack so that it is not modified by
          // the call to clear_stack.
          LIBSEMIGROUPS_ASSERT((*_next_rule_it1)->lhs()
                               != (*_next_rule_it1)->rhs());
          push_stack(new_rule(*_next_rule_it1));
          ++_next_rule_it1;
        }
        _next_rule_it1 = _active_rules.begin();
        size_t nr      = 0;
        while (_next_rule_it1 != _active_rules.cend()
               && _active_rules.size() < _kb->_settings._max_rules
               && !_kb->stopped()) {
          Rule const* rule1 = *_next_rule_it1;
          _next_rule_it2    = _next_rule_it1;
          ++_next_rule_it1;
          overlap(rule1, rule1);
          while (_next_rule_it2 != _active_rules.begin() && rule1->active()) {
            --_next_rule_it2;
            Rule const* rule2 = *_next_rule_it2;
            overlap(rule1, rule2);
            ++nr;
            if (rule1->active() && rule2->active()) {
              ++nr;
              overlap(rule2, rule1);
            }
          }
          if (nr > _kb->_settings._check_confluence_interval) {
            if (confluent()) {
              break;
            }
            nr = 0;
          }
          if (_next_rule_it1 == _active_rules.cend()) {
            clear_stack();
          }
        }
        // LIBSEMIGROUPS_ASSERT(_stack.empty());
        // Seems that the stack can be non-empty here in KnuthBendix 12, 14, 16
        // and maybe more
        bool ret;
        if (_kb->_settings._max_overlap == POSITIVE_INFINITY
            && _kb->_settings._max_rules == POSITIVE_INFINITY
            && !_kb->stopped()) {
          _confluence_known = true;
          _confluent        = true;
          for (Rule* rule : _inactive_rules) {
            delete rule;
          }
          _inactive_rules.clear();
          ret = true;
        } else {
          ret = false;
        }

        REPORT_DEFAULT("stopping with active rules = %d, inactive rules = %d, "
                       "rules defined = %d\n",
                       _active_rules.size(),
                       _inactive_rules.size(),
                       _total_rules);
        REPORT_VERBOSE_DEFAULT("max stack depth = %d", _max_stack_depth);
        REPORT_TIME(timer);
        return ret;
      }

      void knuth_bendix_by_overlap_length() {
        detail::Timer timer;
        size_t        max_overlap = _kb->_settings._max_overlap;
        size_t        check_confluence_interval
            = _kb->_settings._check_confluence_interval;
        _kb->_settings._max_overlap               = 1;
        _kb->_settings._check_confluence_interval = POSITIVE_INFINITY;
        while (!_kb->stopped() && !confluent()) {
          knuth_bendix();
          _kb->_settings._max_overlap++;
        }
        _kb->_settings._max_overlap               = max_overlap;
        _kb->_settings._check_confluence_interval = check_confluence_interval;
        REPORT_TIME(timer);
      }

     private:
      ////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - iterators - private
      ////////////////////////////////////////////////////////////////////////

      using external_rule_type
          = std::pair<external_string_type, external_string_type>;

      struct IteratorMethods {
        external_rule_type
        indirection(KnuthBendixImpl*                       kbi,
                    std::list<Rule const*>::const_iterator it) const {
          auto lhs = std::string(*(*it)->lhs());
          auto rhs = std::string(*(*it)->rhs());
          kbi->internal_to_external_string(lhs);
          kbi->internal_to_external_string(rhs);

          return std::make_pair(lhs, rhs);
        }

        // Not defined!
        external_rule_type const*
        addressof(KnuthBendixImpl*,
                  std::list<Rule const*>::const_iterator) const {
          return nullptr;
        }
      };

     public:
      ////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - iterators - public
      ////////////////////////////////////////////////////////////////////////

      /*using const_iterator = detail::ConstIteratorStateful<
          KnuthBendixImpl const*,                  // state
          IteratorMethods,                        // methods
          std::list<Rule const*>::const_iterator,  // wrapped iterator
          external_rule_type,                      // external value type
          external_rule_type,                      // external const pointer
          external_rule_type&&                     // external const reference
          >;

      const_iterator cbegin_active_rules() const {
        return const_iterator(this, _active_rules.cbegin());
      }

      const_iterator cend_active_rules() const {
        return const_iterator(this, _active_rules.cend());
      }*/

     private:
      ////////////////////////////////////////////////////////////////////////
      // KnuthBendixImpl - data - private
      ////////////////////////////////////////////////////////////////////////

      std::list<Rule const*>           _active_rules;
      mutable std::atomic<bool>        _confluent;
      mutable std::atomic<bool>        _confluence_known;
      mutable std::list<Rule*>         _inactive_rules;
      bool                             _internal_is_same_as_external;
      bool                             _contains_empty_string;
      KnuthBendix*                     _kb;
      size_t                           _min_length_lhs_rule;
      std::list<Rule const*>::iterator _next_rule_it1;
      std::list<Rule const*>::iterator _next_rule_it2;
      OverlapMeasure*                  _overlap_measure;
      std::set<RuleLookup>             _set_rules;
      std::stack<Rule*>                _stack;
      internal_string_type*            _tmp_word1;
      internal_string_type*            _tmp_word2;
      mutable size_t                   _total_rules;

#ifdef LIBSEMIGROUPS_VERBOSE
      //////////////////////////////////////////////////////////////////////////
      // ./configure --enable-verbose functions
      //////////////////////////////////////////////////////////////////////////

      size_t max_active_word_length() {
        auto comp = [](Rule const* p, Rule const* q) -> bool {
          return p->lhs()->size() < q->lhs()->size();
        };
        auto max = std::max_element(
            _active_rules.cbegin(), _active_rules.cend(), comp);
        if (max != _active_rules.cend()) {
          _max_active_word_length
              = std::max(_max_active_word_length, (*max)->lhs()->size());
        }
        return _max_active_word_length;
      }
      size_t                                   _max_stack_depth;
      size_t                                   _max_word_length;
      size_t                                   _max_active_word_length;
      size_t                                   _max_active_rules;
      std::unordered_set<internal_string_type> _unique_lhs_rules;
#endif
    };  // struct KnuthBendixImpl
  }     // namespace fpsemigroup
}  // namespace libsemigroups
#endif  // LIBSEMIGROUPS_SRC_KNUTH_BENDIX_IMPL_HPP_
