/*

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

*/

#include "TableauProof.hpp"

//------------------------------------------------------------------
// TableauProofNode
//------------------------------------------------------------------
string TableauProofNode::path_to_tptp_string() {
    string s("[");
    vector<PathPair> p2(parent->p);

    for (int i = p2.size() - 1; i >= 0; i--) {
        if (i > 0) {
            s += "t";
        }
        s += to_string(p2[i].first);
        s += ":";
        s += to_string(p2[i].second);
        if (i > 0) {
            s += ",";
        }
    }
    s += "]";
    return s;
}
//------------------------------------------------------------------
string TableauProofNode::parent_to_tptp_string() {
    string s("t");
    PathPair p2(parent->p.back());
    s += to_string(p2.first);
    s += ":";
    s += to_string(p2.second);
    return s;
}
//------------------------------------------------------------------
string TableauProofNode::make_path_string() {
    string s;
    if (!params::tptp_proof_show_paths) {
        s += "parent(";
        s += parent_to_tptp_string();
    }
    else {
        s += "path(";
        s += path_to_tptp_string();
    }
    s += ")";
    return s;
}
//------------------------------------------------------------------
string TableauProofNode::lemmas_to_string() {
        string s;
        for (size_t l : lemma_list) {
            s += to_string(l);
            s += "\\ ";
        }
        return s;
    }
//------------------------------------------------------------------
// TableauProof
//------------------------------------------------------------------
void TableauProof::build() {
    for (size_t i = 0; i < stack_size; i++) {
        StackItem si ((*stack_p)[i]);
        Clause C;
        LitNum L;
        ClauseNum C2;
        LitNum L2;
        LitNum P;
        size_t lemma_index;
        Literal next_node_literal;
        Literal complementary_literal;
        Literal negated_literal;
        Literal removed_literal;
        size_t path_index;
        TableauProofNode* new_node;
        TableauProofNode* connection_node;
        TableauProofNode* false_node;
        Substitution s;
        Unifier u;
        size_t new_depth;
        UnificationOutcome u_result;
        Literal lemma_literal;
        switch (si.item_type) {
            case StackItemType::Start:
                matrix_p->make_copy_with_new_unique_vars(si.this_action.C_2, C, *vi, *ti);
                root = new TableauProofNode(C);
                current_node = root;
                current_node->p.push_back(PathPair(0,0));
                current_node->t_child = ++t_count;
                current_node->l_child++;
                current_node->matrix_id = si.this_action.C_2;
                clauses_used_in_proof.insert(matrix_p->get_name(si.this_action.C_2));
                break;
            case StackItemType::Axiom:
                break;
            case StackItemType::Reduction:
                L = si.this_action.Lindex;
                P = si.this_action.index_in_path;
                
                // Here we only need to remove the literal...
                next_node_literal = current_node->c_remaining[L];
                current_node->c_remaining.drop_literal(L);
                
                // ...make reduction and $false nodes ...
                connection_node = new TableauProofNode(next_node_literal);
                connection_node->parent = current_node;
                connection_node->type = TableauNodeType::Reduction;
                connection_node->depth = current_node->depth + 1;
           
                connection_node->lemma = new TableauProofNode(next_node_literal);
                connection_node->lemma->type = TableauNodeType::Lemma;
                connection_node->lemma->lemma_number = ++lemma_count;
                connection_node->lemma->parent = connection_node;

                connection_node->lemma_list = current_node->lemma_list;
                current_node->lemma_list.push_back(lemma_count);
                connection_node->lemma_list_p = current_node->lemma_list_p;
                current_node->lemma_list_p.push_back(connection_node->lemma);

                connection_node->t = current_node->t_child;
                connection_node->l = current_node->l_child;
                connection_node->p = current_node->p;
                connection_node->p.push_back(PathPair(connection_node->t, 
                                                  connection_node->l));
                current_node->l_child++;

                false_node = new TableauProofNode();
                false_node->parent = connection_node;
                false_node->depth = current_node->depth + 2;
                false_node->t = ++t_count;
                false_node->p = connection_node->p;
                false_node->p.push_back(PathPair(false_node->t, 0));
                false_node->needs_reduction_link = true;
                false_node->reduction_node = current_node->p[P+1];

                complementary_literal = current_node->path[P];
                negated_literal = complementary_literal;
                negated_literal.invert();
                u_result = u(negated_literal, next_node_literal);
                if (u_result != UnificationOutcome::Succeed || 
                    !next_node_literal.is_compatible_with(&negated_literal)) {
                        cerr << "There's a problem with your reduction unification." << endl;
                        exit(EXIT_FAILURE);
                }
                else {
                    false_node->sub = u.get_substitution();
                }
                current_node->children.push_back(connection_node);
                connection_node->children.push_back(false_node);
                break;
            case StackItemType::LeftBranch:
                L = si.this_action.Lindex;
                C2 = si.this_action.C_2;
                L2 = si.this_action.Lprime;
                
                // Remove from c_remaining the next literal to make a node for.
                next_node_literal = current_node->c_remaining[L];
                current_node->c_remaining.drop_literal(L);

                right_stack.push_back(current_node->depth);

                // Make the clause copy for the new node.
                matrix_p->make_copy_with_new_unique_vars(C2, C, *vi, *ti);
                //cout << C << endl;

                // Get the complementary literal and find the unification.
                complementary_literal = C[L2];
                negated_literal = complementary_literal;
                negated_literal.invert();
                u_result = u(negated_literal, next_node_literal);
                
                // Make a new node. removed_literal is the one needed 
                // for the child making a connection.
                new_node = new TableauProofNode(C, next_node_literal);
                if (u_result != UnificationOutcome::Succeed || 
                    !next_node_literal.is_compatible_with(&negated_literal)) {
                        cerr << "There's a problem with your extension unification." << endl;
                        exit(EXIT_FAILURE);
                }
                else {
                    new_node->sub = u.get_substitution();
                }
                new_node->parent = current_node;
                new_node->t = current_node->t_child;
                new_node->t_child = ++t_count;
                removed_literal = new_node->c_remaining[L2];
                new_node->c_remaining.drop_literal(L2);
                new_node->depth = current_node->depth + 1;
                new_node->path = current_node->path;
                new_node->path.push_back(next_node_literal);
        
                new_node->lemma = new TableauProofNode(next_node_literal);
                new_node->lemma->type = TableauNodeType::Lemma;
                new_node->lemma->lemma_number = ++lemma_count;
                new_node->lemma->parent = new_node;

                new_node->lemma_list = current_node->lemma_list;
                current_node->lemma_list.push_back(lemma_count);
                new_node->lemma_list_p = current_node->lemma_list_p;
                current_node->lemma_list_p.push_back(new_node->lemma);

                new_node->l = current_node->l_child;
                new_node->l_child = 1;
                new_node->matrix_id = C2;
                current_node->l_child++;
                new_node->p = current_node->p;
                new_node->p.push_back(PathPair(new_node->t, new_node->l));
                
                // Now set up the new child.
                current_node->children.push_back(new_node);
                
                // Now add the connection and $false nodes.
                connection_node = new TableauProofNode(complementary_literal);
                connection_node->parent = new_node;
                connection_node->depth = current_node->depth + 2;

                connection_node->t = t_count;
                connection_node->l = new_node->l_child;
                new_node->l_child++;
                connection_node->p = new_node->p;
                connection_node->p.push_back(PathPair(connection_node->t, 
                                                      connection_node->l));

                false_node = new TableauProofNode();
                false_node->parent = connection_node;
                false_node->depth = current_node->depth + 3;

                false_node->t = ++t_count;

                connection_node->children.push_back(false_node);
                new_node->children.push_back(connection_node);

                current_node = new_node;
                clauses_used_in_proof.insert(matrix_p->get_name(C2));
                break;
            case StackItemType::RightBranch:
                new_depth = right_stack.back();
                right_stack.pop_back();
                while (current_node->depth != new_depth) {
                    current_node = current_node->parent;
                }
                break;
            case StackItemType::Lemmata:
                L = si.this_action.Lindex;
                lemma_index = si.this_action.index_in_lemmata;

                next_node_literal = current_node->c_remaining[L];
                current_node->c_remaining.drop_literal(L);
                
                // ...make reduction and $false nodes ...
                connection_node = new TableauProofNode(next_node_literal);
                connection_node->parent = current_node;
                connection_node->depth = current_node->depth + 1;
                connection_node->t = current_node->t_child;
                connection_node->l = current_node->l_child;
                connection_node->p = current_node->p;
                connection_node->p.push_back(PathPair(connection_node->t, 
                                                  connection_node->l));
                
                current_node->l_child++;
                current_node->children.push_back(connection_node);

                lemma_literal = current_node->lemma_list_p[lemma_index]->lit;
                new_node = new TableauProofNode(lemma_literal);
                new_node->type = TableauNodeType::LemmaExtension;
                new_node->parent = connection_node;
                new_node->t = ++t_count;
                new_node->l = 1; 
                new_node->parent = connection_node;
                new_node->lemma_reduction_node = current_node->lemma_list[lemma_index];
                new_node->needs_lemma_link = true;
                new_node->p = connection_node->p;
                new_node->p.push_back(PathPair(new_node->t, 
                                                      new_node->l));

                // Possibly not needed, but include for now. Check unification works 
                // and store result. Remember not to apply it though!
                //lemma_literal.invert();
                u_result = u(next_node_literal, lemma_literal);
                if (u_result != UnificationOutcome::Succeed || 
                    !next_node_literal.is_compatible_with(&lemma_literal)) {
                    cerr << "There's a problem with your lemma unification." << endl;
                    exit(EXIT_FAILURE);
                }
                else {
                    new_node->sub = u.get_substitution();
                }
                u.backtrack();

                current_node->lemma_list_p[lemma_index]->lemma_is_used=true;

                connection_node->children.push_back(new_node);

                false_node = new TableauProofNode();
                false_node->parent = new_node;
                false_node->depth = current_node->depth + 2;
                false_node->t = ++t_count;
                false_node->p = connection_node->p;
                false_node->p.push_back(PathPair(false_node->t, 0));

                new_node->children.push_back(false_node);
                break;
            default:
                cerr << "Something is really wrong..." << endl;
                break;
        }
        
    }
}
//------------------------------------------------------------------
string TableauProof::tableau_to_tptp_string(TableauProofNode* p) const {
    string s;
    string s2;
    size_t lemma_b1;
    size_t lemma_b2;
    Literal inverted_literal;
    Clause reordered_clause;
    bool subbed = !params::tptp_proof_no_subs;
    switch(p->type) {
        case TableauNodeType::Start:
            s += "cnf(t1, plain,            ";
            for (TableauProofNode* q : p->children) {
                reordered_clause.add_lit(q->lit);
            }
            s += reordered_clause.to_tptp_string(subbed);
            s += ",\n    inference(start, [status(thm), parent(0:0)], [";
            s += matrix_p->get_name(p->matrix_id);
            s += "])  ).";
            s += "\n\n";
            for (TableauProofNode* q : p->children) {
                s += tableau_to_tptp_string(q);
            }
            break;
        case TableauNodeType::Extension:
            s += "cnf(t";
            s += to_string(p->t_child);
            s += ", plain,            ";
            for (TableauProofNode* q : p->children) {
                reordered_clause.add_lit(q->lit);
            }
            s += reordered_clause.to_tptp_string(subbed);
            s += ",\n    inference(extension, [status(thm), ";
            s += p->children[0]->make_path_string();
            if (params::tptp_proof_show_subs && p->sub.size() > 0) {
                s += ", ";
                s += p->sub.to_tptp_string(subbed);
            }
            s += "], [";
            s += matrix_p->get_name(p->matrix_id);
            s += "])  ).";
            s += "\n\n";
            for (TableauProofNode* q : p->children) {
                s += tableau_to_tptp_string(q);
            }
            s += tableau_to_tptp_string(p->lemma);
            break;
        case TableauNodeType::Connection:
            if (p->children[0]->type != TableauNodeType::LemmaExtension) {
                s += "cnf(t";
                s += to_string(p->children[0]->t);
                s += ", plain,            $false,\n";
                s += "    inference(connection, [status(thm), ";
                s += p->children[0]->make_path_string();
                s += "], [t";
                s += to_string(p->t);
                s += ":";
                s += to_string(p->l);
                s += ",t";
                s += to_string(p->parent->t);
                s += ":";
                s += to_string(p->parent->l);
                s += "])  ).";
                s += "\n\n";
            }
            else {
                s += tableau_to_tptp_string(p->children[0]);
            }
            break;
        case TableauNodeType::Reduction:
            s += "cnf(t";
            s += to_string(p->children[0]->t);
            s += ", plain,            $false,\n";
            s += "    inference(reduction, [status(thm), ";
            s += p->children[0]->make_path_string();
            if (params::tptp_proof_show_subs && p->sub.size() > 0) {
                s += ", ";
                s += p->sub.to_tptp_string(subbed);
            }
            s += "], [t";
            s += to_string(p->t);
            s += ":";
            s += to_string(p->l);
            s += ",t";
            s += to_string(p->children[0]->reduction_node.first);
            s += ":";
            s += to_string(p->children[0]->reduction_node.second);
            s += "])  ).";
            s += "\n\n";
            s += tableau_to_tptp_string(p->lemma);
            break;
        case TableauNodeType::Lemma:
            if (p != nullptr && p->lemma_is_used) {
                s += "cnf(l";
                s += to_string(p->lemma_number);
                s += ", lemma,            ";
                inverted_literal = p->lit;
                inverted_literal.invert();
                s += inverted_literal.to_tptp_string(subbed);
                s += ",\n    inference(lemma, [status(cth), ";
                s += p->make_path_string();
                s += ", below(";
                lemma_b1 = p->parent->parent->t;
                lemma_b2 = p->parent->parent->l;
                if (lemma_b1 != 0) {
                    s += "t"; 
                }
                s += to_string(lemma_b1);
                s += ":";
                s += to_string(lemma_b2);
                s += ")], [t";
                s += to_string(p->parent->t);
                s += ":";
                s += to_string(p->parent->l);
                s += "])  ).";
                s += "\n\n";
            }
            break;
        case TableauNodeType::LemmaExtension:
            s += "cnf(t";
            s += to_string(p->t);
            s += ", plain,            ";
            inverted_literal = p->lit;
            inverted_literal.invert();
            s += inverted_literal.to_tptp_string(subbed);    
            s += ",\n    inference(lemma_extension, [status(thm), ";
            s += p->make_path_string();
            //if (params::tptp_proof_show_subs && p->sub.size() > 0) {
            //    s += ", ";
            //    s += p->sub.to_tptp_string(subbed);
            //}
            s += "], [l";
            s += to_string(p->lemma_reduction_node);
            s += ":";
            s += to_string(1);
            s += "])  ).";
            s += "\n\n";
            
            s += "cnf(t";
            s += to_string(p->children[0]->t);
            s += ", plain,            $false,\n";
            s += "    inference(connection, [status(thm), ";
            s += p->children[0]->make_path_string();
            s += "], [t";
            s += to_string(p->t);
            s += ":";
            s += to_string(p->l);
            s += ",t";
            s += to_string(p->parent->t);
            s += ":";
            s += to_string(p->parent->l);
            s += "])  ).";
            s += "\n\n";
            break;
        case TableauNodeType::False:
            break;
        default:
            break;
    }
    return s;
}
//------------------------------------------------------------------
string TableauProof::lit_t_l_to_LaTeX(TableauProofNode* _p) const {
    string s;
    s += "{$";
    Literal literal = _p->lit;
    if (_p->type == TableauNodeType::LemmaExtension) {
        literal.invert();
    }
    s += literal.make_LaTeX(params::sub_LaTeX_proof);
    s += "$";
     if (params::latex_tableau_subs && _p->sub.size() > 0) {
        s += "\\\\ $";
        s += _p->sub.make_LaTeX(params::sub_LaTeX_proof);
        s += "$";
    }
    s += "\\\\ $[$t";
    s += to_string(_p->t);
    s += ":";
    s += to_string(_p->l);
    s += "$]$";
   
    s += "}, align=center, name=t";
    s += to_string(_p->t);
    s += "-";
    s += to_string(_p->l);
    return s;
}
//------------------------------------------------------------------
string TableauProof::tableau_to_LaTeX(TableauProofNode* p) const {
    string s;
    Literal inverted_literal;
    switch(p->type) {
        case TableauNodeType::Start:
            s += "[{[0:0]}, name=0-0";
            for (TableauProofNode* q : p->children) 
                s += tableau_to_LaTeX(q);
            s += "]";
            break;
        case TableauNodeType::Extension:
        case TableauNodeType::Reduction:
            s += "[";
            s += lit_t_l_to_LaTeX(p);
            for (TableauProofNode* q : p->children) 
                s += tableau_to_LaTeX(q);
            if (p->lemma != nullptr) 
                s += tableau_to_LaTeX(p->lemma);
            s += "]";
            break;
        case TableauNodeType::Connection:
        case TableauNodeType::LemmaExtension:
            s += "[";
            s += lit_t_l_to_LaTeX(p);
            for (TableauProofNode* q : p->children) 
                s += tableau_to_LaTeX(q);
            s += "]";
            break;
        case TableauNodeType::Lemma:
            if (p->lemma_is_used) {
                s += "[{$";
                inverted_literal = p->lit;
                inverted_literal.invert();
                s += inverted_literal.make_LaTeX(params::sub_LaTeX_proof);
                s += "$ \\\\ $[$l";
                s += to_string(p->lemma_number);
                s += "$]$}, align=center, ellipse, name=l";
                s += to_string(p->lemma_number);
                s += "]";
            }
            break;
        case TableauNodeType::False:
            s += "[{$\\ensuremath{\\mathtt{\\$false}}$ \\\\ $[$t";
            s += to_string(p->t);
            s += "$]$}, align=center, name=t";
            s += to_string(p->t);
            s += "]";
            break;
        default:
            break;
    }
    if (p->needs_reduction_link) {
        s += "{ \\draw[<-,dotted] () to[out=south,in=south] (t";
        s += to_string(p->reduction_node.first);
        s += "-";
        s += to_string(p->reduction_node.second);
        s += ");}";            
    }
    if (p->needs_lemma_link) {
        s += "{ \\draw[<-,dashed] () to[out=south west,in=east] (l";
        s += to_string(p->lemma_reduction_node);
        s += ");}";       
    }
    return s;
}
//------------------------------------------------------------------
string TableauProof::make_LaTeX() const {
    string s;
    s += "\\begin{forest}";
    s += "for tree=draw, for tree=rounded corners=5mm, for tree=thick";
    s += tableau_to_LaTeX(root);
    s += "\\end{forest}";
    return s;
}
//------------------------------------------------------------------
pair<string, string> TableauProof::lit_t_l_to_Graphviz(TableauProofNode* _p, 
                                         const string& _parent) const {
    string s;
    string name("\"t");
    Literal literal = _p->lit;
    if (_p->type == TableauNodeType::LemmaExtension) {
        literal.invert();
    }
    name += to_string(_p->t);
    name += "-";
    name += to_string(_p->l);
    name += "\"";
    s += name;
    s += " [style=filled, fillcolor=mintcream, fontname=Courier label=\"";
    s += literal.make_Graphviz(params::sub_Graphviz_proof);
    if (params::graphviz_tableau_subs && _p->sub.size() > 0) {
        s += "\n";
        s += _p->sub.make_Graphviz(params::sub_Graphviz_proof);
    }
    s += "\n[";
    s += to_string(_p->t);
    s += ":";
    s += to_string(_p->l);
    s += "]";
    s += "\"];\n";
    s += _parent;
    s += " -> ";
    s += name;
    s += " ;\n";
    return pair<string, string>(name, s);
}
//------------------------------------------------------------------
string TableauProof::tableau_to_Graphviz(TableauProofNode* p, 
                                         const string& _parent) const {
    string s;
    pair<string, string> sp;
    Literal inverted_literal;
    string name;
    switch(p->type) {
        case TableauNodeType::Start:
             s += "\"0-0\" [fontname=Courier label=\"[0:0]\"];\n";
            for (TableauProofNode* q : p->children) 
                s += tableau_to_Graphviz(q, "\"0-0\"");
            break;
        case TableauNodeType::Extension:
        case TableauNodeType::Reduction:
            sp = lit_t_l_to_Graphviz(p, _parent);
            name = sp.first;
            s += sp.second;
            for (TableauProofNode* q : p->children) 
                s += tableau_to_Graphviz(q, sp.first);
            if (p->lemma != nullptr) 
                s += tableau_to_Graphviz(p->lemma, sp.first);
            break;
        case TableauNodeType::Connection:
        case TableauNodeType::LemmaExtension:
            sp = lit_t_l_to_Graphviz(p, _parent);
            name = sp.first;
            s += sp.second;
            for (TableauProofNode* q : p->children) 
                s += tableau_to_Graphviz(q, sp.first);
            break;
        case TableauNodeType::Lemma:
            if (p->lemma_is_used) {
                inverted_literal = p->lit;
                inverted_literal.invert();
                name += "\"l";
                name += to_string(p->lemma_number);
                name += "\"";    
                s += name;
                s += " [style=filled, fillcolor=lightblue, shape=box, fontname=Courier label=\"";
                s += inverted_literal.make_Graphviz(params::sub_Graphviz_proof);
                s += "\nl";
                s += to_string(p->lemma_number);
                s += "\"];\n"; 
                s += _parent;
                s += "->";
                s += name;
                s += ";\n";
            } 
            break;
        case TableauNodeType::False:
            name += "\"t";
            name += to_string(p->t);
            name += "\"";
            s += name;
            s += " [style=filled, fillcolor=lightcyan, fontname=Courier label=\"$false\nt";
            s += to_string(p->t);
            s += "\"];\n";
            s += _parent;
            s += "->";
            s += name;
            s += ";\n";
            break;
        default:
            break;
    }
    if (p->needs_reduction_link) {
        s += "\"t";
        s += to_string(p->reduction_node.first);
        s += "-";
        s += to_string(p->reduction_node.second);
        s += "\" -> ";
        s += name;
        s += " [style=dashed];\n";            
    }
    if (p->needs_lemma_link) {
        s += "\"l";
        s += to_string(p->lemma_reduction_node);
        s += "\" -> ";
        s += name;
        s += " [style=dotted];\n";   
    }
    return s;
}
//------------------------------------------------------------------
string TableauProof::make_Graphviz(const path& problem_path) const {
    string s;
    s += "digraph \"";
    s += problem_path.filename().string();
    s += "\" {\n";
    s += "size=\"";
    s += std::to_string(params::graphviz_width);
    s += ",";
    s += std::to_string(params::graphviz_height);
    s += "\";\n";
    s += "labelloc=\"t\"\nlabel=\"Connect++ proof for: ";
    s += problem_path.filename().string();
    s += "\";\n";
    s += tableau_to_Graphviz(root);
    s += "\n}\n";
    return s;
}



