/*++
Copyright (c) 2006 Microsoft Corporation

Module Name:

    theory_array_base.h

Abstract:

    <abstract>

Author:

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

Revision History:

--*/
#pragma once

#include "smt/smt_theory.h"
#include "smt/theory_array_bapa.h"
#include "ast/array_decl_plugin.h"
#include "model/array_factory.h"

namespace smt {

    class theory_array_base : public theory {
        friend class theory_array_bapa;
    protected:
        bool m_found_unsupported_op;
        unsigned m_array_weak_head;
        svector<theory_var> m_array_weak_trail;
        bool has_propagate_up_trail() const { return m_array_weak_head < m_array_weak_trail.size(); }
        void add_weak_var(theory_var v);
        virtual void set_prop_upward(theory_var v) {}
        void found_unsupported_op(expr * n);
        void found_unsupported_op(enode* n) { found_unsupported_op(n->get_expr()); }
        void found_unsupported_op(theory_var v) { found_unsupported_op(get_enode(v)->get_expr()); }
        
        bool is_store(app const* n) const { return n->is_app_of(get_id(), OP_STORE); }
        bool is_map(app const* n) const { return n->is_app_of(get_id(), OP_ARRAY_MAP); }
        bool is_select(app const* n) const { return n->is_app_of(get_id(), OP_SELECT); }
        bool is_default(app const* n) const { return n->is_app_of(get_id(), OP_ARRAY_DEFAULT); }
        bool is_const(app const* n) const { return n->is_app_of(get_id(), OP_CONST_ARRAY); }
        bool is_array_ext(app const * n) const { return n->is_app_of(get_id(), OP_ARRAY_EXT); }
        bool is_as_array(app const * n) const { return n->is_app_of(get_id(), OP_AS_ARRAY); }
        bool is_array_sort(sort const* s) const { return s->is_sort_of(get_id(), ARRAY_SORT); }
        bool is_array_sort(app const* n) const { return is_array_sort(n->get_sort()); }
        bool is_set_has_size(app const* n) const { return n->is_app_of(get_id(), OP_SET_HAS_SIZE); }
        bool is_set_card(app const* n) const { return n->is_app_of(get_id(), OP_SET_CARD); }

        bool is_store(enode const * n) const { return is_store(n->get_expr()); }
        bool is_map(enode const* n) const { return is_map(n->get_expr()); }
        bool is_select(enode const* n) const { return is_select(n->get_expr()); }
        bool is_const(enode const* n) const { return is_const(n->get_expr()); }
        bool is_as_array(enode const * n) const { return is_as_array(n->get_expr()); }
        bool is_default(enode const* n) const { return is_default(n->get_expr()); }
        bool is_array_sort(enode const* n) const { return is_array_sort(n->get_expr()); }
        bool is_set_has_size(enode const* n) const { return is_set_has_size(n->get_expr()); }
        bool is_set_carde(enode const* n) const { return is_set_card(n->get_expr()); }
        bool is_select_arg(enode* r);

        app * mk_select(unsigned num_args, expr * const * args);
        app * mk_select_reduce(unsigned num_args, expr * * args);
        app * mk_select(expr_ref_vector const& args) { return mk_select(args.size(), args.data()); }
        app * mk_store(unsigned num_args, expr * const * args);
        app * mk_default(expr* a);


        unsigned get_dimension(sort* s) const;
        
        ptr_vector<enode>                   m_axiom1_todo;
        enode_pair_vector                   m_axiom2_todo;
        enode_pair_vector                   m_extensionality_todo;
        enode_pair_vector                   m_congruent_todo;
        scoped_ptr<theory_array_bapa>       m_bapa;

        void assert_axiom(unsigned num_lits, literal * lits);
        void assert_axiom(literal l1, literal l2);
        void assert_axiom(literal l);
        void assert_store_axiom1_core(enode * n);
        void assert_store_axiom2_core(enode * store, enode * select);
        void assert_store_axiom1(enode * n) { m_axiom1_todo.push_back(n); }
        bool assert_store_axiom2(enode * store, enode * select);

        void assert_extensionality_core(enode * a1, enode * a2);
        bool assert_extensionality(enode * a1, enode * a2);

        expr_ref instantiate_lambda(app* e);
        void assert_congruent_core(enode * a1, enode * a2);
        void assert_congruent(enode * a1, enode * a2);

        // --------------------------------------------------
        // Array sort -> extensionality skolems
        // 
        // --------------------------------------------------
        ptr_vector<sort>                     m_sorts_trail;
        obj_map<sort, func_decl_ref_vector*> m_sort2skolem;

        func_decl_ref_vector * register_sort(sort * s_array);

        // --------------------------------------------------
        // array_value table
        //
        // Use select(A, i) nodes to represent an assignment for A.
        // This structure is used to minimize the number of times the
        // extensionality axiom is applied.
        // 
        // --------------------------------------------------
        struct value_chasher {
            unsigned operator()(enode const * n, unsigned idx) const {
                return n->get_arg(idx+1)->get_root()->hash();
            }
        };
        struct value_khasher { unsigned operator()(enode * n) const { return 17; } };
        struct value_hash_proc { 
            unsigned operator()(enode * n) const {
                return get_composite_hash<enode *, value_khasher, value_chasher>(n, n->get_num_args() - 1);
            }
        };
        struct value_eq_proc { bool operator()(enode * n1, enode * n2) const; };
        typedef ptr_hashtable<enode, value_hash_proc, value_eq_proc> array_value;

        array_value m_array_value;
        bool already_diseq(enode * v1, enode * v2);

        // --------------------------------------------------
        // Backtracking
        //
        // 
        // --------------------------------------------------
        struct scope {
            unsigned m_sorts_trail_lim;
            scope(unsigned l):m_sorts_trail_lim(l) {}
        };
        svector<scope>                      m_scopes;
        void restore_sorts(unsigned old_size);
        
        // --------------------------------------------------
        // Interface
        //
        // 
        // --------------------------------------------------
        bool is_shared(theory_var v) const override;
        bool is_beta_redex(enode* p, enode* n) const override;
        void collect_shared_vars(sbuffer<theory_var> & result);
        unsigned mk_interface_eqs();

        bool can_propagate() override;
        void propagate() override;
        void push_scope_eh() override;
        void pop_scope_eh(unsigned num_scopes) override;
        void reset_eh() override;
        
        void reset_queues();
        // -----------------------------------
        //
        // Model generation
        //
        // -----------------------------------

        
        // I need a set of select enodes where select(A,i) = select(B,j) if i->get_root() == j->get_root()
        struct sel_khasher {
            unsigned operator()(enode const * n) const { return 0; }
        };

        struct sel_chasher {
            unsigned operator()(enode const * n, unsigned idx) const { 
                return n->get_arg(idx+1)->get_root()->hash();
            }
        };
        
        struct sel_hash {
            unsigned operator()(enode * n) const;
        };

        struct sel_eq {
            bool operator()(enode * n1, enode * n2) const;
        };
        
        typedef ptr_hashtable<enode, sel_hash, sel_eq> select_set;

        array_factory *              m_factory;
        ptr_vector<enode>            m_defaults;       // temporary field for model construction
        ptr_vector<void>             m_else_values;    // tagged pointer: expr or extra_fresh_value
        svector<int>                 m_parents;        // temporary field for model construction
        obj_map<enode, select_set*>  m_selects;        // mapping from array -> relevant selects
        ptr_vector<enode>            m_selects_domain; 
        ptr_vector<select_set>       m_selects_range;
        bool                         m_use_unspecified_default;  // temporary field for model construction

        theory_var mg_find(theory_var v);
        void mg_merge(theory_var n, theory_var m);

        void set_default(theory_var v, enode* n);
        enode* get_default(theory_var v);

        void init_model(model_generator & m) override;
        bool is_unspecified_default_ok() const;
        void collect_defaults();
        void collect_selects();
        void propagate_select_to_store_parents(enode * r, enode * sel, enode_pair_vector & todo);
        void propagate_selects_to_store_parents(enode * r, enode_pair_vector & todo);
        void propagate_selects();
        select_set * get_select_set(enode * n);
        void finalize_model(model_generator & m) override;
        model_value_proc * mk_value(enode * n, model_generator & m) override;
        bool include_func_interp(func_decl* f) override;
    public:
        theory_array_base(context& ctx);
        ~theory_array_base() override { restore_sorts(0); }
    };

};


