{-# OPTIONS --type-in-type #-}

open import NeilPrelude hiding (result)
open import Logic
open import Extensionality
open import CPO
open import EquationalEq
open import CPOLemmas
open import CPOFunctions
import Fold

module FoldTheorems

  (ext : Extensionality)

  (fix : {τ : Type} → CPO (τ ⇒ τ) → CPO τ)
  (Computation : {τ : Type} → (f : CPO (τ ⇒ τ)) → fix f ≡ f $ (fix f))
  (Induction : {τ : Type} → {x : CPO τ}  → (f : CPO (τ ⇒ τ)) → f $ x ⊑ x → fix f ⊑ x)

  (fix-⊗ : {σ τ : Type} → {f : CPO (σ ⇒ σ)} → {g : CPO (τ ⇒ τ)} → fix (f :×: g) ≡ (fix f , fix g))

  (FixpointInduction : {τ : Type} → (P : CPO τ → Set) → (f : CPO (τ ⇒ τ)) → ChainComplete P → P ⊥ → ((x : CPO τ) → P x → P (f $ x)) → P (fix f))

  (F : Functor)
  (fmap' : {σ τ : Type} → CPO ((σ ⇒ τ) ⇒ (F σ ⇒ F τ)))
  (fmap-id   : {τ : Type} → fmap' $ identity {τ} ≡ identity)
  (fmap-comp : {σ τ υ : Type} → (f : CPO (τ ⇒ υ)) → (g : CPO (σ ⇒ τ)) → fmap' $ (f ∙ g) ≡ (fmap' $ f) ∙ (fmap' $ g))

  (μ : Functor → Type)
  (inn    : CPO (F (μ F) ⇒ μ F))
  (out    : CPO (μ F ⇒ F (μ F)))
  (innSur : inn ∙ out ≡ identity)
  (outInj : out ∙ inn ≡ identity)
  (lemma-fold-inn : (Fold.fold ext fix F fmap' μ out) inn ≡ identity)

where

import FixTheorems
open FixTheorems fix Computation Induction fix-⊗ FixpointInduction public

import ContinuityProps
open ContinuityProps ext fix

open Fold ext fix F fmap' μ out public

------------------------------------------------------------------------

fmap : {σ τ : Type} → CPO (σ ⇒ τ) → CPO (F σ ⇒ F τ)
fmap = _$_ fmap'

------------------------------------------------------------------------

lemma-fold-in : fold inn ≡ identity
lemma-fold-in = lemma-fold-inn

------------------------------------------------------------------------

strict-in : Strict inn
strict-in = StrictCancellation out inn (cpo-func' innSur)

strict-out : Strict out
strict-out = StrictCancellation inn out (cpo-func' outInj)

------------------------------------------------------------------------

FoldDef : {τ : Type} → (f : CPO (F τ ⇒ τ)) → fold f ≡ f ∙ fmap (fold f) ∙ out
FoldDef f =

   fold f
                                               ⟨ refl ⟩
   fix (fold-fun f)
                                               ⟨ Computation (fold-fun f) ⟩
   (λ h → f ∙ fmap h ∙ out) (fix (fold-fun f))
                                               ⟨ refl ⟩
   (λ h → f ∙ fmap h ∙ out) (fold f)
                                               ⟨ refl ⟩
   f ∙ fmap (fold f) ∙ out
                                               QED

------------------------------------------------------------------------

UniPropFoldL : {τ : Type} → (f : CPO (F τ ⇒ τ)) → (h : CPO (μ F ⇒ τ)) → h ≡ fold f → h ∙ inn ≡ f ∙ fmap h
UniPropFoldL f ._ refl = ProveTruth (

   fold f ∙ inn ≡ f ∙ fmap (fold f)
                                               ←⟨ ↔-≡ refl (
                                                     (f ∙ fmap (fold f) ∙ out) ∙ inn
                                                                                      ⟨ ∙-assocLRE f (fmap (fold f)) out inn ⟩
                                                     (f ∙ fmap (fold f)) ∙ out ∙ inn
                                                                                      ⟨ ∙-congL (f ∙ fmap (fold f)) outInj ⟩
                                                     (f ∙ fmap (fold f)) ∙ identity
                                                                                      ⟨ ∙-idR (f ∙ fmap (fold f)) ⟩
                                                     f ∙ fmap (fold f)
                                                                                      QED
                                                     )
                                                ⟩↔
   fold f ∙ inn ≡ (f ∙ fmap (fold f) ∙ out) ∙ inn
                                               ←⟨ ∙-congR inn ⟩
   fold f ≡ f ∙ fmap (fold f) ∙ out
                                               ←⟨ FoldDef f ⟩TRUE
   )


UniPropFoldR : {τ : Type} → (f : CPO (F τ ⇒ τ)) → (h : CPO (μ F ⇒ τ)) → Strict h → h ∙ inn ≡ f ∙ fmap h → h ≡ fold f
UniPropFoldR {τ} f (fun h' ch) sh eq = ProveTruth (

   h ≡ fold f
                                            ←⟨ ↔-≡ (∙-idR h) refl ⟩↔
   h ∙ identity ≡ fold f
                                            ←⟨ ↔-≡ (∙-congL h lemma-fold-in) refl ⟩↔
   h ∙ fold inn ≡ fold f
                                            ←⟨ ↔-refl ⟩↔
   P (fold inn , fold f)
                                            ←⟨ ↔-refl ⟩↔
   P (fix (fold-fun inn) , fix (fold-fun f))
                                            ←⟨ FixpointInduction-⊗ P (fold-fun inn) (fold-fun f) ChainCompleteP base-case inductive-case ⟩TRUE )

   where
     h = fun h' ch

     P : CPO ((μ F ⇒ μ F) ⊗ (μ F ⇒ τ)) → Set
     P (i , j) = h ∙ i ≡ j

     base-case : P (⊥ , ⊥)
     base-case = ProveTruth (
                    P (⊥ , ⊥)
                                           ←⟨ ↔-refl ⟩↔
                    h ∙ ⊥ ≡ ⊥ { μ F ⇒ τ }
                                           ←⟨ PointFreeStrictness h ⟩↔
                    Strict h
                                           ←⟨ sh ⟩TRUE
                    )

     inductive-case : (i : CPO (μ F ⇒ μ F)) → (j : CPO (μ F ⇒ τ)) → h ∙ i ≡ j → h ∙ inn ∙ fmap i ∙ out ≡ f ∙ fmap j ∙ out
     inductive-case i ._ refl =

       h ∙ inn ∙ fmap i ∙ out
                                    ⟨ ∙-assocRL h inn (fmap i ∙ out) ⟩
       (h ∙ inn) ∙ fmap i ∙ out
                                    ⟨ ∙-congR (fmap i ∙ out) eq ⟩
       (f ∙ fmap h) ∙ fmap i ∙ out
                                    ⟨ ∙-assocEC f (fmap h) (fmap i) out ⟩
       f ∙ (fmap h ∙ fmap i) ∙ out
                                    ⟨ ∙-congL f (∙-congR out (sym (fmap-comp h i))) ⟩
       f ∙ fmap (h ∙ i) ∙ out
                                     QED

     ChainCompleteP : ChainComplete P
     ChainCompleteP (Q , ⨆X , ⨆Y) (p , ne) =

       P (⨆ XY)
                                                                                ←⟨ ↔-refl ⟩↔
       P (⨆ X , ⨆ Y)
                                                                                ←⟨ ↔-refl ⟩↔
       h ∙ ⨆ X ≡ ⨆ Y
                                                                                ←⟨ ↔-refl ⟩↔
       ⨆ (chainImage' (_∙_ h) X) ≡ ⨆ Y
                                                                                ←⟨ cong-≣-⨆ (chainImage' (_∙_ h) X) Y ⟩
       chainImage' (_∙_ h) X ≣ Y
                                                                                ←⟨ chain-ext (chainImage' (_∙_ h) X) Y imgXisChain YisChain ⟩↔
       ((y : CPO (μ F ⇒ τ)) → y ∈ chainImage' (_∙_ h) X ↔ y ∈ Y)
                                                                                ←⟨ lem-image-eq (_∙_ h) XY p ⟩
       ((x : CPO (μ F ⇒ μ F)) → (y : CPO (μ F ⇒ τ)) → (x , y) ∈ XY → h ∙ x ≡ y)
                                                                                ←⟨ ↔-refl ⟩↔
       ((x : CPO (μ F ⇒ μ F)) → (y : CPO (μ F ⇒ τ)) → (x , y) ∈ XY → P (x , y))
                                                                                ←⟨ ↔-sym lem-products ⟩↔
       ((xy : CPO ((μ F ⇒ μ F) ⊗ (μ F ⇒ τ))) → xy ∈ XY → P xy)
                                                                                Qed
       where
          XY = (Q , ⨆X , ⨆Y)
          X  = fstChain XY
          Y  = sndChain XY

          XisChain : IsChain X
          XisChain = fstIsChain XY p

          YisChain : IsChain Y
          YisChain = sndIsChain XY p

          imgXisChain : IsChain (chainImage' (_∙_ h) X)
          imgXisChain = fst (imageIsNonEmptyChain (result h) X (fstIsNonEmptyChain XY (p , ne)))

UniversalPropFold : {τ : Type} → (f : CPO (F τ ⇒ τ)) → (h : CPO (μ F ⇒ τ)) → Strict h → h ≡ fold f ↔ h ∙ inn ≡ f ∙ fmap h
UniversalPropFold f h sh = UniPropFoldL f h , UniPropFoldR f h sh

FoldHomo : {τ : Type} → (f : CPO (F τ ⇒ τ)) → fold f ∙ inn ≡ f ∙ fmap (fold f)
FoldHomo f = UniPropFoldL f (fold f) refl

------------------------------------------------------------------------

FmapPreservesStrictness : {σ τ : Type} → (f : CPO (σ ⇒ τ)) → Strict f → Strict (fmap f)
FmapPreservesStrictness f sf = ProveTruth (

   Strict (fmap f)
                                           ←⟨ StrictCancellation (fmap ⊥) (fmap f) ⟩
   Strict (fmap f ∙ fmap ⊥)
                                           ←⟨ ≡→↔ (cong Strict (
                                                      fmap ⊥
                                                                      ⟨ cong fmap (sym (fst (PointFreeStrictness f) sf)) ⟩
                                                      fmap (f ∙ ⊥)
                                                                      ⟨ fmap-comp f ⊥ ⟩
                                                      fmap f ∙ fmap ⊥
                                                                      QED
                                                  ))
                                           ⟩↔
   Strict (fmap ⊥)
                                           ←⟨ (λ p → DownwardClosed (fst p) (snd p)) ⟩
   (Strict identity × (fmap ⊥ ⊑ identity))
                                           ←⟨ (λ p → refl , p) ⟩
   fmap ⊥ ⊑ identity
                                           ←⟨ ↔-⊑L fmap-id ⟩↔
   fmap ⊥ ⊑ fmap identity
                                           ←⟨ monotonic fmap' ⟩
   ⊥ ⊑ identity
                                           ←⟨ ⊑-bot ⟩TRUE
   )

------------------------------------------------------------------------

chainCompleteStrict : {σ τ : Type} → ChainComplete (Strict {σ} {τ})
chainCompleteStrict {σ} {τ} X p =

    Strict (⨆ X)
                                               ←⟨ ↔-refl ⟩↔
    (⨆ X) $ ⊥ ≡ ⊥
                                               ←⟨ flip ⊑-antisym ⊑-bot ⟩
    (⨆ X) $ ⊥ ⊑ ⊥
                                               ←⟨ ↔-refl ⟩↔
    (λ h → h $ ⊥) (⨆ X) ⊑ ⊥
                                               ←⟨ ↔-refl ⟩↔
    ⨆ (chainImage' (λ h → h $ ⊥) X) ⊑ ⊥
                                               ←⟨ continuity (appTo ⊥) X p ⊥ ⟩↔
    ((h : CPO (σ ⇒ τ)) → h ∈ X → (h $ ⊥) ⊑ ⊥)
                                               ←⟨ result2' ⊑-eq ⟩
    ((h : CPO (σ ⇒ τ)) → h ∈ X → (h $ ⊥) ≡ ⊥)
                                               ←⟨ ↔-refl ⟩↔
    ((h : CPO (σ ⇒ τ)) → h ∈ X → Strict h)
                                               Qed

FoldPreservesStrictness : {τ : Type} → (f : CPO (F τ ⇒ τ)) → Strict f → Strict (fold f)
FoldPreservesStrictness {τ} f sf = ProveTruth (

    Strict (fold f)
                               ←⟨ ↔-refl ⟩↔
    Strict (fix (fold-fun f))
                               ←⟨ FixpointInduction Strict (fold-fun f) chainCompleteStrict refl indStep ⟩TRUE)

  where
    indStep : (g : CPO (μ F ⇒ τ)) → Strict g → (f ∙ fmap g ∙ out) $ ⊥ ≡ ⊥
    indStep g sg =
                    f $ fmap g $ out $ ⊥
                                          ⟨ cong (_$_ f ∘ _$_ (fmap g)) strict-out ⟩
                    f $ fmap g $ ⊥
                                          ⟨ cong (_$_ f) (FmapPreservesStrictness g sg) ⟩
                    f $ ⊥
                                          ⟨ sf ⟩
                    ⊥
                                          QED

------------------------------------------------------------------------

RollingRuleFold : {σ τ : Type} → (f : CPO (τ ⇒ σ)) → (g : CPO (F σ ⇒ τ)) → fold (f ∙ g) ≡ f ∙ fold (g ∙ fmap f)
RollingRuleFold f g =

    fold (f ∙ g)
                                   ⟨ refl ⟩
    fix (fold-fun (f ∙ g))
                                   ⟨ cong fix (cpo-ext (λ h → (
                                                                (f ∙ g) ∙ fmap h ∙ out
                                                                                       ⟨ ∙-assocLR f g (fmap h ∙ out) ⟩
                                                                f ∙ g ∙ fmap h ∙ out
                                                                                       QED)))
                                   ⟩
    fix (result f ∙ fold-fun g)
                                   ⟨ RollingRule (result f) (fold-fun g) ⟩
    f ∙ fix (fold-fun g ∙ result f)
                                   ⟨ cong ((_∙_ f) ∘ fix) (cpo-ext ( λ h → (
                                                                             g ∙ (fmap (f ∙ h) ∙ out)
                                                                                                          ⟨ ∙-congL g ( fmap (f ∙ h) ∙ out
                                                                                                                                                ⟨ ∙-congR out (fmap-comp f h) ⟩
                                                                                                                        (fmap f ∙ fmap h) ∙ out
                                                                                                                                                ⟨ ∙-assocLR (fmap f) (fmap h) out ⟩
                                                                                                                        fmap f ∙ (fmap h ∙ out)
                                                                                                                                                QED)
                                                                                                          ⟩
                                                                             g ∙ (fmap f ∙ (fmap h ∙ out))
                                                                                                          ⟨ ∙-assocRL g (fmap f) (fmap h ∙ out) ⟩
                                                                             (g ∙ fmap f) ∙ (fmap h ∙ out)
                                                                                                          QED)))
                                   ⟩
    f ∙ fix (fold-fun (g ∙ fmap f))
                                   ⟨ refl ⟩
    f ∙ fold (g ∙ fmap f)
                                   QED


ComputationFold : {τ : Type} → (f : CPO (F τ ⇒ τ)) → fold f ≡ f ∙ fold (fmap f)
ComputationFold f =

          fold f
                                        ⟨ cong fold (sym (∙-idR f)) ⟩
          fold (f ∙ identity)
                                        ⟨ RollingRuleFold f identity ⟩
          f ∙ fold (identity ∙ fmap f)
                                        ⟨ cong (_∙_ f ∘ fold) (∙-idL (fmap f)) ⟩
          f ∙ fold (fmap f)
                                        QED

------------------------------------------------------------------------

FoldReflectsStrictness : {τ : Type} → (f : CPO (F τ ⇒ τ)) → Strict (fold f) → Strict f
FoldReflectsStrictness f =
                            Strict f
                                                          ←⟨ StrictCancellation (fold (fmap f)) f ⟩
                            Strict (f ∙ fold (fmap f))
                                                          ←⟨ ≡→↔ (cong Strict (ComputationFold f)) ⟩↔
                            Strict (fold f)
                                                          Qed

------------------------------------------------------------------------

FoldFunctorProp : {τ : Type} → (f g : CPO (F τ ⇒ τ)) → fold f ≡ fold g  → fold (fmap f) ≡ fold (fmap g)
FoldFunctorProp f g eq =

   fold (fmap f)
                        ⟨ FFPropAux f ⟩
   fmap (fold f) ∙ out
                        ⟨ ∙-congR out (cong fmap eq) ⟩
   fmap (fold g) ∙ out
                        ⟨ sym (FFPropAux g) ⟩
   fold (fmap g)
                        QED

  where
    FFPropAux : {σ : Type} → (h : CPO (F σ ⇒ σ)) → fold (fmap h) ≡ fmap (fold h) ∙ out
    FFPropAux h =

       fold (fmap h)
                                              ⟨ FoldDef (fmap h) ⟩
       fmap h ∙ fmap (fold (fmap h)) ∙ out
                                              ⟨ ∙-assocRL (fmap h) (fmap (fold (fmap h))) out ⟩
       (fmap h ∙ fmap (fold (fmap h))) ∙ out
                                              ⟨ ∙-congR out (sym (fmap-comp h (fold (fmap h)))) ⟩
       fmap (h ∙ fold (fmap h)) ∙ out
                                              ⟨ ∙-congR out (cong fmap (sym (ComputationFold h))) ⟩
       fmap (fold h) ∙ out
                                              QED

------------------------------------------------------------------------

private
  FoldFusionAux : {σ τ : Type} → (f : CPO (F σ ⇒ σ)) → (g : CPO (F τ ⇒ τ)) → (h : CPO (σ ⇒ τ)) → h ∙ f ≡ g ∙ fmap h → result h ∙ fold-fun f ≡ fold-fun g ∙ result h
  FoldFusionAux f g h eq = cpo-ext (λ e →
                                          h ∙ f ∙ fmap e ∙ out
                                                                    ⟨ ∙-assocRRLL h f (fmap e) out ⟩
                                          ((h ∙ f) ∙ fmap e) ∙ out
                                                                    ⟨ ∙-congR out (
                                                                                    (h ∙ f) ∙ fmap e
                                                                                                           ⟨ ∙-congR (fmap e) eq ⟩
                                                                                    (g ∙ fmap h) ∙ fmap e
                                                                                                           ⟨ ∙-assocLR g (fmap h) (fmap e) ⟩
                                                                                    g ∙ fmap h ∙ fmap e
                                                                                                           ⟨ ∙-congL g (sym (fmap-comp h e)) ⟩
                                                                                    g ∙ fmap (h ∙ e)
                                                                                                           QED)
                                                                    ⟩
                                          (g ∙ fmap (h ∙ e)) ∙ out
                                                                    ⟨ ∙-assocLR g (fmap (h ∙ e)) out  ⟩
                                          g ∙ fmap (h ∙ e) ∙ out
                                                                    QED
                                   )

NonStrictFoldFusion : {σ τ : Type} → (f : CPO (F σ ⇒ σ)) → (g : CPO (F τ ⇒ τ)) → (h : CPO (σ ⇒ τ)) → h ∙ f ≡ g ∙ fmap h → FixedPoint (fold-fun g) (h ∙ fold f)
NonStrictFoldFusion f g h eq = ProveTruth (

   FixedPoint (fold-fun g) (h ∙ fold f)
                                               ←⟨ NonStrictFixFusion (result h) (fold-fun f) (fold-fun g) ⟩
   result h ∙ fold-fun f ≡ fold-fun g ∙ result h
                                               ←⟨ FoldFusionAux f g h eq ⟩TRUE
   )

FoldFusion : {σ τ : Type} → (f : CPO (F σ ⇒ σ)) → (g : CPO (F τ ⇒ τ)) → (h : CPO (σ ⇒ τ)) → Strict h → h ∙ f ≡ g ∙ fmap h → h ∙ fold f ≡ fold g
FoldFusion f g h sh eq = ProveTruth (

   h ∙ fold f ≡ fold g
                                               ←⟨ ↔-refl  ⟩↔
   h ∙ fix (fold-fun f) ≡ fix (fold-fun g)
                                               ←⟨ FixFusion (result h) (fold-fun f) (fold-fun g) (fst (PointFreeStrictness h) sh) ⟩
   result h ∙ fold-fun f ≡ fold-fun g ∙ result h
                                               ←⟨ FoldFusionAux f g h eq ⟩TRUE
   )

------------------------------------------------------------------------