; -- Core Normalization Rules --

(define-cond-rule bv-concat-extract-merge
  ((xs ?BitVec :list)
   (s ?BitVec)
   (ys ?BitVec :list)
   (i Int) (j Int) (j1 Int) (k Int)
  )
  (= j1 (+ j 1))
  (concat xs (extract k j1 s) (extract j i s) ys)
  (concat xs (extract k i s) ys))

; x[i..j][k..l] = x[i+k..i+l]
; note: could be fixed-point but we don't permit conditional fixed point
(define-cond-rule bv-extract-extract
  ((x ?BitVec) (i Int) (j Int) (k Int) (l Int) (ll Int) (kk Int))
  (and (= ll (+ i l)) (= kk (+ i k)))
  (extract l k (extract j i x))
  (extract ll kk x))
(define-cond-rule bv-extract-whole
  ((x ?BitVec) (n Int))
  (>= n (- (@bvsize x) 1))
  (extract n 0 x)
  x)
; Case 1: (< j n) so the extract is self contained
(define-cond-rule bv-extract-concat-1
  ((x ?BitVec) (xs ?BitVec :list) (y ?BitVec)
  (i Int) (j Int))
  (<= j (@bvsize x))
  (extract j i (concat xs y x)) ; (concat ...) needs at least 2 children
  (extract j i x))
; Case 2: (< i n) but (>= j n), the extract crosses the boundary into the next one.
; Note that we do not know the size of the element after x, so we leave it in (extract ... (concat ...)) form
(define-cond-rule bv-extract-concat-2
  ((x ?BitVec) (xs ?BitVec :list) (y ?BitVec) (i Int) (j Int) (u1 Int) (u2 Int))
  (and (< i (@bvsize x)) (>= j (@bvsize x)) (= u1 (- j (@bvsize x))) (= u2 (- (@bvsize x) 1)))
  (extract j i (concat xs y x))
  (concat
    (extract u1 0 (concat xs y))
    (extract u2 i x)))
; Case 3: (>= i n) and (>= j n), extract elides x
(define-cond-rule bv-extract-concat-3
  ((x ?BitVec) (y ?BitVec) (xs ?BitVec :list) (i Int) (j Int) (u2 Int) (l2 Int))
  (and (>= i (@bvsize x)) (= u2 (- j (@bvsize x))) (= l2 (- i (@bvsize x))))
  (extract j i (concat xs y x))
  (extract u2 l2 (concat xs y)))
; Case 4: Elision from the higher portion
(define-cond-rule bv-extract-concat-4
  ((x ?BitVec) (y ?BitVec) (xs ?BitVec :list) (i Int) (j Int))
  (< j (- (@bvsize (concat x y xs)) (@bvsize x)))
  (extract j i (concat x xs y))
  (extract j i (concat xs y)))

; Motivated by TheoryBv::ppAssert, which turns an equality involving
; extract into a solved form for the variable we are extracting from.
(define-cond-rule bv-eq-extract-elim1
  ((x ?BitVec) (y ?BitVec) (i Int) (j Int) (wm1 Int) (jp1 Int) (im1 Int))
  (and (= wm1 (- (@bvsize x) 1)) (= jp1 (+ j 1)) (= im1 (- i 1)) (> wm1 j) (> i 0))
  (= (extract j i x) y)
  (= x (concat (extract wm1 jp1 x) y (extract im1 0 x))))

(define-cond-rule bv-eq-extract-elim2
  ((x ?BitVec) (y ?BitVec) (j Int) (wm1 Int) (jp1 Int))
  (and (= wm1 (- (@bvsize x) 1)) (= jp1 (+ j 1)) (> wm1 j))
  (= (extract j 0 x) y)
  (= x (concat (extract wm1 jp1 x) y)))

(define-cond-rule bv-eq-extract-elim3
  ((x ?BitVec) (y ?BitVec) (i Int) (j Int) (im1 Int))
  (and (= j (- (@bvsize x) 1)) (= im1 (- i 1)) (> i 0))
  (= (extract j i x) y)
  (= x (concat y (extract im1 0 x))))

; -- Normalization Rules --
(define-rule bv-extract-not
  ((x ?BitVec) (i Int) (j Int))
  (extract j i (bvnot x))
  (bvnot (extract j i x)))
(define-cond-rule bv-extract-sign-extend-1
  ((x ?BitVec) (low Int) (high Int) (k Int))
  (< high (@bvsize x))
  (extract high low (sign_extend k x))
  (extract high low x))
(define-cond-rule bv-extract-sign-extend-2
  ((x ?BitVec) (low Int) (high Int) (k Int) (nm1 Int) (sn Int))
  (def (n (@bvsize x)))
  (and (< low n) (>= high n) (= nm1 (- n 1)) (= sn (+ 1 (- high n))))
  (extract high low (sign_extend k x))
  (sign_extend
    sn
    (extract nm1 low x)))
(define-cond-rule bv-extract-sign-extend-3
  ((x ?BitVec) (low Int) (high Int) (k Int) (rn Int) (nm1 Int))
  (def (n (@bvsize x)))
  (and (>= low n) (= rn (+ 1 (- high low))) (= nm1 (- n 1)))
  (extract high low (sign_extend k x))
  (repeat rn (extract nm1 nm1 x)))

(define-rule bv-not-xor
  ((x1 ?BitVec) (x2 ?BitVec) (xs ?BitVec :list))
  (bvnot (bvxor x1 x2 xs))
  (bvxor (bvnot x1) x2 xs))

(define-cond-rule bv-and-simplify-1
  ((xs ?BitVec :list) (ys ?BitVec :list) (zs ?BitVec :list) (x ?BitVec) (w Int))
  (= w (@bvsize x))
  (bvand xs (bvnot x) ys x zs)
  (@bv 0 w))
(define-cond-rule bv-and-simplify-2
  ((xs ?BitVec :list) (ys ?BitVec :list) (zs ?BitVec :list) (x ?BitVec) (w Int))
  (= w (@bvsize x))
  (bvand xs x ys (bvnot x) zs)
  (@bv 0 w))

(define-cond-rule bv-or-simplify-1
  ((xs ?BitVec :list) (ys ?BitVec :list) (zs ?BitVec :list) (x ?BitVec) (w Int))
  (= w (@bvsize x))
  (bvor xs (bvnot x) ys x zs)
  (bvnot (@bv 0 w)))
(define-cond-rule bv-or-simplify-2
  ((xs ?BitVec :list) (ys ?BitVec :list) (zs ?BitVec :list) (x ?BitVec) (w Int))
  (= w (@bvsize x))
  (bvor xs x ys (bvnot x) zs)
  (bvnot (@bv 0 w)))

(define-rule* bv-xor-simplify-1
  ((xs ?BitVec :list) (ys ?BitVec :list) (zs ?BitVec :list) (x ?BitVec))
  (bvxor xs x ys x zs)
  (bvxor xs ys zs))
(define-rule bv-xor-simplify-2
  ((xs ?BitVec :list) (ys ?BitVec :list) (zs ?BitVec :list) (x ?BitVec))
  (bvxor xs x ys (bvnot x) zs)
  (bvnot (bvxor xs ys zs)))
(define-rule bv-xor-simplify-3
  ((xs ?BitVec :list) (ys ?BitVec :list) (zs ?BitVec :list) (x ?BitVec))
  (bvxor xs (bvnot x) ys x zs)
  (bvnot (bvxor xs ys zs)))

; -- Simplify Bitblasting --

; x < ys + 1 + zs <=> (not (ys + zs) < x) and (ys + zs) != 1...1
; we use ys, zs as lists so that 1 may appear on the left or the right of the bvadd term.
(define-cond-rule bv-ult-add-one
  ((x ?BitVec) (ys ?BitVec :list) (zs ?BitVec :list) (c1 ?BitVec) (w Int))
  (and (= c1 (@bv 1 w)) (= w (@bvsize x)))
  (bvult x (bvadd ys c1 zs))
  (and
    (not (= (bvadd ys zs) (bvnot (@bv 0 w))))
    (not (bvult (bvadd ys zs) x))))

(define-cond-rule bv-mult-slt-mult-1
  ((x ?BitVec) (y ?BitVec) (a ?BitVec) (n Int) (m Int) (tn Int) (an Int))
  (and (= tn (@bvsize x)) (= an (@bvsize a)))
  (bvslt
    (bvmul (sign_extend n y) (sign_extend m a))
    (bvmul (sign_extend n x) (sign_extend m a))
  )
  (and
    (not (= (bvsub y x) (@bv 0 tn)))
    (not (= a (@bv 0 an)))
    (= (bvslt y x) (bvsgt a (@bv 0 an)))))
(define-cond-rule bv-mult-slt-mult-2
  ((x ?BitVec) (y ?BitVec) (a ?BitVec) (n Int) (m Int) (tn Int) (an Int))
  (and (= tn (@bvsize x)) (= an (@bvsize a)))
  (bvslt
    (bvmul (zero_extend n y) (sign_extend m a))
    (bvmul (zero_extend n x) (sign_extend m a))
  )
  (and
    (not (= (bvsub y x) (@bv 0 tn)))
    (not (= a (@bv 0 an)))
    (= (bvult y x) (bvsgt a (@bv 0 an)))))


; -- Commutativity --
(define-rule bv-commutative-xor ((x ?BitVec) (y ?BitVec))
  (bvxor x y) (bvxor y x))
(define-rule bv-commutative-comp ((x ?BitVec) (y ?BitVec))
  (bvcomp x y) (bvcomp y x))

(define-rule bv-zero-extend-eliminate-0
  ((x ?BitVec))
  (zero_extend 0 x)
  x)
(define-rule bv-sign-extend-eliminate-0
  ((x ?BitVec))
  (sign_extend 0 x)
  x)
(define-cond-rule bv-not-neq ((x ?BitVec))
  (> (@bvsize x) 0)
  (= x (bvnot x))
  false)

(define-cond-rule bv-ult-ones ((x ?BitVec) (n Int) (w Int))
  (= n (- (int.pow2 w) 1))
  (bvult x (@bv n w))
  (distinct x (@bv n w)))

; Collapse rules

(define-cond-rule bv-concat-merge-const
  ((xs ?BitVec :list)
   (n1 Int) (w1 Int) (n2 Int) (w2 Int) (ww Int)
   (zs ?BitVec :list))
  (= ww (+ w1 w2))
  (concat xs (@bv n1 w1) (@bv n2 w2) zs)
  (concat xs (@bv (+ (* n1 (int.pow2 w2)) (mod n2 (int.pow2 w2))) ww) zs))

; These rules should be subsumed by ARITH_POLY_NORM, but removing them increases the number of holes
(define-rule bv-commutative-add ((x ?BitVec) (y ?BitVec))
  (bvadd x y) (bvadd y x))
(define-rule bv-sub-eliminate
  ((x ?BitVec) (y ?BitVec))
  (bvsub x y)
  (bvadd x (bvneg y)))

(define-rule bv-ite-width-one ((x ?BitVec))
  (ite (= x (@bv 1 1)) (@bv 1 1) (@bv 0 1))
  x)

(define-rule bv-ite-width-one-not ((x ?BitVec))
  (ite (= x (@bv 0 1)) (@bv 1 1) (@bv 0 1))
  (bvnot x))

(define-rule bv-eq-xor-solve ((x ?BitVec) (y ?BitVec) (z ?BitVec))
  (= (= (bvxor x y) z) (= x (bvxor z y)))
  true)

(define-rule bv-eq-not-solve ((x ?BitVec) (y ?BitVec))
  (= (= (bvnot x) y) (= x (bvnot y)))
  true)
