/*++
Copyright (c) 2006 Microsoft Corporation

Module Name:

    dl_instruction.h

Abstract:

    <abstract>

Author:

    Krystof Hoder (t-khoder) 2010-09-14.

Revision History:

--*/
#ifndef _DL_INSTRUCTION_H_
#define _DL_INSTRUCTION_H_

#include<iostream>
#include<string>
#include<utility>
#include "ast.h"
#include "vector.h"
#include "dl_base.h"
#include "dl_costs.h"

namespace datalog {

    class execution_context;
    class instruction_block;

    inline void check_overflow(unsigned i) {
        if (i == UINT_MAX) {
            throw out_of_memory_error();
        }
    }

    // -----------------------------------
    //
    // execution_context
    //
    // -----------------------------------

    class execution_context {
    public:
        typedef relation_base * reg_type;
        typedef vector<reg_type> reg_vector;
        typedef unsigned reg_idx;

        /**
           \brief A register number that should never be referenced to. Can stand e.g. for a tail 
           table in a rule with no tail.
        */
        static const reg_idx void_register = UINT_MAX;
    private:
        typedef u_map<std::string> reg_annotations;

        context &           m_datalog_context;
        reg_vector          m_registers;

        reg_annotations     m_reg_annotation;
        stopwatch *         m_stopwatch;
        unsigned            m_timelimit_ms; //zero means no limit
        /**
           \brief If true, after every operation that may result in an empty relation, a check
           for emptiness will be performed, and if a relation is empty, it will be deleted
           and replaced by zero. This allows us to avoid performing operations that would have
           no effect due to relation emptiness, but if the check for emptiness is expensive, its
           cost may overcome the gains.
         */
        bool                m_eager_emptiness_checking;
    public:
        execution_context(context & datalog_context);
        ~execution_context();

        void reset();

        context & get_datalog_context() { return m_datalog_context; };

        void set_timelimit(unsigned time_in_ms);
        void reset_timelimit();
        bool should_terminate();

        bool eager_emptiness_checking() const { return m_eager_emptiness_checking; }

        /**
           \brief Return reference to \c i -th register that contains pointer to a relation.

           If register contains zero, it should be treated as if it contains an empty relation.
        */
        reg_type reg(reg_idx i) { 
            if (i>=m_registers.size()) {
                check_overflow(i);
                m_registers.resize(i+1,0);
            }
            return m_registers[i];
        }
        /**
           \brief Return value of the register and assign zero into it place.
        */
        reg_type release_reg(reg_idx i) {
            SASSERT(i<m_registers.size());
            SASSERT(m_registers[i]);
            reg_type res = m_registers[i];
            m_registers[i] = 0;
            return res;
        }
        /**
           \brief Assign value to a register. If it was non-empty, deallocate its content first.
        */
        void set_reg(reg_idx i, reg_type val) {
            if(i>=m_registers.size()) {
                check_overflow(i);
                m_registers.resize(i+1,0);
            }
            if(m_registers[i]) {
                m_registers[i]->deallocate();
            }
            m_registers[i]=val;
        }
        void make_empty(reg_idx i) {
            if(reg(i)) {
                set_reg(i, 0);
            }
        }

        unsigned register_count() const {
            return m_registers.size();
        }
        bool get_register_annotation(reg_idx reg, std::string & res) const {
            return m_reg_annotation.find(reg, res);
        }
        void set_register_annotation(reg_idx reg, std::string str) {
            m_reg_annotation.insert(reg, str);
        }

        void report_big_relations(unsigned threshold, std::ostream & out);
    };



    // -----------------------------------
    //
    // instruction
    //
    // -----------------------------------


    /**
    \brief Base class for instructions used in datalog saturation.

    A relation in a register is owned by that register and is not referenced from anywhere else.

    Instructions that move context of one register to another leave the source register empty
    and deallocate the previous content of the target register.
    */
    class instruction : public accounted_object {
        typedef u_map<base_relation_fn *> fn_cache;

        fn_cache m_fn_cache;


        static const int rk_encode_base = 1024;

        inline static unsigned encode_kind(family_id k)
        { SASSERT(k<rk_encode_base); return k; }
        inline static unsigned encode_kinds(family_id k1, family_id k2) 
        { SASSERT(k1<rk_encode_base && k2<rk_encode_base); return (k1+1)*rk_encode_base + k2; }
        inline static unsigned encode_kinds(family_id k1, family_id k2, family_id k3) { 
            SASSERT(k1<rk_encode_base && k2<rk_encode_base && k3<rk_encode_base); 
            return ((k1+1)*rk_encode_base + k2)*rk_encode_base + k3;
        }

    protected:
        friend class instruction_block;

        template<typename T>
        bool find_fn(const relation_base & r, T* & result) const
        { return m_fn_cache.find(encode_kind(r.get_kind()), reinterpret_cast<base_relation_fn*&>(result)); }

        template<typename T>
        bool find_fn(const relation_base & r1, const relation_base & r2, T*& result) const
        { return m_fn_cache.find(encode_kinds(r1.get_kind(), r2.get_kind()), reinterpret_cast<base_relation_fn*&>(result)); }

        template<typename T>
        bool find_fn(const relation_base & r1, const relation_base & r2, const relation_base & r3, T * & result) const
        { return m_fn_cache.find(encode_kinds(r1.get_kind(), r2.get_kind(), r3.get_kind()), reinterpret_cast<base_relation_fn*&>(result)); }

        void store_fn(const relation_base & r, base_relation_fn * fn)
        { m_fn_cache.insert(encode_kind(r.get_kind()), fn); }
        void store_fn(const relation_base & r1, const relation_base & r2, base_relation_fn * fn)
        { m_fn_cache.insert(encode_kinds(r1.get_kind(), r2.get_kind()), fn); }
        void store_fn(const relation_base & r1, const relation_base & r2, const relation_base & r3, 
            base_relation_fn * fn)
        { m_fn_cache.insert(encode_kinds(r1.get_kind(), r2.get_kind(), r3.get_kind()), fn); }

        /**
           Process not only costs associated with the current instruction, but in case of
           block instructions, process also costs associated with its child instructions.
        */
        virtual void process_all_costs();

        /**
           \brief Output one line header of the current instruction.

           The newline character at the end should not be printed.
        */
        virtual void display_head_impl(context & ctx, std::ostream & out) const {
            out << "<instruction>";
        }
        /**
           \brief If relevant, output the body of the current instruction.

           Each line must be prepended by \c indentation and ended by a newline character.
        */
        virtual void display_body_impl(context & ctx, std::ostream & out, std::string indentation) const {}
    public:
        typedef execution_context::reg_type reg_type;
        typedef execution_context::reg_idx reg_idx;

        virtual ~instruction();

        virtual bool perform(execution_context & ctx) = 0;

        virtual void make_annotations(execution_context & ctx)  = 0;

        void display(context & ctx, std::ostream & out) const {
            display_indented(ctx, out, "");
        }
        void display_indented(context & ctx, std::ostream & out, std::string indentation) const;

        static instruction * mk_load(ast_manager & m, func_decl * pred, reg_idx tgt);
        /**
           \brief The store operation moves the relation from a register into the context. The register
           is set to zero after the operation.
        */
        static instruction * mk_store(ast_manager & m, func_decl * pred, reg_idx src);
        static instruction * mk_dealloc(reg_idx reg); //maybe not necessary
        static instruction * mk_clone(reg_idx from, reg_idx to);
        static instruction * mk_move(reg_idx from, reg_idx to);

        /**
           \brief Return instruction that performs \c body as long as at least one register
           in \c control_regs contains non-empty relation.

           The instruction object takes over the ownership of the \c body object.
        */
        static instruction * mk_while_loop(unsigned control_reg_cnt, const reg_idx * control_regs, 
            instruction_block * body);

        static instruction * mk_join(reg_idx rel1, reg_idx rel2, unsigned col_cnt,
            const unsigned * cols1, const unsigned * cols2, reg_idx result);
        static instruction * mk_filter_equal(ast_manager & m, reg_idx reg, const relation_element & value, unsigned col);
        static instruction * mk_filter_identical(reg_idx reg, unsigned col_cnt, const unsigned * identical_cols);
        static instruction * mk_filter_interpreted(reg_idx reg, app_ref & condition);
        static instruction * mk_union(reg_idx src, reg_idx tgt, reg_idx delta);
        static instruction * mk_widen(reg_idx src, reg_idx tgt, reg_idx delta);
        static instruction * mk_projection(reg_idx src, unsigned col_cnt, const unsigned * removed_cols, 
            reg_idx tgt);
        static instruction * mk_join_project(reg_idx rel1, reg_idx rel2, unsigned joined_col_cnt,
            const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, 
            const unsigned * removed_cols, reg_idx result);
        static instruction * mk_rename(reg_idx src, unsigned cycle_len, const unsigned * permutation_cycle, 
            reg_idx tgt);
        static instruction * mk_filter_by_negation(reg_idx tgt, reg_idx neg_rel, unsigned col_cnt,
            const unsigned * cols1, const unsigned * cols2);
        static instruction * mk_select_equal_and_project(ast_manager & m, reg_idx src, 
            const relation_element & value, unsigned col, reg_idx result);
        
        static instruction * mk_unary_singleton(ast_manager & m, func_decl* pred, const relation_sort & s, const relation_element & val, reg_idx tgt);
        static instruction * mk_total(const relation_signature & sig, func_decl* pred, reg_idx tgt);

        /**
           \brief The mark_saturated instruction marks a relation as saturated, so that after
           next restart it does not have to be part of the saturation again.
         */
        static instruction * mk_mark_saturated(ast_manager & m, func_decl * pred);

        static instruction * mk_assert_signature(const relation_signature & s, reg_idx tgt);

    };


    // -----------------------------------
    //
    // instruction_block
    //
    // -----------------------------------

    class instruction_block {
    public:
        struct instruction_observer {
            virtual ~instruction_observer() {}
            virtual void notify(instruction * i) {}
        };
    private:
        typedef ptr_vector<instruction> instr_seq_type;
        instr_seq_type m_data;
        instruction_observer* m_observer;
    public:
        instruction_block() : m_observer(0) {}
        ~instruction_block();
        void reset();

        void push_back(instruction * i) { 
            m_data.push_back(i);
            if(m_observer) {
                m_observer->notify(i);
            }
        }
        void set_observer(instruction_observer * o) { 
            SASSERT(o==0 || m_observer==0);
            m_observer = o;
        }

        /**
           \brief Perform instructions in the block. If the run was interrupted before completion,
           return false; otherwise return true.

           The execution can terminate before completion if the function 
           \c execution_context::should_terminate() returns true.
        */
        bool perform(execution_context & ctx) const;

        void process_all_costs();

        void make_annotations(execution_context & ctx);

        void display(context & ctx, std::ostream & out) const {
            display_indented(ctx, out, "");
        }
        void display_indented(context & ctx, std::ostream & out, std::string indentation) const;
    };


};

#endif /* _DL_INSTRUCTION_H_ */

