/*++
Copyright (c) 2011 Microsoft Corporation

Module Name:

    tactical.h

Abstract:

    Basic combinators

Author:

    Leonardo (leonardo) 2011-10-13

Notes:

--*/
#include"tactical.h"
#include"scoped_timer.h"
#include"cancel_eh.h"
#include"cooperate.h"
#include"scoped_ptr_vector.h"
#include"z3_omp.h"

class binary_tactical : public tactic {
protected:
    tactic *      m_t1;
    tactic *      m_t2;
    volatile bool m_cancel;

    void checkpoint() {
        if (m_cancel)
            throw tactic_exception(TACTIC_CANCELED_MSG);
    }
    
public:
    binary_tactical(tactic * t1, tactic * t2):
        m_t1(t1),
        m_t2(t2),
        m_cancel(false) {
        SASSERT(m_t1);
        SASSERT(m_t2);
        m_t1->inc_ref();
        m_t2->inc_ref();
    }
    
    virtual ~binary_tactical() {
        tactic * t1 = m_t1;
        tactic * t2 = m_t2;
        #pragma omp critical (tactic_cancel)
        {
            m_t1 = 0;
            m_t2 = 0;
        }
        t1->dec_ref();
        t2->dec_ref();
    }
    
    virtual void updt_params(params_ref const & p) {
        m_t1->updt_params(p);
        m_t2->updt_params(p);
    }
    
    virtual void collect_param_descrs(param_descrs & r) {
        m_t1->collect_param_descrs(r);
        m_t2->collect_param_descrs(r);
    }
    
    virtual void collect_statistics(statistics & st) const {
        m_t1->collect_statistics(st);
        m_t2->collect_statistics(st);
    }

    virtual void reset_statistics() { 
        m_t1->reset_statistics();
        m_t2->reset_statistics();
    }
        
    virtual void cleanup() {
        m_t1->cleanup();
        m_t2->cleanup();
    }
    
    virtual void reset() {
        m_t1->reset();
        m_t2->reset();
    }

    virtual void set_front_end_params(front_end_params & p) {
        m_t1->set_front_end_params(p);
        m_t2->set_front_end_params(p);
    }

    virtual void set_logic(symbol const & l) {
        m_t1->set_logic(l);
        m_t2->set_logic(l);
    }

    virtual void set_progress_callback(progress_callback * callback) {
        m_t1->set_progress_callback(callback);
        m_t2->set_progress_callback(callback);
    }

protected:
    /**
       \brief Reset cancel flag of t if this was not canceled.
    */
    void parent_reset_cancel(tactic & t) {
        #pragma omp critical (tactic_cancel)
        {
            if (!m_cancel) {
                t.set_cancel(false);
            }
        }
    }

    virtual void set_cancel(bool f) {
        m_cancel = f;
        m_t1->set_cancel(f);
        m_t2->set_cancel(f);
    }


    template<typename T>
    tactic * translate_core(ast_manager & m) { 
        tactic * new_t1 = m_t1->translate(m);
        tactic * new_t2 = m_t2->translate(m);
        return alloc(T, new_t1, new_t2);
    }
};

struct false_pred {
    bool operator()(goal * g) { return false; }
};



class and_then_tactical : public binary_tactical {
public:
    and_then_tactical(tactic * t1, tactic * t2):binary_tactical(t1, t2) {}
    virtual ~and_then_tactical() {}

    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {

        bool models_enabled = in->models_enabled();
        bool proofs_enabled = in->proofs_enabled();
        bool cores_enabled  = in->unsat_core_enabled();

        ast_manager & m = in->m();                                                                          
        goal_ref_buffer      r1;
        model_converter_ref mc1;                                                                           
        proof_converter_ref pc1;                                                                           
        expr_dependency_ref core1(m);                                                                      
        result.reset();                                                                                     
        mc   = 0;                                                                                
        pc   = 0;        
        core = 0;
        m_t1->operator()(in, r1, mc1, pc1, core1);                                                            
        SASSERT(!is_decided(r1) || (!pc1 && !core1)); // the pc and core of decided goals is 0
        unsigned r1_size = r1.size();                                                                       
        SASSERT(r1_size > 0);                                                                               
        checkpoint();                                                                                       
        if (r1_size == 1) {                                                                                 
            if (r1[0]->is_decided()) {
                result.push_back(r1[0]);                                                                    
                if (models_enabled) mc = mc1;                                                                                 
                SASSERT(!pc); SASSERT(!core);
                return;                                                                                     
            }                                                                                               
            goal_ref r1_0 = r1[0];                                                                          
            m_t2->operator()(r1_0, result, mc, pc, core);                                              
            if (models_enabled) mc = concat(mc1.get(), mc.get());                                                               
            if (proofs_enabled) pc = concat(pc1.get(), pc.get());                                                               
            if (cores_enabled) core = m.mk_join(core1.get(), core);                                                            
        }                                                                                     
        else {                                                                                              
            if (cores_enabled) core = core1;                                                                                   
            proof_converter_ref_buffer pc_buffer;                                                           
            model_converter_ref_buffer mc_buffer;                                                           
            sbuffer<unsigned>          sz_buffer;                                                           
            goal_ref_buffer            r2;                                                                  
            for (unsigned i = 0; i < r1_size; i++) {                                                        
                checkpoint();                                                                               
                goal_ref g = r1[i];                                                                         
                r2.reset();                                                                                 
                model_converter_ref mc2;                                                                   
                proof_converter_ref pc2;                                                                   
                expr_dependency_ref  core2(m);                                                              
                m_t2->operator()(g, r2, mc2, pc2, core2);                                              
                if (is_decided(r2)) {
                    SASSERT(r2.size() == 1);
                    if (is_decided_sat(r2)) {                                                          
                        // found solution...                                                                
                        result.push_back(r2[0]);
                        if (models_enabled) {
                            // mc2 contains the actual model                                                    
                            model_ref md;     
                            md = alloc(model, m);
                            apply(mc2, md, 0);
                            apply(mc1, md, i);
                            mc   = model2model_converter(md.get());                                             
                        }
                        SASSERT(!pc); SASSERT(!core);
                        return;                                                                             
                    }                                                                                   
                    else {                                                                                  
                        SASSERT(is_decided_unsat(r2));                                                 
                        // the proof and unsat core of a decided_unsat goal are stored in the node itself.
                        // pc2 and core2 must be 0.
                        SASSERT(!pc2);
                        SASSERT(!core2);
                        if (models_enabled) mc_buffer.push_back(0);
                        if (proofs_enabled) pc_buffer.push_back(proof2proof_converter(m, r2[0]->pr(0)));
                        if (models_enabled || proofs_enabled) sz_buffer.push_back(0);                                                             
                        if (cores_enabled) core = m.mk_join(core.get(), r2[0]->dep(0));
                    }                                                                         
                }                                                                                       
                else {                                                                                      
                    result.append(r2.size(), r2.c_ptr());                                                   
                    if (models_enabled) mc_buffer.push_back(mc2.get());                                                         
                    if (proofs_enabled) pc_buffer.push_back(pc2.get());                                                         
                    if (models_enabled || proofs_enabled) sz_buffer.push_back(r2.size());                                                         
                    if (cores_enabled) core = m.mk_join(core.get(), core2.get());                                              
                }                                                                                           
            }
            
            if (result.empty()) {                                                                           
                // all subgoals were shown to be unsat.                                                     
                // create an decided_unsat goal with the proof
                in->reset_all();
                proof_ref pr(m);
                if (proofs_enabled)
                    apply(m, pc1, pc_buffer, pr);
                SASSERT(cores_enabled || core == 0);
                in->assert_expr(m.mk_false(), pr, core);
                core = 0;
                result.push_back(in.get());
                SASSERT(!mc); SASSERT(!pc); SASSERT(!core);
            }
            else {
                if (models_enabled) mc = concat(mc1.get(), mc_buffer.size(), mc_buffer.c_ptr(), sz_buffer.c_ptr());                 
                if (proofs_enabled) pc = concat(pc1.get(), pc_buffer.size(), pc_buffer.c_ptr(), sz_buffer.c_ptr()); 
                SASSERT(cores_enabled || core == 0);
            }
        }
    }

    virtual tactic * translate(ast_manager & m) {
        return translate_core<and_then_tactical>(m);
    }

};

tactic * and_then(tactic * t1, tactic * t2) {
    return alloc(and_then_tactical, t1, t2);
}

tactic * and_then(tactic * t1, tactic * t2, tactic * t3) {
    return and_then(t1, and_then(t2, t3));
}

tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4) {
    return and_then(t1, and_then(t2, t3, t4));
}

tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5) {
    return and_then(t1, and_then(t2, t3, t4, t5));
}

tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6) {
    return and_then(t1, and_then(t2, t3, t4, t5, t6));
}

tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7) {
    return and_then(t1, and_then(t2, t3, t4, t5, t6, t7));
}

tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8) {
    return and_then(t1, and_then(t2, t3, t4, t5, t6, t7, t8));
}

tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9) {
    return and_then(t1, and_then(t2, t3, t4, t5, t6, t7, t8, t9));
}

tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9, tactic * t10) {
    return and_then(t1, and_then(t2, t3, t4, t5, t6, t7, t8, t9, t10));
}

tactic * and_then(unsigned num, tactic * const * ts) {
    SASSERT(num > 0);
    unsigned i = num - 1;
    tactic * r = ts[i];
    while (i > 0) {
        --i;
        r = and_then(ts[i], r);
    }
    return r;
}

class nary_tactical : public tactic {
protected:
    ptr_vector<tactic> m_ts;
    volatile bool      m_cancel;

    void checkpoint() {
        if (m_cancel)
            throw tactic_exception(TACTIC_CANCELED_MSG);
    }
public:
    nary_tactical(unsigned num, tactic * const * ts):
        m_cancel(false) {
        for (unsigned i = 0; i < num; i++) {
            SASSERT(ts[i]);
            m_ts.push_back(ts[i]);
            ts[i]->inc_ref();
        }
    }

    virtual ~nary_tactical() {
        ptr_buffer<tactic> old_ts;
        unsigned sz = m_ts.size();
        old_ts.append(sz, m_ts.c_ptr());
        #pragma omp critical (tactic_cancel)
        {
            for (unsigned i = 0; i < sz; i++) {
                m_ts[i] = 0;
            }
        }
        for (unsigned i = 0; i < sz; i++) {
            old_ts[i]->dec_ref();
        }
    }

    virtual void updt_params(params_ref const & p) {
        TRACE("nary_tactical_updt_params", tout << "updt_params: " << p << "\n";);
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->updt_params(p);
    }
    
    virtual void collect_param_descrs(param_descrs & r) {
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->collect_param_descrs(r);
    }
    
    virtual void collect_statistics(statistics & st) const {
        ptr_vector<tactic>::const_iterator it  = m_ts.begin();
        ptr_vector<tactic>::const_iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->collect_statistics(st);
    }

    virtual void reset_statistics() { 
        ptr_vector<tactic>::const_iterator it  = m_ts.begin();
        ptr_vector<tactic>::const_iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->reset_statistics();
    }
        
    virtual void cleanup() {
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->cleanup();
    }
    
    virtual void reset() {
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->reset();
    }

    virtual void set_front_end_params(front_end_params & p) {
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->set_front_end_params(p);
    }

    virtual void set_logic(symbol const & l) {
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->set_logic(l);
    }

    virtual void set_progress_callback(progress_callback * callback) {
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it)
            (*it)->set_progress_callback(callback);
    }

protected:
    /**
       \brief Reset cancel flag of st if this was not canceled.
    */
    void parent_reset_cancel(tactic & t) {
        #pragma omp critical (tactic_cancel)
        {
            if (!m_cancel) {
                t.set_cancel(false);
            }
        }
    }

    virtual void set_cancel(bool f) {
        m_cancel = f;
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it)
            if (*it)
                (*it)->set_cancel(f);
    }

    template<typename T>
    tactic * translate_core(ast_manager & m) { 
        ptr_buffer<tactic> new_ts;
        ptr_vector<tactic>::iterator it  = m_ts.begin();
        ptr_vector<tactic>::iterator end = m_ts.end();
        for (; it != end; ++it) {
            tactic * curr = *it;
            tactic * new_curr = curr->translate(m);
            new_ts.push_back(new_curr);
        }
        return alloc(T, new_ts.size(), new_ts.c_ptr());
    }

};

class or_else_tactical : public nary_tactical {
public:
    or_else_tactical(unsigned num, tactic * const * ts):nary_tactical(num, ts) { SASSERT(num > 0); }

    virtual ~or_else_tactical() {}

    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        goal orig(*(in.get()));
        unsigned sz = m_ts.size();
        unsigned i;
        for (i = 0; i < sz; i++) {
            checkpoint();
            tactic * t = m_ts[i];
            result.reset();
            mc   = 0;
            pc   = 0;
            core = 0;
            SASSERT(sz > 0);
            if (i < sz - 1) {
                try {
                    t->operator()(in, result, mc, pc, core);
                    return;
                }
                catch (tactic_exception &) {
                }
            }
            else {
                t->operator()(in, result, mc, pc, core);
                return;
            }
            in->reset_all();
            in->copy_from(orig);
        }
    }

    virtual tactic * translate(ast_manager & m) { return translate_core<or_else_tactical>(m); }
};

tactic * or_else(unsigned num, tactic * const * ts) {
    return alloc(or_else_tactical, num, ts);
}

tactic * or_else(tactic * t1, tactic * t2) {
    tactic * ts[2] = { t1, t2 };
    return or_else(2, ts);
}

tactic * or_else(tactic * t1, tactic * t2, tactic * t3) {
    tactic * ts[3] = { t1, t2, t3 };
    return or_else(3, ts);
}

tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4) {
    tactic * ts[4] = { t1, t2, t3, t4 };
    return or_else(4, ts);
}

tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5) {
    tactic * ts[5] = { t1, t2, t3, t4, t5 };
    return or_else(5, ts);
}

tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6) {
    tactic * ts[6] = { t1, t2, t3, t4, t5, t6 };
    return or_else(6, ts);
}

tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7) {
    tactic * ts[7] = { t1, t2, t3, t4, t5, t6, t7 };
    return or_else(7, ts);
}

tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8) {
    tactic * ts[8] = { t1, t2, t3, t4, t5, t6, t7, t8 };
    return or_else(8, ts);
}

tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9) {
    tactic * ts[9] = { t1, t2, t3, t4, t5, t6, t7, t8, t9 };
    return or_else(9, ts);
}

tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9, tactic * t10) {
    tactic * ts[10] = { t1, t2, t3, t4, t5, t6, t7, t8, t9, t10 };
    return or_else(10, ts);
}

enum par_exception_kind {
    TACTIC_EX,
    DEFAULT_EX,
    ERROR_EX
};

class par_tactical : public or_else_tactical {
public:
    par_tactical(unsigned num, tactic * const * ts):or_else_tactical(num, ts) {}
    virtual ~par_tactical() {}

    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {

        if (omp_in_parallel()) {
            // execute tasks sequentially
            or_else_tactical::operator()(in, result, mc, pc, core);
            return;
        }
        
        ast_manager & m = in->m();
        
        scoped_ptr_vector<ast_manager> managers;
        goal_ref_vector                in_copies;
        tactic_ref_vector              ts;
        unsigned sz = m_ts.size();
        for (unsigned i = 0; i < sz; i++) {
            ast_manager * new_m = alloc(ast_manager, m, !m.proof_mode());
            managers.push_back(new_m);
            ast_translation translator(m, *new_m);
            in_copies.push_back(in->translate(translator));
            ts.push_back(m_ts.get(i)->translate(*new_m));
        }

        unsigned finished_id = UINT_MAX;
        par_exception_kind ex_kind;
        std::string        ex_msg;
        unsigned           error_code;
        
        #pragma omp parallel for
        for (int i = 0; i < static_cast<int>(sz); i++) {
            goal_ref_buffer     _result;
            model_converter_ref _mc; 
            proof_converter_ref _pc; 
            expr_dependency_ref _core(*(managers[i]));
            
            goal_ref in_copy = in_copies[i];
            tactic & t = *(ts.get(i));
            
            try {
                t(in_copy, _result, _mc, _pc, _core);
                bool first = false;
                #pragma omp critical (par_tactical)
                {
                    if (finished_id == UINT_MAX) {
                        finished_id = i;
                        first = true;
                    }
                }                
                if (first) {
                    for (unsigned j = 0; j < sz; j++) {
                        if (static_cast<unsigned>(i) != j)
                            ts.get(j)->cancel();
                    }
                    ast_translation translator(*(managers[i]), m, false);
                    for (unsigned k = 0; k < _result.size(); k++) {
                        result.push_back(_result[k]->translate(translator));
                    }
                    mc   = _mc ? _mc->translate(translator) : 0;
                    pc   = _pc ? _pc->translate(translator) : 0;
                    expr_dependency_translation td(translator);
                    core = td(_core);
                }
            }
            catch (tactic_exception & ex) {
                if (i == 0) {
                    ex_kind = TACTIC_EX;
                    ex_msg = ex.msg();
                }
            }
            catch (z3_error & err) {
                if (i == 0) {
                    ex_kind = ERROR_EX;
                    error_code = err.error_code();
                }
            }
            catch (z3_exception & z3_ex) {
                if (i == 0) {
                    ex_kind = DEFAULT_EX;
                    ex_msg = z3_ex.msg();
                }
            }
        }
        if (finished_id == UINT_MAX) {
            mc = 0;
            switch (ex_kind) {
            case ERROR_EX: throw z3_error(error_code);
            case TACTIC_EX: throw tactic_exception(ex_msg.c_str());
            default:
                throw default_exception(ex_msg.c_str());
            }
        }
    }    

    virtual tactic * translate(ast_manager & m) { return translate_core<par_tactical>(m); }
};

tactic * par(unsigned num, tactic * const * ts) {
    return alloc(par_tactical, num, ts);
}

tactic * par(tactic * t1, tactic * t2) {
    tactic * ts[2] = { t1, t2 };
    return par(2, ts);
}

tactic * par(tactic * t1, tactic * t2, tactic * t3) {
    tactic * ts[3] = { t1, t2, t3 };
    return par(3, ts);
}

tactic * par(tactic * t1, tactic * t2, tactic * t3, tactic * t4) {
    tactic * ts[4] = { t1, t2, t3, t4 };
    return par(4, ts);
}

class par_and_then_tactical : public and_then_tactical {
public:
    par_and_then_tactical(tactic * t1, tactic * t2):and_then_tactical(t1, t2) {}
    virtual ~par_and_then_tactical() {}

    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        if (omp_in_parallel()) {
            // execute tasks sequentially
            and_then_tactical::operator()(in, result, mc, pc, core);
            return;
        }

        bool models_enabled = in->models_enabled();
        bool proofs_enabled = in->proofs_enabled();
        bool cores_enabled  = in->unsat_core_enabled();

        ast_manager & m = in->m();                                                                          
        goal_ref_buffer      r1;
        model_converter_ref mc1;                                                                           
        proof_converter_ref pc1;                                                                           
        expr_dependency_ref core1(m);                                                                      
        result.reset();                                                                                     
        mc   = 0;                                                                                
        pc   = 0;        
        core = 0;
        m_t1->operator()(in, r1, mc1, pc1, core1);                                                            
        SASSERT(!is_decided(r1) || (!pc1 && !core1)); // the pc and core of decided goals is 0
        unsigned r1_size = r1.size();                                                                       
        SASSERT(r1_size > 0);                                                                               
        checkpoint();                                                                                       
        if (r1_size == 1) {                                                                                 
            // Only one subgoal created... no need for parallelism
            if (r1[0]->is_decided()) {
                result.push_back(r1[0]);                                                                    
                if (models_enabled) mc = mc1;                                                                                 
                SASSERT(!pc); SASSERT(!core);
                return;                                                                                     
            }                                                                                               
            goal_ref r1_0 = r1[0];                                                                          
            m_t2->operator()(r1_0, result, mc, pc, core);                                              
            if (models_enabled) mc = concat(mc1.get(), mc.get());                                                               
            if (proofs_enabled) pc = concat(pc1.get(), pc.get());                                                               
            if (cores_enabled) core = m.mk_join(core1.get(), core);                                                            
        }                                                                                     
        else {                                                                                              
            if (cores_enabled) core = core1;                                                                                   

            scoped_ptr_vector<ast_manager> managers;
            tactic_ref_vector              ts2;
            goal_ref_vector                g_copies;

            ast_manager & m = in->m();

            for (unsigned i = 0; i < r1_size; i++) {
                ast_manager * new_m = alloc(ast_manager, m, !m.proof_mode());
                managers.push_back(new_m);
                ast_translation translator(m, *new_m);
                g_copies.push_back(r1[i]->translate(translator));
                ts2.push_back(m_t2->translate(*new_m));
            }

            proof_converter_ref_buffer             pc_buffer;                                                           
            model_converter_ref_buffer             mc_buffer;                                                           
            scoped_ptr_vector<expr_dependency_ref> core_buffer;
            scoped_ptr_vector<goal_ref_buffer>     goals_vect;

            pc_buffer.resize(r1_size);
            mc_buffer.resize(r1_size);
            core_buffer.resize(r1_size);
            goals_vect.resize(r1_size);

            bool found_solution = false;
            bool failed         = false;
            par_exception_kind ex_kind;
            unsigned error_code;
            std::string  ex_msg;

            #pragma omp parallel for
            for (int i = 0; i < static_cast<int>(r1_size); i++) {                                                        
                ast_manager & new_m = *(managers[i]);
                goal_ref new_g = g_copies[i];

                goal_ref_buffer r2;
                model_converter_ref mc2;                                                                   
                proof_converter_ref pc2;                                                                   
                expr_dependency_ref core2(new_m);                                                              
                
                bool curr_failed = false;

                try {
                    ts2[i]->operator()(new_g, r2, mc2, pc2, core2);                                              
                }
                catch (tactic_exception & ex) {
                    #pragma omp critical (par_and_then_tactical)
                    {
                        if (!failed && !found_solution) {
                            curr_failed = true;
                            failed      = true;
                            ex_kind     = TACTIC_EX;
                            ex_msg      = ex.msg();
                        }
                    }
                }
                catch (z3_error & err) {
                    #pragma omp critical (par_and_then_tactical)
                    {
                        if (!failed && !found_solution) {
                            curr_failed = true;
                            failed      = true;
                            ex_kind     = ERROR_EX;
                            error_code  = err.error_code();
                        }
                    }                    
                }
                catch (z3_exception & z3_ex) {
                    #pragma omp critical (par_and_then_tactical)
                    {
                        if (!failed && !found_solution) {
                            curr_failed = true;
                            failed      = true;
                            ex_kind     = DEFAULT_EX;
                            ex_msg      = z3_ex.msg();
                        }
                    }
                }

                if (curr_failed) {
                    for (unsigned j = 0; j < r1_size; j++) {
                        if (static_cast<unsigned>(i) != j)
                            ts2.get(j)->cancel();
                    }
                }
                else {
                    if (is_decided(r2)) {
                        SASSERT(r2.size() == 1);
                        if (is_decided_sat(r2)) {                                                          
                            // found solution... 
                            bool first = false;
                            #pragma omp critical (par_and_then_tactical)
                            {
                                if (!found_solution) {
                                    failed         = false;
                                    found_solution = true;
                                    first          = true;
                                }
                            }
                            if (first) {
                                for (unsigned j = 0; j < r1_size; j++) {
                                    if (static_cast<unsigned>(i) != j)
                                        ts2.get(j)->cancel();
                                }
                                ast_translation translator(new_m, m, false);
                                SASSERT(r2.size() == 1);
                                result.push_back(r2[0]->translate(translator));
                                if (models_enabled) {
                                    // mc2 contains the actual model                                                    
                                    mc2  = mc2 ? mc2->translate(translator) : 0;
                                    model_ref md;     
                                    md = alloc(model, m);
                                    apply(mc2, md, 0);
                                    apply(mc1, md, i);
                                    mc   = model2model_converter(md.get());                                             
                                }
                                SASSERT(!pc); SASSERT(!core);
                            }       
                        }                                                     
                        else {                                                                                  
                            SASSERT(is_decided_unsat(r2));                                                 
                            // the proof and unsat core of a decided_unsat goal are stored in the node itself.
                            // pc2 and core2 must be 0.
                            SASSERT(!pc2);
                            SASSERT(!core2);
                            
                            if (models_enabled) mc_buffer.set(i, 0);
                            if (proofs_enabled) {
                                proof * pr = r2[0]->pr(0);
                                pc_buffer.push_back(proof2proof_converter(m, pr));
                            }
                            if (cores_enabled && r2[0]->dep(0) != 0) {
                                expr_dependency_ref * new_dep = alloc(expr_dependency_ref, new_m);
                                *new_dep = r2[0]->dep(0);
                                core_buffer.set(i, new_dep);
                            }
                        }                                                                 
                    }                                                                                       
                    else {                                                                                      
                        goal_ref_buffer * new_r2 = alloc(goal_ref_buffer);
                        goals_vect.set(i, new_r2);
                        new_r2->append(r2.size(), r2.c_ptr());
                        mc_buffer.set(i, mc2.get());
                        pc_buffer.set(i, pc2.get());
                        if (cores_enabled && core2 != 0) {
                            expr_dependency_ref * new_dep = alloc(expr_dependency_ref, new_m);
                            *new_dep = core2;
                            core_buffer.set(i, new_dep);
                        }
                    }                                                                                           
                }
            }
            
            if (failed) {
                switch (ex_kind) {
                case ERROR_EX: throw z3_error(error_code);
                case TACTIC_EX: throw tactic_exception(ex_msg.c_str());
                default:
                    throw default_exception(ex_msg.c_str());
                }
            }

            if (found_solution)
                return;

            core = 0;
            sbuffer<unsigned> sz_buffer;                                                           
            for (unsigned i = 0; i < r1_size; i++) {
                ast_translation translator(*(managers[i]), m, false);
                goal_ref_buffer * r = goals_vect[i];
                if (r != 0) {
                    for (unsigned k = 0; k < r->size(); k++) {
                        result.push_back((*r)[k]->translate(translator));
                    }
                    sz_buffer.push_back(r->size());
                }
                else {
                    sz_buffer.push_back(0);
                }
                if (mc_buffer[i] != 0)
                    mc_buffer.set(i, mc_buffer[i]->translate(translator));
                if (pc_buffer[i] != 0)
                    pc_buffer.set(i, pc_buffer[i]->translate(translator));
                expr_dependency_translation td(translator);
                if (core_buffer[i] != 0) {
                    expr_dependency_ref curr_core(m);
                    curr_core = td(*(core_buffer[i]));
                    core = m.mk_join(curr_core, core);
                }
            }

            if (result.empty()) {
                // all subgoals were shown to be unsat.                                                     
                // create an decided_unsat goal with the proof
                in->reset_all();
                proof_ref pr(m);
                if (proofs_enabled)
                    apply(m, pc1, pc_buffer, pr);
                SASSERT(cores_enabled || core == 0);
                in->assert_expr(m.mk_false(), pr, core);
                core = 0;
                result.push_back(in.get());
                SASSERT(!mc); SASSERT(!pc); SASSERT(!core);
            }
            else {
                if (models_enabled) mc = concat(mc1.get(), mc_buffer.size(), mc_buffer.c_ptr(), sz_buffer.c_ptr());                 
                if (proofs_enabled) pc = concat(pc1.get(), pc_buffer.size(), pc_buffer.c_ptr(), sz_buffer.c_ptr()); 
                SASSERT(cores_enabled || core == 0);
            }
        }
    }

    virtual tactic * translate(ast_manager & m) {
        return translate_core<and_then_tactical>(m);
    }

};

// Similar to and_then combinator, but t2 is applied in parallel to all subgoals produced by t1
tactic * par_and_then(tactic * t1, tactic * t2) {
    return alloc(par_and_then_tactical, t1, t2);
}

tactic * par_and_then(unsigned num, tactic * const * ts) {
    unsigned i = num - 1;
    tactic * r = ts[i];
    while (i > 0) {
        --i;
        r = par_and_then(ts[i], r);
    }
    return r;
}

class unary_tactical : public tactic {
protected:
    tactic * m_t;
    volatile bool m_cancel;

    void checkpoint() {
        if (m_cancel)
            throw tactic_exception(TACTIC_CANCELED_MSG);

    }

public:
    unary_tactical(tactic * t): 
        m_t(t),
        m_cancel(false) { 
        SASSERT(t); 
        t->inc_ref(); 
    }    

    virtual ~unary_tactical() { 
        tactic * t = m_t;
        #pragma omp critical (tactic_cancel)
        {
            m_t = 0;
        }
        t->dec_ref();
    }

    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        m_t->operator()(in, result, mc, pc, core);
    }
   
    virtual void cleanup(void) { m_t->cleanup(); }
    virtual void collect_statistics(statistics & st) const { m_t->collect_statistics(st); }
    virtual void reset_statistics() { m_t->reset_statistics(); }    
    virtual void set_front_end_params(front_end_params & p) { m_t->set_front_end_params(p); }
    virtual void updt_params(params_ref const & p) { m_t->updt_params(p); }
    virtual void collect_param_descrs(param_descrs & r) { m_t->collect_param_descrs(r); }
    virtual void reset() { m_t->reset(); }
    virtual void set_logic(symbol const& l) { m_t->set_logic(l); }    
    virtual void set_progress_callback(progress_callback * callback) { m_t->set_progress_callback(callback); }
protected:
    virtual void set_cancel(bool f) { 
        m_cancel = f;
        if (m_t) 
            m_t->set_cancel(f); 
    }

    template<typename T>
    tactic * translate_core(ast_manager & m) { 
        tactic * new_t = m_t->translate(m);
        return alloc(T, new_t);
    }
};

class repeat_tactical : public unary_tactical {
    unsigned m_max_depth;
    
    void operator()(unsigned depth,
                    goal_ref const & in, 
                    goal_ref_buffer & result, 
                    model_converter_ref & mc, 
                    proof_converter_ref & pc, 
                    expr_dependency_ref & core) {
        // TODO: implement a non-recursive version.
        if (depth > m_max_depth) {
            result.push_back(in.get());
            mc   = 0;
            pc   = 0;
            core = 0;
            return;
        }

        bool models_enabled = in->models_enabled();
        bool proofs_enabled = in->proofs_enabled();
        bool cores_enabled  = in->unsat_core_enabled();

        ast_manager & m = in->m();                                                                          
        goal_ref_buffer      r1;                                                                            
        model_converter_ref mc1;                                                                           
        proof_converter_ref pc1;                                                                           
        expr_dependency_ref core1(m);                                                                      
        result.reset();                                                                                     
        mc   = 0;                                                                                             
        pc   = 0;             
        core = 0;
        {
            goal orig_in(in->m());
            orig_in.copy_from(*(in.get()));
            m_t->operator()(in, r1, mc1, pc1, core1);                                                            
            if (is_equal(orig_in, *(in.get()))) {
                result.push_back(r1[0]);                                                                    
                if (models_enabled) mc = mc1;                                                                                 
                if (proofs_enabled) pc = pc1;                                                                                 
                if (cores_enabled)  core = core1;                                                                               
                return;                                                                                     
            }
        }
        unsigned r1_size = r1.size();                                                                       
        SASSERT(r1_size > 0);                                                                               
        checkpoint();                                                                                       
        if (r1_size == 1) {                                                                                 
            if (r1[0]->is_decided()) {
                result.push_back(r1[0]);                                                                    
                if (models_enabled) mc = mc1;                                                                                 
                SASSERT(!pc); SASSERT(!core);
                return;                                                                                     
            }                                                                                               
            goal_ref r1_0 = r1[0];                                                                          
            operator()(depth+1, r1_0, result, mc, pc, core);                                              
            if (models_enabled) mc = concat(mc.get(), mc1.get());                                                               
            if (proofs_enabled) pc = concat(pc.get(), pc1.get());                                                               
            if (cores_enabled)  core = m.mk_join(core1.get(), core);                                                            
        }                                                                                                   
        else {                                                                                              
            if (cores_enabled) core = core1;                                                                                   
            proof_converter_ref_buffer pc_buffer;                                                           
            model_converter_ref_buffer mc_buffer;                                                           
            sbuffer<unsigned>          sz_buffer;                                                           
            goal_ref_buffer            r2;                                                                  
            for (unsigned i = 0; i < r1_size; i++) {                                                        
                checkpoint();                                                                               
                goal_ref g = r1[i];                                                                         
                r2.reset();                                                                                 
                model_converter_ref mc2;                                                                   
                proof_converter_ref pc2;                                                                   
                expr_dependency_ref  core2(m);                                                              
                operator()(depth+1, g, r2, mc2, pc2, core2);                                              
                if (is_decided(r2)) {
                    SASSERT(r2.size() == 1);
                    if (is_decided_sat(r2)) {                                                          
                        // found solution...                                                                
                        result.push_back(r2[0]);
                        if (models_enabled) {
                            // mc2 contains the actual model                                                    
                            model_ref md;                                                                      
                            if (mc2) (*mc2)(md, 0);                                                             
                            if (mc1) (*mc1)(md, i);                                                             
                            mc   = model2model_converter(md.get());                                             
                        }
                        SASSERT(!pc); SASSERT(!core);
                        return;                                                                             
                    }                                                                                       
                    else {                                                                                  
                        SASSERT(is_decided_unsat(r2));
                        SASSERT(!pc2); 
                        SASSERT(!core2);
                        if (models_enabled) mc_buffer.push_back(0);
                        if (proofs_enabled) pc_buffer.push_back(proof2proof_converter(m, r2[0]->pr(0)));
                        if (models_enabled || proofs_enabled) sz_buffer.push_back(0);                                                             
                        if (cores_enabled) core = m.mk_join(core.get(), r2[0]->dep(0));
                    }                                                                                       
                }                                                                                           
                else {                                                                                      
                    result.append(r2.size(), r2.c_ptr());                                                   
                    if (models_enabled) mc_buffer.push_back(mc2.get());                                                         
                    if (proofs_enabled) pc_buffer.push_back(pc2.get());                                                         
                    if (models_enabled || proofs_enabled) sz_buffer.push_back(r2.size());                                                         
                    if (cores_enabled) core = m.mk_join(core.get(), core2.get());                                              
                }                                                                                           
            }    
                                                                                           
            if (result.empty()) {                                                                           
                // all subgoals were shown to be unsat.                                                     
                // create an decided_unsat goal with the proof
                in->reset_all();
                proof_ref pr(m);
                if (proofs_enabled)
                    apply(m, pc1, pc_buffer, pr);
                SASSERT(cores_enabled || core == 0);
                in->assert_expr(m.mk_false(), pr, core);
                core = 0;
                result.push_back(in.get());
                SASSERT(!mc); SASSERT(!pc); SASSERT(!core);
            }
            else {
                if (models_enabled) mc = concat(mc1.get(), mc_buffer.size(), mc_buffer.c_ptr(), sz_buffer.c_ptr());                 
                if (proofs_enabled) pc = concat(pc1.get(), pc_buffer.size(), pc_buffer.c_ptr(), sz_buffer.c_ptr());                 
                SASSERT(cores_enabled || core == 0);
            }
        }
    }

public:
    repeat_tactical(tactic * t, unsigned max_depth):
        unary_tactical(t), 
        m_max_depth(max_depth) {
    }
    
    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        operator()(0, in, result, mc, pc, core);
    }

    virtual tactic * translate(ast_manager & m) { 
        tactic * new_t = m_t->translate(m);
        return alloc(repeat_tactical, new_t, m_max_depth);
    }
};

tactic * repeat(tactic * t, unsigned max) {
    return alloc(repeat_tactical, t, max);
}

class fail_if_branching_tactical : public unary_tactical {
    unsigned m_threshold;
public:
    fail_if_branching_tactical(tactic * t, unsigned threshold):unary_tactical(t), m_threshold(threshold) {}

    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        m_t->operator()(in, result, mc, pc, core);
        if (result.size() > m_threshold) {
            result.reset();
            mc   = 0;
            pc   = 0;
            core = 0;
            throw tactic_exception("failed-if-branching tactical");
        }
    };    

    virtual tactic * translate(ast_manager & m) { 
        tactic * new_t = m_t->translate(m);
        return alloc(fail_if_branching_tactical, new_t, m_threshold);
    }
};

tactic * fail_if_branching(tactic * t, unsigned threshold) {
    return alloc(fail_if_branching_tactical, t, threshold);
}

class cleanup_tactical : public unary_tactical {
public:
    cleanup_tactical(tactic * t):unary_tactical(t) {}

    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        m_t->operator()(in, result, mc, pc, core);
        m_t->cleanup();
    }    

    virtual tactic * translate(ast_manager & m) { 
        tactic * new_t = m_t->translate(m);
        return alloc(cleanup_tactical, new_t);
    }
};

tactic * clean(tactic * t) {
    return alloc(cleanup_tactical, t);
}

class try_for_tactical : public unary_tactical {
    unsigned m_timeout;
public:
    try_for_tactical(tactic * t, unsigned ts):unary_tactical(t), m_timeout(ts) {}
    
    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        cancel_eh<tactic> eh(*m_t);
        { 
            // Warning: scoped_timer is not thread safe in Linux.
            scoped_timer timer(m_timeout, &eh);
            m_t->operator()(in, result, mc, pc, core);
        }
    }

    virtual tactic * translate(ast_manager & m) { 
        tactic * new_t = m_t->translate(m);
        return alloc(try_for_tactical, new_t, m_timeout);
    }
};

tactic * try_for(tactic * t, unsigned msecs) {
    return alloc(try_for_tactical, t, msecs);
}

class using_params_tactical : public unary_tactical {
    params_ref m_params;
public:
    using_params_tactical(tactic * t, params_ref const & p):unary_tactical(t), m_params(p) {
        t->updt_params(p);
    }

    virtual void updt_params(params_ref const & p) {
        TRACE("using_params", 
              tout << "before p: " << p << "\n";
              tout << "m_params: " << m_params << "\n";
              ;);
        
        params_ref new_p = p;
        new_p.append(m_params);
        unary_tactical::updt_params(new_p);
        
        TRACE("using_params", 
              tout << "after p: " << p << "\n";
              tout << "m_params: " << m_params << "\n";
              tout << "new_p: " << new_p << "\n";);
    }

    virtual tactic * translate(ast_manager & m) { 
        tactic * new_t = m_t->translate(m);
        return alloc(using_params_tactical, new_t, m_params);
    }
};

tactic * using_params(tactic * t, params_ref const & p) {
    return alloc(using_params_tactical, t, p);
}

class cond_tactical : public binary_tactical {
    probe * m_p;
public:
    cond_tactical(probe * p, tactic * t1, tactic * t2):
        binary_tactical(t1, t2),
        m_p(p) { 
        SASSERT(m_p);
        m_p->inc_ref(); 
    }

    ~cond_tactical() {
        m_p->dec_ref();
    }
    
    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        if (m_p->operator()(*(in.get())).is_true())
            m_t1->operator()(in, result, mc, pc, core);
        else
            m_t2->operator()(in, result, mc, pc, core);
    }

    virtual tactic * translate(ast_manager & m) { 
        tactic * new_t1 = m_t1->translate(m);
        tactic * new_t2 = m_t2->translate(m);
        return alloc(cond_tactical, m_p, new_t1, new_t2);
    }
};

tactic * cond(probe * p, tactic * t1, tactic * t2) {
    return alloc(cond_tactical, p, t1, t2);
}

tactic * when(probe * p, tactic * t) {
    return cond(p, t, mk_skip_tactic());
}

class fail_if_tactic : public tactic {
    probe * m_p;
public:
    fail_if_tactic(probe * p):
        m_p(p) { 
        SASSERT(m_p);
        m_p->inc_ref(); 
    }
    
    ~fail_if_tactic() {
        m_p->dec_ref();
    }

    void cleanup() {}

    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        mc   = 0;
        pc   = 0;
        core = 0;
        if (m_p->operator()(*(in.get())).is_true()) {
            throw tactic_exception("fail-if tactic");
        }
        result.push_back(in.get());
    }

    virtual tactic * translate(ast_manager & m) { 
        return this;
    }
};

tactic * fail_if(probe * p) {
    return alloc(fail_if_tactic, p);
}

tactic * fail_if_not(probe * p) {
    return fail_if(mk_not(p));
}

class if_no_proofs_tactical : public unary_tactical {
public:
    if_no_proofs_tactical(tactic * t):unary_tactical(t) {}
    
    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        if (in->proofs_enabled()) {
            mc = 0; pc = 0; core = 0;
            result.reset();
            result.push_back(in.get());
        }
        else {
            m_t->operator()(in, result, mc, pc, core);
        }
    }

    virtual tactic * translate(ast_manager & m) { return translate_core<if_no_proofs_tactical>(m); }
};

class if_no_unsat_cores_tactical : public unary_tactical {
public:
    if_no_unsat_cores_tactical(tactic * t):unary_tactical(t) {}
    
    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        if (in->unsat_core_enabled()) {
            mc = 0; pc = 0; core = 0;
            result.reset();
            result.push_back(in.get());
        }
        else {
            m_t->operator()(in, result, mc, pc, core);
        }
    }

    virtual tactic * translate(ast_manager & m) { return translate_core<if_no_unsat_cores_tactical>(m); }
};

class if_no_models_tactical : public unary_tactical {
public:
    if_no_models_tactical(tactic * t):unary_tactical(t) {}
    
    virtual void operator()(goal_ref const & in, 
                            goal_ref_buffer & result, 
                            model_converter_ref & mc, 
                            proof_converter_ref & pc, 
                            expr_dependency_ref & core) {
        if (in->models_enabled()) {
            mc = 0; pc = 0; core = 0;
            result.reset();
            result.push_back(in.get());
        }
        else {
            m_t->operator()(in, result, mc, pc, core);
        }
    }

    virtual tactic * translate(ast_manager & m) { return translate_core<if_no_models_tactical>(m); }
};

tactic * if_no_proofs(tactic * t) {
    return alloc(if_no_proofs_tactical, t);
}

tactic * if_no_unsat_cores(tactic * t) {
    return alloc(if_no_unsat_cores_tactical, t);
}

tactic * if_no_models(tactic * t) {
    return alloc(if_no_models_tactical, t);
}

tactic * skip_if_failed(tactic * t) {
    return or_else(t, mk_skip_tactic());
}

    
