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

open import Examples.Sorting.Sequential.Comparable

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

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

open import Calf costMonoid
open import Calf.Types.Bool
open import Calf.Types.Nat
open import Calf.Types.List
open import Calf.Types.Eq
open import Calf.Types.Bounded costMonoid

open import Relation.Nullary
open import Relation.Binary.PropositionalEquality as Eq using (_≡_; refl; module ≡-Reasoning)
open import Data.Product using (_×_; _,_; ; proj₁; proj₂)
open import Function
open import Data.Nat as Nat using (; zero; suc; z≤n; s≤s; _+_; _*_)
import Data.Nat.Properties as N


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

merge/clocked : cmp (Π nat λ _  Π pair λ _  F (list A))
merge/clocked zero    (l₁     , l₂    ) = ret (l₁ ++ l₂)
merge/clocked (suc k) ([]     , l₂    ) = ret l₂
merge/clocked (suc k) (x  xs , []    ) = ret (x  xs)
merge/clocked (suc k) (x  xs , y  ys) =
  bind (F (list A)) (x ≤ᵇ y) λ b 
    if b
      then (bind (F (list A)) (merge/clocked k (xs , y  ys)) (ret  (x ∷_)))
      else (bind (F (list A)) (merge/clocked k (x  xs , ys)) (ret  (y ∷_)))

merge/clocked/correct :  k l₁ l₂ 
   ( λ l  merge/clocked k (l₁ , l₂)  ret l × (length l₁ + length l₂ Nat.≤ k  Sorted l₁  Sorted l₂  SortedOf (l₁ ++ l₂) l))
merge/clocked/correct zero    l₁       l₂       u = l₁ ++ l₂ , refl , λ { h [] []  refl , [] }
merge/clocked/correct (suc k) []       l₂       u = l₂ , refl , λ { h [] sorted₂  refl , sorted₂ }
merge/clocked/correct (suc k) (x  xs) []       u = x  xs , refl , λ { h sorted₁ []  ++-identityʳ (x  xs) , sorted₁ }
merge/clocked/correct (suc k) (x  xs) (y  ys) u with h-cost x y
merge/clocked/correct (suc k) (x  xs) (y  ys) u |  b withCost q [ _ , h-eq ] rewrite eq/ref h-eq
  with ≤ᵇ-reflects-≤ u (Eq.trans (eq/ref h-eq) (step/ext (F bool) (ret b) q u))
merge/clocked/correct (suc k) (x  xs) (y  ys) u |  false withCost q [ _ , h-eq ] | ofⁿ ¬p =
  let (l ,  , h-sorted) = merge/clocked/correct k (x  xs) ys u in
  y  l , (
    let open ≡-Reasoning in
    begin
      step (F (list A)) q (bind (F (list A)) (merge/clocked k (x  xs , ys)) (ret  (y ∷_)))
    ≡⟨ step/ext (F (list A)) (bind (F (list A)) (merge/clocked k _) _) q u 
      bind (F (list A)) (merge/clocked k (x  xs , ys)) (ret  (y ∷_))
    ≡⟨ Eq.cong  e  bind (F (list A)) e _)  
      ret (y  l)
    
  ) , (
    λ { (s≤s h) (h₁  sorted₁) (h₂  sorted₂) 
      let h = Eq.subst (Nat._≤ k) (N.+-suc (length xs) (length ys)) h in
      let ( , sorted) = h-sorted h (h₁  sorted₁) sorted₂ in
      (
        let open PermutationReasoning in
        begin
          (x  xs ++ y  ys)
        ↭⟨ ++-comm-↭ (x  xs) (y  ys) 
          (y  ys ++ x  xs)
        ≡⟨⟩
          y  (ys ++ x  xs)
        <⟨ ++-comm-↭ ys (x  xs) 
          y  (x  xs ++ ys)
        <⟨  
          y  l
        
      ) , (
        let p = ≰⇒≥ ¬p in
        All-resp-↭ () (++⁺-All (p  ≤-≤* p h₁) h₂)  sorted
      )
    }
  )
merge/clocked/correct (suc k) (x  xs) (y  ys) u |  true withCost q [ _ , h-eq ] | ofʸ p =
  let (l ,  , h-sorted) = merge/clocked/correct k xs (y  ys) u in
  x  l , (
    let open ≡-Reasoning in
    begin
      step (F (list A)) q (bind (F (list A)) (merge/clocked k (xs , y  ys)) (ret  (x ∷_)))
    ≡⟨ step/ext (F (list A)) (bind (F (list A)) (merge/clocked k _) _) q u 
      bind (F (list A)) (merge/clocked k (xs , y  ys)) (ret  (x ∷_))
    ≡⟨ Eq.cong  e  bind (F (list A)) e _)  
      ret (x  l)
    
  ) , (
    λ { (s≤s h) (h₁  sorted₁) (h₂  sorted₂) 
      let ( , sorted) = h-sorted h sorted₁ (h₂  sorted₂)  in
      prep x  , All-resp-↭ () (++⁺-All h₁ (p  ≤-≤* p h₂))  sorted
    }
  )

merge/clocked/cost : cmp (Π nat λ _  Π pair λ _  cost)
merge/clocked/cost zero    (l₁     , l₂    ) = zero
merge/clocked/cost (suc k) ([]     , l₂    ) = zero
merge/clocked/cost (suc k) (x  xs , []    ) = zero
merge/clocked/cost (suc k) (x  xs , y  ys) =
  bind cost (x ≤ᵇ y) λ b 
    1 + (
      if b
        then (bind cost (merge/clocked k (xs , y  ys))  l  merge/clocked/cost k (xs , y  ys) + 0))
        else (bind cost (merge/clocked k (x  xs , ys))  l  merge/clocked/cost k (x  xs , ys) + 0))
    )

merge/clocked/cost/closed : cmp (Π nat λ _  Π pair λ _  cost)
merge/clocked/cost/closed k _ = k

merge/clocked/cost≤merge/clocked/cost/closed :  k p   (merge/clocked/cost k p Nat.≤ merge/clocked/cost/closed k p)
merge/clocked/cost≤merge/clocked/cost/closed zero    (l₁     , l₂    ) u = N.≤-refl
merge/clocked/cost≤merge/clocked/cost/closed (suc k) ([]     , l₂    ) u = z≤n
merge/clocked/cost≤merge/clocked/cost/closed (suc k) (x  xs , []    ) u = z≤n
merge/clocked/cost≤merge/clocked/cost/closed (suc k) (x  xs , y  ys) u with h-cost x y
... |  false withCost q [ _ , h-eq ] rewrite eq/ref h-eq =
  let (l ,  , _) = merge/clocked/correct k (x  xs) ys u in
  begin
    step cost q (1 + bind cost (merge/clocked k (x  xs , ys))  l  merge/clocked/cost k (x  xs , ys) + 0))
  ≡⟨ step/ext cost _ q u 
    1 + bind cost (merge/clocked k (x  xs , ys))  l  merge/clocked/cost k (x  xs , ys) + 0)
  ≡⟨⟩
    suc (bind cost (merge/clocked k (x  xs , ys))  l  merge/clocked/cost k (x  xs , ys) + 0))
  ≡⟨ Eq.cong  e  suc (bind cost e λ l  merge/clocked/cost k (x  xs , ys) + 0)) () 
    suc (merge/clocked/cost k (x  xs , ys) + 0)
  ≡⟨ Eq.cong suc (N.+-identityʳ _) 
    suc (merge/clocked/cost k (x  xs , ys))
  ≤⟨ s≤s (merge/clocked/cost≤merge/clocked/cost/closed k (x  xs , ys) u) 
    suc (merge/clocked/cost/closed k (x  xs , ys))
  ≡⟨⟩
    suc k
  
    where open ≤-Reasoning
... |  true withCost q [ _ , h-eq ] rewrite eq/ref h-eq =
  let (l ,  , _) = merge/clocked/correct k xs (y  ys) u in
  begin
    step cost q (1 + bind cost (merge/clocked k (xs , y  ys))  l  merge/clocked/cost k (xs , y  ys) + 0))
  ≡⟨ step/ext cost _ q u 
    1 + bind cost (merge/clocked k (xs , y  ys))  l  merge/clocked/cost k (xs , y  ys) + 0)
  ≡⟨⟩
    suc (bind cost (merge/clocked k (xs , y  ys))  l  merge/clocked/cost k (xs , y  ys) + 0))
  ≡⟨ Eq.cong  e  suc (bind cost e λ l  merge/clocked/cost k (xs , y  ys) + 0)) () 
    suc (merge/clocked/cost k (xs , y  ys) + 0)
  ≡⟨ Eq.cong suc (N.+-identityʳ _) 
    suc (merge/clocked/cost k (xs , y  ys))
  ≤⟨ s≤s (merge/clocked/cost≤merge/clocked/cost/closed k (xs , y  ys) u) 
    suc (merge/clocked/cost/closed k (xs , y  ys))
  ≡⟨⟩
    suc k
  
    where open ≤-Reasoning

merge/clocked≤merge/clocked/cost :  k p  IsBounded (list A) (merge/clocked k p) (merge/clocked/cost k p)
merge/clocked≤merge/clocked/cost zero    (l₁     , l₂    ) = bound/ret
merge/clocked≤merge/clocked/cost (suc k) ([]     , l₂    ) = bound/ret
merge/clocked≤merge/clocked/cost (suc k) (x  xs , []    ) = bound/ret
merge/clocked≤merge/clocked/cost (suc k) (x  xs , y  ys) =
  bound/bind 1 _ (h-cost x y) λ b 
    bound/bool {p = λ b  if_then_else_ b _ _} b
      (bound/bind (merge/clocked/cost k (x  xs , ys)) _ (merge/clocked≤merge/clocked/cost k (x  xs , ys)) λ l  bound/ret)
      (bound/bind (merge/clocked/cost k (xs , y  ys)) _ (merge/clocked≤merge/clocked/cost k (xs , y  ys)) λ l  bound/ret)

merge/clocked≤merge/clocked/cost/closed :  k p  IsBounded (list A) (merge/clocked k p) (merge/clocked/cost/closed k p)
merge/clocked≤merge/clocked/cost/closed k p = bound/relax (merge/clocked/cost≤merge/clocked/cost/closed k p) (merge/clocked≤merge/clocked/cost k p)

merge : cmp (Π pair λ _  F (list A))
merge (l₁ , l₂) = merge/clocked (length l₁ + length l₂) (l₁ , l₂)

merge/correct :  l₁ l₂ 
   ( λ l  merge (l₁ , l₂)  ret l × (Sorted l₁  Sorted l₂  SortedOf (l₁ ++ l₂) l))
merge/correct l₁ l₂ u =
  let (l ,  , h-sorted) = merge/clocked/correct (length l₁ + length l₂) l₁ l₂ u in
  l ,  , h-sorted N.≤-refl

merge/cost : cmp (Π pair λ _  cost)
merge/cost (l₁ , l₂) = merge/clocked/cost (length l₁ + length l₂) (l₁ , l₂)

merge/cost/closed : cmp (Π pair λ _  cost)
merge/cost/closed (l₁ , l₂) = merge/clocked/cost/closed (length l₁ + length l₂) (l₁ , l₂)

merge≤merge/cost :  p  IsBounded (list A) (merge p) (merge/cost p)
merge≤merge/cost (l₁ , l₂) = merge/clocked≤merge/clocked/cost (length l₁ + length l₂) (l₁ , l₂)

merge≤merge/cost/closed :  p  IsBounded (list A) (merge p) (merge/cost/closed p)
merge≤merge/cost/closed (l₁ , l₂) = merge/clocked≤merge/clocked/cost/closed (length l₁ + length l₂) (l₁ , l₂)