/*++
Copyright (c) 2011 Microsoft Corporation

Module Name:

    arith_rewriter.h

Abstract:

    Basic rewriting rules for arithmetic

Author:

    Leonardo (leonardo) 2011-04-10

Notes:

--*/
#pragma once

#include "ast/rewriter/poly_rewriter.h"
#include "ast/arith_decl_plugin.h"
#include "ast/seq_decl_plugin.h"
#include "math/polynomial/algebraic_numbers.h"

class arith_rewriter_core {
protected:
    typedef rational numeral;
    ast_manager& m;
    arith_util  m_util;
    scoped_ptr<seq_util> m_seq;
    bool        m_expand_power = false;
    bool        m_mul2power = false;
    bool        m_expand_tan = false;
    
    family_id get_fid() const { return m_util.get_family_id(); }
    seq_util& seq();
    
    bool is_numeral(expr * n) const { return m_util.is_numeral(n); }
    bool is_numeral(expr * n, numeral & r) const { return m_util.is_numeral(n, r); }
    bool is_minus_one(expr * n) const { return m_util.is_minus_one(n); }
    void normalize(numeral & c, sort * s) {}
    app * mk_numeral(numeral const & r, sort * s) { return m_util.mk_numeral(r, s); }
    decl_kind add_decl_kind() const { return OP_ADD; }
    decl_kind mul_decl_kind() const { return OP_MUL; }
    bool use_power() const { return m_mul2power && !m_expand_power; }
    decl_kind power_decl_kind() const { return OP_POWER; }
    app* mk_power(expr* x, rational const& r, sort* s);
    expr* coerce(expr* x, sort* s);
public:
    arith_rewriter_core(ast_manager & m):m(m), m_util(m) {}
    bool is_zero(expr * n) const { return m_util.is_zero(n); }
};

class arith_rewriter : public poly_rewriter<arith_rewriter_core> {
    bool m_arith_lhs;
    bool m_arith_ineq_lhs;
    bool m_gcd_rounding;
    bool m_elim_to_real;
    bool m_push_to_real;
    bool m_anum_simp;
    bool m_elim_rem;
    bool m_eq2ineq;
    unsigned m_max_degree;

    bool get_range(expr* e, rational& lo, rational& hi);
    void get_coeffs_gcd(expr * t, numeral & g, bool & first, unsigned & num_consts);
    enum const_treatment { CT_FLOOR, CT_CEIL, CT_FALSE };
    bool div_polynomial(expr * t, numeral const & g, const_treatment ct, expr_ref & result);
    enum op_kind { LE, GE, EQ };
    static op_kind inv(op_kind k) { return k == LE ? GE : (k == GE ? LE : EQ); }
    bool is_bound(expr * arg1, expr * arg2, op_kind kind, expr_ref & result);
    br_status is_separated(expr * arg1, expr * arg2, op_kind kind, expr_ref & result);
    bool is_non_negative(expr* e);
    br_status mk_le_ge_eq_core(expr * arg1, expr * arg2, op_kind kind, expr_ref & result);
    bool is_add_factor(expr* s, expr* t);
    bool is_mul_factor(expr* s, expr* t);
    expr* find_nl_factor(expr* t);
    void get_nl_muls(expr* t, ptr_buffer<expr>& muls);
    expr_ref remove_factor(expr* s, expr* t);
    br_status factor_le_ge_eq(expr * arg1, expr * arg2, op_kind kind, expr_ref & result);

    bool elim_to_real_var(expr * var, expr_ref & new_var);
    bool elim_to_real_mon(expr * monomial, expr_ref & new_monomial);
    bool elim_to_real_pol(expr * p, expr_ref & new_p);
    bool elim_to_real(expr * arg1, expr * arg2, expr_ref & new_arg1, expr_ref & new_arg2);

    void updt_local_params(params_ref const & p);

    bool is_anum_simp_target(unsigned num_args, expr * const * args);
    bool is_algebraic_numeral(expr* n, scoped_anum& a);

    br_status mk_div_irrat_rat(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_div_rat_irrat(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_div_irrat_irrat(expr * arg1, expr * arg2, expr_ref & result);
    
    bool is_reduce_power_target(expr * arg, bool is_eq);
    expr * reduce_power(expr * arg, bool is_eq);
    br_status reduce_power(expr * arg1, expr * arg2, op_kind kind, expr_ref & result);

    bool is_arith_term(expr * n) const;

    bool is_pi_multiple(expr * t, rational & k);
    bool is_pi_offset(expr * t, rational & k, expr * & m);
    bool is_2_pi_integer(expr * t);
    bool is_2_pi_integer_offset(expr * t, expr * & m);
    bool is_pi_integer(expr * t);
    bool is_pi_integer_offset(expr * t, expr * & m);
    bool is_neg_poly(expr* e, expr_ref& neg);
    expr_ref neg_monomial(expr * e);
    expr * mk_sin_value(rational const & k);
    app * mk_sqrt(rational const & k);
    bool get_divides(expr* d, expr* n, expr_ref& result);
    expr_ref remove_divisor(expr* arg, expr* num, expr* den); 
    void flat_mul(expr* e, ptr_buffer<expr>& args); 
    void remove_divisor(expr* d, ptr_buffer<expr>& args);

    bool mk_eq_mod(expr* arg1, expr* arg2, expr_ref& result);
public:
    arith_rewriter(ast_manager & m, params_ref const & p = params_ref()):
        poly_rewriter<arith_rewriter_core>(m, p) {
        updt_local_params(p);
    }

    void updt_params(params_ref const & p);

    static void get_param_descrs(param_descrs & r);

    br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result);
    void mk_app(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) {
        if (mk_app_core(f, num_args, args, result) == BR_FAILED)
            result = m.mk_app(f, num_args, args);
    }

    br_status mk_eq_core(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_le_core(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_lt_core(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_ge_core(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_gt_core(expr * arg1, expr * arg2, expr_ref & result);

    br_status mk_add_core(unsigned num_args, expr * const * args, expr_ref & result);
    br_status mk_mul_core(unsigned num_args, expr * const * args, expr_ref & result);

    void mk_eq(expr * arg1, expr * arg2, expr_ref & result) {
        if (mk_eq_core(arg1, arg2, result) == BR_FAILED)
            result = m_util.mk_eq(arg1, arg2);
    }
    void mk_le(expr * arg1, expr * arg2, expr_ref & result) {
        if (mk_le_core(arg1, arg2, result) == BR_FAILED)
            result = m_util.mk_le(arg1, arg2);
    }
    void mk_lt(expr * arg1, expr * arg2, expr_ref & result) { mk_lt_core(arg1, arg2, result); }
    void mk_ge(expr * arg1, expr * arg2, expr_ref & result) {
        if (mk_ge_core(arg1, arg2, result) == BR_FAILED)
            result = m_util.mk_ge(arg1, arg2);
    }
    void mk_gt(expr * arg1, expr * arg2, expr_ref & result) { mk_gt_core(arg1, arg2, result); }

    br_status mk_abs_core(expr * arg, expr_ref & result);

    br_status mk_and_core(unsigned n, expr* const* args, expr_ref& result);

    br_status mk_div_core(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_idiv_core(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_idivides(unsigned k, expr * arg, expr_ref & result);
    br_status mk_mod_core(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_rem_core(expr * arg1, expr * arg2, expr_ref & result);
    br_status mk_power_core(expr* arg1, expr* arg2, expr_ref & result);
    br_status mk_band_core(unsigned sz, expr* arg1, expr* arg2, expr_ref& result);
    br_status mk_shl_core(unsigned sz, expr* arg1, expr* arg2, expr_ref& result);
    br_status mk_lshr_core(unsigned sz, expr* arg1, expr* arg2, expr_ref& result);
    br_status mk_ashr_core(unsigned sz, expr* arg1, expr* arg2, expr_ref& result);
    void mk_div(expr * arg1, expr * arg2, expr_ref & result) {
        if (mk_div_core(arg1, arg2, result) == BR_FAILED)
            result = m.mk_app(get_fid(), OP_DIV, arg1, arg2);
    }
    void mk_idiv(expr * arg1, expr * arg2, expr_ref & result) {
        if (mk_idiv_core(arg1, arg2, result) == BR_FAILED)
            result = m.mk_app(get_fid(), OP_IDIV, arg1, arg2);
    }
    void mk_mod(expr * arg1, expr * arg2, expr_ref & result) {
        if (mk_mod_core(arg1, arg2, result) == BR_FAILED)
            result = m.mk_app(get_fid(), OP_MOD, arg1, arg2);
    }
    void mk_rem(expr * arg1, expr * arg2, expr_ref & result) {
        if (mk_rem_core(arg1, arg2, result) == BR_FAILED)
            result = m.mk_app(get_fid(), OP_REM, arg1, arg2);
    }
    
    br_status mk_to_int_core(expr * arg, expr_ref & result);
    br_status mk_to_real_core(expr * arg, expr_ref & result);
    void mk_to_int(expr * arg, expr_ref & result) { 
        if (mk_to_int_core(arg, result) == BR_FAILED)
            result = m.mk_app(get_fid(), OP_TO_INT, 1, &arg); 
    }
    void mk_to_real(expr * arg, expr_ref & result) { 
        if (mk_to_real_core(arg, result) == BR_FAILED)  
            result = m.mk_app(get_fid(), OP_TO_REAL, 1, &arg); 
    }
    br_status mk_is_int(expr * arg, expr_ref & result);

    br_status mk_sin_core(expr * arg, expr_ref & result);
    br_status mk_cos_core(expr * arg, expr_ref & result);
    br_status mk_tan_core(expr * arg, expr_ref & result);

    br_status mk_asin_core(expr * arg, expr_ref & result);
    br_status mk_acos_core(expr * arg, expr_ref & result);
    br_status mk_atan_core(expr * arg, expr_ref & result);

    br_status mk_sinh_core(expr * arg, expr_ref & result);
    br_status mk_cosh_core(expr * arg, expr_ref & result);
    br_status mk_tanh_core(expr * arg, expr_ref & result);
};

