/*

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 TPTPRECORDS_HPP
#define TPTPRECORDS_HPP

#include<iostream>
#include<string>
#include<vector>
#include<filesystem>
#include<unordered_set>

#include "Parameters.hpp"
#include "Clause.hpp"

namespace fs = std::filesystem;

using std::string;
using std::vector;
using std::cerr;
using std::endl;
using std::to_string;
using std::unordered_set;

/*
* System for allowing the construction of TPTP-formatted proofs. This 
* turns out to be quite challenging because of the level of detail 
* involved.
*
* There are quite a lot of different places where different things  
* are needed to add information to a TPTP-formatted proof.
*
* Maintaining a single TPTPRecords aims to simplify this - just add the 
* basic info as you progress and leave the formatting until the end.
*/

/** 
* Various different kinds of record for different parts of a proof.
* This is likely to expand over time.
*/
enum class TPTPRecordType {Empty, 
                           FileCNF, 
                           FileFOF, 
                           Introduced, 
                           Inference,
                           Skolem,
                           Theory,
                           TFClause};

/**
* Nothing much to see here, just the usual function for printing 
* TPTPRecordType.
*/
ostream& operator<<(ostream&, const TPTPRecordType&);
/**
* \brief Class representing individual TPTP records of different kinds.
*
* There's quite a lot of information here as we're covering all 
* possibilities - some things will be unused, depending on what's 
* being represented. OK, could have done this with inheritance  
* but I prefer it this way.
*/
class TPTPRecord {
private:
    /**
    * \brief Type of TPTPRecord.
    */
    TPTPRecordType type;
    /**
    * \brief Unique name identifying this proof step.
    */
    string name;
    /**
    * \brief Things like clause conversions with construct the 
    *        unique name as "name_index_1_index_2".
    */
    size_t index_1;
    /**
    * \brief Things like clause conversions with construct the 
    *        unique name as "name_index_1_index_2".
    */
    size_t index_2;
    /**
    * \brief The role of the proof step.
    */
    string role;
    /**
    * \brief One or more sources from which the proof step was 
    *        derived.
    */
    vector<string> source;
    /**
    * \brief This is pretty general, and used to store specific 
    *        details to be included in the output string.
    */
    string info;
    /**
    * \brief Allows lookup of names for records corresponding to 
    *        FileCNF.
    */
    size_t clause_file_number;
    /**
    * \brief Allows lookup of names for records corresponding to 
    *        FileFOF.
    */
    size_t fof_file_number;
    /**
    * \brief Does f represent a clause?
    */
    bool has_clause;
    /**
    * \brief Does f represent a formula?
    */
    bool has_formula;
    /**
    * \brief String copy of the clause or formula involved.
    */
    string f;
    /**
    * \brief Some records record the filename from which the contents 
    *        were taken. This indicates whether that's the case.
    */
    bool has_path;
    /**
    * \brief Filename from which the contents were taken.
    */
    fs::path path;
public:
    /**
    * \brief Pretty much all the work is done by constructors 
    *        as this really only stores basic information for 
    *        formatting later.
    */
    TPTPRecord(const string& _name, 
               const string& _role) 
    : type(TPTPRecordType::Empty)
    , name(_name)
    , index_1(0)
    , index_2(0)
    , role(_role)
    , source()
    , info()
    , clause_file_number(0)
    , fof_file_number(0)
    , has_clause(false)
    , has_formula(false)
    , f()
    , has_path(false)
    , path()
    {} 
    /**
    * \brief Pretty much all the work is done by constructors 
    *        as this really only stores basic information for 
    *        formatting later.
    */
    TPTPRecord(const string& _name,
               size_t _index_1, 
               const string& _info, 
               const string& _c) 
    : TPTPRecord(_name, "axiom")
    {
            type = TPTPRecordType::Theory;
            index_1 = _index_1;
            info = _info;
            has_clause = true;
            f = _c;
            source.push_back(make_full_name());
    }
    /**
    * \brief Pretty much all the work is done by constructors 
    *        as this really only stores basic information for 
    *        formatting later.
    */
    TPTPRecord(const string& _name,
               size_t _index_1, 
               size_t _index_2,
               const string& _role, 
               const string& _f,
               const string& _source, 
               const string& _info) 
    : TPTPRecord(_name, _role)
    {
            type = TPTPRecordType::Skolem;
            index_1 = _index_1;
            index_2 = _index_2;
            source.push_back(_source);
            f = _f;
            info = _info;
    }
    /**
    * \brief Pretty much all the work is done by constructors 
    *        as this really only stores basic information for 
    *        formatting later.
    */
    TPTPRecord(const string& _name,
               size_t _index_1, 
               size_t _index_2,
               const string& _role, 
               const string& _info,
               bool cnf_or_fof,
               const string& _f,
               const string& _source) 
    : TPTPRecord(_name, _role)
    {
            type = TPTPRecordType::Inference;
            index_1 = _index_1;
            index_2 = _index_2;
            info = _info;
            cnf_or_fof ? has_formula = true : has_clause = true;
            f = _f;
            source.push_back(_source);
    }
    /**
    * \brief Pretty much all the work is done by constructors 
    *        as this really only stores basic information for 
    *        formatting later.
    */
    TPTPRecord(const TPTPRecordType _type,
              size_t _index,
              const string& _name, 
              const string& _role, 
              const string& _f,
              const fs::path& _path) 
    : TPTPRecord(_name, _role) 
    {
            info = _name;
            type = _type;
            has_path = true;
            path = _path;
            f = _f;
            source.push_back(_name);
            if (_type == TPTPRecordType::FileFOF) {
                has_formula = true;
                fof_file_number = _index;
            }
            else if (_type == TPTPRecordType::FileCNF) {
                has_clause = true;
                clause_file_number = _index;
            }
            else {
                cerr << "Stop that!!!" << endl;
            }
    }
    /**
    * \brief Pretty much all the work is done by constructors 
    *        as this really only stores basic information for 
    *        formatting later.
    */
    TPTPRecord(const string& _name,
               const string& _source,
               const string& _f)
    : TPTPRecord(_name, "plain")
    {
        type = TPTPRecordType::TFClause;
        source.push_back(_source);
        has_clause = true;
        f = _f;
    } 
    /**
    * \brief Construct the full name for a proof step based on 
    *        its general name and any relevant indices.
    */
    string make_full_name() const {
        string s(name);
        if (index_1 != 0) {
            s += "_";
            s += to_string(index_1);
            if (index_2 != 0) {
                s += "_";
                s += to_string(index_2);
            }
        }
        return s;
    }
    /**
    * \brief Format a single TPTPRecord to the necessary output format.
    */
    string to_tptp_string() const;

    friend ostream& operator<<(ostream&, const TPTPRecord&);
    friend class TPTPRecords;
};

/**
* \brief Class collecting a bunch of TPTPRecord items for later 
*        formatting.
*
* This is slightly more complicated than just a store for records, 
* as it also takes care of keeping track of indices while records 
* are being added, as you generally don't know in advance how many 
* will be needed at a given stage.
*
* Further, when printing a full proof you only want to include 
* steps that were actually used, and this takes case of that part 
* of the process.
*/
class TPTPRecords {
private:
    vector<TPTPRecord> records;
    vector<bool> used_records;
    size_t current_index_1;
    size_t current_index_2;
    string current_name;
    string current_source;
    string last_f;
    string current_role;
    /**
    * \brief Given the names of clauses used in a proof, 
    *        mark the records that need to be displayed.
    */
    void mark_all_used_records(const unordered_set<string>&);
    /**
    * \brief Given a record name, recursively mark its 
    *        anscestors as used.
    */
    void mark_used_records(const string&);
    /**
    * \brief Given a name, find the index of its record. 
    */
    size_t find_record_by_name(const string&) const;

public:
    TPTPRecords() 
    : records()
    , used_records()
    , current_index_1(0)
    , current_index_2(0)
    , current_name()
    , current_source()
    , last_f()
    , current_role()
    {}
    /**
    * \brief Add a record.
    */
    void add(const TPTPRecord& _r) {
        records.push_back(_r);
    }
    size_t size() const {
        return records.size();
    }

    /**
    * \brief Format everything.
    */
    string to_tptp_string(const unordered_set<string>&); 
    /**
    * \brief Format everything, including every step whether 
    *        used or not.
    */
    string to_tptp_string_all();
    /**
    * \brief Clear everything.
    */
    void clear() {
        records.clear();
        used_records.clear();
        current_index_1 = 0;
        current_index_2 = 0;
        current_name = "";
        current_source = "";
        last_f = "";
        current_role = "";
    }

    inline void set_current_name(const string& _s) {
        current_name = _s;
    }

    inline void set_current_source(const string& _s) {
        current_source = _s;
    }

    inline void set_current_role(const string& _s) {
        current_role = _s;
    }

    inline void reset_index_1() {
        current_index_1 = 0;
    }

    inline void reset_index_2() {
        current_index_2 = 0;
    }

   inline void reset_indices() {
        current_index_1 = 0;
        current_index_2 = 0;
    }

    inline void next_index_1() {
        current_index_1++;
    }

    inline void reset_last_f() {
        last_f = "";
    }

    string last_full_name() const {
        return records.back().make_full_name();
    }


    string find_clause_name(size_t) const;
    string find_fof_name(size_t) const;
    void add_cnf_file_record(const string&, 
                    const string&, 
                    const string&,
                    const fs::path&);

    void add_fof_file_record(const string&, 
                    const string&, 
                    const string&,
                    const fs::path&);
    void add_theory_record(const string&,
                    const string&, 
                    const string&);
    void add_fof_inference_record(const string&,
                    const string&);
    void add_skolemization_record(const string&,
                    const string&,
                    const string&,
                    const string&);
    void add_herbrandization_record(const string&,
                    const string&,
                    const string&,
                    const string&);
    void add_definitional_record(const string&,
                    const string&);
    string add_clause_record(const Clause&, const string&);
    string add_clause_record(const Clause&, const string&, const string&);
    void add_tf_clause_record(const string&, const string&, const string&);

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


#endif