/*++
Copyright (c) 2011 Microsoft Corporation

Module Name:

    bool_rewriter.h

Abstract:

    Basic rewrites for Boolean operators.

Author:

    Leonardo (leonardo) 2011-04-04

Notes:

--*/
#include"bool_rewriter.h"
#include"rewriter_def.h"

void bool_rewriter::updt_params(params_ref const & p) {
    m_flat                 = p.get_bool(":flat", true);
    m_elim_and             = p.get_bool(":elim-and", false);
    m_local_ctx            = p.get_bool(":local-ctx", false);
    m_local_ctx_limit      = p.get_uint(":local-ctx-limit", UINT_MAX);
    m_blast_distinct       = p.get_bool(":blast-distinct", false); 
    m_ite_extra_rules      = p.get_bool(":ite-extra-rules", false);
}

void bool_rewriter::get_param_descrs(param_descrs & r) {
    r.insert(":ite-extra-rules", CPK_BOOL, "(default: false) extra ite simplifications, these additional simplifications may reduce size locally but increase globally.");
    r.insert(":flat", CPK_BOOL, "(default: true) create nary applications for and,or,+,*,bvadd,bvmul,bvand,bvor,bvxor.");
    r.insert(":elim-and", CPK_BOOL, "(default: false) conjunctions are rewritten using negation and disjunctions.");
    r.insert(":local-ctx", CPK_BOOL, "(default: false) perform local (i.e., cheap) context simplifications.");
    r.insert(":local-ctx-limit", CPK_UINT, "(default: inf) limit for applying local context simplifier.");
    r.insert(":blast-distinct", CPK_BOOL, "(default: false) expand a distinct predicate into a quadratic number of disequalities."); 
}

br_status bool_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) {
    SASSERT(f->get_family_id() == m().get_basic_family_id());
    switch (f->get_decl_kind()) {
    case OP_EQ:
    case OP_IFF:
        SASSERT(num_args == 2);
        return mk_eq_core(args[0], args[1], result);
    case OP_DISTINCT:
        return mk_distinct_core(num_args, args, result);
    case OP_AND:
        return mk_and_core(num_args, args, result);
    case OP_OR:
        return mk_or_core(num_args, args, result);
    case OP_NOT:
        SASSERT(num_args == 1);
        return mk_not_core(args[0], result);
    case OP_ITE:
        SASSERT(num_args == 3);
        return mk_ite_core(args[0], args[1], args[2], result);
    case OP_IMPLIES:
        SASSERT(num_args == 2);
        mk_implies(args[0], args[1], result);
        return BR_DONE;
    case OP_XOR:
        SASSERT(num_args == 2);
        mk_xor(args[0], args[1], result);
        return BR_DONE;
    default:
        return BR_FAILED;
    }
}

void bool_rewriter::mk_and_as_or(unsigned num_args, expr * const * args, expr_ref & result) {
    expr_ref_buffer new_args(m());
    for (unsigned i = 0; i < num_args; i++) {
        expr_ref tmp(m());
        mk_not(args[i], tmp);
        new_args.push_back(tmp);
    }
    expr_ref tmp(m());
    mk_or(new_args.size(), new_args.c_ptr(), tmp);
    mk_not(tmp, result);
}

br_status bool_rewriter::mk_nflat_and_core(unsigned num_args, expr * const * args, expr_ref & result) {
    bool s = false;
    ptr_buffer<expr> buffer;
    expr_fast_mark1 neg_lits;
    expr_fast_mark2 pos_lits;
    
    for (unsigned i = 0; i < num_args; i++) {
        expr * arg  = args[i];
        if (m().is_true(arg)) {
            s = true;
            continue;
        }
        if (m().is_false(arg)) {
            result = m().mk_false();
            return BR_DONE;
        }
        if (m().is_not(arg)) {
            expr * atom = to_app(arg)->get_arg(0);
            if (neg_lits.is_marked(atom)) {
                s = true;
                continue;
            }
            if (pos_lits.is_marked(atom)) {
                result = m().mk_false();
                return BR_DONE;
            }
            neg_lits.mark(atom);
        }
        else {
            if (pos_lits.is_marked(arg)) {
                s = true;
                continue;
            }
            if (neg_lits.is_marked(arg)) {
                result = m().mk_false();
                return BR_DONE;
            }
            pos_lits.mark(arg);
        }
        buffer.push_back(arg);
    }
    
    unsigned sz = buffer.size();
    
    switch(sz) {
    case 0:
        result = m().mk_true();
        return BR_DONE;
    case 1:
        result = buffer.back();
        return BR_DONE;
    default:
        if (s) {
            result = m().mk_and(sz, buffer.c_ptr());
            return BR_DONE;
        }
        return BR_FAILED;
    }
}

br_status bool_rewriter::mk_flat_and_core(unsigned num_args, expr * const * args, expr_ref & result) {
    unsigned i;
    for (i = 0; i < num_args; i++) {
        if (m().is_and(args[i]))
            break;
    }
    if (i < num_args) {
        // has nested ANDs
        ptr_buffer<expr> flat_args;
        flat_args.append(i, args);
        for (; i < num_args; i++) {
            expr * arg = args[i];
            // Remark: all rewrites are depth 1.
            if (m().is_and(arg)) {
                unsigned num = to_app(arg)->get_num_args();
                for (unsigned j = 0; j < num; j++)
                    flat_args.push_back(to_app(arg)->get_arg(j));
            }
            else {
                flat_args.push_back(arg);
            }
        }
        if (mk_nflat_and_core(flat_args.size(), flat_args.c_ptr(), result) == BR_FAILED)
            result = m().mk_and(flat_args.size(), flat_args.c_ptr());
        return BR_DONE;
    }
    return mk_nflat_and_core(num_args, args, result);
}

br_status bool_rewriter::mk_nflat_or_core(unsigned num_args, expr * const * args, expr_ref & result) {
    bool s = false;
    ptr_buffer<expr> buffer;
    expr_fast_mark1 neg_lits;
    expr_fast_mark2 pos_lits;
    for (unsigned i = 0; i < num_args; i++) {
        expr * arg  = args[i];
        if (m().is_true(arg)) {
            result = m().mk_true();
            return BR_DONE;
        }
        if (m().is_false(arg)) {
            s = true;
            continue;
        }
        if (m().is_not(arg)) {
            expr * atom = to_app(arg)->get_arg(0);
            if (neg_lits.is_marked(atom)) {
                s = true;
                continue;
            }
            if (pos_lits.is_marked(atom)) {
                result = m().mk_true();
                return BR_DONE;
            }
            neg_lits.mark(atom);
        }
        else {
            if (pos_lits.is_marked(arg)) {
                s = true;
                continue;
            }
            if (neg_lits.is_marked(arg)) {
                result = m().mk_true();
                return BR_DONE;
            }
            pos_lits.mark(arg);
        }
        buffer.push_back(arg);
    }
    
    unsigned sz = buffer.size();
    
    switch(sz) {
    case 0:
        result = m().mk_false();
        return BR_DONE;
    case 1:
        result = buffer.back();
        return BR_DONE;
    default:
        if (m_local_ctx && m_local_ctx_cost <= m_local_ctx_limit) {
            neg_lits.reset();
            pos_lits.reset();
            if (local_ctx_simp(sz, buffer.c_ptr(), result)) 
                return BR_DONE;
        }
        if (s) {
            result = m().mk_or(sz, buffer.c_ptr());
            return BR_DONE;
        }
        return BR_FAILED;
    }
}


br_status bool_rewriter::mk_flat_or_core(unsigned num_args, expr * const * args, expr_ref & result) {
    unsigned i;
    for (i = 0; i < num_args; i++) {
        if (m().is_or(args[i]))
            break;
    }
    if (i < num_args) {
        // has nested ORs
        ptr_buffer<expr> flat_args;
        flat_args.append(i, args);
        for (; i < num_args; i++) {
            expr * arg = args[i];
            // Remark: all rewrites are depth 1.
            if (m().is_or(arg)) {
                unsigned num = to_app(arg)->get_num_args();
                for (unsigned j = 0; j < num; j++)
                    flat_args.push_back(to_app(arg)->get_arg(j));
            }
            else {
                flat_args.push_back(arg);
            }
        }
        if (mk_nflat_or_core(flat_args.size(), flat_args.c_ptr(), result) == BR_FAILED)
            result = m().mk_or(flat_args.size(), flat_args.c_ptr());
        return BR_DONE;
    }
    return mk_nflat_or_core(num_args, args, result);
}

expr * bool_rewriter::mk_or_app(unsigned num_args, expr * const * args) {
    switch(num_args) {
    case 0: return m().mk_false();
    case 1: return args[0];
    default: return m().mk_or(num_args, args);
    }
}

/**
   \brief Auxiliary method for local_ctx_simp.
   
   Replace args[i] by true if marked in neg_lits.
   Replace args[i] by false if marked in pos_lits.
*/
bool bool_rewriter::simp_nested_not_or(unsigned num_args, expr * const * args, 
                                       expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, expr_ref & result) {
    ptr_buffer<expr> new_args;
    bool simp = false;
    m_local_ctx_cost += num_args;
    for (unsigned i = 0; i < num_args; i++) {
        expr * arg = args[i];
        if (neg_lits.is_marked(arg)) {
            result = m().mk_false();
            return true;
        }
        if (pos_lits.is_marked(arg)) {
            simp = true;
            continue;
        }
        if (m().is_not(arg)) {
            expr * atom = to_app(arg)->get_arg(0);
            if (neg_lits.is_marked(atom)) {
                simp = true;
                continue;
            }
            if (pos_lits.is_marked(atom)) {
                result = m().mk_false();
                return true;
            }
        }
        new_args.push_back(arg);
    }
    if (simp) {
        switch(new_args.size()) {
        case 0: 
            result = m().mk_true(); 
            return true;
        case 1: 
            mk_not(new_args[0], result); 
            return true;
        default: 
            result = m().mk_not(m().mk_or(new_args.size(), new_args.c_ptr()));
            return true;
        }
    }
    return false;
}


expr * bool_rewriter::simp_arg(expr * arg, expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, bool & modified) {
    if (m().is_not(arg)) {
        expr * atom = to_app(arg)->get_arg(0);
        if (neg_lits.is_marked(atom)) {
            modified = true;
            return m().mk_false();
        }
        if (pos_lits.is_marked(atom)) {
            modified = true;
            return m().mk_true();
        }
        return arg;
    }
    else {
        if (neg_lits.is_marked(arg)) {
            modified = true;
            return m().mk_true();
        }
        if (pos_lits.is_marked(arg)) {
            modified = true;
            return m().mk_false();
        }
        return arg;
    }
}

/**
   \brief Simpler version of mk_ite, that will not invoke mk_or/mk_and.
   It is used byt local_ctx_simp to prevent a recursive call to local_ctx_simp.
   See comment at simp_nested_eq_ite.
*/
void bool_rewriter::mk_nested_ite(expr * c, expr * t, expr * e, expr_ref & result) {
    if (m().is_true(c)) {
        result = t;
        return;
    }
    
    if (m().is_false(c)) {
        result = e;
        return;
    }

    if (t == e) {
        result = t;
        return;
    }
    
    if (m().is_bool(t)) {
        if (m().is_true(t)) {
            if (m().is_false(e)) {
                result = c;
                return;
            }
            result = m().mk_or(c, e);
            return;
        }
        if (m().is_false(t)) {
            if (m().is_true(e)) {
                mk_not(c, result);
                return;
            }
            expr_ref tmp(m());
            mk_not(e, tmp); 
            result = m().mk_not(m().mk_or(c, tmp));
            return;
        }
        if (m().is_true(e)) {
            expr_ref tmp(m());
            mk_not(c, tmp); 
            result = m().mk_or(tmp, t);
            return;
        }
        if (m().is_false(e) || c == e) {
            expr_ref tmp1(m());
            expr_ref tmp2(m());
            mk_not(c, tmp1);
            mk_not(t, tmp2);
            result = m().mk_not(m().mk_or(tmp1, tmp2));
            return;
        }
        if (c == t) {
            result = m().mk_or(c, e);
            return;
        }
        if (m().is_complement_core(t, e)) { // t = not(e) 
            mk_eq(c, t, result);
            return;
        }
        if (m().is_complement_core(e, t)) { // e = not(t)
            mk_eq(c, t, result);
            return;
        }
    }
    result = m().mk_ite(c, t, e);
}

bool bool_rewriter::simp_nested_eq_ite(expr * t, expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, expr_ref & result) {
    bool neg = false;
    m_local_ctx_cost += 3;
    if (m().is_not(t)) {
        neg = true;
        t = to_app(t)->get_arg(0);
    }
    if (m().is_iff(t) || m().is_eq(t)) {
        bool modified = false;
        expr * new_lhs = simp_arg(to_app(t)->get_arg(0), neg_lits, pos_lits, modified);
        expr * new_rhs = simp_arg(to_app(t)->get_arg(1), neg_lits, pos_lits, modified);
        if (!modified)
            return false;
        mk_eq(new_lhs, new_rhs, result);
        if (neg)
            mk_not(result, result);
        return true;
    }
    if (m().is_ite(t)) {
        bool modified = false;
        expr * new_c = simp_arg(to_app(t)->get_arg(0), neg_lits, pos_lits, modified);
        expr * new_t = simp_arg(to_app(t)->get_arg(1), neg_lits, pos_lits, modified);
        expr * new_e = simp_arg(to_app(t)->get_arg(2), neg_lits, pos_lits, modified);
        if (!modified)
            return false;
        // It is not safe to invoke mk_ite here, since it can recursively call 
        // local_ctx_simp by 
        //     - transforming the ITE into an OR
        //     - and invoked mk_or, that will invoke local_ctx_simp
        // mk_ite(new_c, new_t, new_e, result);
        mk_nested_ite(new_c, new_t, new_e, result);
        if (neg)
            mk_not(result, result);
        return true;
    }
    return false;
}

/**
   \brief Apply local context simplification at (OR args[0] ... args[num_args-1])
   Basic idea:
   - Replace args[i] by false in the other arguments
   - If args[i] is of the form (not t), then replace t by true in the other arguments.
   To make sure the simplification is efficient we bound the depth.
*/
bool bool_rewriter::local_ctx_simp(unsigned num_args, expr * const * args, expr_ref & result) {
    expr_ref_vector old_args(m());
    expr_ref_vector new_args(m());
    expr_ref        new_arg(m());
    expr_fast_mark1 neg_lits;
    expr_fast_mark2 pos_lits;
    bool simp     = false;
    bool modified = false;
    bool forward  = true;
    unsigned rounds = 0;

    while (true) {
        rounds++;
#if 0
        if (rounds > 10)
            verbose_stream() << "rounds: " << rounds << "\n";
#endif

#define PUSH_NEW_ARG(ARG) {                     \
    new_args.push_back(ARG);                    \
    if (m().is_not(ARG))                        \
        neg_lits.mark(to_app(ARG)->get_arg(0)); \
    else                                        \
        pos_lits.mark(ARG);                     \
}

#define PROCESS_ARG()                                                                           \
        {                                                                                       \
            expr * arg = args[i];                                                               \
            if (m().is_not(arg) && m().is_or(to_app(arg)->get_arg(0)) &&                        \
                simp_nested_not_or(to_app(to_app(arg)->get_arg(0))->get_num_args(),             \
                                   to_app(to_app(arg)->get_arg(0))->get_args(),                 \
                                   neg_lits,                                                    \
                                   pos_lits,                                                    \
                                   new_arg)) {                                                  \
                modified = true; simp = true;                                                   \
                arg = new_arg;                                                                  \
            }                                                                                   \
            if (simp_nested_eq_ite(arg, neg_lits, pos_lits, new_arg)) {                         \
                modified = true; simp = true;                                                   \
                 arg = new_arg;                                                                 \
            }                                                                                   \
            if (m().is_false(arg))                                                              \
                continue;                                                                       \
            if (m().is_true(arg)) {                                                             \
                result = arg;                                                                   \
                return true;                                                                    \
            }                                                                                   \
            if (m_flat && m().is_or(arg)) {                                                     \
                unsigned sz = to_app(arg)->get_num_args();                                      \
                for (unsigned j = 0; j < sz; j++) {                                             \
                    expr * arg_arg = to_app(arg)->get_arg(j);                                   \
                    PUSH_NEW_ARG(arg_arg);                                                      \
                }                                                                               \
            }                                                                                   \
            else {                                                                              \
                PUSH_NEW_ARG(arg);                                                              \
            }                                                                                   \
        }
        
        m_local_ctx_cost += 2*num_args;
#if 0
        static unsigned counter = 0;
        counter++;
        if (counter % 10000 == 0)
            verbose_stream() << "local-ctx-cost: " << m_local_ctx_cost << "\n";
#endif

        if (forward) {
            for (unsigned i = 0; i < num_args; i++) {
                PROCESS_ARG();
            }
            forward = false;
        }
        else {
            unsigned i = num_args;
            while (i > 0) {
                --i;
                PROCESS_ARG();
            }
            if (!modified) {
                if (simp) {
                    result = mk_or_app(num_args, args);
                    return true;
                }
                return false; // didn't simplify
            }
            // preserve the original order...
            std::reverse(new_args.c_ptr(), new_args.c_ptr() + new_args.size());
            modified = false;
            forward  = true;
        }
        pos_lits.reset();
        neg_lits.reset();
        old_args.reset();
        old_args.swap(new_args);
        SASSERT(new_args.empty());
        args     = old_args.c_ptr();
        num_args = old_args.size();
    }
}

/**
   \brief Apply simplification if ite is an if-then-else tree where every leaf is a value.
   
   This is an efficient way to 
   
*/
br_status bool_rewriter::try_ite_value(app * ite, app * val, expr_ref & result) {
    SASSERT(m().is_ite(ite));
    SASSERT(m().is_value(val));

    expr * t = ite->get_arg(1);
    expr * e = ite->get_arg(2);
    if (!m().is_value(t) || !m().is_value(e))
        return BR_FAILED;

    if (t != val && e != val) {
        TRACE("try_ite_value", tout << mk_ismt2_pp(t, m()) << " " << mk_ismt2_pp(e, m()) << " " << mk_ismt2_pp(val, m()) << "\n";
              tout << t << " " << e << " " << val << "\n";);
        result = m().mk_false();
        return BR_DONE;
    } 

    if (t == val && e == val) {
        result = m().mk_true();
        return BR_DONE;
    }

    if (t == val) {
        result = ite->get_arg(0);
        return BR_DONE;
    }

    SASSERT(e == val);
    
    mk_not(ite->get_arg(0), result);
    return BR_DONE;
}

#if 0
// Return true if ite is an if-then-else tree where the leaves are values,
// and they are all different from val
static bool is_ite_value_tree_neq_value(ast_manager & m, app * ite, app * val) {
    SASSERT(m.is_ite(ite));
    SASSERT(m.is_value(val));

    expr_fast_mark1 visited;
    ptr_buffer<app> todo;
    todo.push_back(ite);

#define VISIT(ARG) {                                            \
        if (m.is_value(ARG)) {                                  \
            if (ARG == val)                                     \
                return false;                                   \
        }                                                       \
        else if (m.is_ite(ARG)) {                               \
            if (!visited.is_marked(ARG)) {                      \
                visited.mark(ARG);                              \
                todo.push_back(to_app(ARG));                    \
            }                                                   \
        }                                                       \
        else {                                                  \
            return false;                                       \
        }                                                       \
    }

    while (!todo.empty()) {
        app * ite = todo.back();
        todo.pop_back();
        SASSERT(m.is_ite(ite));
        expr * t = ite->get_arg(1);
        expr * e = ite->get_arg(2);
        VISIT(t);
        VISIT(e);
    }

    return true;
}
#endif 

br_status bool_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) {
    if (lhs == rhs) {
        result = m().mk_true();
        return BR_DONE;
    }
    
    if (m().are_distinct(lhs, rhs)) {
        result = m().mk_false();
        return BR_DONE;
    }

    br_status r = BR_FAILED;
    if (m().is_ite(lhs) && m().is_value(rhs)) {
        // if (is_ite_value_tree_neq_value(m(), to_app(lhs), to_app(rhs))) {
        //    result = m().mk_false();
        //    return BR_DONE;
        // }
        r = try_ite_value(to_app(lhs), to_app(rhs), result);
        CTRACE("try_ite_value", r != BR_FAILED, 
               tout << mk_ismt2_pp(lhs, m()) << "\n" << mk_ismt2_pp(rhs, m()) << "\n--->\n" << mk_ismt2_pp(result, m()) << "\n";);
    }
    else if (m().is_ite(rhs) && m().is_value(lhs)) {
        // if (is_ite_value_tree_neq_value(m(), to_app(rhs), to_app(lhs))) {
        //    result = m().mk_false();
        //    return BR_DONE;
        // }
        r = try_ite_value(to_app(rhs), to_app(lhs), result);
        CTRACE("try_ite_value", r != BR_FAILED, 
               tout << mk_ismt2_pp(lhs, m()) << "\n" << mk_ismt2_pp(rhs, m()) << "\n--->\n" << mk_ismt2_pp(result, m()) << "\n";);
    }
    if (r != BR_FAILED)
        return r;

    if (m().is_bool(lhs)) {
        bool unfolded = false;
        if (m().is_not(lhs) && m().is_not(rhs)) {
            lhs = to_app(lhs)->get_arg(0);
            rhs = to_app(rhs)->get_arg(0);
            unfolded = true;
        }
        if (m().is_true(lhs)) {
            result = rhs;
            return BR_DONE;
        }
        if (m().is_false(lhs)) {
            mk_not(rhs, result);
            return BR_DONE;
        }
        if (m().is_true(rhs)) {
            result = lhs;
            return BR_DONE;
        }
        if (m().is_false(rhs)) {
            mk_not(lhs, result);
            return BR_DONE;
        }
        if (m().is_complement(lhs, rhs)) {
            result = m().mk_false();
            return BR_DONE;
        }
        if (unfolded) {
            result = m().mk_eq(lhs, rhs);
            return BR_DONE;
        }
    }
    return BR_FAILED;
}

br_status bool_rewriter::mk_distinct_core(unsigned num_args, expr * const * args, expr_ref & result) {
    if (num_args <= 1) {
        result = m().mk_true();
        return BR_DONE;
    }

    if (num_args == 2) {
        expr_ref tmp(m());
        result = m().mk_not(m().mk_eq(args[0], args[1]));
        return BR_REWRITE2; // mk_eq may be dispatched to other rewriters.
    }

    expr_fast_mark1 visited;
    bool all_value = true;
    for (unsigned i = 0; i < num_args; i++) {
        expr * arg = args[i];
        if (visited.is_marked(arg)) {
            result = m().mk_false();
            return BR_DONE;
        }
        visited.mark(arg);
        if (!m().is_value(arg))
            all_value = false;
    }
    if (all_value) {
        result = m().mk_true();
        return BR_DONE;
    }

    SASSERT(num_args > 2);
    if (m().is_bool(args[0])) {
        result = m().mk_false();
        return BR_DONE;
    }

    if (m_blast_distinct) {
        ptr_buffer<expr> new_diseqs;
        for (unsigned i = 0; i < num_args; i++) {
            for (unsigned j = i + 1; j < num_args; j++)
                new_diseqs.push_back(m().mk_not(m().mk_eq(args[i], args[j])));
        }
        result = m().mk_and(new_diseqs.size(), new_diseqs.c_ptr());
        return BR_REWRITE3;
    }
    
    return BR_FAILED;
}

br_status bool_rewriter::mk_ite_core(expr * c, expr * t, expr * e, expr_ref & result) {
    bool s = false;
    
    // (ite (not c) a b) ==> (ite c b a)
    if (m().is_not(c)) {
        c = to_app(c)->get_arg(0);
        std::swap(t, e);
        s = true;
    }

    // (ite c (ite c t1 t2) t3)       ==> (ite c t1 t3)
    if (m().is_ite(t) && to_app(t)->get_arg(0) == c) {
        // Remark: (ite c (ite (not c) t1 t2) t3) ==> (ite c t2 t3) does not happen if applying rewrites bottom up
        t = to_app(t)->get_arg(1);
        s = true;
    }

    // (ite c t1 (ite c t2 t3))       ==> (ite c t1 t3)
    if (m().is_ite(e) && to_app(e)->get_arg(0) == c) {
        // Remark: (ite c t1 (ite (not c) t2 t3)) ==> (ite c t1 t2) does not happen if applying rewrites bottom up
        e = to_app(e)->get_arg(2);
        s = true;
    }

    if (m().is_true(c)) {
        result = t;
        return BR_DONE;
    }
    
    if (m().is_false(c)) {
        result = e;
        return BR_DONE;
    }

    if (t == e) {
        result = t;
        return BR_DONE;
    }

    if (m().is_bool(t)) {
        if (m().is_true(t)) {
            if (m().is_false(e)) {
                result = c;
                return BR_DONE;
            }
            mk_or(c, e, result);
            return BR_DONE;
        }
        if (m().is_false(t)) {
            if (m().is_true(e)) {
                mk_not(c, result);
                return BR_DONE;
            }
            expr_ref tmp(m());
            mk_not(c, tmp); 
            mk_and(tmp, e, result);
            return BR_DONE;
        }
        if (m().is_true(e)) {
            expr_ref tmp(m());
            mk_not(c, tmp); 
            mk_or(tmp, t, result);
            return BR_DONE;
        }
        if (m().is_false(e)) {
            mk_and(c, t, result); 
            return BR_DONE;
        }
        if (c == e) {
            mk_and(c, t, result);
            return BR_DONE;
        }
        if (c == t) {
            mk_or(c, e, result); 
            return BR_DONE;
        }
        if (m().is_complement_core(t, e)) { // t = not(e) 
            mk_eq(c, t, result);
            return BR_DONE;
        }
        if (m().is_complement_core(e, t)) { // e = not(t)
            mk_eq(c, t, result);
            return BR_DONE;
        }
    }

    if (m().is_ite(t) && m_ite_extra_rules) {
        // (ite c1 (ite c2 t1 t2) t1) ==> (ite (and c1 (not c2)) t2 t1)
        if (e == to_app(t)->get_arg(1)) {
            expr_ref not_c2(m());
            mk_not(to_app(t)->get_arg(0), not_c2);
            expr_ref new_c(m());
            mk_and(c, not_c2, new_c);
            result = m().mk_ite(new_c, to_app(t)->get_arg(2), e);
            return BR_REWRITE1;
        }
        // (ite c1 (ite c2 t1 t2) t2) ==> (ite (and c1 c2) t1 t2)
        if (e == to_app(t)->get_arg(2)) {
            expr_ref new_c(m());
            mk_and(c, to_app(t)->get_arg(0), new_c);
            result = m().mk_ite(new_c, to_app(t)->get_arg(1), e);
            return BR_REWRITE1;
        }
        
        if (m().is_ite(e)) {
            // (ite c1 (ite c2 t1 t2) (ite c3 t1 t2)) ==> (ite (or (and c1 c2) (and (not c1) c3)) t1 t2)
            if (to_app(t)->get_arg(1) == to_app(e)->get_arg(1) && 
                to_app(t)->get_arg(2) == to_app(e)->get_arg(2)) {
                expr_ref and1(m());
                expr_ref and2(m());
                expr_ref notc(m());
                mk_and(c, to_app(t)->get_arg(0), and1);
                mk_not(c, notc);
                mk_and(notc, to_app(e)->get_arg(0), and2);
                expr_ref new_c(m());
                mk_or(and1, and2, new_c);
                result = m().mk_ite(new_c, to_app(t)->get_arg(1), to_app(t)->get_arg(2));
                return BR_REWRITE1;
            }
            
            // (ite c1 (ite c2 t1 t2) (ite c3 t2 t1)) ==> (ite (or (and c1 c2) (and (not c1) (not c3))) t1 t2)
            if (to_app(t)->get_arg(1) == to_app(e)->get_arg(2) && 
                to_app(t)->get_arg(2) == to_app(e)->get_arg(1)) {
                expr_ref and1(m());
                expr_ref and2(m());
                expr_ref notc(m());
                mk_and(c, to_app(t)->get_arg(0), and1);
                mk_not(c, notc);
                expr_ref notc3(m());
                mk_not(to_app(e)->get_arg(0), notc3);
                mk_and(notc, notc3, and2);
                expr_ref new_c(m());
                mk_or(and1, and2, new_c);
                result = m().mk_ite(new_c, to_app(t)->get_arg(1), to_app(t)->get_arg(2));
                return BR_REWRITE1;
            }
        }
    }

    if (m().is_ite(e) && m_ite_extra_rules) {
        // (ite c1 t1 (ite c2 t1 t2)) ==> (ite (or c1 c2)        t1 t2)
        if (t == to_app(e)->get_arg(1)) {
            expr_ref new_c(m());
            mk_or(c, to_app(e)->get_arg(0), new_c);
            result = m().mk_ite(new_c, t, to_app(e)->get_arg(2));
            return BR_REWRITE1;
        }
        // (ite c1 t1 (ite c2 t2 t1)) ==> (ite (or c1 (not c2))  t1 t2)
        if (t == to_app(e)->get_arg(2)) {
            expr_ref not_c2(m());
            mk_not(to_app(e)->get_arg(0), not_c2);
            expr_ref new_c(m());
            mk_or(c, not_c2, new_c);
            result = m().mk_ite(new_c, t, to_app(e)->get_arg(1));
            return BR_REWRITE1;
        }
    }

    if (s) {
        result = m().mk_ite(c, t, e);
        return BR_DONE;
    }
    
    return BR_FAILED;
}
 
br_status bool_rewriter::mk_not_core(expr * t, expr_ref & result) {
    if (m().is_not(t)) {
        result = to_app(t)->get_arg(0);
        return BR_DONE;
    }
    if (m().is_true(t)) {
        result = m().mk_false();
        return BR_DONE;
    }
    if (m().is_false(t)) {
        result = m().mk_true();
        return BR_DONE;
    }
    if (is_eq(t) && m().is_bool(to_app(t)->get_arg(0))) {
        expr_ref tmp(m());
        mk_not(to_app(t)->get_arg(0), tmp);
        mk_eq(tmp, to_app(t)->get_arg(1), result);
        return BR_DONE;
    }
    return BR_FAILED;
}

void bool_rewriter::mk_xor(expr * lhs, expr * rhs, expr_ref & result) {
    expr_ref tmp(m());
    mk_not(lhs, tmp);
    mk_eq(tmp, rhs, result);
}

void bool_rewriter::mk_implies(expr * lhs, expr * rhs, expr_ref & result) {
    expr_ref tmp(m());
    mk_not(lhs, tmp);
    mk_or(tmp, rhs, result);
}

void bool_rewriter::mk_nand(unsigned num_args, expr * const * args, expr_ref & result) {
    expr_ref tmp(m_manager);
    mk_and(num_args, args, tmp);
    mk_not(tmp, result);
}

void bool_rewriter::mk_nor(unsigned num_args, expr * const * args, expr_ref & result) {
    expr_ref tmp(m_manager);
    mk_or(num_args, args, tmp);
    mk_not(tmp, result);
}

void bool_rewriter::mk_nand(expr * arg1, expr * arg2, expr_ref & result) {
    expr_ref tmp(m_manager);
    mk_and(arg1, arg2, tmp);
    mk_not(tmp, result);
}

void bool_rewriter::mk_nor(expr * arg1, expr * arg2, expr_ref & result) {
    expr_ref tmp(m_manager);
    mk_or(arg1, arg2, tmp);
    mk_not(tmp, result);
}

template class rewriter_tpl<bool_rewriter_cfg>;
