{-# OPTIONS --rewriting #-}

open import Examples.Sorting.Sequential.Comparable

module Examples.Sorting.Sequential.MergeSort (M : Comparable) where

open Comparable M
open import Examples.Sorting.Sequential.Core M

open import Calf costMonoid hiding (A)
open import Calf.Data.Product
open import Calf.Data.Bool
open import Calf.Data.Nat
open import Calf.Data.List using (list; []; _∷_; _∷ʳ_; [_]; length; _++_; reverse)
open import Calf.Data.Equality using (_≡_; refl)
open import Calf.Data.IsBoundedG costMonoid
open import Calf.Data.IsBounded costMonoid
open import Calf.Data.BigO costMonoid

open import Relation.Nullary
open import Relation.Nullary.Negation
open import Relation.Binary.PropositionalEquality as Eq using (_≡_; refl; module ≡-Reasoning)
open import Data.Sum using (inj₁; inj₂)
open import Function
open import Data.Nat as Nat using (; zero; suc; z≤n; s≤s; _+_; _*_; ⌊_/2⌋; ⌈_/2⌉)
import Data.Nat.Properties as N
open import Data.Nat.Square
open import Data.Nat.Log2


open import Examples.Sorting.Sequential.MergeSort.Split M public
open import Examples.Sorting.Sequential.MergeSort.Merge M public

sort/clocked : cmp $ Π nat λ k  Π (list A) λ l  Π (meta⁺ (⌈log₂ length l  Nat.≤ k)) λ _  F (sort-result l)
sort/clocked zero    l h = ret (l , refl , short-sorted (⌈log₂n⌉≡0⇒n≤1 (N.n≤0⇒n≡0 h)))
sort/clocked (suc k) l h =
  bind (F (sort-result l)) (split l) λ ((l₁ , l₂) , length₁ , length₂ , l↭l₁++l₂) 
  let
    h₁ , h₂ =
      let open N.≤-Reasoning in
      (begin
        ⌈log₂ length l₁ 
      ≡⟨ Eq.cong ⌈log₂_⌉ length₁ 
        ⌈log₂  length l /2⌋ 
      ≤⟨ log₂-mono (N.⌊n/2⌋≤⌈n/2⌉ (length l)) 
        ⌈log₂  length l /2⌉ 
      ≤⟨ log₂-suc (length l) h 
        k
      ) ,
      (begin
        ⌈log₂ length l₂ 
      ≡⟨ Eq.cong ⌈log₂_⌉ length₂ 
        ⌈log₂  length l /2⌉ 
      ≤⟨ log₂-suc (length l) h 
        k
      )
  in
  bind (F (sort-result l)) (sort/clocked k l₁ h₁) λ (l₁' , l₁↭l₁' , sorted-l₁') 
  bind (F (sort-result l)) (sort/clocked k l₂ h₂) λ (l₂' , l₂↭l₂' , sorted-l₂') 
  bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
  ret (l' , trans l↭l₁++l₂ (trans (++⁺-↭ l₁↭l₁' l₂↭l₂') l₁'++l₂'↭l) , l'-sorted)

sort/clocked/total :  k l h  IsValuable (sort/clocked k l h)
sort/clocked/total zero    l h u =  refl
sort/clocked/total (suc k) l h u rewrite Valuable.proof (split/total l u) = 
  let
    ((l₁ , l₂) , length₁ , length₂ , l↭l₁++l₂) = Valuable.value (split/total l u)
    h₁ , h₂ =
      let open N.≤-Reasoning in
      (begin
        ⌈log₂ length l₁ 
      ≡⟨ Eq.cong ⌈log₂_⌉ length₁ 
        ⌈log₂  length l /2⌋ 
      ≤⟨ log₂-mono (N.⌊n/2⌋≤⌈n/2⌉ (length l)) 
        ⌈log₂  length l /2⌉ 
      ≤⟨ log₂-suc (length l) h 
        k
      ) ,
      (begin
        ⌈log₂ length l₂ 
      ≡⟨ Eq.cong ⌈log₂_⌉ length₂ 
        ⌈log₂  length l /2⌉ 
      ≤⟨ log₂-suc (length l) h 
        k
      )
    (l₁' , l₁↭l₁' , sorted-l₁') = Valuable.value (sort/clocked/total k l₁ h₁ u)
    (l₂' , l₂↭l₂' , sorted-l₂') = Valuable.value (sort/clocked/total k l₂ h₂ u)
  in
  let open ≡-Reasoning in
  begin
    ( bind (F (sort-result l)) (sort/clocked k l₁ h₁) λ (l₁' , l₁↭l₁' , sorted-l₁') 
      bind (F (sort-result l)) (sort/clocked k l₂ h₂) λ (l₂' , l₂↭l₂' , sorted-l₂') 
      bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret (l' , trans l↭l₁++l₂ (trans (++⁺-↭ l₁↭l₁' l₂↭l₂') l₁'++l₂'↭l) , l'-sorted)
    )
  ≡⟨
    Eq.cong
       e 
        bind (F (sort-result l)) e λ (l₁' , l₁↭l₁' , sorted-l₁') 
        bind (F (sort-result l)) (sort/clocked k l₂ h₂) λ (l₂' , l₂↭l₂' , sorted-l₂') 
        bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
        ret (l' , trans l↭l₁++l₂ (trans (++⁺-↭ l₁↭l₁' l₂↭l₂') l₁'++l₂'↭l) , l'-sorted)
      )
      (Valuable.proof (sort/clocked/total k l₁ h₁ u))
  
    ( bind (F (sort-result l)) (sort/clocked k l₂ h₂) λ (l₂' , l₂↭l₂' , sorted-l₂') 
      bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret (l' , trans l↭l₁++l₂ (trans (++⁺-↭ l₁↭l₁' l₂↭l₂') l₁'++l₂'↭l) , l'-sorted)
    )
  ≡⟨
    Eq.cong
       e 
        bind (F (sort-result l)) e λ (l₂' , l₂↭l₂' , sorted-l₂') 
        bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
        ret (l' , trans l↭l₁++l₂ (trans (++⁺-↭ l₁↭l₁' l₂↭l₂') l₁'++l₂'↭l) , l'-sorted)
      )
      (Valuable.proof (sort/clocked/total k l₂ h₂ u))
  
    ( bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret (l' , trans l↭l₁++l₂ (trans (++⁺-↭ l₁↭l₁' l₂↭l₂') l₁'++l₂'↭l) , l'-sorted)
    )
  ≡⟨
    Eq.cong
       e 
        bind (F (sort-result l)) e λ (l' , l₁'++l₂'↭l , l'-sorted) 
        ret (l' , trans l↭l₁++l₂ (trans (++⁺-↭ l₁↭l₁' l₂↭l₂') l₁'++l₂'↭l) , l'-sorted)
      )
      (Valuable.proof (merge/total (l₁' , l₂') (sorted-l₁' , sorted-l₂') u))
  
    ret _
  

sort/clocked/cost : cmp $ Π nat λ k  Π (list A) λ l  Π (meta⁺ (⌈log₂ length l  Nat.≤ k)) λ _  F unit
sort/clocked/cost k l _ = step⋆ (k * length l)

sort/clocked/is-bounded :  k l h  IsBoundedG (sort-result l) (sort/clocked k l h) (sort/clocked/cost k l h)
sort/clocked/is-bounded zero    l h = ≤⁻-refl
sort/clocked/is-bounded (suc k) l h =
  bound/bind/const
    {e = split l}
    {f = λ ((l₁ , l₂) , length₁ , length₂ , l↭l₁++l₂) 
      bind (F (sort-result l)) (sort/clocked k l₁ _) λ (l₁' , l₁↭l₁' , sorted-l₁') 
      bind (F (sort-result l)) (sort/clocked k l₂ _) λ (l₂' , l₂↭l₂' , sorted-l₂') 
      bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret {sort-result l} (l' , ↭-trans l↭l₁++l₂ (↭-trans (_↭_.trans (++⁺ʳ-↭ l₂ l₁↭l₁') (++⁺ˡ-↭ l₁' l₂↭l₂')) (↭-trans l₁'++l₂'↭l _↭_.refl)) , l'-sorted)
    }
    0
    (suc k * length l)
    (split/is-bounded l)
    λ ((l₁ , l₂) , length₁ , length₂ , l↭l₁++l₂) 
  Eq.subst
    (IsBounded (sort-result l) $
      bind (F (sort-result l)) (sort/clocked k l₁ _) λ (l₁' , l₁↭l₁' , sorted-l₁') 
      bind (F (sort-result l)) (sort/clocked k l₂ _) λ (l₂' , l₂↭l₂' , sorted-l₂') 
      bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret {sort-result l} (l' , ↭-trans l↭l₁++l₂ (↭-trans (_↭_.trans (++⁺ʳ-↭ l₂ l₁↭l₁') (++⁺ˡ-↭ l₁' l₂↭l₂')) (↭-trans l₁'++l₂'↭l _↭_.refl)) , l'-sorted)
    )
    (let open ≡-Reasoning in
    begin
      k * length l₁ + (k * length l₂ + (length l₁ + length l₂))
    ≡˘⟨ N.+-assoc (k * length l₁) (k * length l₂) (length l₁ + length l₂) 
      (k * length l₁ + k * length l₂) + (length l₁ + length l₂)
    ≡˘⟨ Eq.cong (_+ (length l₁ + length l₂)) (N.*-distribˡ-+ k (length l₁) (length l₂)) 
      k * (length l₁ + length l₂) + (length l₁ + length l₂)
    ≡˘⟨ N.+-comm (length l₁ + length l₂) (k * (length l₁ + length l₂)) 
      suc k * (length l₁ + length l₂)
    ≡˘⟨ Eq.cong (suc k *_) (length-++ l₁) 
      suc k * (length (l₁ ++ l₂))
    ≡˘⟨ Eq.cong (suc k *_) (↭-length l↭l₁++l₂) 
      suc k * length l
    ) $
  bound/bind/const
    {e = sort/clocked k l₁ _}
    {f = λ (l₁' , l₁↭l₁' , sorted-l₁') 
      bind (F (sort-result l)) (sort/clocked k l₂ _) λ (l₂' , l₂↭l₂' , sorted-l₂') 
      bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret {sort-result l} (l' , ↭-trans l↭l₁++l₂ (↭-trans (_↭_.trans (++⁺ʳ-↭ l₂ l₁↭l₁') (++⁺ˡ-↭ l₁' l₂↭l₂')) (↭-trans l₁'++l₂'↭l _↭_.refl)) , l'-sorted)
    }
    (k * length l₁)
    (k * length l₂ + (length l₁ + length l₂))
    (sort/clocked/is-bounded k l₁ _)
    λ (l₁' , l₁↭l₁' , sorted-l₁') 
  bound/bind/const
    {e = sort/clocked k l₂ _}
    {f = λ (l₂' , l₂↭l₂' , sorted-l₂') 
      bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret {sort-result l} (l' , ↭-trans l↭l₁++l₂ (↭-trans (_↭_.trans (++⁺ʳ-↭ l₂ l₁↭l₁') (++⁺ˡ-↭ l₁' l₂↭l₂')) (↭-trans l₁'++l₂'↭l _↭_.refl)) , l'-sorted)
    }
    (k * length l₂)
    (length l₁ + length l₂)
    (sort/clocked/is-bounded k l₂ _)
    λ (l₂' , l₂↭l₂' , sorted-l₂') 
  Eq.subst
    (IsBounded (sort-result l) $
      bind (F (sort-result l)) (merge (l₁' , l₂') (sorted-l₁' , sorted-l₂')) λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret {sort-result l} (l' , ↭-trans l↭l₁++l₂ (↭-trans (_↭_.trans (++⁺ʳ-↭ l₂ l₁↭l₁') (++⁺ˡ-↭ l₁' l₂↭l₂')) (↭-trans l₁'++l₂'↭l _↭_.refl)) , l'-sorted)
    )
    (let open ≡-Reasoning in
    begin
      length l₁' + length l₂' + 0
    ≡⟨ N.+-identityʳ (length l₁' + length l₂') 
      length l₁' + length l₂'
    ≡˘⟨ Eq.cong₂ _+_ (↭-length l₁↭l₁') (↭-length l₂↭l₂') 
      length l₁ + length l₂
    ) $
  bound/bind/const {B = sort-result l}
    {e = merge (l₁' , l₂') _}
    {f = λ (l' , l₁'++l₂'↭l , l'-sorted) 
      ret {sort-result l} (l' , ↭-trans l↭l₁++l₂ (↭-trans (_↭_.trans (++⁺ʳ-↭ l₂ l₁↭l₁') (++⁺ˡ-↭ l₁' l₂↭l₂')) (↭-trans l₁'++l₂'↭l _↭_.refl)) , l'-sorted)}
    (length l₁' + length l₂')
    0
    (merge/is-bounded (l₁' , l₂') _)
    λ _ 
  ≤⁻-refl


sort : cmp (Π (list A) λ l  F (sort-result l))
sort l = sort/clocked ⌈log₂ length l  l N.≤-refl

sort/total : IsTotal sort
sort/total l = sort/clocked/total ⌈log₂ length l  l N.≤-refl

sort/cost : cmp (Π (list A) λ _  cost)
sort/cost l = sort/clocked/cost ⌈log₂ length l  l N.≤-refl

sort/is-bounded :  l  IsBoundedG (sort-result l) (sort l) (sort/cost l)
sort/is-bounded l = sort/clocked/is-bounded ⌈log₂ length l  l N.≤-refl

sort/asymptotic : given (list A) measured-via length , sort ∈𝓞 n  n * ⌈log₂ n )
sort/asymptotic = f[n]≤g[n]via λ l 
  Eq.subst
    (IsBounded (sort-result l) (sort l))
    (N.*-comm ⌈log₂ length l  (length l))
    (sort/is-bounded l)