{-# OPTIONS --prop --rewriting #-}

open import Examples.Sorting.Parallel.Comparable

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

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

open import Calf costMonoid
open import Calf.ParMetalanguage parCostMonoid
open import Calf.Types.Nat
open import Calf.Types.List
open import Calf.Types.Bounded costMonoid
open import Calf.Types.BigO costMonoid

open import Relation.Binary.PropositionalEquality as Eq using (_≡_; refl; module ≡-Reasoning)
open import Data.Product using (_×_; _,_; ; proj₁; proj₂)
open import Data.Nat as Nat using (; zero; suc; z≤n; _+_; _*_; ⌊_/2⌋; ⌈_/2⌉; _⊔_)
open import Data.Nat.Properties as N using (module ≤-Reasoning)
open import Data.Nat.Log2


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

sort/clocked : cmp (Π nat λ _  Π (list A) λ _  F (list A))
sort/clocked zero    l = ret l
sort/clocked (suc k) l =
  bind (F (list A)) (split l) λ (l₁ , l₂) 
    bind (F (list A)) (sort/clocked k l₁ & sort/clocked k l₂) merge

sort/clocked/correct :  k l  ⌈log₂ length l  Nat.≤ k  SortResult (sort/clocked k) l
sort/clocked/correct zero    l h u = l , refl , refl , short-sorted (⌈log₂n⌉≡0⇒n≤1 (N.n≤0⇒n≡0 h))
sort/clocked/correct (suc k) l h u =
  let (l₁ , l₂ ,  , length₁ , length₂ , ) = split/correct l u in
  let (l₁' , ≡₁ , ↭₁ , sorted₁) = sort/clocked/correct k l₁ (
                                    let open ≤-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
                                    
                                  ) u in
  let (l₂' , ≡₂ , ↭₂ , sorted₂) = sort/clocked/correct k l₂ (
                                    let open ≤-Reasoning in
                                    begin
                                      ⌈log₂ length l₂ 
                                    ≡⟨ Eq.cong ⌈log₂_⌉ length₂ 
                                      ⌈log₂  length l /2⌉ 
                                    ≤⟨ log₂-suc (length l) h 
                                      k
                                    
                                  ) u in
  let (l' , ≡' , h-sorted) = merge/correct l₁' l₂' u
      (↭' , sorted) = h-sorted sorted₁ sorted₂
  in
  l' , (
    let open ≡-Reasoning in
    begin
      sort/clocked (suc k) l
    ≡⟨⟩
      (bind (F (list A)) (split l) λ (l₁ , l₂) 
        bind (F (list A)) (sort/clocked k l₁ & sort/clocked k l₂) merge)
    ≡⟨ Eq.cong  e  bind (F (list A)) e _)  
      bind (F (list A)) (sort/clocked k l₁ & sort/clocked k l₂) merge
    ≡⟨ Eq.cong  e  bind (F (list A)) e merge) (Eq.cong₂ _&_ ≡₁ ≡₂) 
      merge (l₁' , l₂')
    ≡⟨ ≡' 
      ret l'
    
  ) , (
    let open PermutationReasoning in
    begin
      l
    ↭⟨  
      l₁ ++ l₂
    ↭⟨ ++⁺-↭ ↭₁ ↭₂ 
      l₁' ++ l₂'
    ↭⟨ ↭' 
      l'
    
  ) , sorted

sort/clocked/cost : cmp (Π nat λ _  Π (list A) λ _  cost)
sort/clocked/cost zero    l = 𝟘
sort/clocked/cost (suc k) l =
  bind cost (split l) λ (l₁ , l₂)  split/cost l 
    bind cost (sort/clocked k l₁ & sort/clocked k l₂) λ (l₁' , l₂')  (sort/clocked/cost k l₁  sort/clocked/cost k l₂) 
      merge/cost/closed (l₁' , l₂')

sort/clocked/cost/closed : cmp (Π nat λ _  Π (list A) λ _  cost)
sort/clocked/cost/closed k l = k * length l , 2 * length l + k

sort/clocked/cost≤sort/clocked/cost/closed :  k l  ⌈log₂ length l  Nat.≤ k   (sort/clocked/cost k l ≤ₚ sort/clocked/cost/closed k l)
sort/clocked/cost≤sort/clocked/cost/closed zero    l h u = z≤n , z≤n
sort/clocked/cost≤sort/clocked/cost/closed (suc k) l h u =
  let (l₁ , l₂ ,  , length₁ , length₂ , ) = split/correct l u in
  let h₁ : ⌈log₂ length l₁  Nat.≤ k
      h₁ =
        let open ≤-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
        

      h₂ : ⌈log₂ length l₂  Nat.≤ k
      h₂ =
        let open ≤-Reasoning in
        begin
          ⌈log₂ length l₂ 
        ≡⟨ Eq.cong ⌈log₂_⌉ length₂ 
          ⌈log₂  length l /2⌉ 
        ≤⟨ log₂-suc (length l) h 
          k
        
  in
  let (l₁' , ≡₁ , ↭₁ , sorted₁) = sort/clocked/correct k l₁ h₁ u in
  let (l₂' , ≡₂ , ↭₂ , sorted₂) = sort/clocked/correct k l₂ h₂ u in
  let open ≤ₚ-Reasoning in
  begin
    sort/clocked/cost (suc k) l
  ≡⟨⟩
    (bind cost (split l) λ (l₁ , l₂)  split/cost l 
      bind cost (sort/clocked k l₁ & sort/clocked k l₂) λ (l₁' , l₂')  (sort/clocked/cost k l₁  sort/clocked/cost k l₂) 
        merge/cost/closed (l₁' , l₂'))
  ≡⟨ Eq.cong  e  bind cost e _) () 
    (split/cost l 
      bind cost (sort/clocked k l₁ & sort/clocked k l₂) λ (l₁' , l₂')  (sort/clocked/cost k l₁  sort/clocked/cost k l₂) 
        merge/cost/closed (l₁' , l₂'))
  ≡⟨⟩
    (𝟘 
      bind cost (sort/clocked k l₁ & sort/clocked k l₂) λ (l₁' , l₂')  (sort/clocked/cost k l₁  sort/clocked/cost k l₂) 
        merge/cost/closed (l₁' , l₂'))
  ≡⟨ ⊕-identityˡ _ 
    (bind cost (sort/clocked k l₁ & sort/clocked k l₂) λ (l₁' , l₂')  (sort/clocked/cost k l₁  sort/clocked/cost k l₂) 
      merge/cost/closed (l₁' , l₂'))
  ≡⟨ Eq.cong  e  bind cost e λ (l₁' , l₂')  (sort/clocked/cost k l₁  sort/clocked/cost k l₂)  merge/cost/closed (l₁' , l₂')) (Eq.cong₂ _&_ ≡₁ ≡₂) 
    (sort/clocked/cost k l₁  sort/clocked/cost k l₂)  merge/cost/closed (l₁' , l₂')
  ≤⟨
    ⊕-monoˡ-≤ (merge/cost/closed (l₁' , l₂')) (
      ⊗-mono-≤
        (sort/clocked/cost≤sort/clocked/cost/closed k l₁ h₁ u)
        (sort/clocked/cost≤sort/clocked/cost/closed k l₂ h₂ u)
    )
  
    (sort/clocked/cost/closed k l₁  sort/clocked/cost/closed k l₂)  merge/cost/closed (l₁' , l₂')
  ≡⟨⟩
    (sort/clocked/cost/closed k l₁  sort/clocked/cost/closed k l₂) 
      (length l₁' + length l₂' , length l₁' + length l₂')
  ≡˘⟨
    Eq.cong ((sort/clocked/cost/closed k l₁  sort/clocked/cost/closed k l₂) ⊕_) (
      Eq.cong₂  n₁ n₂  (n₁ + n₂ , n₁ + n₂))
        (↭-length ↭₁)
        (↭-length ↭₂)
    )
  
    (sort/clocked/cost/closed k l₁  sort/clocked/cost/closed k l₂) 
      (length l₁ + length l₂ , length l₁ + length l₂)
  ≡⟨⟩
    ((k * length l₁ , 2 * length l₁ + k)  (k * length l₂ , 2 * length l₂ + k)) 
      (length l₁ + length l₂ , length l₁ + length l₂)
  ≡⟨
    Eq.cong₂
       n₁ n₂  ((k * n₁ , 2 * n₁ + k)  (k * n₂ , 2 * n₂ + k))  (n₁ + n₂ , n₁ + n₂))
      length₁
      length₂
  
    ((k *  length l /2⌋ , 2 *  length l /2⌋ + k)  (k *  length l /2⌉ , 2 *  length l /2⌉ + k)) 
      ( length l /2⌋ +  length l /2⌉ ,  length l /2⌋ +  length l /2⌉)
  ≡⟨
    Eq.cong (((k *  length l /2⌋ , 2 *  length l /2⌋ + k)  (k *  length l /2⌉ , 2 *  length l /2⌉ + k)) ⊕_) (
      Eq.cong₂ _,_ (N.⌊n/2⌋+⌈n/2⌉≡n (length l)) (N.⌊n/2⌋+⌈n/2⌉≡n (length l))
    )
  
    ((k *  length l /2⌋ , 2 *  length l /2⌋ + k)  (k *  length l /2⌉ , 2 *  length l /2⌉ + k)) 
      (length l , length l)
  ≤⟨ arithmetic/work (length l) , arithmetic/span (length l) 
    suc k * length l , 2 * length l + suc k
  ≡⟨⟩
    sort/clocked/cost/closed (suc k) l
  
    where
      arithmetic/work :  n  k *  n /2⌋ + k *  n /2⌉ + n Nat.≤ suc k * n
      arithmetic/work n =
        begin
          k *  n /2⌋ + k *  n /2⌉ + n
        ≡⟨ N.+-comm _ n 
          n + (k *  n /2⌋ + k *  n /2⌉)
        ≡˘⟨ Eq.cong (n +_) (N.*-distribˡ-+ k _ _) 
          n + k * ( n /2⌋ +  n /2⌉)
        ≡⟨ Eq.cong  m  n + k * m) (N.⌊n/2⌋+⌈n/2⌉≡n n) 
          n + k * n
        ≡⟨⟩
          suc k * n
        
          where open ≤-Reasoning

      lemma/2n≡n+n :  n  2 * n  n + n
      lemma/2n≡n+n n = Eq.cong  m  n + m) (N.+-identityʳ n)

      arithmetic/span :  n  (2 *  n /2⌋ + k)  (2 *  n /2⌉ + k) + n Nat.≤ 2 * n + suc k
      arithmetic/span n =
        begin
          (2 *  n /2⌋ + k)  (2 *  n /2⌉ + k) + n
        ≤⟨ N.+-monoˡ-≤ n (N.⊔-monoˡ-≤ (2 *  n /2⌉ + k) (N.+-monoˡ-≤ k (N.*-monoʳ-≤ 2 (N.⌊n/2⌋≤⌈n/2⌉ n)))) 
          (2 *  n /2⌉ + k)  (2 *  n /2⌉ + k) + n
        ≡⟨ Eq.cong (_+ n) (N.⊔-idem _) 
          2 *  n /2⌉ + k + n
        ≡⟨ N.+-assoc (2 *  n /2⌉) k n 
          2 *  n /2⌉ + (k + n)
        ≡⟨ Eq.cong (_+ (k + n)) (lemma/2n≡n+n  n /2⌉) 
          ( n /2⌉ +  n /2⌉) + (k + n)
        ≡⟨⟩
          ( suc n /2⌋ +  n /2⌉) + (k + n)
        ≤⟨ N.+-monoˡ-≤ (k + n) (N.+-monoʳ-≤  suc n /2⌋ (N.⌈n/2⌉-mono (N.n≤1+n n))) 
          ( suc n /2⌋ +  suc n /2⌉) + (k + n)
        ≡⟨ Eq.cong (_+ (k + n)) (N.⌊n/2⌋+⌈n/2⌉≡n (suc n)) 
          suc n + (k + n)
        ≡⟨⟩
          suc (n + (k + n))
        ≡⟨ Eq.cong  m  suc (n + m)) (N.+-comm k n) 
          suc (n + (n + k))
        ≡˘⟨ Eq.cong suc (N.+-assoc n n k) 
          suc ((n + n) + k)
        ≡˘⟨ N.+-suc (n + n) k 
          (n + n) + suc k
        ≡˘⟨ Eq.cong (_+ suc k) (lemma/2n≡n+n n) 
          2 * n + suc k
        
          where open ≤-Reasoning

sort/clocked≤sort/clocked/cost :  k l  IsBounded (list A) (sort/clocked k l) (sort/clocked/cost k l)
sort/clocked≤sort/clocked/cost zero l = bound/ret
sort/clocked≤sort/clocked/cost (suc k) l =
  bound/bind _ _ (split≤split/cost l) λ (l₁ , l₂) 
    bound/bind _ _ (bound/par (sort/clocked≤sort/clocked/cost k l₁) (sort/clocked≤sort/clocked/cost k l₂)) λ (l₁' , l₂') 
      merge≤merge/cost/closed (l₁' , l₂')

sort/clocked≤sort/clocked/cost/closed :  k l  ⌈log₂ length l  Nat.≤ k  IsBounded (list A) (sort/clocked k l) (sort/clocked/cost/closed k l)
sort/clocked≤sort/clocked/cost/closed k l h = bound/relax (sort/clocked/cost≤sort/clocked/cost/closed k l h) (sort/clocked≤sort/clocked/cost k l)

sort/depth : cmp (Π (list A) λ _  meta )
sort/depth l = ⌈log₂ length l 

sort : cmp (Π (list A) λ _  F (list A))
sort l = sort/clocked (sort/depth l) l

sort/correct : IsSort sort
sort/correct l = sort/clocked/correct (sort/depth l) l N.≤-refl

sort/cost : cmp (Π (list A) λ _  cost)
sort/cost l = sort/clocked/cost (sort/depth l) l

sort/cost/closed : cmp (Π (list A) λ _  cost)
sort/cost/closed l = sort/clocked/cost/closed (sort/depth l) l

sort≤sort/cost :  l  IsBounded (list A) (sort l) (sort/cost l)
sort≤sort/cost l = sort/clocked≤sort/clocked/cost (sort/depth l) l

sort≤sort/cost/closed :  l  IsBounded (list A) (sort l) (sort/cost/closed l)
sort≤sort/cost/closed l = sort/clocked≤sort/clocked/cost/closed (sort/depth l) l N.≤-refl

sort/asymptotic : given (list A) measured-via length , sort ∈𝓞 n  n * ⌈log₂ n  , n)
sort/asymptotic = 0 ≤n⇒f[n]≤ 3 g[n]via λ l _ 
  bound/relax
     u  let open ≤-Reasoning in
      (
        begin
          ⌈log₂ length l  * length l
        ≡⟨ N.*-comm ⌈log₂ length l  (length l) 
          length l * ⌈log₂ length l 
        ≤⟨ N.m≤m+n (length l * ⌈log₂ length l ) _ 
          3 * (length l * ⌈log₂ length l )
        
      ) , (
        begin
          2 * length l + ⌈log₂ length l 
        ≤⟨ N.+-monoʳ-≤ (2 * length l) (⌈log₂n⌉≤n (length l)) 
          2 * length l + length l
        ≡⟨ N.+-comm (2 * length l) (length l) 
          3 * length l
        
      )
    )
    (sort≤sort/cost/closed l)