/*++
Copyright (c) 2010 Microsoft Corporation

Module Name:

    qe.h

Abstract:

    Quantifier-elimination procedures 

Author:

    Nikolaj Bjorner (nbjorner) 2010-02-19

Revision History:


--*/

#ifndef __QE_H__
#define __QE_H__

#include "ast.h"
#include "front_end_params.h"
#include "statistics.h"
#include "lbool.h"
#include "expr_functors.h"
#include "simplifier.h"
#include "rewriter.h"
#include "model.h"
#include "params.h"

namespace qe {


    class i_nnf_atom {
    public:
        virtual void operator()(expr* e, bool pol, expr_ref& result) = 0;
    };

    typedef obj_hashtable<app> atom_set;

    class qe_solver_plugin;


    class i_solver_context {
    protected:
        class is_relevant : public i_expr_pred {
            i_solver_context& m_s;
        public:
            is_relevant(i_solver_context& s):m_s(s) {}
            virtual bool operator()(expr* e);
        };
        class mk_atom_fn : public i_nnf_atom {
            i_solver_context& m_s;
        public:
            mk_atom_fn(i_solver_context& s) : m_s(s) {}
            void operator()(expr* e, bool p, expr_ref& result);
        };

        is_relevant                  m_is_relevant;
        mk_atom_fn                   m_mk_atom;
        ptr_vector<qe_solver_plugin> m_plugins;      // fid -> plugin

    public:
        i_solver_context():m_is_relevant(*this), m_mk_atom(*this) {}

        virtual ~i_solver_context();

        void add_plugin(qe_solver_plugin* p);

        bool has_plugin(app* x);

        qe_solver_plugin& plugin(app* x);

        qe_solver_plugin& plugin(unsigned fid) { return *m_plugins[fid]; }

        void mk_atom(expr* e, bool p, expr_ref& result);

        virtual ast_manager& get_manager() = 0;

        // set of atoms in current formula.
        virtual atom_set const& pos_atoms() const = 0;
        virtual atom_set const& neg_atoms() const = 0;        

        // Access current set of variables to solve
        virtual unsigned      get_num_vars() const = 0;
        virtual app*          get_var(unsigned idx) const = 0;
        virtual app*const*    get_vars() const = 0;
        virtual bool          is_var(expr* e, unsigned& idx) const;
        virtual contains_app& contains(unsigned idx) = 0;

        // callback to replace variable at index 'idx' with definition 'def' and updated formula 'fml'
        virtual void          elim_var(unsigned idx, expr* fml, expr* def) = 0;        

        // callback to add new variable to branch.
        virtual void          add_var(app* x) = 0;

        // callback to add constraints in branch.
        virtual void          add_constraint(bool use_var, expr* l1 = 0, expr* l2 = 0, expr* l3 = 0) = 0;

        // eliminate finite domain variable 'var' from fml.
        virtual void blast_or(app* var, expr_ref& fml) = 0;

        i_expr_pred& get_is_relevant() { return m_is_relevant; }
        
        i_nnf_atom& get_mk_atom() { return m_mk_atom; }
    };

    class conj_enum {
        ast_manager& m;
        expr_ref_vector m_conjs;
    public:
        conj_enum(ast_manager& m, expr* e);

        class iterator {
            conj_enum* m_super;
            unsigned m_index;
        public:
            iterator(conj_enum& c, bool first) : m_super(&c), m_index(first?0:c.m_conjs.size()) {}
            expr* operator*() { return m_super->m_conjs[m_index].get(); }
            iterator& operator++() { m_index++; return *this; }
            bool operator==(iterator const& it) const { return m_index == it.m_index; }
            bool operator!=(iterator const& it) const { return m_index != it.m_index; }
        };

        void extract_equalities(expr_ref_vector& eqs);

        iterator begin() { return iterator(*this, true); }
        iterator end() { return iterator(*this, false); }
    };


    //
    // interface for plugins to eliminate quantified variables.
    // 
    class qe_solver_plugin {
    protected:
        ast_manager&      m;
        family_id         m_fid;
        i_solver_context& m_ctx;
    public:
        qe_solver_plugin(ast_manager& m, family_id fid, i_solver_context& ctx) : 
            m(m), 
            m_fid(fid),
            m_ctx(ctx)
        {}
        
        virtual ~qe_solver_plugin() {}
        
        family_id get_family_id() { return m_fid; }

        /**
           \brief Return number of case splits for variable x in fml.
        */
        virtual bool get_num_branches(contains_app& x, expr* fml, rational& num_branches) = 0;

        /**
           \brief Given a case split index 'vl' assert the constraints associated with it.
        */
        virtual void assign(contains_app& x, expr* fml, rational const& vl) = 0;


        /**
           \brief The case splits associated with 'vl' are satisfiable. 
           Apply the elimination for fml corresponding to case split.
           If def is non-null, then retrieve the realizer corresponding to the case split.
        */                     
        virtual void subst(contains_app& x, rational const& vl, expr_ref& fml, expr_ref* def) = 0;


        /**
           \brief solve quantified variable.
        */
        virtual bool solve(conj_enum& conjs, expr* fml) = 0;

        /**
           \brief project variable x for fml given model.

           Assumption: model |= fml[x]

           Projection updates fml to fml', such that:

           - fml' -> fml
           - model |= fml'
           - fml' does not contain x           

           return false if the method is not implemented.
        */
        virtual bool project(contains_app& x, model_ref& model, expr_ref& fml) { return false; }

        /**
           \brief assign branching penalty to variable x for 'fml'.
        */
        virtual unsigned get_weight(contains_app& x, expr* fml) { return UINT_MAX; }

        /**
           \brief simplify formula.
        */
        virtual bool simplify(expr_ref& fml) { return false; }

        /**
           \brief Normalize literal during NNF conversion.
        */
        virtual bool mk_atom(expr* e, bool p, expr_ref& result) { return false; }

        /**
           \brief Determine whether sub-term is uninterpreted with respect to quantifier elimination.
        */
        virtual bool is_uninterpreted(app* f) { return true; }
    };

    qe_solver_plugin* mk_datatype_plugin(i_solver_context& ctx);

    qe_solver_plugin* mk_bool_plugin(i_solver_context& ctx);

    qe_solver_plugin* mk_bv_plugin(i_solver_context& ctx);

    qe_solver_plugin* mk_dl_plugin(i_solver_context& ctx);

    qe_solver_plugin* mk_array_plugin(i_solver_context& ctx);

    qe_solver_plugin* mk_arith_plugin(i_solver_context& ctx, bool produce_models, front_end_params& p);

    class def_vector {
        func_decl_ref_vector m_vars;
        expr_ref_vector      m_defs;
    public:
        def_vector(ast_manager& m): m_vars(m), m_defs(m) {}
        void push_back(func_decl* v, expr* e) {
            m_vars.push_back(v);
            m_defs.push_back(e);
        }
        void reset() { m_vars.reset(); m_defs.reset(); }
        void append(def_vector const& o) { m_vars.append(o.m_vars); m_defs.append(o.m_defs); }
        unsigned size() const { return m_defs.size(); }
        void shrink(unsigned sz) { m_vars.shrink(sz); m_defs.shrink(sz); }
        bool empty() const { return m_defs.empty(); }
        func_decl* var(unsigned i) const { return m_vars[i]; }
        expr*  def(unsigned i) const { return m_defs[i]; }
        expr_ref_vector::element_ref  def_ref(unsigned i) { return m_defs[i]; }
    };

    class quant_elim; 

    class expr_quant_elim {
        ast_manager&            m;
        front_end_params const& m_fparams;
        params_ref              m_params;
        expr_ref_vector         m_trail;
        obj_map<expr,expr*>     m_visited;
        quant_elim*             m_qe;
        expr*                   m_assumption;
        bool                    m_use_new_qe;
    public:
        expr_quant_elim(ast_manager& m, front_end_params const& fp, params_ref const& p = params_ref());
        ~expr_quant_elim(); 

        void operator()(expr* assumption, expr* fml, expr_ref& result);

        void collect_statistics(statistics & st) const;

        void updt_params(params_ref const& p);

        void collect_param_descrs(param_descrs& r);

        /**
           \brief try to eliminate 'vars' and find first satisfying assignment.

           return l_true if satisfiable, l_false if unsatisfiable, l_undef if 
           the formula could not be satisfied modulo eliminating the quantified variables.
        */
        lbool first_elim(unsigned num_vars, app* const* vars, expr_ref& fml, def_vector& defs);

        /**
           \brief solve for (exists (var) fml).
           Return false if operation failed.
           Return true  and list of pairs (t_i, fml_i) in <terms, fmls>
                          such that fml[t_1] \/ ... \/ fml[t_n] == (exists (var) fml)
                          and       fml_i == fml[t_1]
         */
        bool solve_for_var(app* var, expr* fml, expr_ref_vector& terms, expr_ref_vector& fmls);


        void set_cancel(bool f);

    private:
        void instantiate_expr(expr_ref_vector& bound, expr_ref& fml);
        void abstract_expr(unsigned sz, expr* const* bound, expr_ref& fml);
        void elim(expr_ref& result);
        void init_qe();
    };

    class expr_quant_elim_star1 : public simplifier {
    protected:
        expr_quant_elim m_quant_elim;
        expr*       m_assumption;
        virtual bool visit_quantifier(quantifier * q);
        virtual void reduce1_quantifier(quantifier * q);
        virtual bool is_target(quantifier * q) const { return q->get_num_patterns() == 0 && q->get_num_no_patterns() == 0; }
    public:
        expr_quant_elim_star1(ast_manager & m, front_end_params const& p);
        virtual ~expr_quant_elim_star1() {}

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

        void reduce_with_assumption(expr* ctx, expr* fml, expr_ref& result);

        lbool first_elim(unsigned num_vars, app* const* vars, expr_ref& fml, def_vector& defs) {
            return m_quant_elim.first_elim(num_vars, vars, fml, defs);
        }

        void set_cancel(bool f) {} // TBD: replace simplifier by rewriter

    };

    void hoist_exists(expr_ref& fml, app_ref_vector& vars);

    void mk_exists(unsigned num_vars, app* const* vars, expr_ref& fml);

    void get_nnf(expr_ref& fml, i_expr_pred& pred, i_nnf_atom& mk_atom, atom_set& pos, atom_set& neg); 

    class simplify_rewriter_cfg : public default_rewriter_cfg {
        class impl;
        impl* imp;
    public:
        simplify_rewriter_cfg(ast_manager& m);

        ~simplify_rewriter_cfg();
        
        bool reduce_quantifier(
            quantifier * old_q, 
            expr * new_body, 
            expr * const * new_patterns, 
            expr * const * new_no_patterns,
            expr_ref & result,
            proof_ref & result_pr);
        
        bool pre_visit(expr* e);

    };
    
    class simplify_rewriter_star : public rewriter_tpl<simplify_rewriter_cfg> {
        simplify_rewriter_cfg m_cfg;
    public:
        simplify_rewriter_star(ast_manager& m):
            rewriter_tpl<simplify_rewriter_cfg>(m, false, m_cfg),
            m_cfg(m) {}
    };

};

#endif