{-# OPTIONS --safe --without-K #-}

open import Prelude

module Generics.RecursionScheme where

open import Generics.Telescope
open import Generics.Description
open import Generics.Algebra
open import Generics.Recursion

private variable
  rb  : RecB
  cb  : ConB
  cbs : ConBs

FoldOpTᶜ : {I : Set ℓⁱ} (D : ConD I cb)  (I  Set )  Set (max-π cb  max-σ cb  )
FoldOpTᶜ (ι i  ) X = X i
FoldOpTᶜ (σ A D) X = (a : A)  FoldOpTᶜ (D a) X
FoldOpTᶜ (ρ D E) X =  D ⟧ʳ X  FoldOpTᶜ E X

FoldOpTelᶜˢ : {I : Set ℓⁱ} (D : ConDs I cbs)  (I  Set )
             Tel (maxMap max-π cbs  maxMap max-σ cbs  hasCon?  cbs)
FoldOpTelᶜˢ []       X = []
FoldOpTelᶜˢ (D  Ds) X = [ _  FoldOpTᶜ D X ] FoldOpTelᶜˢ Ds X

fold-opᶜ : {I : Set ℓⁱ} (D : ConD I cb) {X : Carrierᶜ D }  FoldOpTᶜ D X  Algᶜ D X
fold-opᶜ (ι i  ) f refl       = f
fold-opᶜ (σ A D) f (a  , xs ) = fold-opᶜ (D a) (f a ) xs
fold-opᶜ (ρ D E) f (xs , xs') = fold-opᶜ  E    (f xs) xs'

fold-opᶜˢ : {I : Set ℓⁱ} (D : ConDs I cbs) {X : Carrierᶜˢ D }
            FoldOpTelᶜˢ D X ⟧ᵗ  Algᶜˢ D X
fold-opᶜˢ (D  Ds) (f , fs) (inl xs) = fold-opᶜ  D  f  xs
fold-opᶜˢ (D  Ds) (f , fs) (inr xs) = fold-opᶜˢ Ds fs xs

fold-operator :  (n : Name) {D N}  C : Named n (DataC D N)   FoldP
fold-operator _ {D}  named C  = record
  { Conv    = C
  ; #levels = suc (DataD.#levels D)
  ; level   = snd
  ; Param   = λ ( , ℓs)  let Dᵖ = DataD.applyL D ℓs in
      [[ ps  PDataD.Param Dᵖ ]]
      [  X   Curriedᵗ (PDataD.Index Dᵖ ps) (constTelInfo visible)  _  Set ) ]
              FoldOpTelᶜˢ (PDataD.applyP Dᵖ ps) (uncurryᵗ X)
  ; param   = fst
  ; ParamV  = constTelInfo hidden ++ λ _  hidden  λ _  constTelInfo visible
  ; ParamN  = constTelInfo "p" ++ λ _  "X"  λ _  constTelInfo "alg"
  ; Carrier = λ _ (_ , X , _)  uncurryᵗ X
  ; algebra = λ (ps , _ , args)  fold-opᶜˢ (PDataD.applyP (DataD.applyL D _) ps) args }

Homᶜ : {I : Set ℓⁱ} (D : ConD I cb) {X : I  Set ℓˣ} {Y : I  Set ℓʸ}
      FoldOpTᶜ D X  FoldOpTᶜ D Y  (∀ {i}  X i  Y i)
       {i}   D ⟧ᶜ X i  Set (max-π cb  ℓʸ)
Homᶜ (ι i  ) x y h _ = h x  y
Homᶜ (σ A D) f g h (a , xs) = Homᶜ (D a) (f a) (g a) h xs
Homᶜ (ρ D E) {X} {Y} f g h (xs , xs') =
  (ys :  D ⟧ʳ Y)  ExtEqʳ D (fmapʳ D h xs) ys  Homᶜ E (f xs) (g ys) h xs'

∀ᶜ : {I : Set ℓⁱ} (D : ConD I cb) {X : Carrierᶜ D ℓˣ}
    (∀ {i}   D ⟧ᶜ X i  Set ℓʸ)  Set (max-π cb  max-σ cb  hasRec? ℓˣ cb  ℓʸ)
∀ᶜ (ι i  )     Y = Y refl
∀ᶜ (σ A D)     Y = (a : A)  ∀ᶜ (D a) (curry Y a)
∀ᶜ (ρ D E) {X} Y = (xs :  D ⟧ʳ X)  ∀ᶜ E (curry Y xs)

∀ᶜ-apply : {I : Set ℓⁱ} (D : ConD I cb)
           {X : Carrierᶜ D ℓˣ} {Y :  {i}   D ⟧ᶜ X i  Set ℓʸ}
          ∀ᶜ D Y   {i} (xs :  D ⟧ᶜ X i)  Y xs
∀ᶜ-apply (ι i  ) y refl       = y
∀ᶜ-apply (σ A D) f (a ,  xs ) = ∀ᶜ-apply (D a) (f a) xs
∀ᶜ-apply (ρ D E) f (xs , xs') = ∀ᶜ-apply E (f xs) xs'

Homᶜˢ : {I : Set ℓⁱ} (D : ConDs I cbs) {X : I  Set ℓˣ} {Y : I  Set ℓʸ}
        FoldOpTelᶜˢ D X ⟧ᵗ   FoldOpTelᶜˢ D Y ⟧ᵗ  (∀ {i}  X i  Y i)
       Tel (maxMap max-π cbs  maxMap max-σ cbs  maxMap (hasRec? ℓˣ) cbs  hasCon? ℓʸ cbs)
Homᶜˢ []       _        _        h = []
Homᶜˢ (D  Ds) (f , fs) (g , gs) h = [ _  ∀ᶜ D (Homᶜ D f g h) ] Homᶜˢ Ds fs gs h

fold-fusionʳ :
    {I : Set ℓⁱ} (D : RecD I rb) {N : I  Set } {X : I  Set ℓˣ} {Y : I  Set ℓʸ}
    (fold-fs :  {i}  N i  X i) (fold-gs :  {i}  N i  Y i)
   (h :  {i}  X i  Y i) (ns :  D ⟧ʳ N)  Allʳ D  _ n  h (fold-fs n)  fold-gs n) ns
   ExtEqʳ D (fmapʳ D h (fmapʳ D fold-fs ns)) (fmapʳ D fold-gs ns)
fold-fusionʳ (ι i  ) fold-fs fold-gs h n  eq  = eq
fold-fusionʳ (π A D) fold-fs fold-gs h ns all =
  λ a  fold-fusionʳ (D a) fold-fs fold-gs h (ns a) (all a)

fold-fusionᶜ :
    {I : Set ℓⁱ} (D : ConD I cb) {N : I  Set } {X : I  Set ℓˣ} {Y : I  Set ℓʸ}
    (f : FoldOpTᶜ D X) (g : FoldOpTᶜ D Y)
    (fold-fs :  {i}  N i  X i) (fold-gs :  {i}  N i  Y i)
   (h :  {i}  X i  Y i)  (∀ {i} (xs :  D ⟧ᶜ X i)  Homᶜ D f g h xs)
    {i} (ns :  D ⟧ᶜ N i)  Allᶜ D  _ n  h (fold-fs n)  fold-gs n) ns ℓ'
   h (fold-opᶜ D f (fmapᶜ D fold-fs ns))  fold-opᶜ D g (fmapᶜ D fold-gs ns)
fold-fusionᶜ (ι i  ) x y fold-fs fold-gs h hom refl all = hom refl
fold-fusionᶜ (σ A D) f g fold-fs fold-gs h hom (a , ns) all =
  fold-fusionᶜ (D a) (f a) (g a) fold-fs fold-gs h (curry hom a) ns all
fold-fusionᶜ (ρ D E) f g fold-fs fold-gs h hom (ns , ns') (all , all') =
  fold-fusionᶜ E (f (fmapʳ D fold-fs ns)) (g (fmapʳ D fold-gs ns)) fold-fs fold-gs h
     xs  hom (fmapʳ D fold-fs ns , xs) (fmapʳ D fold-gs ns)
                (fold-fusionʳ D fold-fs fold-gs h ns all)) ns' all'

fold-fusionᶜˢ :
    {I : Set ℓⁱ} (D : ConDs I cbs) {N : I  Set } {X : I  Set ℓˣ} {Y : I  Set ℓʸ}
    (fs :  FoldOpTelᶜˢ D X ⟧ᵗ) (gs :  FoldOpTelᶜˢ D Y ⟧ᵗ)
    (fold-fs :  {i}  N i  X i) (fold-gs :  {i}  N i  Y i)
   (h :  {i}  X i  Y i)   Homᶜˢ D fs gs h ⟧ᵗ
    {i} (ns :  D ⟧ᶜˢ N i)  Allᶜˢ D  _ n  h (fold-fs n)  fold-gs n) ns ℓ'
   h (fold-opᶜˢ D fs (fmapᶜˢ D fold-fs ns))  fold-opᶜˢ D gs (fmapᶜˢ D fold-gs ns)
fold-fusionᶜˢ (D  Ds) (f , _ ) (g , _ ) fold-fs fold-gs h (hom , _) (inl ns) all =
  fold-fusionᶜ  D  f  g  fold-fs fold-gs h (∀ᶜ-apply D hom) ns all
fold-fusionᶜˢ (D  Ds) (_ , fs) (_ , gs) fold-fs fold-gs h (_ , hom) (inr ns) all =
  fold-fusionᶜˢ Ds fs gs fold-fs fold-gs h hom ns all

fold-fusion :  (n : Name) {D N}  C : Named n (DataC D N) 
              {fold}  foldC : FoldC (fold-operator n  C ⦄) fold   IndP
fold-fusion _ {D}  named C  {fold}  foldC  = record
  { Conv    = C
  ; #levels = suc (suc (DataD.#levels D))
  ; level   = snd  snd
  ; Param   = λ (ℓˣ , ℓʸ , ℓs)  let Dᵖ = DataD.applyL D ℓs in
      [[ ps  PDataD.Param Dᵖ ]]
      let ITel = PDataD.Index Dᵖ ps; Dᶜˢ = PDataD.applyP Dᵖ ps in
      [  X   Curriedᵗ ITel (constTelInfo visible)  _  Set ℓˣ) ]
      [  Y   Curriedᵗ ITel (constTelInfo visible)  _  Set ℓʸ) ]
      [  h   Curriedᵗ ITel (constTelInfo hidden )  is  uncurryᵗ X is  uncurryᵗ Y is) ]
      [[ fs  FoldOpTelᶜˢ Dᶜˢ (uncurryᵗ X) ]]
      [[ gs  FoldOpTelᶜˢ Dᶜˢ (uncurryᵗ Y) ]]
              Homᶜˢ Dᶜˢ fs gs  {is}  uncurryᵗ h is)
  ; param   = fst
  ; ParamV  = constTelInfo hidden ++ λ _ 
              hidden  λ _  hidden  λ _  visible  λ _ 
              constTelInfo hidden ++ λ _  constTelInfo hidden ++ λ _  constTelInfo visible
  ; ParamN  = constTelInfo "p" ++ λ _  "X"  λ _  "Y"  λ _  "h"  λ _ 
              constTelInfo "f" ++ λ _  constTelInfo "g" ++ λ _  constTelInfo "hom"
  ; Carrier = λ _ (ps , X , Y , h , fs , gs , _) is n 
                uncurryᵗ h is (fold _ (ps , X , fs) n)  fold _ (ps , Y , gs) n
  ; algebra = λ (ps , X , Y , h , fs , gs , hom) {is} ns all 
      let Dᶜˢ = PDataD.applyP (DataD.applyL D _) ps in
      begin
        uncurryᵗ h is (fold _ (ps , X , fs) (DataC.toN C ns))
          ≡⟨ cong (uncurryᵗ h is) (FoldC.equation foldC ns) 
        uncurryᵗ h is (fold-opᶜˢ Dᶜˢ fs (fmapᶜˢ Dᶜˢ (fold _ (ps , X , fs)) ns))
          ≡⟨ fold-fusionᶜˢ Dᶜˢ fs gs (fold _ (ps , X , fs)) (fold _ (ps , Y , gs))
                {is}  uncurryᵗ h is) hom ns all ⟩'
        fold-opᶜˢ Dᶜˢ gs (fmapᶜˢ Dᶜˢ (fold _ (ps , Y , gs)) ns)
          ≡⟨ sym (FoldC.equation foldC ns) ⟩'
        fold _ (ps , Y , gs) (DataC.toN C ns)
       } where open ≡-Reasoning

IndOpTʳ : {I : Set ℓⁱ} (D : RecD I rb) {N : I  Set }   D ⟧ʳ N
         (∀ i  N i  Set ℓ')  Set (max-ℓ rb  ℓ')
IndOpTʳ (ι i  ) n  P = P i n
IndOpTʳ (π A D) ns P = (a : A)  IndOpTʳ (D a) (ns a) P

IndOpTᶜ : {I : Set ℓⁱ} (D : ConD I cb) {N : Carrierᶜ D }  Algᶜ D N
         (P : IndCarrierᶜ D N ℓ')  Set (max-π cb  max-σ cb  hasRec?  cb  ℓ')
IndOpTᶜ (ι i  )     f P = P i (f refl)
IndOpTᶜ (σ A D)     f P = (a : A)  IndOpTᶜ (D a) (curry f a) P
IndOpTᶜ (ρ D E) {N} f P = (ns :  D ⟧ʳ N)  IndOpTʳ D ns P  IndOpTᶜ E (curry f ns) P

IndOpTelᶜˢ : {I : Set ℓⁱ} (D : ConDs I cbs) {N : Carrierᶜˢ D }  Algᶜˢ D N
            (P : IndCarrierᶜˢ D N ℓ')  Tel (maxMap max-π cbs  maxMap max-σ cbs 
                                              maxMap (hasRec? ) cbs  hasCon? ℓ' cbs)
IndOpTelᶜˢ []       f P = []
IndOpTelᶜˢ (D  Ds) f P = [ _  IndOpTᶜ D (f  inl) P ] IndOpTelᶜˢ Ds (f  inr) P

ind-opʳ : {I : Set ℓⁱ} (D : RecD I rb) {N : I  Set } (ns :  D ⟧ʳ N)
          {P :  i  N i  Set ℓ'}  Allʳ D P ns  IndOpTʳ D ns P
ind-opʳ (ι i  ) n  p   = p
ind-opʳ (π A D) ns all = λ a  ind-opʳ (D a) (ns a) (all a)

ind-opᶜ : {I : Set ℓⁱ} (D : ConD I cb) {N : Carrierᶜ D } (f : Algᶜ D N)
          {P : IndCarrierᶜ D N ℓ'}  IndOpTᶜ D f P  IndAlgᶜ D f P ℓ''
ind-opᶜ (ι i  ) f p refl        all = p
ind-opᶜ (σ A D) f g (a  , ns )  all = ind-opᶜ (D a) (curry f a) (g a) ns all
ind-opᶜ (ρ D E) f g (ns , ns') (all , all') =
  ind-opᶜ E (curry f ns) (g ns (ind-opʳ D ns all)) ns' all'

ind-opᶜˢ : {I : Set ℓⁱ} (D : ConDs I cbs) {N : Carrierᶜˢ D } (f : Algᶜˢ D N)
           {P : IndCarrierᶜˢ D N ℓ'}   IndOpTelᶜˢ D f P ⟧ᵗ  IndAlgᶜˢ D f P ℓ''
ind-opᶜˢ (D  Ds) f (g , gs) (inl ps) = ind-opᶜ  D  (f  inl) g  ps
ind-opᶜˢ (D  Ds) f (g , gs) (inr ps) = ind-opᶜˢ Ds (f  inr) gs ps

ind-operator :  (n : Name) {D N}  C : Named n (DataC D N)   IndP
ind-operator _ {D} {N}  named C  = record
  { Conv    = C
  ; #levels = suc (DataD.#levels D)
  ; level   = snd
  ; Param   = λ ( , ℓs)  let Dᵖ = DataD.applyL D ℓs in
      [[ ps  PDataD.Param Dᵖ ]] let Dᶜˢ = PDataD.applyP Dᵖ ps in
      [  P   Curriedᵗ (PDataD.Index Dᵖ ps) (constTelInfo hidden)
                 is  N ℓs ps is  Set ) ]
              IndOpTelᶜˢ Dᶜˢ (DataC.toN C) (uncurryᵗ P)
  ; param   = fst
  ; ParamV  = constTelInfo hidden ++ λ _  visible  λ _  constTelInfo visible
  ; ParamN  = constTelInfo "p" ++ λ _  "P"  λ _  constTelInfo "ind-case"
  ; Carrier = λ _ (_ , P , _) is n  uncurryᵗ P is n
  ; algebra = λ (ps , P , fs) 
      ind-opᶜˢ (PDataD.applyP (DataD.applyL D _) ps) (DataC.toN C) fs }