/*++
Copyright (c) 2006 Microsoft Corporation

Module Name:

    smt_enode.h

Abstract:

    <abstract>

Author:

    Leonardo de Moura (leonardo) 2008-02-18.

Revision History:

--*/
#ifndef _SMT_ENODE_H_
#define _SMT_ENODE_H_

#include"ast.h"
#include"smt_types.h"
#include"smt_eq_justification.h"
#include"smt_theory_var_list.h"
#include"approx_set.h"

namespace smt {
    /**
       \brief Justification for the transitivity rule.
    */
    struct trans_justification {
        enode *           m_target;
        eq_justification  m_justification;
        trans_justification():
            m_target(0),
            m_justification(null_eq_justification) {
        }
    };

    class tmp_enode;

    /**
       \brief Aditional data-structure for implementing congruence closure,
       equality propagation, and the theory central bus of equalities.
    */
    class enode {
        app  *              m_owner;    //!< The application that 'owns' this enode.
        enode *             m_root;     //!< Representative of the equivalence class
        enode *             m_next;     //!< Next element in the equivalence class.
        enode *             m_cg;       
        unsigned            m_class_size;    //!< Size of the equivalence class if the enode is the root.
        unsigned            m_generation; //!< Tracks how many quantifier instantiation rounds were needed to generate this enode.

        unsigned            m_func_decl_id; //!< Id generated by the congruence table for fast indexing.

        unsigned            m_mark:1;        //!< Multi-purpose auxiliary mark. 
        unsigned            m_mark2:1;       //!< Multi-purpose auxiliary mark. 
        unsigned            m_interpreted:1; //!< True if the node is an interpreted constant.
        unsigned            m_suppress_args:1;  //!< True if the arguments of m_owner should not be accessed by this enode.
        unsigned            m_eq:1;             //!< True if it is an equality
        unsigned            m_commutative:1;    //!< True if commutative app
        unsigned            m_bool:1;           //!< True if it is a boolean enode
        unsigned            m_merge_tf:1;       //!< True if the enode should be merged with true/false when the associated boolean variable is assigned.
        unsigned            m_cgc_enabled:1;    //!< True if congruence closure is enabled for this enode.
        unsigned            m_iscope_lvl;       //!< When the enode was internalized
        /*
          The following property is valid for m_parents
          
          If this = m_root, then for every term f(a) such that a->get_root() == m_root,
          there is a f(b) in m_parents such that b->get_root() == m_root, and f(a) and f(b) are
          congruent.
          Remark: f(a) and f(b) may have other arguments.

          Exception: If f(a) and f(b) are terms of the form (= a c) and (= b d), then
          m_parents will not contains (= b d) if b->get_root() == d->get_root().

          Remark regarding relevancy propagation: relevancy is propagated to all
          elements of an equivalence class. So, if there is a f(a) that is relevant,
          then the congruent f(b) in m_parents will also be relevant. 
        */
        enode_vector        m_parents;          //!< Parent enodes of the equivalence class.
        theory_var_list     m_th_var_list;      //!< List of theories that 'care' about this enode.
        trans_justification m_trans;            //!< A justification for the enode being equal to its root.
        char                m_lbl_hash;         //!< It is different from -1, if enode is used in a pattern
        approx_set          m_lbls;
        approx_set          m_plbls;
        enode *             m_args[0];          //!< Cached args
        
        friend class context;
        friend class euf_manager;
        friend class conflict_resolution;

        theory_var_list * get_th_var_list() { 
            return m_th_var_list.get_th_var() == null_theory_var ? 0 : &m_th_var_list; 
        }

        friend class set_merge_tf_trail;
        /**
           \brief Return true if the enode should be merged with the true (false) enodes when
           the associated boolean variable is assigned to true (false).
        */
        bool merge_tf() const {
            return m_merge_tf;
        }

        friend class add_th_var_trail;
        friend class replace_th_var_trail;

        void add_th_var(theory_var v, theory_id id, region & r);

        void replace_th_var(theory_var v, theory_id id);

        void del_th_var(theory_id id);

        friend class tmp_enode;

        static enode * init(ast_manager & m, void * mem, ptr_vector<enode> const & app2enode, app * owner, 
                            unsigned generation, bool suppress_args, bool merge_tf, unsigned iscope_lvl,
                            bool cgc_enabled, bool update_children_parent);
    public:

        static unsigned get_enode_size(unsigned num_args) {
            return sizeof(enode) + num_args * sizeof(enode*);
        }
        
        static enode * mk(ast_manager & m, region & r, ptr_vector<enode> const & app2enode, app * owner, 
                          unsigned generation, bool suppress_args, bool merge_tf, unsigned iscope_lvl,
                          bool cgc_enabled, bool update_children_parent);

        static enode * mk_dummy(ast_manager & m, ptr_vector<enode> const & app2enode, app * owner);
        
        static void del_dummy(enode * n) { dealloc_svect(reinterpret_cast<char*>(n)); }

        unsigned get_func_decl_id() const {
            return m_func_decl_id;
        }

        void set_func_decl_id(unsigned id) {
            m_func_decl_id = id;
        }

        void mark_as_interpreted() {
            SASSERT(!m_interpreted);
            SASSERT(m_owner->get_num_args() == 0);
            SASSERT(m_class_size == 1);
            m_interpreted = true;
        }

        void del_eh(ast_manager & m, bool update_children_parent = true);
        
        app * get_owner() const { 
            return m_owner; 
        }

        unsigned get_owner_id() const {
            return m_owner->get_id();
        }

        func_decl * get_decl() const {
            return m_owner->get_decl();
        }

        unsigned get_decl_id() const {
            return m_owner->get_decl()->get_decl_id();
        }

        unsigned hash() const {
            return m_owner->hash();
        }

        enode * get_root() const { 
            return m_root; 
        }

        enode * get_next() const { 
            return m_next; 
        }

        unsigned get_num_args() const { 
            return m_suppress_args ? 0 : m_owner->get_num_args(); 
        }

        enode * get_arg(unsigned idx) const {
            SASSERT(idx < get_num_args());
            return m_args[idx];
        }

        enode * const * get_args() const {
            return m_args;
        }

        // unsigned get_id() const { 
        //    return m_id; 
        // }

        unsigned get_class_size() const { 
            return m_class_size; 
        }

        bool is_bool() const {
            return m_bool;
        }

        bool is_eq() const { 
            return m_eq; 
        }

        bool is_true_eq() const {
            return m_eq && get_arg(0)->get_root() == get_arg(1)->get_root();
        }

        bool is_marked() const { 
            return m_mark; 
        }

        void set_mark() { 
            SASSERT(!m_mark); m_mark = true; 
        }

        void unset_mark() { 
            SASSERT(m_mark); m_mark = false; 
        }

        bool is_marked2() const { 
            return m_mark2; 
        }

        void set_mark2() { 
            SASSERT(!m_mark2); m_mark2 = true; 
        }

        void unset_mark2() { 
            SASSERT(m_mark2); m_mark2 = false; 
        }

        bool is_interpreted() const { 
            return m_interpreted; 
        }

        /**
           \brief Return true if node is not a constant and it is the root of its congruence class.
           
           \remark if get_num_args() == 0, then is_cgr() = false.
        */
        bool is_cgr() const {
            return m_cg == this;
        }

        enode * get_cg() const { 
            return m_cg;
        }

        bool is_cgc_enabled() const {
            return m_cgc_enabled;
        }

        bool is_commutative() const {
            return m_commutative;
        }

        unsigned get_num_parents() const {
            return m_parents.size();
        }

        enode_vector::iterator begin_parents() { 
            return m_parents.begin(); 
        }

        enode_vector::iterator end_parents() { 
            return m_parents.end(); 
        }

        enode_vector::const_iterator begin_parents() const { 
            return m_parents.begin(); 
        }
        
        enode_vector::const_iterator end_parents() const { 
            return m_parents.end(); 
        }
        
        theory_var_list const * get_th_var_list() const { 
            return m_th_var_list.get_th_var() == null_theory_var ? 0 : &m_th_var_list; 
        }

        bool has_th_vars() const {
            return m_th_var_list.get_th_var() != null_theory_var;
        }

        unsigned get_num_th_vars() const;

        theory_var get_th_var(theory_id th_id) const;

        unsigned get_generation() const {
            return m_generation;
        }

        void set_generation(context & ctx, unsigned generation);
        
        /**
           \brief Return the enode n that is in the eqc of *this, and has the minimal generation.
           That is, there is no other enode with smaller generation.
        */
        enode * get_eq_enode_with_min_gen();

        unsigned get_iscope_lvl() const {
            return m_iscope_lvl;
        }

        void set_lbl_hash(context & ctx);
        
        bool has_lbl_hash() const {
            return m_lbl_hash >= 0;
        }
        
        unsigned char get_lbl_hash() const {
            SASSERT(m_lbl_hash >= 0 && static_cast<unsigned>(m_lbl_hash) < approx_set_traits<unsigned long long>::capacity);
            return static_cast<unsigned char>(m_lbl_hash);
        }
        
        approx_set & get_lbls() {
            return m_lbls;
        }
        
        approx_set & get_plbls() {
            return m_plbls;
        }
        
        const approx_set & get_lbls() const {
            return m_lbls;
        }
        
        const approx_set & get_plbls() const {
            return m_plbls;
        }

        void display_lbls(std::ostream & out) const;
        
#ifdef Z3DEBUG
        bool check_invariant() const;
        bool trans_reaches(enode * n) const;
        bool check_parent_invariant() const;
        bool contains_parent_congruent_to(enode * p) const;
#endif
    };

    inline bool same_eqc(enode const * n1 , enode const * n2) { return n1->get_root() == n2->get_root(); }

    /**
       \brief Return true, if n1 and n2 are congruent.
       Set comm to true, if the nodes are congruent modulo commutativity.
    */
    bool congruent(enode * n1, enode * n2, bool & comm);

    inline bool congruent(enode * n1, enode * n2) {
        bool aux;
        return congruent(n1, n2, aux);
    }

    unsigned get_max_generation(unsigned num_enodes, enode * const * enodes);
    
    void unmark_enodes(unsigned num_enodes, enode * const * enodes);

    void unmark_enodes2(unsigned num_enodes, enode * const * enodes);
    
    class tmp_enode {
        tmp_app  m_app;
        unsigned m_capacity;
        char *   m_enode_data;
        enode * get_enode() { return reinterpret_cast<enode*>(m_enode_data); }
        void set_capacity(unsigned new_capacity);
    public:
        tmp_enode();
        ~tmp_enode();
        enode * set(func_decl * f, unsigned num_args, enode * const * args);
        void reset();
    };

};

#endif /* _SMT_ENODE_H_ */

