/*++
 Copyright (c) 2016 Microsoft Corporation

 Module Name:

  bv_bound_chk_tactic.cpp

 Abstract:


 Author:

 Mikolas Janota

 Revision History:
--*/
#include "tactic/bv/bv_bound_chk_tactic.h"
#include "ast/ast.h"
#include "params/rewriter_params.hpp"
#include "ast/rewriter/rewriter.h"
#include "ast/rewriter/rewriter_def.h"
#include "ast/rewriter/bv_bounds.h"
#include "ast/rewriter/bool_rewriter.h"

struct bv_bound_chk_stats {
    unsigned            m_unsats;
    unsigned            m_singletons;
    unsigned            m_reduces;
    bv_bound_chk_stats() : m_unsats(0), m_singletons(0), m_reduces(0) {};
};

struct bv_bound_chk_rewriter_cfg : public default_rewriter_cfg {
    ast_manager &       m_m;
    unsigned                 m_bv_ineq_consistency_test_max;
    bool_rewriter       m_b_rw;
    unsigned long long  m_max_steps;
    unsigned long long  m_max_memory; // in bytes
    bv_bound_chk_stats& m_stats;
    

    bv_bound_chk_rewriter_cfg(ast_manager & m, bv_bound_chk_stats& stats)
        : m_m(m), m_b_rw(m), m_stats(stats) {}

    void updt_params(params_ref const & _p) {
        rewriter_params p(_p);
        m_bv_ineq_consistency_test_max = p.bv_ineq_consistency_test_max();        
        m_max_memory = p.max_memory();
        m_max_steps = p.max_steps();
    }

    ast_manager & m() const { return m_m; }

    bool rewrite_patterns() const { return false; }

    bool flat_assoc(func_decl * f) const { return true; }

    br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) {
        const br_status st = reduce_app_core(f, num, args, result, result_pr);
        CTRACE("bv_bound_chk_step", st != BR_FAILED,
            tout << f->get_name() << "\n";
        for (unsigned i = 0; i < num; i++) tout << mk_ismt2_pp(args[i], m()) << "\n";
        tout << "---------->\n" << mk_ismt2_pp(result, m()) << "\n";);
        return st;
    }

    br_status reduce_app_core(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) {
        result_pr = nullptr;
        const family_id fid = f->get_family_id();
        if (fid != m_b_rw.get_fid()) return BR_FAILED;
        bv_bounds bvb(m());
        const br_status rv = bvb.rewrite(m_bv_ineq_consistency_test_max, f, num, args, result);
        if (rv != BR_FAILED && (m_m.is_false(result) || m_m.is_true(result))) m_stats.m_unsats++;
        else if (rv != BR_FAILED && !bvb.singletons().empty()) m_stats.m_singletons++;
        else if (rv != BR_FAILED && is_app(result) && to_app(result)->get_num_args() < num) m_stats.m_reduces++;
        return rv;
    }

    bool max_steps_exceeded(unsigned long long num_steps) const {
        if (num_steps > m_max_steps)
            return true;
        if (memory::get_allocation_size() > m_max_memory)
            throw tactic_exception(TACTIC_MAX_MEMORY_MSG);
        return false;
    }

    void reset_statistics() {
        m_stats.m_unsats = 0;
        m_stats.m_singletons = 0;
        m_stats.m_reduces = 0;
    }

    void collect_statistics(statistics & st) const {
        st.update("unsat bv bounds", m_stats.m_unsats);
        st.update("bv singletons", m_stats.m_singletons);
        st.update("bv reduces", m_stats.m_reduces);
    }
};

struct bv_bound_chk_rewriter : public rewriter_tpl<bv_bound_chk_rewriter_cfg> {
    bv_bound_chk_rewriter_cfg m_cfg;

    bv_bound_chk_rewriter(ast_manager & m, params_ref const & p, bv_bound_chk_stats& stats)
        : rewriter_tpl<bv_bound_chk_rewriter_cfg>(m, false, m_cfg)
        , m_cfg(m, stats)
    {
        updt_params(p);
    }

    void updt_params(params_ref const & _p) {
        m_cfg.updt_params(_p);
    }

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


    void reset_statistics() {
        m_cfg.reset_statistics();
    }

};

class bv_bound_chk_tactic : public tactic {
    class imp;
    imp *                       m_imp;
    params_ref                  m_params;
    bv_bound_chk_stats          m_stats;
public:
    bv_bound_chk_tactic(ast_manager & m, params_ref const & p);
    void operator()(goal_ref const & g, goal_ref_buffer & result) override;
    ~bv_bound_chk_tactic() override;
    tactic * translate(ast_manager & m) override;
    void updt_params(params_ref const & p) override;
    void cleanup() override;
    void collect_statistics(statistics & st) const override;
    void reset_statistics() override;
    char const* name() const override { return "bv_bound_chk"; }
};

class bv_bound_chk_tactic::imp {
    bv_bound_chk_rewriter      m_rw;
public:
    imp(ast_manager & m, params_ref const & p, bv_bound_chk_stats& stats)
        : m_rw(m, p, stats) {    }

    ast_manager& m() { return m_rw.m(); }

    void operator()(goal_ref const & g) {
        tactic_report report("bv-bound-chk", *g);
        ast_manager& m(g->m());
        expr_ref   new_curr(m);
        const unsigned size = g->size();
        for (unsigned idx = 0; idx < size; idx++) {
            if (g->inconsistent()) break;
            expr * curr = g->form(idx);
            m_rw(curr, new_curr);
            g->update(idx, new_curr);
        }
        m_rw.m_cfg.cleanup();
    }

    void updt_params(params_ref const & p) {
        m_rw.updt_params(p);
    }

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

    void reset_statistics() {
        m_rw.reset_statistics();
    }
};

bv_bound_chk_tactic::bv_bound_chk_tactic(ast_manager & m, params_ref const & p)
: m_params(p)
{
    m_imp = alloc(imp, m, p, m_stats);
}


bv_bound_chk_tactic::~bv_bound_chk_tactic() {
    dealloc(m_imp);
}

void bv_bound_chk_tactic::operator()(goal_ref const & g, goal_ref_buffer & result) {
    fail_if_proof_generation("bv-bound-chk", g);
    fail_if_unsat_core_generation("bv-bound-chk", g);
    result.reset();
    m_imp->operator()(g);
    g->inc_depth();
    result.push_back(g.get());
}

tactic * bv_bound_chk_tactic::translate(ast_manager & m) {
    return alloc(bv_bound_chk_tactic, m, m_params);
}


void bv_bound_chk_tactic::updt_params(params_ref const & p) {
    m_params.append(p);
    m_imp->updt_params(m_params);
}

void bv_bound_chk_tactic::cleanup() {
    imp * d = alloc(imp, m_imp->m(), m_params, m_stats);
    std::swap(d, m_imp);
    dealloc(d);
}

void bv_bound_chk_tactic::collect_statistics(statistics & st) const {
    m_imp->collect_statistics(st);
}

void bv_bound_chk_tactic::reset_statistics() {
    m_imp->reset_statistics();
}


tactic* mk_bv_bound_chk_tactic(ast_manager & m, params_ref const & p) {
    return alloc(bv_bound_chk_tactic, m, p);
}
