{-# OPTIONS --safe --with-K #-}

open import Prelude

module Generics.Ornament.Algebraic where
open import Prelude.List as List
open import Generics.Telescope
open import Generics.Description
open import Generics.Algebra
open import Generics.Recursion
open import Generics.Ornament
open import Generics.Ornament.Description

private variable
  rb  : RecB
  cb  : ConB
  cbs : ConBs

algODʳ : {I : Set ℓⁱ} (D : RecD I rb)
         {X : I  Set } (xs :  D ⟧ʳ X)  RecOD (Σ[ i  I ] X i × ) fst D
algODʳ (ι i  ) x  = ι (_ , x , tt)
algODʳ (π A D) xs = π λ a  algODʳ (D a) (xs a)

algConB : Level  ConB  ConB
algConB  []            = []
algConB  (inl ℓ'  cb) = inl ℓ'  algConB  cb
algConB  (inr rb  cb) = inl (max-ℓ rb  )  inr rb  algConB  cb

algODᶜ : {I : Set ℓⁱ} (D : ConD I cb) {X : I  Set }
        Algᶜ D X  ConOD (Σ[ i  I ] X i × ) fst D (algConB  cb)
algODᶜ (ι i  ) f = ι (_ , f refl , tt)
algODᶜ (σ A D) f = σ λ a  algODᶜ (D a) λ xs  f (a , xs)
algODᶜ (ρ D E) f = Δ ( D ⟧ʳ _) λ xs 
                   ρ (algODʳ D xs) (algODᶜ E  xs'  f (xs , xs')))

algODᶜˢ : {I : Set ℓⁱ} (D : ConDs I cbs) {X : I  Set }
         Algᶜˢ D X  ConODs (Σ[ i  I ] X i × ) fst D (List.map (algConB ) cbs)
algODᶜˢ []       f = []
algODᶜˢ (D  Ds) f = algODᶜ D (f  inl)   algODᶜˢ Ds (f  inr)

module _ ( : Level) where

  algConB-lemma₀ :  cb  max-π (algConB  cb)  max-π cb
  algConB-lemma₀ []            = refl
  algConB-lemma₀ (inl ℓ'  cb) = algConB-lemma₀ cb
  algConB-lemma₀ (inr rb  cb) = cong (max-ℓ rb ⊔_) (algConB-lemma₀ cb)

  algConB-lemma₁ :  cbs  maxMap max-π (List.map (algConB ) cbs)  maxMap max-π cbs
  algConB-lemma₁ []         = refl
  algConB-lemma₁ (cb  cbs) = cong₂ _⊔_ (algConB-lemma₀ cb) (algConB-lemma₁ cbs)

  algConB-lemma₂ :  cb  max-σ (algConB  cb)  max-π cb  max-σ cb  hasRec?  cb
  algConB-lemma₂ []            = refl
  algConB-lemma₂ (inl ℓ'  cb) = cong (ℓ' ⊔_) (algConB-lemma₂ cb)
  algConB-lemma₂ (inr rb  cb) = cong (  max-ℓ rb ⊔_) (algConB-lemma₂ cb)

  algConB-lemma₃ :  cbs  maxMap max-σ (List.map (algConB ) cbs)
                          maxMap max-π cbs  maxMap max-σ cbs  maxMap (hasRec? ) cbs
  algConB-lemma₃ []         = refl
  algConB-lemma₃ (cb  cbs) = cong₂ _⊔_ (algConB-lemma₂ cb) (algConB-lemma₃ cbs)

  algOD-level-inequality :
      (ℓᵈ : Level) (cbs : ConBs)
     maxMap max-π cbs  maxMap max-σ cbs  ℓᵈ  ℓᵈ
     maxMap max-π (List.map (algConB ) cbs) 
      maxMap max-σ (List.map (algConB ) cbs)  ℓᵈ    ℓᵈ  
  algOD-level-inequality ℓᵈ cbs ineq =
    begin
      maxMap max-π (List.map (algConB ) cbs) 
      maxMap max-σ (List.map (algConB ) cbs)  ℓᵈ  
        ≡⟨ cong (ℓᵈ   ⊔_) (cong₂ _⊔_ (algConB-lemma₁ cbs) (algConB-lemma₃ cbs)) 
      maxMap max-π cbs  maxMap max-σ cbs  maxMap (hasRec? ) cbs  ℓᵈ  
        ≡⟨ cong (maxMap max-π cbs  maxMap max-σ cbs  ℓᵈ ⊔_)
                (maxMap-bound (hasRec? ) (hasRec?-bound ) cbs) 
      maxMap max-π cbs  maxMap max-σ cbs  ℓᵈ  
        ≡⟨ cong ( ⊔_) ineq 
      ℓᵈ  
     where open ≡-Reasoning

AlgOD : (P : FoldP)  DataOD (FoldP.Desc P)
DataOD.#levels (AlgOD P) = FoldP.#levels P
DataOD.level   (AlgOD P) = FoldP.level P
DataOD.applyL  (AlgOD P) ℓs = AlgODᵖᵈ where
  open FoldP P
  Dᵖ = DataD.applyL Desc (level ℓs)
  AlgODᵖᵈ : PDataOD Dᵖ
  PDataOD.alevel AlgODᵖᵈ = PDataD.alevel (DataD.applyL Desc (level ℓs))
  PDataOD.plevel AlgODᵖᵈ = _
  PDataOD.ilevel AlgODᵖᵈ = _
  PDataOD.struct AlgODᵖᵈ = _
  PDataOD.level-inequality AlgODᵖᵈ = algOD-level-inequality
    (clevel ℓs) (PDataD.dlevel Dᵖ) (PDataD.struct Dᵖ) (PDataD.level-inequality Dᵖ)
  PDataOD.Param  AlgODᵖᵈ = FoldP.Param P ℓs
  PDataOD.param  AlgODᵖᵈ = FoldP.param P
  PDataOD.Index  AlgODᵖᵈ ps = PDataD.Index Dᵖ (param ps) ++ λ is 
                              Carrier ℓs ps is  λ _  []
  PDataOD.index  AlgODᵖᵈ _  = fst
  PDataOD.applyP AlgODᵖᵈ ps = algODᶜˢ (PDataD.applyP Dᵖ (param ps)) (algebra ps)

rememberʳ :
    {I : Set ℓⁱ} (D : RecD I rb) {X : I  Set ℓˣ}
    {N : I  Set } (fold :  {i}  N i  X i) {N' : Σ[ i  I ] X i ×   Set ℓ'}
   (ns :  D ⟧ʳ N)  Allʳ D  i' n  N' (i' , fold n , tt)) ns
     algODʳ D (fmapʳ D fold ns) ⌋ʳ ⟧ʳ N'
rememberʳ (ι i  ) fold ns n'  = n'
rememberʳ (π A D) fold ns all = λ a  rememberʳ (D a) fold (ns a) (all a)

rememberᶜ :
    {I : Set ℓⁱ} (D : ConD I cb) {X : I  Set ℓˣ} (f : Algᶜ D X)
    {N : I  Set } (fold :  {i}  N i  X i) {N' : Σ[ i  I ] X i ×   Set ℓ'}
    {i} (ns :  D ⟧ᶜ N i)  Allᶜ D  i' n  N' (i' , fold n , tt)) ns ℓ''
     algODᶜ D f ⌋ᶜ ⟧ᶜ N' (i , f (fmapᶜ D fold ns) , tt)
rememberᶜ (ι i  ) f fold refl        all = refl
rememberᶜ (σ A D) f fold (a  , ns )  all = a , rememberᶜ (D a) (curry f a) fold ns all
rememberᶜ (ρ D E) f fold (ns , ns') (all , all') =
  fmapʳ D fold ns , rememberʳ D fold ns all , rememberᶜ E (curry f _) fold ns' all'

rememberᶜˢ :
    {I : Set ℓⁱ} (D : ConDs I cbs) {X : I  Set ℓˣ} (f : Algᶜˢ D X)
    {N : I  Set } (fold :  {i}  N i  X i) {N' : Σ[ i  I ] X i ×   Set ℓ'}
    {i} (ns :  D ⟧ᶜˢ N i)  Allᶜˢ D  i' n  N' (i' , fold n , tt)) ns ℓ''
     algODᶜˢ D f ⌋ᶜˢ ⟧ᶜˢ N' (i , f (fmapᶜˢ D fold ns) , tt)
rememberᶜˢ (D  Ds) f fold (inl ns) all = inl (rememberᶜ  D  (f  inl) fold ns all)
rememberᶜˢ (D  Ds) f fold (inr ns) all = inr (rememberᶜˢ Ds (f  inr) fold ns all)

remember :  (n : Name) {P f}  _ : FoldC P f 
           {N'}  _ : Named n (DataC  AlgOD P ⌋ᵈ N')   IndP
remember _ {P} {f}  C  {N'}  named C'  = let open FoldP P in record
  { Conv    = Conv
  ; #levels = #levels
  ; level   = level
  ; Param   = Param
  ; param   = param
  ; ParamV  = constTelInfo hidden
  ; ParamN  = ParamN
  ; Carrier = λ ℓs ps is n  N' ℓs ps (is , f ℓs ps n , tt)
  ; algebra = λ ps ns all  DataC.toN C'
      (subst  x    AlgOD P ⌋ᵈ ⟧ᵈ (N' _ ps) (_ , x , tt))
             (sym (FoldC.equation C ns))
             (rememberᶜˢ (PDataD.applyP (DataD.applyL Desc _) (param ps))
               (algebra ps) (f _ ps) ns all)) }