||| There are different flavours of induction-recursion. This is the one
||| introduced in Dybjer and Setzer's paper:
||| Indexed induction-recursion

module Data.InductionRecursion.DybjerSetzer

public export
data Code : (input : sort -> Type) -> (output : Type) -> Type where
  Yield  : output -> Code input output
  Store  : (payload : Type) -> (payload -> Code input output) -> Code input output
  Branch : (label : Type) -> (toSort : label -> sort) ->
           (((l : label) -> input (toSort l)) -> Code input output) ->
           Code input output

public export
DecodeType
  : Code input output -> (x : sort -> Type) ->
    (f : {s : sort} -> x s -> input s) ->
    Type
DecodeType (Yield _) x f = ()
DecodeType (Store payload k) x f = (v : payload ** DecodeType (k v) x f)
DecodeType (Branch label toSort k) x f
  = (r : ((l : label) -> x (toSort l)) ** DecodeType (k (\ l => f (r l))) x f)

public export
DecodeOutput
  : (c : Code input output) -> (x : Lazy (sort -> Type)) ->
    (f : {s : sort} -> x s -> input s) ->
    DecodeType c x f -> output
DecodeOutput (Yield o) x f _ = o
DecodeOutput (Store p k) x f (v ** d) = DecodeOutput (k v) x f d
DecodeOutput (Branch l s k) x f (r ** d) = DecodeOutput (k (\ l => f (r l))) x f d

mutual

  public export
  data Mu : (f : (s : sort) -> Code input (input s)) -> (s : sort) -> Type where
    MkMu : {f : (s : sort) -> Code input (input s)} -> {s : sort} ->
           DecodeType (f s) (Mu f) Decode -> Mu {input} f s

  public export
  Decode : {f : (s : sort) -> Code input (input s)} ->
           {s : sort} -> Mu {input} f s -> input s
  Decode (MkMu d) = DecodeOutput (f s) (Mu f) (\ d => assert_total (Decode d)) d

public export
bind : Code i o -> (o -> Code i o') -> Code i o'
bind (Yield v) f = f v
bind (Store p k) f = Store p (\ v => bind (k v) f)
bind (Branch l s k) f = Branch l s (\ r => bind (k r) f)

public export
Functor (Code i) where
  map f v = bind v (Yield . f)

public export
Applicative (Code i) where
  pure = Yield
  cf <*> co = bind cf (\ f => map (f $) co)

public export
Monad (Code i) where
  (>>=) = bind
