/*++
Copyright (c) 2011 Microsoft Corporation

Module Name:

    goal.h

Abstract:

    A goal is essentially a set of formulas. Tactics are used to build 
    proof and model finding procedures for these sets.
    
    Remark: In a previous version of Z3, goals were called assertion_sets.
    Here is a summary of the main changes:
       - Goals track whether they are the result of applying over/under approximation steps.
         This prevent users from creating unsound strategies (e.g., user uses nia2sat, but does not check the sat_preserving flag).
       - Goals track dependencies (aka light proofs) for unsat core extraction, and building multi-tier solvers.
         This kind of dependency tracking is more powerful than the one used in the current Z3, since
         it does not prevent the use of preprocessing steps such as "Gaussian Elimination".
    
Author:

    Leonardo de Moura (leonardo) 2011-10-12

Revision History:

--*/
#pragma once

#include "ast/ast.h"
#include "ast/ast_translation.h"
#include "ast/ast_printer.h"
#include "ast/for_each_expr.h"
#include "util/ref.h"
#include "util/ref_vector.h"
#include "util/ref_buffer.h"
#include "tactic/model_converter.h"
#include "tactic/proof_converter.h"
#include "tactic/dependency_converter.h"

class goal {
public:
    enum precision {
        PRECISE,      
        UNDER,   // goal is the product of an under-approximation 
        OVER,    // goal is the product of an over-approximation
        UNDER_OVER // goal is garbage: the produce of combined under and over approximation steps.
    };

    static precision mk_union(precision p1, precision p2);
    
protected:
    ast_manager &         m_manager;
    model_converter_ref   m_mc;
    proof_converter_ref   m_pc;
    dependency_converter_ref m_dc;
    unsigned              m_ref_count;
    expr_array            m_forms;
    expr_array            m_proofs;
    expr_dependency_array m_dependencies;
    // attributes
    unsigned              m_depth:26;          // depth of the goal in the goal tree.
    unsigned              m_models_enabled:1;  // model generation is enabled.
    unsigned              m_proofs_enabled:1;  // proof production is enabled. m_manager.proofs_enabled() must be true if m_proofs_enabled == true
    unsigned              m_core_enabled:1;    // unsat core extraction is enabled.
    unsigned              m_inconsistent:1;    // true if the goal is known to be inconsistent. 
    unsigned              m_precision:2;       // PRECISE, UNDER, OVER.

    void push_back(expr * f, proof * pr, expr_dependency * d);
    void quick_process(bool save_first, expr_ref & f, expr_dependency * d);
    void process_and(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr);
    void process_not_or(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr);
    void slow_process(bool save_first, expr * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr);
    void slow_process(expr * f, proof * pr, expr_dependency * d);
    unsigned get_idx(expr * f) const;
    unsigned get_not_idx(expr * f) const;
    void shrink(unsigned j);
    void reset_core();
    bool is_literal(expr* f) const;
    
public:
    goal(ast_manager & m, bool models_enabled = true, bool core_enabled = false);
    goal(ast_manager & m, bool proofs_enabled, bool models_enabled, bool core_enabled);
    goal(goal const & src);
    // Copy configuration: depth, models/proofs/cores flags, and precision from src.
    // The assertions are not copied
    goal(goal const & src, bool);
    ~goal();

    void inc_ref() { ++m_ref_count; }
    void dec_ref() { --m_ref_count; if (m_ref_count == 0) dealloc(this); }

    ast_manager & m() const { return m_manager; }

    unsigned depth() const { return m_depth; }
    bool models_enabled() const { return m_models_enabled; }
    bool proofs_enabled() const { return m_proofs_enabled; }
    bool unsat_core_enabled() const { return m_core_enabled; }
    bool inconsistent() const { return m_inconsistent; }
    precision prec() const { return static_cast<precision>(m_precision); }
    
    void set_depth(unsigned d) { m_depth = d; }
    void inc_depth() { m_depth++; }
    void set_prec(precision d) { m_precision = d; }
    void updt_prec(precision d) { m_precision = mk_union(prec(), d); }

    void reset_all(); // reset goal and precision and depth attributes.
    void reset(); // reset goal but preserve precision and depth attributes.

    void copy_to(goal & target) const;
    void copy_from(goal const & src) { src.copy_to(*this); }

    void assert_expr(expr * f, proof * pr, expr_dependency * d);
    void assert_expr(expr * f, expr_dependency * d);
    void assert_expr(expr * f, expr * d) { assert_expr(f, m().mk_leaf(d)); }
    void assert_expr(expr * f) { assert_expr(f, static_cast<expr_dependency*>(nullptr)); }
    
    unsigned size() const { return m().size(m_forms); }

    unsigned num_exprs() const;
  
    expr * form(unsigned i) const { return m().get(m_forms, i); }
    proof * pr(unsigned i) const { return m().size(m_proofs) > i ? static_cast<proof*>(m().get(m_proofs, i)) : nullptr; }
    expr_dependency * dep(unsigned i) const { return unsat_core_enabled() ? m().get(m_dependencies, i) : nullptr; }

    void update(unsigned i, expr * f, proof * pr = nullptr, expr_dependency * dep = nullptr);

    void get_formulas(ptr_vector<expr> & result) const;
    void get_formulas(expr_ref_vector & result) const;
    
    void elim_true();
    void elim_redundancies();

    void display(ast_printer & prn, std::ostream & out) const;
    void display(ast_printer_context & ctx) const;
    void display(std::ostream & out) const;
    void display_ll(std::ostream & out) const;
    void display_as_and(std::ostream & out) const;
    void display_dimacs(std::ostream & out, bool include_names) const;
    void display_with_dependencies(ast_printer & prn, std::ostream & out) const;
    void display_with_dependencies(ast_printer_context & ctx) const;
    void display_with_dependencies(std::ostream & out) const;
    void display_with_proofs(std::ostream& out) const;

    bool sat_preserved() const;
    bool unsat_preserved() const;
    bool is_decided_sat() const;
    bool is_decided_unsat() const;
    bool is_decided() const;
    bool is_well_formed() const;

    dependency_converter* dc() { return m_dc.get(); }
    model_converter* mc() const { return m_mc.get(); }
    proof_converter* pc() const { return inconsistent() ? proof2proof_converter(m(), pr(0)) : m_pc.get(); }
    void add(dependency_converter* d) { m_dc = dependency_converter::concat(m_dc.get(), d); }
    void add(model_converter* m) { m_mc = concat(m_mc.get(), m); }
    void add(proof_converter* p) { m_pc = concat(m_pc.get(), p); }
    void set(dependency_converter* d) { m_dc = d; }
    void set(model_converter* m) { m_mc = m; }
    void set(proof_converter* p) { m_pc = p; }

    bool is_cnf() const;

    goal * translate(ast_translation & translator) const;
};

std::ostream & operator<<(std::ostream & out, goal::precision p);

typedef ref<goal> goal_ref;
typedef sref_vector<goal> goal_ref_vector;
typedef sref_buffer<goal> goal_ref_buffer;

template<typename GoalCollection>
inline bool is_decided(GoalCollection const & c) { return c.size() == 1 && c[0]->is_decided(); }
template<typename GoalCollection>
inline bool is_decided_sat(GoalCollection const & c) { return c.size() == 1 && c[0]->is_decided_sat(); }
template<typename GoalCollection>
inline bool is_decided_unsat(GoalCollection const & c) { return c.size() == 1 && c[0]->is_decided_unsat(); }

template<typename ForEachProc>
void for_each_expr_at(ForEachProc& proc, goal const & s) {
    expr_mark visited;
    for (unsigned i = 0; i < s.size(); ++i) {
        for_each_expr(proc, visited, s.form(i));
    }
}

bool is_equal(goal const & g1, goal const & g2);

template<typename Predicate>
bool test(goal const & g, Predicate & proc) {
    expr_fast_mark1 visited;
    try {
        unsigned sz = g.size();
        for (unsigned i = 0; i < sz; i++)
            quick_for_each_expr(proc, visited, g.form(i));
    }
    catch (const typename Predicate::found &) {
        return true;
    }
    return false;
}

template<typename Predicate>
bool test(goal const & g) {
    Predicate proc(g.m());
    return test(g, proc);
}

