/*++
Copyright (c) 2006 Microsoft Corporation

Module Name:

    smt_case_split_queue.cpp

Abstract:

    <abstract>

Author:

    Leonardo de Moura (leonardo) 2009-01-20.

Revision History:

--*/
#include "smt/smt_context.h"
#include "smt/smt_case_split_queue.h"
#include "util/warning.h"
#include "util/stopwatch.h"
#include "ast/for_each_expr.h"
#include "ast/ast_pp.h"
#include "util/map.h"
#include "util/hashtable.h"

using namespace smt;

namespace {

    typedef map<bool_var, double, int_hash, default_eq<bool_var> > theory_var_priority_map;

    struct bool_var_act_lt {
        svector<double> const & m_activity;
        bool_var_act_lt(svector<double> const & a):m_activity(a) {}
        bool operator()(bool_var v1, bool_var v2) const {
            return m_activity[v1] > m_activity[v2];
        }
    };

    typedef heap<bool_var_act_lt> bool_var_act_queue;

    struct theory_aware_act_lt {
        svector<double> const & m_activity;
        theory_var_priority_map const & m_theory_var_priority;
        theory_aware_act_lt(svector<double> const & act, theory_var_priority_map const & a):m_activity(act),m_theory_var_priority(a) {}
        bool operator()(bool_var v1, bool_var v2) const {
            double p_v1, p_v2;
            if (!m_theory_var_priority.find(v1, p_v1)) {
                p_v1 = 0.0;
            }
            if (!m_theory_var_priority.find(v2, p_v2)) {
                p_v2 = 0.0;
            }
            // add clause activity
            p_v1 += m_activity[v1];
            p_v2 += m_activity[v2];
            return p_v1 > p_v2;
        }
    };

    typedef heap<theory_aware_act_lt> theory_aware_act_queue;

    /**
       \brief Case split queue based on activity and random splits.
    */
    class act_case_split_queue : public case_split_queue {
    protected:
        context &          m_context;
        smt_params &       m_params;  
        bool_var_act_queue m_queue;
    public:
        act_case_split_queue(context & ctx, smt_params & p):
            m_context(ctx),
            m_params(p),
            m_queue(1024, bool_var_act_lt(ctx.get_activity_vector())) {
        }
            
        void activity_increased_eh(bool_var v) override {
            if (m_queue.contains(v))
                m_queue.decreased(v);
        }

        void activity_decreased_eh(bool_var v) override {
            if (m_queue.contains(v))
                m_queue.increased(v);
        }

        void mk_var_eh(bool_var v) override {
            m_queue.reserve(v+1);
            SASSERT(!m_queue.contains(v));
            m_queue.insert(v);
        }

        void del_var_eh(bool_var v) override {
            if (m_queue.contains(v))
                m_queue.erase(v);
        }

        void unassign_var_eh(bool_var v) override {
            if (!m_queue.contains(v))
                m_queue.insert(v);
        }

        void relevant_eh(expr * n) override {}

        void init_search_eh() override {}

        void end_search_eh() override {}

        void reset() override {
            m_queue.reset();
        }

        void push_scope() override {}

        void pop_scope(unsigned num_scopes) override {}

        void next_case_split(bool_var & next, lbool & phase) override {
            phase = l_undef;
            
            if (m_context.get_random_value() < static_cast<int>(m_params.m_random_var_freq * random_gen::max_value())) {
                next = m_context.get_random_value() % m_context.get_num_b_internalized(); 
                TRACE("random_split", tout << "next: " << next << " get_assignment(next): " << m_context.get_assignment(next) << "\n";);
                if (m_context.get_assignment(next) == l_undef)
                    return;
            }
            
            while (!m_queue.empty()) {
                next = m_queue.erase_min();
                if (m_context.get_assignment(next) == l_undef)
                    return;
            }
            
            next = null_bool_var;
        }

        void display(std::ostream & out) override {
            bool first = true;
            for (unsigned v : m_queue) {
                if (m_context.get_assignment(v) == l_undef) {
                    if (first) {
                        out << "remaining case-splits:\n";
                        first = false;
                    }
                    out << "#" << m_context.bool_var2expr(v)->get_id() << " ";
                }
            }
            if (!first)
                out << "\n";            
        }

        ~act_case_split_queue() override {};
    };

    /**
       \brief Similar to dact_case_split_queue, but delay case splits
       created during the search.
    */
    class dact_case_split_queue : public act_case_split_queue {
        bool_var_act_queue m_delayed_queue;
    public:
        dact_case_split_queue(context & ctx, smt_params & p):
            act_case_split_queue(ctx, p),
            m_delayed_queue(1024, bool_var_act_lt(ctx.get_activity_vector())) {
        }

        void activity_increased_eh(bool_var v) override {
            act_case_split_queue::activity_increased_eh(v);
            if (m_queue.contains(v))
                m_queue.decreased(v);
            if (m_delayed_queue.contains(v))
                m_delayed_queue.decreased(v);
        }

        void activity_decreased_eh(bool_var v) override {
            act_case_split_queue::activity_decreased_eh(v);
            if (m_queue.contains(v))
                m_queue.increased(v);
            if (m_delayed_queue.contains(v))
                m_delayed_queue.increased(v);
        }

        void mk_var_eh(bool_var v) override {
            m_queue.reserve(v+1);
            m_delayed_queue.reserve(v+1);
            SASSERT(!m_delayed_queue.contains(v));
            SASSERT(!m_queue.contains(v));
            if (m_context.is_searching()) 
                m_delayed_queue.insert(v);
            else
                m_queue.insert(v);
        }

        void del_var_eh(bool_var v) override {
            act_case_split_queue::del_var_eh(v);
            if (m_delayed_queue.contains(v))
                m_delayed_queue.erase(v);
        }

        void relevant_eh(expr * n) override {}

        void init_search_eh() override {}

        void end_search_eh() override {}

        void reset() override {
            act_case_split_queue::reset();
            m_delayed_queue.reset();
        }

        void push_scope() override {}

        void pop_scope(unsigned num_scopes) override {}

        void next_case_split(bool_var & next, lbool & phase) override {
            act_case_split_queue::next_case_split(next, phase);
            if (next != null_bool_var)
                return;
            
            m_queue.swap(m_delayed_queue);
            SASSERT(m_delayed_queue.empty());
            
            while (!m_queue.empty()) {
                next = m_queue.erase_min();
                if (m_context.get_assignment(next) == l_undef)
                    return;
            }
            
            next = null_bool_var;
        }
    };

    /**
       \brief Case split queue based on activity and random splits.
    */
    class cact_case_split_queue : public act_case_split_queue {
        obj_map<expr, double>  m_cache;
        expr_ref_vector        m_cache_domain;
    public:
        cact_case_split_queue(context & ctx, smt_params & p):
            act_case_split_queue(ctx, p),
            m_cache_domain(ctx.get_manager()) {
        }
            
        void mk_var_eh(bool_var v) override {
            expr * n = m_context.bool_var2expr(v);
            double act;
            if (n && m_cache.find(n, act))
                m_context.set_activity(v, act);
            act_case_split_queue::mk_var_eh(v);
        }
        
        void del_var_eh(bool_var v) override {
            if (m_context.is_searching()) {
                double act = m_context.get_activity(v);
                if (act > 0.0) {
                    expr * n = m_context.bool_var2expr(v);
                    if (n) {
                        m_cache.insert(n, act);
                        m_cache_domain.push_back(n);
                    }
                }
            }
            act_case_split_queue::del_var_eh(v);
        }

        void init_search_eh() override {
            m_cache.reset();
            m_cache_domain.reset();
        }

        void end_search_eh() override {}

        void reset() override {
            init_search_eh();
        }
    };

    static bool has_child_assigned_to(context & ctx, app * parent, lbool val, expr * & undef_child, unsigned order) {
        ptr_vector<expr> undef_children;
        bool found_undef  = false;
        unsigned num_args = parent->get_num_args();
        for (unsigned i = 0; i < num_args; i++) {
            expr * arg    = parent->get_arg(i);
            lbool arg_val = ctx.get_assignment(arg);
            if (arg_val == val)
                return true;
            if (found_undef && order == 0)
                continue;
            if (arg_val == l_undef) {
                if (order == 1)
                    undef_children.push_back(arg);
                else
                    undef_child = arg;
                found_undef = true;
            }
        }
        if (order == 1) {
            if (undef_children.empty()) {
                // a bug?
            } else if (undef_children.size() == 1) {
                undef_child = undef_children[0];
            } else {
                undef_child = undef_children[ctx.get_random_value() % undef_children.size()];
            }
        }
        return false;
    }

    /**
       \brief Case split queue based on relevancy propagation
    */
    class rel_case_split_queue : public case_split_queue {
        struct scope {
            unsigned m_queue_trail;
            unsigned m_head_old;
            unsigned m_queue2_trail;
            unsigned m_head2_old;
        };
        typedef int_hashtable<int_hash, default_eq<int> > bool_var_set;
        context &         m_context;
        smt_params &m_params;  
        ast_manager &     m_manager;
        ptr_vector<expr>  m_queue;
        unsigned          m_head;
        bool_var          m_bs_num_bool_vars; //!< Number of boolean variable before starting to search.
        ptr_vector<expr>  m_queue2;
        unsigned          m_head2;
        svector<scope>    m_scopes;
    public:
        rel_case_split_queue(context & ctx, smt_params & p):
            m_context(ctx),
            m_params(p),
            m_manager(ctx.get_manager()),
            m_head(0),
            m_bs_num_bool_vars(UINT_MAX),
            m_head2(0) {
        }
        
        void activity_increased_eh(bool_var v) override {}

        void activity_decreased_eh(bool_var v) override {}

        void mk_var_eh(bool_var v) override {}

        void del_var_eh(bool_var v) override {}

        void unassign_var_eh(bool_var v) override {}

        void relevant_eh(expr * n) override {
            if (!m_manager.is_bool(n))
                return;
            bool is_or     = m_manager.is_or(n);
            bool intern    = m_context.b_internalized(n);
            if (!intern && !is_or) 
                return;
            bool_var var = null_bool_var;
            if (intern) {
                var = m_context.get_bool_var(n);
                SASSERT(var != null_bool_var);
                bool is_and = m_manager.is_and(n);
                lbool val   = m_context.get_assignment(var);
                if (!(
                      val == l_undef || // n was not assigned yet
                      (is_or && val == l_true) || // need to justify a child
                      (is_and && val == l_false) // need to justify a child
                      ))
                    return;
            }
            if (!intern && m_context.is_searching()) {
                SASSERT(is_or);
                m_queue2.push_back(n);
                return;
            }
            if (var < m_bs_num_bool_vars) 
                m_queue.push_back(n);
            else
                m_queue2.push_back(n);
        }

        void init_search_eh() override {
            m_bs_num_bool_vars = m_context.get_num_bool_vars();
        }

        void end_search_eh() override {
            m_bs_num_bool_vars = UINT_MAX;
        }

        void reset() override {
            m_queue.reset();
            m_head = 0;
            m_queue2.reset();
            m_head2 = 0;
            m_scopes.reset();
        }

        void push_scope() override {
            m_scopes.push_back(scope());
            scope & s = m_scopes.back();
            s.m_queue_trail  = m_queue.size();
            s.m_head_old     = m_head;
            s.m_queue2_trail = m_queue2.size();
            s.m_head2_old    = m_head2;
            TRACE("case_split", tout << "head: " << m_head << "\n";);
        }

        void pop_scope(unsigned num_scopes) override {
            SASSERT(num_scopes <= m_scopes.size());
            unsigned new_lvl    = m_scopes.size() - num_scopes;
            scope & s           = m_scopes[new_lvl];
            m_queue.shrink(s.m_queue_trail);
            m_head              = s.m_head_old;
            m_queue2.shrink(s.m_queue2_trail);
            m_head2             = s.m_head2_old;
            m_scopes.shrink(new_lvl);
            SASSERT(m_head <= m_queue.size());
            TRACE("case_split", display(tout); tout << "head: " << m_head << "\n";);
        }
        
        void next_case_split_core(ptr_vector<expr> & queue, unsigned & head, bool_var & next, lbool & phase) {
            phase = l_undef;
            unsigned sz = queue.size();
            for (; head < sz; head++) {
                expr * curr = queue[head];
                bool is_or  = m_manager.is_or(curr);
                bool is_and = m_manager.is_and(curr);
                bool intern = m_context.b_internalized(curr);
                SASSERT(intern || is_or);
                lbool val   = l_undef;
                if (intern) {
                    next = m_context.get_bool_var(curr);
                    val  = m_context.get_assignment(next);
                }
                else {
                    SASSERT(is_or); // top level clause
                    val  = l_true;
                }
                if ((is_or && val == l_true) || (is_and && val == l_false)) {
                    expr * undef_child = nullptr;
                    if (!has_child_assigned_to(m_context, to_app(curr), val, undef_child, m_params.m_rel_case_split_order)) {
                        if (m_manager.has_trace_stream()) {
                            m_manager.trace_stream() << "[decide-and-or] #" << curr->get_id() << " #" << undef_child->get_id() << "\n";
                        }
                        TRACE("case_split", tout << "found AND/OR candidate: #" << curr->get_id() << " #" << undef_child->get_id() << "\n";);
                        literal l = m_context.get_literal(undef_child);
                        next  = l.var();
                        phase = l.sign() ? l_false : l_true;
                        TRACE("case_split", display(tout););
                        return;
                    }
                }
                else if (val == l_undef) {
                    SASSERT(intern && m_context.get_bool_var(curr) == next);
                    TRACE("case_split", tout << "found candidate: #" << curr->get_id() << "\n";);
                    phase = l_undef;
                    TRACE("case_split", display(tout););
                    return;
                }
            }
            next = null_bool_var;
        }

        void next_case_split(bool_var & next, lbool & phase) override {
            next_case_split_core(m_queue, m_head, next, phase);
            if (next == null_bool_var)
                next_case_split_core(m_queue2, m_head2, next, phase);
            // Force l_false is next is an equality that is known to be disequal in the logical context.
            if (m_params.m_lookahead_diseq && next != null_bool_var && phase != l_false && m_context.has_enode(next)) {
                enode * n = m_context.bool_var2enode(next);
                if (n->is_eq()) {
                    enode * lhs = n->get_arg(0);
                    enode * rhs = n->get_arg(1);
                    if (m_context.is_ext_diseq(lhs, rhs, 2))
                        phase = l_false;
                }
            }
        }

        void display_core(std::ostream & out, ptr_vector<expr> & queue, unsigned head, unsigned idx) {
            if (queue.empty())
                return;
            unsigned sz = queue.size();
            for (unsigned i = 0; i < sz; i++) {
                if (i == head) 
                    out << "[HEAD" << idx << "]=> ";
                out << "#" << queue[i]->get_id() << " ";
            }
            out << "\n";
        }

        void display(std::ostream & out) override {
            if (m_queue.empty() && m_queue2.empty())
                return;
            out << "case-splits:\n";
            display_core(out, m_queue, m_head, 1);
            display_core(out, m_queue2, m_head2, 2);
        }
   };
   
    /**
       \brief Case split queue based on relevancy propagation
    */
    class rel_act_case_split_queue : public case_split_queue {
        struct scope {
            unsigned m_queue_trail;
            unsigned m_head_old;
        };
        typedef int_hashtable<int_hash, default_eq<int> > bool_var_set;
        context &         m_context;
        ast_manager &     m_manager;
        smt_params &m_params;  
        ptr_vector<expr>  m_queue;
        unsigned          m_head;
        bool_var          m_bs_num_bool_vars; //!< Number of boolean variable before starting to search.
        bool_var_act_queue m_delayed_queue;
        svector<scope>    m_scopes;
    public:
        rel_act_case_split_queue(context & ctx, smt_params & p):
            m_context(ctx),
            m_manager(ctx.get_manager()),
            m_params(p),
            m_head(0),
            m_bs_num_bool_vars(UINT_MAX),
            m_delayed_queue(1024, bool_var_act_lt(ctx.get_activity_vector())) {
        }

        void activity_increased_eh(bool_var v) override {}

        void activity_decreased_eh(bool_var v) override {}

        void mk_var_eh(bool_var v) override {
            if (m_context.is_searching()) {
                SASSERT(v >= m_bs_num_bool_vars);
                m_delayed_queue.reserve(v+1);
                m_delayed_queue.insert(v);
            }
        }

        void del_var_eh(bool_var v) override {
            if (v >= m_bs_num_bool_vars && m_delayed_queue.contains(v))
                m_delayed_queue.erase(v);
        }

        void unassign_var_eh(bool_var v) override {
            if (v < m_bs_num_bool_vars)
                return;
            if (!m_delayed_queue.contains(v))
                m_delayed_queue.insert(v);
        }

        void relevant_eh(expr * n) override {
            if (!m_manager.is_bool(n))
                return;
            bool is_or     = m_manager.is_or(n);
            bool intern    = m_context.b_internalized(n);
            if (!intern && !is_or) 
                return;
            bool_var var = null_bool_var;
            if (intern) {
                var = m_context.get_bool_var(n);
                SASSERT(var != null_bool_var);
                bool is_and = m_manager.is_and(n);
                lbool val   = m_context.get_assignment(var);
                if (!(
                      val == l_undef || // n was not assigned yet
                      (is_or && val == l_true) || // need to justify a child
                      (is_and && val == l_false) // need to justify a child
                      ))
                    return;
            }
            if (!intern) {
                if (!m_context.is_searching())
                    m_queue.push_back(n);
                return;
            }
            if (var < m_bs_num_bool_vars)
                m_queue.push_back(n);
        }
        
        void init_search_eh() override {
            m_bs_num_bool_vars = m_context.get_num_bool_vars();
        }

        void end_search_eh() override {
            m_bs_num_bool_vars = UINT_MAX;
        }

        void reset() override {
            m_queue.reset();
            m_head = 0;
            m_delayed_queue.reset();
            m_scopes.reset();
        }

        void push_scope() override {
            m_scopes.push_back(scope());
            scope & s = m_scopes.back();
            s.m_queue_trail  = m_queue.size();
            s.m_head_old     = m_head;
            TRACE("case_split", tout << "head: " << m_head << "\n";);
        }

        void pop_scope(unsigned num_scopes) override {
            SASSERT(num_scopes <= m_scopes.size());
            unsigned new_lvl    = m_scopes.size() - num_scopes;
            scope & s           = m_scopes[new_lvl];
            m_queue.shrink(s.m_queue_trail);
            m_head              = s.m_head_old;
            m_scopes.shrink(new_lvl);
            SASSERT(m_head <= m_queue.size());
            TRACE("case_split", display(tout); tout << "head: " << m_head << "\n";);
        }

        void next_case_split_core(bool_var & next, lbool & phase) {
            phase = l_undef;
            unsigned sz = m_queue.size();
            for (; m_head < sz; m_head++) {
                expr * curr = m_queue[m_head];
                bool is_or  = m_manager.is_or(curr);
                bool is_and = m_manager.is_and(curr);
                bool intern = m_context.b_internalized(curr);
                SASSERT(intern || is_or);
                lbool val   = l_undef;
                if (intern) {
                    next = m_context.get_bool_var(curr);
                    val  = m_context.get_assignment(next);
                }
                else {
                    SASSERT(is_or); // top level clause
                    val  = l_true;
                }
                if ((is_or && val == l_true) || (is_and && val == l_false)) {
                    expr * undef_child = nullptr;
                    if (!has_child_assigned_to(m_context, to_app(curr), val, undef_child, m_params.m_rel_case_split_order)) {
                        TRACE("case_split", tout << "found AND/OR candidate: #" << curr->get_id() << " #" << undef_child->get_id() << "\n";);
                        literal l = m_context.get_literal(undef_child);
                        next  = l.var();
                        phase = l.sign() ? l_false : l_true;
                        TRACE("case_split", display(tout););
                        return;
                    }
                }
                else if (val == l_undef) {
                    SASSERT(intern && m_context.get_bool_var(curr) == next);
                    TRACE("case_split", tout << "found candidate: #" << curr->get_id() << "\n";);
                    phase = l_undef;
                    TRACE("case_split", display(tout););
                    return;
                }
            }
            next = null_bool_var;
        }

        void next_case_split(bool_var & next, lbool & phase) override {
            if (m_context.get_random_value() < static_cast<int>(0.02 * random_gen::max_value())) {
                next = m_context.get_random_value() % m_context.get_num_b_internalized(); 
                TRACE("random_split", tout << "next: " << next << " get_assignment(next): " << m_context.get_assignment(next) << "\n";);
                if (m_context.get_assignment(next) == l_undef)
                    return;
            }
            
            next_case_split_core(next, phase);
            if (next != null_bool_var)
                return;
            phase = l_undef;
            while (!m_delayed_queue.empty()) {
                next = m_delayed_queue.erase_min();
                if (m_context.get_assignment(next) == l_undef)
                    return;
            }
            next = null_bool_var;
        }

        void display_core(std::ostream & out) {
            if (m_queue.empty())
                return;
            unsigned sz = m_queue.size();
            for (unsigned i = 0; i < sz; i++) {
                if (i == m_head) 
                    out << "[HEAD]=> ";
                out << "#" << m_queue[i]->get_id() << " ";
            }
            out << "\n";
        }

        void display(std::ostream & out) override {
            if (m_queue.empty())
                return;
            out << "case-splits:\n";
            display_core(out);
        }
    };


    /**
       \brief Case split queue based on relevancy propagation and generation/goal-similarity
    */
    class rel_goal_case_split_queue : public case_split_queue {
#if 0
#define GOAL_START() m_goal_time.start()
#define GOAL_STOP()  m_goal_time.stop()
#else
#define GOAL_START() do {} while (0)
#define GOAL_STOP()  do {} while (0)
#endif

        struct queue_entry {
            expr *    m_expr;
            unsigned  m_generation;
            int       m_last_decided;

            queue_entry(expr * e, unsigned gen):
                    m_expr(e),
                    m_generation(gen),
                    m_last_decided(-1) {}
        };

        struct generation_lt {
            rel_goal_case_split_queue & m_parent;
            generation_lt(rel_goal_case_split_queue & p):m_parent(p) {}
            bool operator()(int v1, int v2) const {
                unsigned g1 = m_parent.m_queue2[v1].m_generation;
                unsigned g2 = m_parent.m_queue2[v2].m_generation;

                if (g1 == g2)
                    return v1 < v2;
                else
                    return g1 < g2;
            }
        };

        struct scope {
            unsigned m_queue_trail;
            unsigned m_head_old;
            unsigned m_queue2_trail;
            unsigned m_generation;
            expr *   m_goal;
        };

        typedef int_hashtable<int_hash, default_eq<int> > bool_var_set;
        context &            m_context;
        smt_params &   m_params;  
        ast_manager &        m_manager;
        ptr_vector<expr>     m_queue;
        unsigned             m_head;
        bool_var             m_bs_num_bool_vars; //!< Number of boolean variable before starting to search.
        svector<queue_entry> m_queue2;
        svector<scope>       m_scopes;
        unsigned             m_current_generation;

        // The heap holds indices into m_queue2, i in m_priority_queue2 <==> m_queue2[i].m_last_assigned == -1
        heap<generation_lt>     m_priority_queue2;
        expr                  * m_current_goal;
        stopwatch               m_goal_time;

        static const unsigned start_gen = 0;
        static const unsigned goal_gen_decrement = 100;


    public:
        rel_goal_case_split_queue(context & ctx, smt_params & p):
            m_context(ctx),
            m_params(p),
            m_manager(ctx.get_manager()),
            m_head(0),
            m_bs_num_bool_vars(UINT_MAX),
            m_priority_queue2(0, generation_lt(*this)),
            m_current_goal(nullptr) {
            set_global_generation();
        }
        
        void activity_increased_eh(bool_var v) override {}

        void activity_decreased_eh(bool_var v) override {}

        void mk_var_eh(bool_var v) override {}

        void del_var_eh(bool_var v) override {}

        void unassign_var_eh(bool_var v) override {}

        void relevant_eh(expr * n) override {
            if (get_generation(n) == 0 && m_current_generation != 0)
                set_generation_rec(n, m_current_generation);

            if (!m_manager.is_bool(n))
                return;
            bool is_or     = m_manager.is_or(n);
            bool intern    = m_context.b_internalized(n);
            if (!intern && !is_or) 
                return;
            bool_var var = null_bool_var;
            if (intern) {
                var = m_context.get_bool_var(n);
                SASSERT(var != null_bool_var);
                bool is_and = m_manager.is_and(n);
                lbool val   = m_context.get_assignment(var);
                if (!(
                      val == l_undef || // n was not assigned yet
                      (is_or && val == l_true) || // need to justify a child
                      (is_and && val == l_false) // need to justify a child
                      ))
                    return;
            }
            if (!intern && m_context.is_searching()) {
                SASSERT(is_or);
                add_to_queue2(n);
                return;
            }
            if (var < m_bs_num_bool_vars) 
                m_queue.push_back(n);
            else
                add_to_queue2(n);
        }

        void internalize_instance_eh(expr * e, unsigned gen) override
        {
            //lower_generation(e, gen);
        }

        void init_search_eh() override {
            m_bs_num_bool_vars = m_context.get_num_bool_vars();
            set_global_generation();
        }

        void end_search_eh() override {
            m_bs_num_bool_vars = UINT_MAX;
        }

        void reset() override {
            m_queue.reset();
            m_head = 0;
            m_queue2.reset();
            m_scopes.reset();
            m_priority_queue2.reset();
            set_global_generation();
        }

        void push_scope() override {
            m_scopes.push_back(scope());
            scope & s = m_scopes.back();
            s.m_queue_trail  = m_queue.size();
            s.m_head_old     = m_head;
            s.m_queue2_trail = m_queue2.size();
            s.m_generation   = m_current_generation;
            s.m_goal         = m_current_goal;
            TRACE("case_split", tout << "head: " << m_head << "\n";);
        }

        void pop_scope(unsigned num_scopes) override {
            SASSERT(num_scopes <= m_scopes.size());
            unsigned new_lvl     = m_scopes.size() - num_scopes;
            scope & s            = m_scopes[new_lvl];
            m_queue.shrink(s.m_queue_trail);
            m_head               = s.m_head_old;
            m_current_generation = s.m_generation;
            m_current_goal       = s.m_goal;

            for (unsigned i = s.m_queue2_trail; i < m_queue2.size(); i++) {
                //TRACE("case_split", tout << "ld[" << i << "] = " << m_queue2[i].m_last_decided << " cont " << 
                SASSERT((m_queue2[i].m_last_decided == -1) == m_priority_queue2.contains(i));
                if (m_priority_queue2.contains(i))
                    m_priority_queue2.erase(i);
            }
            
            for (unsigned i = 0; i < s.m_queue2_trail; i++) {
                queue_entry & e = m_queue2[i];

                if (e.m_last_decided > static_cast<int>(new_lvl)) {
                    SASSERT(!m_priority_queue2.contains(i));
                    // Note that the generation might be reset by the pop, and we keep the heap
                    // ordered by the old generation. It's unlikely to affect performance I think.
                    m_priority_queue2.insert(i);
                    e.m_last_decided = -1;
                }
            }
            m_queue2.shrink(s.m_queue2_trail);
            m_scopes.shrink(new_lvl);
            SASSERT(m_head <= m_queue.size());
            TRACE("case_split", display(tout); tout << "head: " << m_head << "\n";);        
        }
        
        void next_case_split_core(expr * curr, bool_var & next, lbool & phase) {
            bool is_or  = m_manager.is_or(curr);
            bool is_and = m_manager.is_and(curr);
            bool intern = m_context.b_internalized(curr);
            SASSERT(intern || is_or);
            lbool val   = l_undef;
            if (intern) {
                next = m_context.get_bool_var(curr);
                val  = m_context.get_assignment(next);
            }
            else {
                SASSERT(is_or); // top level clause
                val  = l_true;
            }
            if ((is_or && val == l_true) || (is_and && val == l_false)) {
                expr * undef_child = nullptr;
                if (!has_child_assigned_to(m_context, to_app(curr), val, undef_child, m_params.m_rel_case_split_order)) {
                    if (m_manager.has_trace_stream()) {
                        m_manager.trace_stream() << "[decide-and-or] #" << curr->get_id() << " #" << undef_child->get_id() << "\n";
                    }
                    TRACE("case_split", tout << "found AND/OR candidate: #" << curr->get_id() << " #" << undef_child->get_id() << "\n";);
                    literal l = m_context.get_literal(undef_child);
                    next  = l.var();
                    phase = l.sign() ? l_false : l_true;
                    TRACE("case_split", display(tout););
                    return;
                }
            }
            else if (val == l_undef) {
                SASSERT(intern && m_context.get_bool_var(curr) == next);
                TRACE("case_split", tout << "found candidate: #" << curr->get_id() << "\n";);
                phase = l_undef;
                TRACE("case_split", display(tout););
                return;
            }
            next = null_bool_var;
        }

        void next_case_split(bool_var & next, lbool & phase) override {
            phase = l_undef;
            next = null_bool_var;

            unsigned sz = m_queue.size();
            for (; m_head < sz; m_head++) {
                expr * curr = m_queue[m_head];
                next_case_split_core(curr, next, phase);
                if (next != null_bool_var)
                    return;
            }

            while (!m_priority_queue2.empty()) {
                unsigned idx = static_cast<unsigned>(m_priority_queue2.erase_min());
                TRACE("case_split", tout << "q " << m_queue2.size() << " idx " << idx << "\n"; );
                SASSERT(idx < m_queue2.size());
                queue_entry & e = m_queue2[idx];
                SASSERT(e.m_last_decided == -1);
                e.m_last_decided = m_scopes.size();

                next_case_split_core(e.m_expr, next, phase);
                
                if (next != null_bool_var) {
                    // Push the last guy back in; the other queue doesn't increment
                    // the m_head in case of return and the code in decide() actually
                    // does the push after calling us
                    m_priority_queue2.insert(idx);
                    e.m_last_decided = -1;
                    return;
                }
            }
        }

        void display_core(std::ostream & out, ptr_vector<expr> & queue, unsigned head, unsigned idx) {
            if (queue.empty())
                return;
            unsigned sz = queue.size();
            for (unsigned i = 0; i < sz; i++) {
                if (i == head) 
                    out << "[HEAD" << idx << "]=> ";
                out << "#" << queue[i]->get_id() << " ";
            }
            out << "\n";
        }

        void display(std::ostream & out) override {
            if (m_queue.empty() && m_queue2.empty())
                return;
            out << "case-splits:\n";
            display_core(out, m_queue, m_head, 1);
            //display_core(out, m_queue2, m_head2, 2);
        }

        void assign_lit_eh(literal l) override {
            expr * e = m_context.bool_var2expr(l.var());
            if (e == m_current_goal)
                return;
            bool sign = l.sign();
            if ( ((m_manager.is_and(e) && !sign) ||
                  (m_manager.is_or(e) && sign)) &&
                  to_app(e)->get_num_args() == 2) {
                
                expr * lablit = to_app(e)->get_arg(1);
                if (m_manager.is_not(lablit)) {
                    sign = !sign;
                    lablit = to_app(lablit)->get_arg(0);
                }
                if (sign) return;
                if (!m_manager.is_label_lit(lablit))
                    return;

                TRACE("case_split", tout << "Found goal\n" << mk_pp(e, m_manager) << "\n"; );

                set_goal(e);
            }
        }

    private:

        unsigned get_generation(expr * e)
        {
            unsigned maxgen = 0;
            unsigned mingen = (unsigned)-1;
            ptr_vector<expr> stack;

            stack.push_back(e);
            while (!stack.empty()) {
                unsigned gen;
                expr * curr;

                curr = stack.back();
                stack.pop_back();

                if (m_context.e_internalized(curr)) {
                    gen = m_context.get_enode(curr)->get_generation();
                    if (gen > maxgen)
                        maxgen = gen;
                    if (gen < mingen)
                        mingen = gen;
                }
                else if (is_app(curr)) {
                    app * a = to_app(curr);
                    for (unsigned i = 0; i < a->get_num_args(); ++i)
                        stack.push_back(a->get_arg(i));
                }
            }

            return maxgen;
        }

        void add_to_queue2(expr * e)
        {
            int      idx = m_queue2.size();

            GOAL_START();
            m_queue2.push_back(queue_entry(e, get_generation(e)));
            m_priority_queue2.reserve(idx+1);
            m_priority_queue2.insert(idx);
            GOAL_STOP();
        }

        struct set_generation_fn { 
            context & m_context;
            unsigned m_generation;
            set_generation_fn(context & ctx, unsigned gen) : m_context(ctx), m_generation(gen) { }
            void operator()(expr * e) {
                if (m_context.e_internalized(e)) {
                    enode * n = m_context.get_enode(e);
                    n->set_generation(m_context, m_generation);
                }
            }
        };

        void set_generation_rec(expr * e, unsigned gen)
        {
            set_generation_fn proc(m_context, gen);
            for_each_expr(proc, e);
        }

        void lower_generation(expr * e, unsigned gen)
        {
            ptr_vector<expr> stack;

            stack.push_back(e);
            while (!stack.empty()) {
                expr * curr;

                curr = stack.back();
                stack.pop_back();

                if (m_context.e_internalized(curr)) {
                    unsigned curr_gen = m_context.get_enode(curr)->get_generation();
                    if (curr_gen > gen) {
                        // Lower it.
                        set_generation_rec(e, gen);
                        continue; // Don't add children.
                    }
                    else if (curr_gen < gen) {
                        // All the children will be lower as well, don't add them.
                        continue;
                    }
                }

                if (is_app(curr)) {
                    app * a = to_app(curr);
                    for (unsigned i = 0; i < a->get_num_args(); ++i)
                        stack.push_back(a->get_arg(i));
                }
            }
        }

        void set_goal(expr * e)
        {
            if (e == m_current_goal) return;

            GOAL_START();

            m_current_goal = e;

#if 1
            if (m_current_generation >= goal_gen_decrement) {
                set_generation_rec(m_current_goal, m_current_generation - goal_gen_decrement);

                /*
                m_priority_queue2.reset();
                m_priority_queue2.reserve(m_queue2.size());

                for (unsigned i = 0; i < m_queue2.size(); ++i) {
                    queue_entry & e = m_queue2[i];
                    e.m_generation = get_generation(e.m_expr);
                    if (e.m_last_decided == -1)
                        m_priority_queue2.insert(i);
                }
                */
            }
#endif

            GOAL_STOP();
        }

        void set_global_generation()
        {
            m_current_generation = start_gen;
            m_context.set_global_generation(start_gen);
            if (m_params.m_qi_eager_threshold < start_gen)
                m_params.m_qi_eager_threshold += start_gen;
        }
    };

    class theory_aware_branching_queue : public case_split_queue {
    protected:
        context &          m_context;
        smt_params &       m_params;
        theory_var_priority_map m_theory_var_priority;
        theory_aware_act_queue m_queue;

        int_hashtable<int_hash, default_eq<bool_var> > m_theory_vars;
        map<bool_var, lbool, int_hash, default_eq<bool_var> > m_theory_var_phase;
    public:
        theory_aware_branching_queue(context & ctx, smt_params & p):
            m_context(ctx),
            m_params(p),
            m_theory_var_priority(),
            m_queue(1024, theory_aware_act_lt(ctx.get_activity_vector(), m_theory_var_priority)) {
        }

        void activity_increased_eh(bool_var v) override {
            if (m_queue.contains(v))
                m_queue.decreased(v);
        }

        void activity_decreased_eh(bool_var v) override {
            if (m_queue.contains(v))
                m_queue.increased(v);
        }

        void mk_var_eh(bool_var v) override {
            m_queue.reserve(v+1);
            m_queue.insert(v);
        }

        void del_var_eh(bool_var v) override {
            if (m_queue.contains(v))
                m_queue.erase(v);
        }

        void unassign_var_eh(bool_var v) override {
            if (!m_queue.contains(v))
                m_queue.insert(v);
        }

        void relevant_eh(expr * n) override {}

        void init_search_eh() override {}

        void end_search_eh() override {}

        void reset() override {
            m_queue.reset();
        }

        void push_scope() override {}

        void pop_scope(unsigned num_scopes) override {}

        void next_case_split(bool_var & next, lbool & phase) override {
            int threshold = static_cast<int>(m_params.m_random_var_freq * random_gen::max_value());
            SASSERT(threshold >= 0);
            if (m_context.get_random_value() < threshold) {
                SASSERT(m_context.get_num_b_internalized() > 0);
                next = m_context.get_random_value() % m_context.get_num_b_internalized(); 
                TRACE("random_split", tout << "next: " << next << " get_assignment(next): " << m_context.get_assignment(next) << "\n";);
                if (m_context.get_assignment(next) == l_undef)
                    return;
            }

            while (!m_queue.empty()) {
                next = m_queue.erase_min();
                if (m_context.get_assignment(next) == l_undef)
                    return;
            }

            next = null_bool_var;
            if (m_theory_vars.contains(next)) {
                if (!m_theory_var_phase.find(next, phase)) {
                    phase = l_undef;
                }
            }
        }

        void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) override {
            TRACE("theory_aware_branching", tout << "Add theory-aware branching information for l#" << v << ": priority=" << priority << std::endl;);
            m_theory_vars.insert(v);
            m_theory_var_phase.insert(v, phase);
            m_theory_var_priority.insert(v, priority);
            if (m_queue.contains(v)) {
                if (priority > 0.0) {
                    m_queue.decreased(v);
                } else {
                    m_queue.increased(v);
                }
            }
            // m_theory_queue.reserve(v+1);
            // m_theory_queue.insert(v);
        }

        void display(std::ostream & out) override {
            bool first = true;
            bool_var_act_queue::const_iterator it  = m_queue.begin();
            bool_var_act_queue::const_iterator end = m_queue.end();
            for (; it != end ; ++it) {
                unsigned v = *it;
                if (m_context.get_assignment(v) == l_undef) {
                    if (first) {
                        out << "remaining case-splits:\n";
                        first = false;
                    }
                    out << "#" << m_context.bool_var2expr(v)->get_id() << " ";
                }
            }
            if (!first)
                out << "\n";

        }

        ~theory_aware_branching_queue() override {};
    };
}

namespace smt {
    case_split_queue * mk_case_split_queue(context & ctx, smt_params & p) {
        if (ctx.relevancy_lvl() < 2 && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || 
                                        p.m_case_split_strategy == CS_RELEVANCY_GOAL)) {
            warning_msg("relevancy must be enabled to use option CASE_SPLIT=3, 4 or 5");
            p.m_case_split_strategy = CS_ACTIVITY;
        }
        if (p.m_auto_config && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || 
                                p.m_case_split_strategy == CS_RELEVANCY_GOAL)) {
            warning_msg("auto configuration (option AUTO_CONFIG) must be disabled to use option CASE_SPLIT=3, 4 or 5");
            p.m_case_split_strategy = CS_ACTIVITY;
        }
        switch (p.m_case_split_strategy) {
        case CS_ACTIVITY_DELAY_NEW:
            return alloc(dact_case_split_queue, ctx, p);
        case CS_ACTIVITY_WITH_CACHE:
            return alloc(cact_case_split_queue, ctx, p);
        case CS_RELEVANCY:
            return alloc(rel_case_split_queue, ctx, p);
        case CS_RELEVANCY_ACTIVITY:
            return alloc(rel_act_case_split_queue, ctx, p);
        case CS_RELEVANCY_GOAL:
            return alloc(rel_goal_case_split_queue, ctx, p);
        case CS_ACTIVITY_THEORY_AWARE_BRANCHING:
            return alloc(theory_aware_branching_queue, ctx, p);
        default:
            return alloc(act_case_split_queue, ctx, p);
        }
    }

}
