{-# OPTIONS --prop --without-K --rewriting #-}

module Data.Nat.PredExp2 where

open import Data.Nat
open import Data.Nat.Properties

open import Relation.Nullary
open import Relation.Nullary.Negation
open import Relation.Binary
open import Relation.Binary.PropositionalEquality as Eq using (_≡_; refl; _≢_; module ≡-Reasoning)

open import Data.Nat.Log2 using (⌈log₂_⌉)

pred[2^_] :   
pred[2^ n ] = pred (2 ^ n)

lemma/2^suc :  n  2 ^ n + 2 ^ n  2 ^ suc n
lemma/2^suc n =
  begin
    2 ^ n + 2 ^ n
  ≡˘⟨ Eq.cong ((2 ^ n) +_) (*-identityˡ (2 ^ n)) 
    2 ^ n + (2 ^ n + 0)
  ≡⟨⟩
    2 ^ n + (2 ^ n + 0 * (2 ^ n))
  ≡⟨⟩
    2 * (2 ^ n)
  ≡⟨⟩
    2 ^ suc n
  
    where open ≡-Reasoning

private
  lemma/1≤2^n :  n  1  2 ^ n
  lemma/1≤2^n zero    = ≤-refl {1}
  lemma/1≤2^n (suc n) =
    begin
      1
    ≤⟨ s≤s z≤n 
      1 + 1
    ≤⟨ +-mono-≤ (lemma/1≤2^n n) (lemma/1≤2^n n) 
      2 ^ n + 2 ^ n
    ≡⟨ lemma/2^suc n 
      2 ^ suc n
    
      where open ≤-Reasoning

  lemma/2^n≢0 :  n  2 ^ n  zero
  lemma/2^n≢0 n 2^n≡0 with 2 ^ n | lemma/1≤2^n n
  ... | zero | ()

pred[2^]-mono : pred[2^_] Preserves _≤_  _≤_
pred[2^]-mono m≤n = pred-mono (2^-mono m≤n)
  where
    2^-mono : (2 ^_) Preserves _≤_  _≤_
    2^-mono {y = y} z≤n = lemma/1≤2^n y
    2^-mono (s≤s m≤n) = *-monoʳ-≤ 2 (2^-mono m≤n)

pred[2^suc[n]] : (n : )  suc (pred[2^ n ] + pred[2^ n ])  pred[2^ suc n ]
pred[2^suc[n]] n =
  begin
    suc (pred[2^ n ] + pred[2^ n ])
  ≡⟨⟩
    suc (pred (2 ^ n) + pred (2 ^ n))
  ≡˘⟨ +-suc (pred (2 ^ n)) (pred (2 ^ n)) 
    pred (2 ^ n) + suc (pred (2 ^ n))
  ≡⟨ Eq.cong (pred (2 ^ n) +_) (suc-pred (2 ^ n) {{m^n≢0 2 n}}) 
    pred (2 ^ n) + 2 ^ n
  ≡⟨ lemma/pred-+ (2 ^ n) (2 ^ n) (lemma/2^n≢0 n) 
    pred (2 ^ n + 2 ^ n)
  ≡⟨ Eq.cong pred (lemma/2^suc n) 
    pred (2 ^ suc n)
  ≡⟨⟩
    pred[2^ suc n ]
  
    where
      open ≡-Reasoning

      lemma/pred-+ :  m n  m  zero  pred m + n  pred (m + n)
      lemma/pred-+ zero    n m≢zero = contradiction refl m≢zero
      lemma/pred-+ (suc m) n m≢zero = refl

pred[2^log₂] : (n : )  pred[2^ ⌈log₂ suc  n /2⌉  ]  n
pred[2^log₂] n = strong-induction n n ≤-refl
  where
    strong-induction : (n m : )  m  n  pred[2^ ⌈log₂ suc  m /2⌉  ]  m
    strong-induction n zero    h = z≤n
    strong-induction n (suc zero) h = s≤s z≤n
    strong-induction (suc (suc n)) (suc (suc m)) (s≤s (s≤s h)) =
      begin
        pred[2^ ⌈log₂ suc  suc (suc m) /2⌉  ]
      ≡⟨⟩
        pred[2^ suc ⌈log₂  suc  suc (suc m) /2⌉ /2⌉  ]
      ≡˘⟨ pred[2^suc[n]] ⌈log₂  suc  suc (suc m) /2⌉ /2⌉  
        suc (pred[2^ ⌈log₂  suc  suc (suc m) /2⌉ /2⌉  ] + pred[2^ ⌈log₂  suc  suc (suc m) /2⌉ /2⌉  ])
      ≡⟨⟩
        suc (pred[2^ ⌈log₂  suc (suc  m /2⌉) /2⌉  ] + pred[2^ ⌈log₂  suc (suc  m /2⌉) /2⌉  ])
      ≡⟨⟩
        suc (pred[2^ ⌈log₂ suc   m /2⌉ /2⌉  ] + pred[2^ ⌈log₂ suc   m /2⌉ /2⌉  ])
      ≤⟨
        s≤s (
          +-mono-≤
            (strong-induction (suc n)  m /2⌉ (≤-trans (⌊n/2⌋≤n (suc m)) (s≤s h)))
            (strong-induction (suc n)  m /2⌉ (≤-trans (⌊n/2⌋≤n (suc m)) (s≤s h)))
        )
      
        suc ( m /2⌉ +  m /2⌉)
      ≡⟨⟩
        suc ( suc m /2⌋ +  m /2⌉)
      ≤⟨ s≤s (+-monoʳ-≤  suc m /2⌋ (⌈n/2⌉-mono (n≤1+n m))) 
        suc ( suc m /2⌋ +  suc m /2⌉)
      ≡⟨ Eq.cong suc (⌊n/2⌋+⌈n/2⌉≡n (suc m)) 
        suc (suc m)
      
        where open ≤-Reasoning