/*++
Copyright (c) 2013 Microsoft Corporation

Module Name:

    clp_context.cpp

Abstract:

    Bounded CLP (symbolic simulation using Z3) context.

Author:

    Nikolaj Bjorner (nbjorner) 2013-04-26

Revision History:

--*/

#include "muz/clp/clp_context.h"
#include "muz/base/dl_context.h"
#include "ast/substitution/unifier.h"
#include "ast/rewriter/var_subst.h"
#include "ast/substitution/substitution.h"
#include "smt/smt_kernel.h"
#include "muz/transforms/dl_transforms.h"

namespace datalog {

    class clp::imp {
        struct stats {
            stats() { reset(); }
            void reset() { memset(this, 0, sizeof(*this)); }
            unsigned m_num_unfold;
            unsigned m_num_no_unfold;
            unsigned m_num_subsumed;
        };

        context&               m_ctx;
        ast_manager&           m;
        rule_manager&          rm;
        smt_params             m_fparams;
        smt::kernel            m_solver;
        var_subst              m_var_subst;
        expr_ref_vector        m_ground;
        app_ref_vector         m_goals;
        stats                  m_stats;
    public:
        imp(context& ctx):
            m_ctx(ctx), 
            m(ctx.get_manager()),
            rm(ctx.get_rule_manager()),
            m_solver(m, m_fparams),      // TBD: can be replaced by efficient BV solver.
            m_var_subst(m, false),
            m_ground(m),
            m_goals(m)
        {
            // m_fparams.m_relevancy_lvl = 0;
            m_fparams.m_mbqi = false;
        }

        lbool query(expr* query) {
            m_ctx.ensure_opened();
            m_solver.reset();
            m_goals.reset();
            rm.mk_query(query, m_ctx.get_rules());
            apply_default_transformation(m_ctx);
            const rule_set& rules = m_ctx.get_rules();
            if (rules.get_output_predicates().empty()) {
                return l_false;
            }
            func_decl *head_decl = rules.get_output_predicate();
            rule_vector const& rv = rules.get_predicate_rules(head_decl);
            if (rv.empty()) {
                return l_false;
            }
            expr_ref head(rv[0]->get_head(), m);
            ground(head);
            m_goals.push_back(to_app(head));
            return search(20, 0);
        }
            

        void reset_statistics() {
            m_stats.reset();
        }

        void collect_statistics(statistics& st) const {
            //st.update("tab.num_unfold", m_stats.m_num_unfold);
            //st.update("tab.num_unfold_fail", m_stats.m_num_no_unfold);
            //st.update("tab.num_subsumed", m_stats.m_num_subsumed);
        }

        void display_certificate(std::ostream& out) const {
            expr_ref ans = get_answer();
            out << mk_pp(ans, m) << "\n";    

        }

        expr_ref get_answer() const {
            return expr_ref(m.mk_true(), m);
        }

    private:

        void reset_ground() {
            m_ground.reset();
        }
        
        void ground(expr_ref& e) {
            expr_free_vars fv;
            fv(e);
            if (m_ground.size() < fv.size()) {
                m_ground.resize(fv.size());
            }
            for (unsigned i = 0; i < fv.size(); ++i) {
                if (fv[i] && !m_ground[i].get()) {
                    m_ground[i] = m.mk_fresh_const("c", fv[i]);
                }
            }
            e = m_var_subst(e, m_ground.size(), m_ground.data());
        }

        static bool rule_sort_fn(const rule *r1, const rule *r2) {
            return r1->get_uninterpreted_tail_size() < r2->get_uninterpreted_tail_size();
        }

        lbool search(unsigned depth, unsigned index) {
            if (index == m_goals.size()) {
                return l_true;
            }
            if (depth == 0) {
                return l_undef;
            }
            IF_VERBOSE(1, verbose_stream() << "search " << depth << " " << index << "\n";);
            unsigned num_goals = m_goals.size();
            app* head = m_goals[index].get();

            rule_vector rules(m_ctx.get_rules().get_predicate_rules(head->get_decl()));
            std::stable_sort(rules.begin(), rules.end(), rule_sort_fn);

            lbool status = l_false;
            for (unsigned i = 0; i < rules.size(); ++i) {
                rule* r = rules[i];
                m_solver.push();
                reset_ground();
                expr_ref tmp(m);
                tmp = r->get_head();
                IF_VERBOSE(2, verbose_stream() << index << " " << mk_pp(tmp, m) << "\n";);
                ground(tmp);
                for (unsigned j = 0; j < head->get_num_args(); ++j) {
                    expr_ref eq(m);
                    eq = m.mk_eq(head->get_arg(j), to_app(tmp)->get_arg(j));
                    m_solver.assert_expr(eq);
                }
                for (unsigned j = r->get_uninterpreted_tail_size(); j < r->get_tail_size(); ++j) {
                    tmp = r->get_tail(j);
                    ground(tmp);
                    m_solver.assert_expr(tmp);
                }
                lbool is_sat = m_solver.check();
                switch (is_sat) {
                case l_false:
                    break;
                case l_true:
                    if (depth == 1 && (index+1 > m_goals.size() || r->get_uninterpreted_tail_size() > 0)) {
                        status = l_undef;
                        break;
                    }
                    for (unsigned j = 0; j < r->get_uninterpreted_tail_size(); ++j) {
                        tmp = r->get_tail(j);
                        ground(tmp);
                        m_goals.push_back(to_app(tmp));
                    }
                    switch(search(depth-1, index+1)) {
                    case l_undef:
                        status = l_undef;
                        // fallthrough
                    case l_false:
                        m_goals.resize(num_goals);   
                        break;
                    case l_true:
                        return l_true;
                    }
                    break;
                case l_undef:
                    status = l_undef;
                    throw default_exception("undef");
                }
                m_solver.pop(1);
            }
            return status;
        }
    
    
        proof_ref get_proof() const {
            return proof_ref(nullptr, m);
        }
    };
    
    clp::clp(context& ctx):
        engine_base(ctx.get_manager(), "clp"),
        m_imp(alloc(imp, ctx)) {        
    }
    clp::~clp() {
        dealloc(m_imp);
    }    
    lbool clp::query(expr* query) {
        return m_imp->query(query);
    }

    void clp::reset_statistics() {
        m_imp->reset_statistics();
    }
    void clp::collect_statistics(statistics& st) const {
        m_imp->collect_statistics(st);
    }
    void clp::display_certificate(std::ostream& out) const {
        m_imp->display_certificate(out);
    }
    expr_ref clp::get_answer() {
        return m_imp->get_answer();
    }

};
