{-# OPTIONS --rewriting #-}

module Examples.Exp2 where

open import Algebra.Cost

parCostMonoid = ℕ²-ParCostMonoid
open ParCostMonoid parCostMonoid

open import Calf costMonoid
open import Calf.Parallel parCostMonoid
open import Calf.Data.Bool
open import Calf.Data.Nat
open import Calf.Data.IsBounded costMonoid
open import Calf.Data.BigO costMonoid

open import Relation.Binary.PropositionalEquality as Eq using (_≡_; refl; _≢_; module ≡-Reasoning)
open import Data.Nat as Nat using (_+_; pred; _*_; _^_; _⊔_)
import Data.Nat.Properties as N
open import Data.Nat.PredExp2
open import Data.Empty

open import Function using (_∘_)


Correct : cmp (Π nat λ _  F nat)  Set
Correct exp₂ = (n : )   (exp₂ n  ret (2 ^ n))

module Slow where
  exp₂ : cmp (Π nat λ _  F nat)
  exp₂ zero = ret (suc zero)
  exp₂ (suc n) =
    bind (F nat) (exp₂ n  exp₂ n) λ (r₁ , r₂) 
      step (F nat) (1 , 1) (ret (r₁ + r₂))

  exp₂/bound : cmp (Π nat λ _  F nat)
  exp₂/bound n = step (F nat) (pred[2^ n ] , n) (ret (2 ^ n))

  exp₂/is-bounded :  n  exp₂ n ≤⁻[ F nat ] exp₂/bound n
  exp₂/is-bounded zero    = ≤⁻-refl
  exp₂/is-bounded (suc n) =
    let open ≤⁻-Reasoning (F nat) in
    begin
      (bind (F nat) (exp₂ n  exp₂ n) λ (r₁ , r₂) 
        step (F nat) (1 , 1) (ret (r₁ + r₂)))
    ≲⟨
      ≤⁻-mono₂  e₁ e₂  bind (F nat) (e₁  e₂) λ (r₁ , r₂)  step (F nat) (1 , 1) (ret (r₁ + r₂)))
        (exp₂/is-bounded n)
        (exp₂/is-bounded n)
    
      (bind (F nat) ((step (F nat) (pred[2^ n ] , n) (ret (2 ^ n)))  (step (F nat) (pred[2^ n ] , n) (ret (2 ^ n)))) λ (r₁ , r₂) 
        step (F nat) (1 , 1) (ret (r₁ + r₂)))
    ≡⟨⟩
      step (F nat) (pred[2^ n ] + pred[2^ n ] + 1 , n  n + 1) (ret (2 ^ n + 2 ^ n))
    ≡⟨
      Eq.cong₂ (step (F nat))
        (Eq.cong₂ _,_
          (Eq.trans (N.+-comm _ 1) (pred[2^suc[n]] n))
          (Eq.trans (N.+-comm _ 1) (Eq.cong (1 +_) (N.⊔-idem n))))
        (Eq.cong ret (lemma/2^suc n))
    
      step (F nat) (pred[2^ suc n ] , suc n) (ret (2 ^ suc n))
    

  exp₂/correct : Correct exp₂
  exp₂/correct n u = Eq.trans (≤⁻-ext-≡ u (exp₂/is-bounded n)) (step/ext (F nat) (ret (2 ^ n)) (pred[2^ n ] , n) u)

  exp₂/asymptotic : given nat measured-via  n  n) , exp₂ ∈𝓞 n  2 ^ n , n)
  exp₂/asymptotic =
    f[n]≤g[n]via λ n 
      ≤⁻-mono  e  bind (F _) e  _  ret triv))
        (≤⁻-trans (exp₂/is-bounded n) (step-monoˡ-≤⁻ (ret (2 ^ n)) (N.pred[n]≤n {2 ^ n} , N.≤-refl {n})))


module Fast where

  exp₂ : cmp (Π nat λ _  F nat)
  exp₂ zero = ret (suc zero)
  exp₂ (suc n) =
    bind (F nat) (exp₂ n) λ r 
      step (F nat) (1 , 1) (ret (r + r))

  exp₂/bound : cmp (Π nat λ _  F nat)
  exp₂/bound n = step (F nat) (n , n) (ret (2 ^ n))

  exp₂/is-bounded :  n  exp₂ n ≤⁻[ F nat ] exp₂/bound n
  exp₂/is-bounded zero    = ≤⁻-refl
  exp₂/is-bounded (suc n) =
    let open ≤⁻-Reasoning (F nat) in
    begin
      (bind (F nat) (exp₂ n) λ r 
        step (F nat) (1 , 1) (ret (r + r)))
    ≲⟨ ≤⁻-mono  e  bind (F nat) e λ r  step (F nat) (1 , 1) (ret (r + r))) (exp₂/is-bounded n) 
      (bind (F nat) (step (F nat) (n , n) (ret (2 ^ n))) λ r 
        step (F nat) (1 , 1) (ret (r + r)))
    ≡⟨⟩
      step (F nat) (n + 1 , n + 1) (ret (2 ^ n + 2 ^ n))
    ≡⟨
      Eq.cong₂ (step (F nat))
        (Eq.cong₂ _,_ (N.+-comm _ 1) (N.+-comm _ 1))
        (Eq.cong ret (lemma/2^suc n))
    
      step (F nat) (suc n , suc n) (ret (2 ^ suc n))
    

  exp₂/correct : Correct exp₂
  exp₂/correct n u = Eq.trans (≤⁻-ext-≡ u (exp₂/is-bounded n)) (step/ext (F nat) (ret (2 ^ n)) (n , n) u)

  exp₂/asymptotic : given nat measured-via  n  n) , exp₂ ∈𝓞 n  n , n)
  exp₂/asymptotic = f[n]≤g[n]via (≤⁻-mono  e  bind (F _) e _)  exp₂/is-bounded)


slow≡fast :  (Slow.exp₂  Fast.exp₂)
slow≡fast u = funext λ n 
  begin
    Slow.exp₂ n
  ≡⟨ Slow.exp₂/correct n u 
    ret (2 ^ n)
  ≡˘⟨ Fast.exp₂/correct n u 
    Fast.exp₂ n
  
    where open ≡-Reasoning