//
// 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/>.
//

#include "libsemigroups/felsch-tree.hpp"

#include <algorithm>                    // for binary_search, max
#include <cstddef>                      // for size_t
#include <iterator>                     // for distance
#include <vector>                       // for vector
                                        //
#include "libsemigroups/constants.hpp"  // for Undefined, UNDEFINED
#include "libsemigroups/debug.hpp"      // for LIBSEMIGROUPS_ASSERT
#include "libsemigroups/types.hpp"      // for letter_type

namespace libsemigroups {

  namespace detail {
    void FelschTree::init(size_t n) {
      _automata.reshape(n, 1);
      _automata.fill(0);
      _index  = {std::vector<index_type>({})};
      _parent = {state_type(UNDEFINED)};
      _length = 0;
    }

    void FelschTree::add_relations(word_iterator first, word_iterator last) {
      size_t number_of_words = 0;
      LIBSEMIGROUPS_ASSERT(std::distance(first, last) % 2 == 0);
      for (auto wit = first; wit != last; ++wit) {
        // For every prefix [wit->cbegin(), last)
        for (auto suffix = wit->cend(); suffix > wit->cbegin(); --suffix) {
          // For every suffix [prefix, suffix) of the prefix [wit->cbegin(),
          // suffix)
          for (auto prefix = wit->cbegin(); prefix < suffix; ++prefix) {
            // Find the maximal suffix of [prefix, suffix) that corresponds to
            // an existing state . . .
            auto       it = suffix - 1;
            state_type s  = initial_state;
            while (_automata.get(s, *it) != initial_state && it > prefix) {
              s = _automata.get(s, *it);
              --it;
            }
            if (_automata.get(s, *it) == initial_state) {
              // [it + 1, suffix) is the maximal suffix of [prefix, suffix)
              // that corresponds to the existing state s
              size_t number_of_states = _automata.number_of_rows();
              _automata.add_rows((it + 1) - prefix);
              _index.resize(_index.size() + ((it + 1) - prefix), {});
              _parent.resize(_parent.size() + ((it + 1) - prefix), UNDEFINED);
              while (it >= prefix) {
                // Add [it, suffix) as a new state
                _automata.set(s, *it, number_of_states);
                _parent[number_of_states] = s;
                s                         = number_of_states;
                number_of_states++;
                it--;
              }
            }
          }
          // Find the state corresponding to the prefix [wit->cbegin(),
          // suffix)
          auto       it = suffix - 1;
          state_type s  = initial_state;
          while (it >= wit->cbegin()) {
            s = _automata.get(s, *it);
            LIBSEMIGROUPS_ASSERT(s != initial_state);
            --it;
          }
          index_type m = ((number_of_words % 2) == 0 ? number_of_words
                                                     : number_of_words - 1);
          // The index corresponding to the side of the relation
          // corresponding to the prefix is what's pushed into _index[s], so
          // some care is required when using the return value of this.
          if (!std::binary_search(_index[s].cbegin(), _index[s].cend(), m)) {
            _index[s].push_back(number_of_words);
          }
        }
        number_of_words++;
      }
      LIBSEMIGROUPS_ASSERT(_index[0].empty());
    }

    bool FelschTree::push_front(letter_type x) {
      LIBSEMIGROUPS_ASSERT(x < _automata.number_of_cols());
      auto y = _automata.get(_current_state, x);
      if (y != initial_state) {
        _length++;
        _current_state = y;
        return true;
      } else {
        return false;
      }
    }

    size_t FelschTree::height() const noexcept {
      size_t result = 0;
      for (state_type s = 1; s < _parent.size(); ++s) {
        size_t     h = 0;
        state_type t = s;
        while (t != initial_state) {
          h++;
          t = _parent[t];
        }
        result = std::max(h, result);
      }
      return result;
    }

  }  // namespace detail
}  // namespace libsemigroups
