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

open import Prelude

module Generics.Recursion where

open import Agda.Builtin.Reflection public using (Name)
open import Generics.Telescope
open import Generics.Description
open import Generics.Algebra

private variable
  rb  : RecB
  cb  : ConB
  cbs : ConBs

DataT : DataD  Setω
DataT D = Carriers D  ℓs  PDataD.dlevel (DataD.applyL D ℓs))

record DataC (D : DataD) (N : DataT D) : Setω where
  constructor datac
  field
    toN   : Algs   D N
    fromN : Coalgs D N
    fromN-toN :  {ℓs ps is} (ns :  D ⟧ᵈ (N ℓs ps) is)  fromN (toN ns)  ns
    toN-fromN :  {ℓs ps is}          (n : N ℓs ps  is)  toN (fromN n )  n

record Named (n : Name) (A : Setω) : Setω where
  constructor named
  field
    unNamed : A

open Named public

findNamed : (n : Name) (A : Setω)   Named n A   A
findNamed _ _  named a  = a

findDataC :  (n : Name) {D N}   Named n (DataC D N)   DataC D N
findDataC n = findNamed n _

findDataD :  (n : Name) {D N}   Named n (DataC D N)   DataD
findDataD _ {D} = D

record FoldP : Setω where
  field
    {Desc}   : DataD
    {Native} : DataT Desc
    Conv     : DataC Desc Native
    #levels  : 
  Levels : Set
  Levels = Level ^ #levels
  field
    level    : Levels  DataD.Levels Desc
    {plevel} : Levels  Level
    Param    :  ℓs  Tel (plevel ℓs)
    ParamV   :  {ℓs}  TelInfo Visibility (Param ℓs)
    ParamN   :  {ℓs}  TelInfo String     (Param ℓs)
    param    :  {ℓs}   Param ℓs ⟧ᵗ   PDataD.Param (DataD.applyL Desc (level ℓs)) ⟧ᵗ
    {clevel} : Levels  Level
    Carrier  :  ℓs (ps :  Param ℓs ⟧ᵗ)
              Carrierᶜˢ (PDataD.applyP (DataD.applyL Desc (level ℓs)) (param ps))
                         (clevel ℓs)
    algebra  :  {ℓs} (ps :  Param ℓs ⟧ᵗ)
              Algᶜˢ (PDataD.applyP (DataD.applyL Desc (level ℓs)) (param ps))
                     (Carrier ℓs ps)

FoldT : FoldP  Setω
FoldT P = let open FoldP P in
          (ℓs)  let open PDataD (DataD.applyL Desc (level ℓs)) in
           (ps :  FoldP.Param P ℓs ⟧ᵗ) {is :  Index (param ps) ⟧ᵗ}
          Native (level ℓs) (param ps) is  Carrier ℓs ps is

FoldNT : (P : FoldP)    let open FoldP P in
         (ℓs : Levels)  let open PDataD (DataD.applyL Desc (level ℓs)) hiding (plevel) in
         Set (alevel  ilevel  FoldP.plevel P ℓs  clevel ℓs)
FoldNT P ℓs =
  Curriedᵗ (FoldP.Param P ℓs) (FoldP.ParamV P) λ ps 
  Curriedᵗ (Index (param ps)) (constTelInfo hidden) λ is 
  Native (level ℓs) (param ps) is  Carrier ℓs ps is
  where open FoldP P
        open PDataD (DataD.applyL Desc (level ℓs))

fold-base : (P : FoldP)   {ℓs}  FoldNT P ℓs  FoldNT P ℓs
fold-base P {ℓs} rec = let open FoldP P in
  curryᵗ λ ps  curryᵗ λ is 
    algebra ps {is}
   fmapᵈ Desc  {is}  uncurryᵗ (uncurryᵗ rec ps) is)
   DataC.fromN Conv

record FoldC (P : FoldP) (f : FoldT P) : Setω where
  field
    equation : let open FoldP P in
              {ℓs ps is} (ns :  Desc ⟧ᵈ (Native (level ℓs) (param ps)) is)
              f ℓs ps (DataC.toN Conv ns)  algebra ps (fmapᵈ Desc (f ℓs ps) ns)

record IndP : Setω where
  field
    {Desc}   : DataD
    {Native} : DataT Desc
    Conv     : DataC Desc Native
    #levels  : 
  Levels : Set
  Levels = Level ^ #levels
  field
    level    : Levels  DataD.Levels Desc
    {plevel} : Levels  Level
    Param    :  ℓs  Tel (plevel ℓs)
    param    :  {ℓs}   Param ℓs ⟧ᵗ   PDataD.Param (DataD.applyL Desc (level ℓs)) ⟧ᵗ
    ParamV   :  {ℓs}  TelInfo Visibility (Param ℓs)
    ParamN   :  {ℓs}  TelInfo String     (Param ℓs)
    {clevel} : Levels  Level
    Carrier  :  ℓs (ps :  Param ℓs ⟧ᵗ)
              IndCarrierᶜˢ (PDataD.applyP (DataD.applyL Desc (level ℓs)) (param ps))
                            (Native (level ℓs) (param ps)) (clevel ℓs)
    algebra  :  {ℓs} (ps :  Param ℓs ⟧ᵗ)
              IndAlgᶜˢ (PDataD.applyP (DataD.applyL Desc (level ℓs)) (param ps))
                        (DataC.toN Conv) (Carrier ℓs ps) lzero

IndT : IndP  Setω
IndT P = let open IndP P in
         ℓs  let open PDataD (DataD.applyL Desc (level ℓs)) in
          (ps :  IndP.Param P ℓs ⟧ᵗ) {is :  Index (param ps) ⟧ᵗ}
         (n : Native (level ℓs) (param ps) is)  Carrier ℓs ps is n

IndNT : (P : IndP)     let open IndP P in
        (ℓs : Levels)  let open PDataD (DataD.applyL Desc (level ℓs)) hiding (plevel) in
        Set (alevel  ilevel  plevel ℓs  clevel ℓs)
IndNT P ℓs =
  Curriedᵗ (IndP.Param P ℓs) ParamV λ ps 
  Curriedᵗ (Index (param ps)) (constTelInfo hidden) λ is 
  (n : Native (level ℓs) (param ps) is)  Carrier ℓs ps is n
  where open IndP P
        open PDataD (DataD.applyL Desc (level ℓs))

ind-base : (P : IndP)  (∀ {ℓs}  IndNT P ℓs  IndNT P ℓs)
ind-base P {ℓs} rec = let open IndP P in
  curryᵗ λ ps  curryᵗ λ is n 
  subst (Carrier ℓs ps is) (DataC.toN-fromN Conv n)
        (algebra ps _ (ind-fmapᵈ Desc
                                  {is} 
                                    uncurryᵗ (uncurryᵗ rec ps) is)
                                 (DataC.fromN Conv n)))

record IndC (P : IndP) (f : IndT P) : Setω where
  field
    equation : let open IndP P in
              {ℓs ps is} (ns :  Desc ⟧ᵈ (Native (level ℓs) (param ps)) is)
              f _ ps (DataC.toN Conv ns)  algebra ps _ (ind-fmapᵈ Desc (f _ ps) ns)