/*++
Copyright (c) 2012 Microsoft Corporation

Module Name:

    smt_solver.cpp

Abstract:

    Wraps smt::kernel as a solver for the external API and cmd_context.

Author:

    Leonardo (leonardo) 2012-10-21

Notes:

--*/

#include "util/dec_ref_util.h"
#include "ast/reg_decl_plugins.h"
#include "ast/for_each_expr.h"
#include "ast/ast_pp.h"
#include "ast/func_decl_dependencies.h"
#include "smt/smt_kernel.h"
#include "smt/params/smt_params.h"
#include "smt/params/smt_params_helper.hpp"
#include "solver/solver_na2as.h"
#include "solver/mus.h"

namespace {

    class smt_solver : public solver_na2as {

        struct cuber {
            smt_solver& m_solver;
            unsigned    m_round;
            expr_ref_vector  m_result;
            unsigned    m_depth;
            cuber(smt_solver& s):
                m_solver(s),
                m_round(0),
                m_result(s.get_manager()),
                m_depth(s.m_smt_params.m_cube_depth) {}
            expr_ref cube() {
                if (m_round == 0) {
                    m_result = m_solver.m_context.cubes(m_depth);
                }
                expr_ref r(m_result.m());
                if (m_round < m_result.size()) {
                    r = m_result.get(m_round);
                }
                else {
                    r = m_result.m().mk_false();
                }
                ++m_round;
                return r;
            }
        };

        smt_params           m_smt_params;
        smt::kernel          m_context;
        cuber*               m_cuber;
        symbol               m_logic;
        bool                 m_minimizing_core;
        bool                 m_core_extend_patterns;
        unsigned             m_core_extend_patterns_max_distance;
        bool                 m_core_extend_nonlocal_patterns;
        obj_map<expr, expr*> m_name2assertion;

    public:
        smt_solver(ast_manager & m, params_ref const & p, symbol const & l) :
            solver_na2as(m),
            m_smt_params(p),
            m_context(m, m_smt_params),
            m_cuber(nullptr),
            m_minimizing_core(false),
            m_core_extend_patterns(false),
            m_core_extend_patterns_max_distance(UINT_MAX),
            m_core_extend_nonlocal_patterns(false) {            
            m_logic = l;
            if (m_logic != symbol::null)
                m_context.set_logic(m_logic);
            updt_params(p);
        }

        solver * translate(ast_manager & m, params_ref const & p) override {
            ast_translation translator(get_manager(), m);

            smt_solver * result = alloc(smt_solver, m, p, m_logic);
            smt::kernel::copy(m_context, result->m_context, true);

            if (mc0()) 
                result->set_model_converter(mc0()->translate(translator));

            for (auto & [k, v] : m_name2assertion) {
                expr* val = translator(k);
                expr* key = translator(v);
                result->assert_expr(val, key);
            }

            return result;
        }

        ~smt_solver() override {
            dealloc(m_cuber);
            for (auto& [k,v] : m_name2assertion) {
                get_manager().dec_ref(k);
                get_manager().dec_ref(v);
            }
        }

        void updt_params(params_ref const & p) override {
            solver::updt_params(p);
            m_smt_params.updt_params(solver::get_params());
            m_context.updt_params(solver::get_params());
            smt_params_helper smth(solver::get_params());
            m_core_extend_patterns = smth.core_extend_patterns();
            m_core_extend_patterns_max_distance = smth.core_extend_patterns_max_distance();
            m_core_extend_nonlocal_patterns = smth.core_extend_nonlocal_patterns();
        }

        params_ref m_params_save;
        smt_params m_smt_params_save;

        void push_params() override {
            m_params_save.reset();           
            m_params_save.copy(solver::get_params());
            m_smt_params_save = m_smt_params;
        }

        void pop_params() override {
            m_smt_params = m_smt_params_save;
            solver::reset_params(m_params_save);
        }

        void collect_param_descrs(param_descrs & r) override {
            m_context.collect_param_descrs(r);
            insert_timeout(r);
            insert_rlimit(r);
            insert_max_memory(r);
            insert_ctrl_c(r);
        }

        void collect_statistics(statistics & st) const override {
            m_context.collect_statistics(st);
        }

        lbool get_consequences_core(expr_ref_vector const& assumptions, expr_ref_vector const& vars, expr_ref_vector& conseq) override {
            expr_ref_vector unfixed(m_context.m());
            return m_context.get_consequences(assumptions, vars, conseq, unfixed);
        }

        lbool find_mutexes(expr_ref_vector const& vars, vector<expr_ref_vector>& mutexes) override {
            return m_context.find_mutexes(vars, mutexes);
        }

        void assert_expr_core(expr * t) override {
            m_context.assert_expr(t);
        }
        void set_phase(expr* e) override { m_context.set_phase(e); }
        phase* get_phase() override { return m_context.get_phase(); }
        void set_phase(phase* p) override { m_context.set_phase(p); }
        void move_to_front(expr* e) override { m_context.move_to_front(e); }

        void assert_expr_core2(expr * t, expr * a) override {
            if (m_name2assertion.contains(a)) {
                throw default_exception("named assertion defined twice");
            }
            solver_na2as::assert_expr_core2(t, a);
            get_manager().inc_ref(t);
            get_manager().inc_ref(a);
            m_name2assertion.insert(a, t);
        }

        void push_core() override {
            m_context.push();
        }

        void pop_core(unsigned n) override {
            unsigned cur_sz = m_assumptions.size();
            if (n > 0 && cur_sz > 0) {
                unsigned lvl = m_scopes.size();
                SASSERT(n <= lvl);
                unsigned new_lvl = lvl - n;
                unsigned old_sz = m_scopes[new_lvl];
                for (unsigned i = cur_sz; i-- > old_sz; ) {
                    expr * key = m_assumptions.get(i);
                    expr * value = m_name2assertion.find(key);
                    m_name2assertion.erase(key);
                    m.dec_ref(value);
                    m.dec_ref(key);
                }
            }
            m_context.pop(n);
        }

        lbool check_sat_core2(unsigned num_assumptions, expr * const * assumptions) override {
            TRACE("solver_na2as", tout << "smt_solver::check_sat_core: " << num_assumptions << "\n";);
            return m_context.check(num_assumptions, assumptions);
        }

        lbool check_sat_cc_core(expr_ref_vector const& cube, vector<expr_ref_vector> const& clauses) override {
            return m_context.check(cube, clauses);
        }

        void get_levels(ptr_vector<expr> const& vars, unsigned_vector& depth) override {
            m_context.get_levels(vars, depth);
        }

        expr_ref_vector get_trail(unsigned max_level) override {
            return m_context.get_trail(max_level);
        }

        void register_on_clause(void* ctx, user_propagator::on_clause_eh_t& on_clause) override {
            m_context.register_on_clause(ctx, on_clause);
        }

        void user_propagate_init(
            void*                ctx, 
            user_propagator::push_eh_t&   push_eh,
            user_propagator::pop_eh_t&    pop_eh,
            user_propagator::fresh_eh_t&  fresh_eh) override {
            m_context.user_propagate_init(ctx, push_eh, pop_eh, fresh_eh);
        }
        
        void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) override {
            m_context.user_propagate_register_fixed(fixed_eh);
        }

        void user_propagate_register_final(user_propagator::final_eh_t& final_eh) override {
            m_context.user_propagate_register_final(final_eh);
        }
        
        void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) override {
            m_context.user_propagate_register_eq(eq_eh);
        }
        
        void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override {
            m_context.user_propagate_register_diseq(diseq_eh);
        }

        void user_propagate_register_expr(expr* e) override { 
            m_context.user_propagate_register_expr(e);
        }

        void user_propagate_register_created(user_propagator::created_eh_t& c) override {
            m_context.user_propagate_register_created(c);
        }

        void user_propagate_register_decide(user_propagator::decide_eh_t& c) override {
            m_context.user_propagate_register_decide(c);
        }

        void user_propagate_initialize_value(expr* var, expr* value) override {
            m_context.user_propagate_initialize_value(var, value);
        }

        struct scoped_minimize_core {
            smt_solver& s;
            expr_ref_vector m_assumptions;
            scoped_minimize_core(smt_solver& s) : s(s), m_assumptions(s.m_assumptions) {
                s.m_minimizing_core = true;
                s.m_assumptions.reset();
            }

            ~scoped_minimize_core() {
                s.m_minimizing_core = false;
                s.m_assumptions.append(m_assumptions);
            }
        };

        void get_unsat_core(expr_ref_vector & r) override {

            unsigned sz = m_context.get_unsat_core_size();
            for (unsigned i = 0; i < sz; i++) {
                r.push_back(m_context.get_unsat_core_expr(i));
            }

            if (!m_minimizing_core && smt_params_helper(get_params()).core_minimize()) {
                scoped_minimize_core scm(*this);
                mus mus(*this);
                mus.add_soft(r.size(), r.data());
                expr_ref_vector r2(m);
                if (l_true == mus.get_mus(r2)) {
                    r.reset();
                    r.append(r2);
                }
            }

            if (m_core_extend_patterns)
                add_pattern_literals_to_core(r);
            if (m_core_extend_nonlocal_patterns)
                add_nonlocal_pattern_literals_to_core(r);
        }

        void get_model_core(model_ref & m) override {
            m_context.get_model(m);
        }

        proof * get_proof_core() override {
            return m_context.get_proof();
        }

        std::string reason_unknown() const override {
            return m_context.last_failure_as_string();
        }

        void set_reason_unknown(char const* msg) override {
            m_context.set_reason_unknown(msg);
        }

        void get_labels(svector<symbol> & r) override {
            buffer<symbol> tmp;
            m_context.get_relevant_labels(nullptr, tmp);
            r.append(tmp.size(), tmp.data());
        }

        ast_manager & get_manager() const override { return m_context.m(); }

        void set_progress_callback(progress_callback * callback) override {
            m_context.set_progress_callback(callback);
        }

        unsigned get_num_assertions() const override {
            return m_context.size();
        }

        expr * get_assertion(unsigned idx) const override {
            SASSERT(idx < get_num_assertions());
            return m_context.get_formula(idx);
        }

        void get_units_core(expr_ref_vector& units) override {
            m_context.get_units(units);
        }

        expr* congruence_next(expr* e) override { return m_context.congruence_next(e); }
        expr* congruence_root(expr* e) override { return m_context.congruence_root(e); }
        bool  solve_for(expr* e, expr_ref& term) override { return m_context.solve_for(e, term); }


        expr_ref_vector cube(expr_ref_vector& vars, unsigned cutoff) override {
            ast_manager& m = get_manager();
            if (!m_cuber) {
                m_cuber = alloc(cuber, *this);
                // force propagation
                push_core();
                pop_core(1);
            }
            expr_ref result = m_cuber->cube();
            expr_ref_vector lits(m);
            if (m.is_false(result)) {
                dealloc(m_cuber);
                m_cuber = nullptr;
            }
            if (m.is_true(result)) {
                dealloc(m_cuber);
                m_cuber = nullptr;
                return lits;
            }
            lits.push_back(result);
            return lits;
        }

        struct collect_fds_proc {
            ast_manager & m;
            func_decl_set & m_fds;
            collect_fds_proc(ast_manager & m, func_decl_set & fds) :
                m(m), m_fds(fds) {
            }
            void operator()(var * n) {}
            void operator()(app * n) {
                func_decl * fd = n->get_decl();
                if (fd->get_family_id() == null_family_id)
                    m_fds.insert_if_not_there(fd);
            }
            void operator()(quantifier * n) {}
        };

        struct collect_pattern_fds_proc {
            ast_manager & m;
            expr_fast_mark1 m_visited;
            func_decl_set & m_fds;
            collect_pattern_fds_proc(ast_manager & m, func_decl_set & fds) :
                m(m), m_fds(fds) {
                m_visited.reset();
            }
            void operator()(var * n) {}
            void operator()(app * n) {}
            void operator()(quantifier * n) {
                collect_fds_proc p(m, m_fds);

                unsigned sz = n->get_num_patterns();
                for (unsigned i = 0; i < sz; i++)
                    quick_for_each_expr(p, m_visited, n->get_pattern(i));

                sz = n->get_num_no_patterns();
                for (unsigned i = 0; i < sz; i++)
                    quick_for_each_expr(p, m_visited, n->get_no_pattern(i));
            }
        };

        void collect_pattern_fds(expr_ref & e, func_decl_set & fds) {
            collect_pattern_fds_proc p(get_manager(), fds);
            expr_mark visited;
            for_each_expr(p, visited, e);
        }

        void compute_assrtn_fds(expr_ref_vector & core, vector<func_decl_set> & assrtn_fds) {
            assrtn_fds.resize(m_name2assertion.size());            
            unsigned i = 0;
            for (auto & kv : m_name2assertion) {
                if (!core.contains(kv.m_key)) {
                    collect_fds_proc p(m, assrtn_fds[i]);
                    expr_fast_mark1 visited;
                    quick_for_each_expr(p, visited, kv.m_value);
                }
                ++i;
            }
        }

        bool fds_intersect(func_decl_set & pattern_fds, func_decl_set & assrtn_fds) {
            for (func_decl * fd : pattern_fds) {
                if (assrtn_fds.contains(fd))
                    return true;
            }
            return false;
        }

        void add_pattern_literals_to_core(expr_ref_vector & core) {
            ast_manager & m = get_manager();
            expr_ref_vector new_core_literals(m);

            func_decl_set pattern_fds;
            vector<func_decl_set> assrtn_fds;

            for (unsigned d = 0; d < m_core_extend_patterns_max_distance; d++) {
                new_core_literals.reset();

                for (expr* c : core) {
                    expr_ref name(c, m);
                    expr* f = nullptr;
                    if (m_name2assertion.find(name, f)) {
                        expr_ref assrtn(f, m);
                        collect_pattern_fds(assrtn, pattern_fds);
                    }
                }

                if (!pattern_fds.empty()) {
                    if (assrtn_fds.empty())
                        compute_assrtn_fds(core, assrtn_fds);

                    unsigned i = 0;
                    for (auto & kv : m_name2assertion) {
                        if (!core.contains(kv.m_key) &&
                            fds_intersect(pattern_fds, assrtn_fds[i]))
                            new_core_literals.push_back(kv.m_key);
                        ++i;
                    }
                }

                core.append(new_core_literals.size(), new_core_literals.data());

                if (new_core_literals.empty())
                    break;
            }
        }

        struct collect_body_fds_proc {
            ast_manager & m;
            func_decl_set & m_fds;
            collect_body_fds_proc(ast_manager & m, func_decl_set & fds) :
                m(m), m_fds(fds) {
            }
            void operator()(var * n) {}
            void operator()(app * n) {}
            void operator()(quantifier * n) {
                collect_fds_proc p(m, m_fds);
                expr_fast_mark1 visited;
                quick_for_each_expr(p, visited, n->get_expr());
            }
        };

        void collect_body_func_decls(expr_ref & e, func_decl_set & fds) {
            ast_manager & m = get_manager();
            collect_body_fds_proc p(m, fds);
            expr_mark visited;
            for_each_expr(p, visited, e);
        }

        void add_nonlocal_pattern_literals_to_core(expr_ref_vector & core) {
            ast_manager & m = get_manager();
            for (auto const& kv : m_name2assertion) {
                expr_ref name(kv.m_key, m);
                expr_ref assrtn(kv.m_value, m);

                if (!core.contains(name)) {
                    func_decl_set pattern_fds, body_fds;
                    collect_pattern_fds(assrtn, pattern_fds);
                    collect_body_func_decls(assrtn, body_fds);

                    for (func_decl *fd : pattern_fds) {
                        if (!body_fds.contains(fd) && !core.contains(name)) {
                            core.push_back(name);
                            break;
                        }
                    }
                }
            }
        }
    };
}

solver * mk_smt_solver(ast_manager & m, params_ref const & p, symbol const & logic) {
    return alloc(smt_solver, m, p, logic);
}

namespace {
class smt_solver_factory : public solver_factory {
public:
    solver * operator()(ast_manager & m, params_ref const & p, bool proofs_enabled, bool models_enabled, bool unsat_core_enabled, symbol const & logic) override {
        return mk_smt_solver(m, p, logic);
    }
};
}

solver_factory * mk_smt_solver_factory() {
    return alloc(smt_solver_factory);
}

