/*

Copyright © 2023-26 Sean Holden. All rights reserved.

*/
/*

This file is part of Connect++.

Connect++ 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.

Connect++ 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 Connect++. If not, see <https://www.gnu.org/licenses/>. 

*/

#ifndef UNIFIER_HPP
#define UNIFIER_HPP

#include<iostream>
#include<string>
#include<deque>

#include "Substitution.hpp"
#include "Literal.hpp"

using std::deque;
using std::pair;
using std::ostream;
using std::endl;
using std::string;

/**
* \brief The unification algorithm used deals fundamentally 
*        with pairs of Terms.
*/
using UPair = pair<Term* ,Term*>;
/**
* \brief Enumeration of possible outcomes from attempting 
*        unification.
*/
enum class UnificationOutcome {Succeed, ConflictFail, OccursFail};
/**
* \brief Obviously, you want to be able to print out unification 
*        outcomes. This is mostly for debugging purposes.
*/
ostream& operator<<(ostream&, const UnificationOutcome&);
/**
* \brief Wrap up various applications of unificiation into a 
*        single class: all the unification you need to do happens
*        here.
*
* You may find yourself unifying Terms, Literals and so on. This 
* puts it all in one place. The class is a function class with 
* various overloaded operator() methods.
*
* All the real work happens in the "complete_unification" and 
* "unify_terms" methods, depending on whether you're using the 
* traditional or polynomial time version.
*
* For the polynomial time version, see the Wikipedia page. 
* This is apparently due to Montelli and
* Montanari (1982). Looking at "An Efficient Unification Algorithm --
* yes, indeed it is. The algorithm as stated in the paper is
* nondeterministic, and apparently you get a bunch of different
* outcomes depending on the details of the implementation.
*
* So: this one uses a vector, taking something from the end and
* placing any results on the end as well. 
*
* The traditional version is based on Figure 1, page 454, 
* Handbook of Automated Reasoning Volume 1.
*
* Note that some modifications have to be made compared with the
* original presentations, because the method used to make 
* substitutions takes immediate effect. As a result, for example, 
* substitutions get removed by the Delete rule, so you might 
* as well remove them immediately. Also, when substituting x=? 
* you don't need to check whether x appears in the other equations.
*/
class Unifier {
private:
    /**
    * \brief Build the resulting substitution here.
    */
    Substitution s;
    /**
    * \brief Queue used by complete_unification method.
    */
    vector<UPair> to_do;
    /**
    * \brief Implementation of unification: for the polynomial-time 
    *        approach. All the real work happens here.
    */
    UnificationOutcome complete_unification();
public:
    /**
    * \brief There isn't really much to initialize.
    */
    Unifier() : s(), to_do() {}
    /**
    * \brief You really only need one Unifier!
    *
    * As always, copying is a bad idea so let the compiler help 
    * you out.
    */
    Unifier(const Unifier&) = delete;
    Unifier(const Unifier&&) = delete;
    Unifier& operator=(const Unifier&) = delete;
    Unifier& operator=(const Unifier&&) = delete;
    /**
    * \brief Trivial get methods for the result.
    */
    Substitution get_substitution() const { return s; }
    /**
    * \brief Implementation of unification for a pair of Terms.
    *
    * @param term1 Pointer to first term
    * @param term2 Pointer to second term
    */
    UnificationOutcome operator()(Term*, Term*);
    /**
    * \brief Implementation of unification for a pair of Terms.
    *
    * @param term1 Pointer to first term
    * @param term2 Pointer to second term
    */
    UnificationOutcome unify(Term*, Term*);
    /**
    * \brief Main method implementing the traditional unification 
    * algorithm.
    */
    UnificationOutcome unify_terms(Term*, Term*);
    /**
    * \brief The same as the main operator(), but you've already 
    *        extracted the arguments for the things of 
    *        interest into lists.
    *
    * @param t1s Reference to vector of pointers to Terms
    * @param t2s Reference to vector of pointers to Terms
    */
    UnificationOutcome operator()(const vector<Term*>&,
                                  const vector<Term*>&);
    /**
    * \brief Unification of Literals.
    *
    * @param l1 Pointer to first Literal
    * @param l2 Pointer to second Literal
    */
    UnificationOutcome operator()(Literal*, Literal*);
    /**
    * Unification of Literals.
    *
    * This is the version that is used most of the time, so there is 
    * an attempt here to optimize it.
    *
    * @param l1 Reference to first Literal
    * @param l2 Reference to second Literal
    */
    inline UnificationOutcome operator()(const Literal& l1, const Literal& l2) {
        if (params::poly_unification) {
            to_do.clear();
            size_t s = l1.get_arity();
            for (size_t i = 0; i < s; i++) {
                to_do.push_back(UPair(l1[i], l2[i]));
            }
            return complete_unification();
        }
        else {
            auto i = l1.get_args().begin();
            for (Term* term1 : l2.get_args()) {
                UnificationOutcome outcome = unify_terms(term1, *i);
                if (outcome != UnificationOutcome::Succeed) {
                    backtrack();
                    return outcome;
                }
                i++;
            }   
            return UnificationOutcome::Succeed;
        }
    }
    /**
    * \brief Apply the backtracking process to the 
    *        substitution that has been constructed.
    */
    void backtrack();
    /**
    * \brief Make a nice string representation.
    *
    * @param subbed Use substitutions if true.
    */
    string to_string(bool subbed = false) const {
        return s.to_string(subbed);
    }

    friend ostream& operator<<(ostream&, const Unifier&);
};

#endif
