/*++
Copyright (c) 2006 Microsoft Corporation

Module Name:

    smt_clause.h

Abstract:

    <abstract>

Author:

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

Revision History:

--*/
#pragma once

#include "ast/ast.h"
#include "smt/smt_literal.h"
#include "util/tptr.h"
#include "util/obj_hashtable.h"
#include "smt/smt_justification.h"

namespace smt {

    class clause;

    /**
       \brief Abstract functor: clause deletion event handler.
    */
    class clause_del_eh {
    public:
        virtual ~clause_del_eh() {}
        virtual void operator()(ast_manager & m, clause * cls) = 0;
    };

    enum clause_kind {
        CLS_AUX,         // an input assumption
        CLS_TH_AXIOM,    // a theory axiom
        CLS_LEARNED,     // learned through conflict resolution
        CLS_TH_LEMMA     // a theory lemma
    };

    inline bool is_axiom(clause_kind k) { return k == CLS_AUX || k == CLS_TH_AXIOM; }
    inline bool is_lemma(clause_kind k) { return k == CLS_LEARNED || k == CLS_TH_LEMMA; }
    
    /**
       \brief A SMT clause.
       
       A clause has several optional fields, I store space for them only if they are actually used.
    */
    class clause {
        unsigned m_num_literals;
        unsigned m_capacity:24;           //!< some of the clause literals can be simplified and removed, this field contains the original number of literals (used for GC).
        unsigned m_kind:2;                //!< kind
        unsigned m_reinit:1;              //!< true if the clause is in the reinit stack (only for learned clauses and aux_lemmas)
        unsigned m_reinternalize_atoms:1; //!< true if atoms must be reinitialized during reinitialization
        unsigned m_has_atoms:1;           //!< true if the clause has memory space for storing atoms.
        unsigned m_has_del_eh:1;          //!< true if must notify event handler when deleted.
        unsigned m_has_justification:1;   //!< true if the clause has a justification attached to it.
        unsigned m_deleted:1;             //!< true if the clause is marked for deletion by was not deleted yet because it is referenced by some data-structure (e.g., m_lemmas)
        literal  m_lits[0];

        static unsigned get_obj_size(unsigned num_lits, clause_kind k, bool has_atoms, bool has_del_eh, bool has_justification) {
            unsigned r = sizeof(clause) + sizeof(literal) * num_lits;
            if (smt::is_lemma(k)) 
                r += sizeof(unsigned);
            /* dvitek: Fix alignment issues on 64-bit platforms.  The
             * 'if' statement below probably isn't worthwhile since
             * I'm guessing the allocator is probably going to round
             * up internally anyway.
             */
            //if (has_atoms || has_del_eh || has_justification)
            r = (r + (sizeof(void*)-1)) & ~(sizeof(void*)-1);
            if (has_atoms)
                r += sizeof(expr*) * num_lits;
            if (has_del_eh)
                r += sizeof(clause_del_eh *);
            if (has_justification)
                r += sizeof(justification *);
            return r;
        }

        unsigned const * get_activity_addr() const {
            return reinterpret_cast<unsigned const *>(m_lits + m_capacity);
        }

        unsigned * get_activity_addr() {
            return reinterpret_cast<unsigned *>(m_lits + m_capacity);
        }

        clause_del_eh * const * get_del_eh_addr() const {
            unsigned const * addr = get_activity_addr();
            if (is_lemma())
                addr ++;
            /* dvitek: It would be better to use uintptr_t than
             * size_t, but we need to wait until c++11 support is
             * really available.
             */
            size_t as_int = reinterpret_cast<size_t>(addr);
            as_int = (as_int + sizeof(void*)-1) & ~static_cast<size_t>(sizeof(void*)-1);
            return reinterpret_cast<clause_del_eh * const *>(as_int);
        }

        justification * const * get_justification_addr() const {
            clause_del_eh * const * addr = get_del_eh_addr();
            if (m_has_del_eh)
                addr ++;
            return reinterpret_cast<justification * const *>(addr);
        }

        expr * const * get_atoms_addr() const {
            justification * const * addr = get_justification_addr();
            if (m_has_justification)
                addr ++;
            return reinterpret_cast<expr * const *>(addr);
        }
        
        friend class context;

        void swap_lits(unsigned idx1, unsigned idx2) {
            SASSERT(idx1 < m_num_literals);
            SASSERT(idx2 < m_num_literals);
            std::swap(m_lits[idx1], m_lits[idx2]);
        }

        bool is_watch(literal l) const {
            return m_lits[0] == l || m_lits[1] == l;
        }

        void set_literal(unsigned idx, literal l) {
            m_lits[idx] = l;
        }

        void set_num_literals(unsigned n) {
            SASSERT(!m_reinit);
            m_num_literals = n;
        }

        void set_justification(justification * new_js) {
            SASSERT(m_has_justification);
            SASSERT(!m_reinit);
            SASSERT(!is_lemma() || new_js == 0 || !new_js->in_region());
            justification ** js_addr = const_cast<justification**>(get_justification_addr());
            *js_addr = new_js;
        }

        void release_atoms(ast_manager & m);
        
    public:
        static clause * mk(ast_manager & m, unsigned num_lits, literal * lits, clause_kind k, justification * js = nullptr,
                           clause_del_eh * del_eh = nullptr, bool save_atoms = false, expr * const * bool_var2expr_map = nullptr);
        
        void deallocate(ast_manager & m);
        
        clause_kind get_kind() const {
            return static_cast<clause_kind>(m_kind);
        }

        bool is_lemma() const {
            return smt::is_lemma(get_kind());
        }

        bool is_learned() const {
            return get_kind() == CLS_LEARNED;
        }

        bool is_th_lemma() const {
            return get_kind() == CLS_TH_LEMMA;
        }


        bool in_reinit_stack() const { 
            return m_reinit;
        }

        bool reinternalize_atoms() const {
            return m_reinternalize_atoms;
        }
        
        unsigned get_num_literals() const {
            return m_num_literals;
        }

        literal & operator[](unsigned idx) {
            SASSERT(idx < m_num_literals);
            return m_lits[idx];            
        }

        literal operator[](unsigned idx) const {
            SASSERT(idx < m_num_literals);
            return m_lits[idx];            
        }

        literal get_literal(unsigned idx) const {
            return (*this)[idx];
        }

        literal & get_literal(unsigned idx) {
            return (*this)[idx];
        }

        literal * begin() { return m_lits; }

        literal * end() { return m_lits + m_num_literals; }

        literal const * begin() const { return m_lits; }

        literal const * end() const { return m_lits + m_num_literals; }

        unsigned get_activity() const {
            SASSERT(is_lemma());
            return *(get_activity_addr());
        }

        void set_activity(unsigned act) {
            SASSERT(is_lemma());
            *(get_activity_addr()) = act;
        }

        clause_del_eh * get_del_eh() const {
            return m_has_del_eh ? *(get_del_eh_addr()) : nullptr;
        }

        justification * get_justification() const {
            return m_has_justification ? *(get_justification_addr()) : nullptr;
        }

        unsigned get_num_atoms() const {
            return m_reinternalize_atoms ? m_num_literals : 0;
        }

        expr * get_atom(unsigned idx) const {
            SASSERT(idx < get_num_atoms());
            return UNTAG(expr*, get_atoms_addr()[idx]);
        }

        bool get_atom_sign(unsigned idx) const {
            SASSERT(idx < get_num_atoms());
            return GET_TAG(get_atoms_addr()[idx]) == 1;
        }

        bool erase_atom(unsigned idx);

        void inc_clause_activity() {
            SASSERT(is_lemma());
            set_activity(get_activity() + 1);
        }

        std::ostream& display(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const;
        
        std::ostream& display_smt2(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const;

        std::ostream& display_compact(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const;

        unsigned hash() const {
            return get_ptr_hash(this); 
        }

        void mark_as_deleted(ast_manager & m) {
            SASSERT(!m_deleted);
            m_deleted = true;
            clause_del_eh * del_eh = get_del_eh();
            if (del_eh) {
                (*del_eh)(m, this);
                *(const_cast<clause_del_eh **>(get_del_eh_addr())) = nullptr;
            }
        }

        bool deleted() const { 
            return m_deleted; 
        }

    };

    typedef ptr_vector<clause> clause_vector;

    typedef obj_hashtable<clause> clause_set;
};


