{-# OPTIONS --safe #-}

open import Prelude

module Codata.Chain {} (S : Type )  where

open import Data.Link S

record Chain (A : Type a) : Type ( ℓ⊔ a) where
  coinductive; constructor _◂_
  field  head  : A
         tail  : Link (Chain A)
open Chain

module _ (ψ : A  B × Link A) where
  ana     : A  Chain B
  ana′    : B × Link A  Chain B
  map-ana : Link A  Link (Chain B)

  ana = ana′  ψ

  ana′ r .head = r .fst
  ana′ r .tail = map-ana (r .snd)

  map-ana (w  x) = w  ana x
  map-ana ⟨⟩ = ⟨⟩

cout : Chain A  A × Link (Chain A)
cout = head  tail

map-chain : (A  B)  Chain A  Chain B
map-chain f = ana (map₁ f  cout)

module _ (f : A  B) (ψ : C  A × Link C) where
  map∘ana :  r  map-chain f (ana ψ r)  ana (map₁ f  ψ) r
  map∘ana′ :  r  map-ana (map₁ f  cout) (map-ana ψ r)  map-ana (map₁ f  ψ) r

  map∘ana r i .head = f (ψ r .fst)
  map∘ana r i .tail = map∘ana′ (ψ r .snd) i

  map∘ana′ (s  x) = cong (s ∝_) (map∘ana x)
  map∘ana′ ⟨⟩ = refl

module _ (R : A  A  Type c)  where
  R″ : Link A  Link A  Type ( ℓ⊔ c)
  R″ ⟨⟩ ⟨⟩ = Poly-⊤
  R″ (x  xs) (y  ys) = (x  y) × R xs ys
  R″ _ _ = Poly-⊥

  R′ : B × Link A  B × Link A  Type _
  R′ (x , xs) (y , ys) = x  y × R″ xs ys

  module _ (ψ : A  B × Link A) (p :  x y  R x y  R′ (ψ x) (ψ y)) where
    ana-bisim :  x y  R x y  ana ψ x  ana ψ y
    ana-bisim′ :  x y  R″ x y  map-ana ψ x  map-ana ψ y

    ana-bisim x y r i .head = p x y r .fst i
    ana-bisim x y r i .tail = ana-bisim′ (ψ x .snd) (ψ y .snd) (p x y r .snd) i

    ana-bisim′ (x  xs) (y  ys) (r , rs) = cong₂ _∝_ r (ana-bisim xs ys rs)
    ana-bisim′ ⟨⟩ ⟨⟩ r = refl

Chain⁺ : Type a  Type ( ℓ⊔ a)
Chain⁺ A = Link (Chain A)

tabulate  : (  Link A)  Chain⁺ A
tabulate⁺ : (  Link A)  Link A  Chain⁺ A
tabulate′ : (  Link A)  A  Chain A

tabulate  f = tabulate⁺ f (f zero)

tabulate⁺ f (s  x) = s  tabulate′ f x
tabulate⁺ f ⟨⟩ = ⟨⟩

tabulate′ f x .head = x
tabulate′ f x .tail = tabulate (f  suc)

open import Data.Nat.Order

module _ {A : Type a} where
  _≲_ : Maybe A  Maybe A  Type _
  just x   y = 
  nothing  just _ = 
  nothing  nothing = 

  isProp-≲ :  {x y}  isProp (x  y)
  isProp-≲ {fsuc x} {y} _ _ = refl
  isProp-≲ {⟨⟩} {fsuc x} ()
  isProp-≲ {⟨⟩} {⟨⟩} _ _ = refl

module _ {A : Type a} where

  index : Chain⁺ A    Link A
  index′ : S  Chain A    Link A

  index′ s xs zero    = s  head xs
  index′ s xs (suc n) = index (tail xs) n

  index ⟨⟩ _ = nothing
  index (s  xs) = index′ s xs

  final : (  Link A)  Type _
  final p =  m n  m  n  p m  p n

  final-ind :  xs  final (index xs)
  final-ind ⟨⟩ m n m≤n = tt
  final-ind (s  x) zero zero m≤n = tt
  final-ind (s  x) zero (suc n) m≤n = tt
  final-ind (s  x) (suc m) (suc n) m≤n = final-ind (tail x) m n m≤n

  isProp-final :  f  isProp (final f)
  isProp-final f = isPropΠ λ m  isPropΠ λ n  isProp→ isProp-≲

  index∘tabulate :  f  final f   n  index (tabulate f) n  f n
  index∘tabulate f p zero with f zero
  index∘tabulate f p zero | _  _ = refl
  index∘tabulate f p zero | ⟨⟩ = refl
  index∘tabulate f p (suc n) with f zero | f (suc n) | inspect f (suc n) | p zero (suc n) tt
  ... | s  x | _ |  fs  | q = index∘tabulate (f  suc)  m n  p (suc m) (suc n)) n ; fs
  ... | ⟨⟩     | ⟨⟩ | _ | q = refl

  tabulate∘index :  x  tabulate (index x)  x
  tabulate∘index′ :  s x  tabulate′ (index′ s x) (head x)  x

  tabulate∘index ⟨⟩ i = ⟨⟩
  tabulate∘index (s  x) i = s  tabulate∘index′ s x i

  tabulate∘index′ s x i .head = head x
  tabulate∘index′ s x i .tail = tabulate∘index (tail x) i

Chain⁺′ : Type a  Type _
Chain⁺′ A = Σ (  Link A) final

open import Cubical.Data.Sigma using (Σ≡Prop)

chain⁺-iso : Chain⁺ A  Chain⁺′ A
chain⁺-iso .fun xs .fst = index xs
chain⁺-iso .fun xs .snd = final-ind xs
chain⁺-iso .inv (xs , _) = tabulate xs
chain⁺-iso .rightInv (f , p) = Σ≡Prop isProp-final (funExt (index∘tabulate f p))
chain⁺-iso .leftInv = tabulate∘index

chain-iso : Chain A  (A × Chain⁺ A)
chain-iso .fun xs = xs .head , xs .tail
chain-iso .inv (x , xs) = x  xs
chain-iso .rightInv _ = refl
chain-iso .leftInv x i .head = x .head
chain-iso .leftInv x i .tail = x .tail

open import Data.Maybe.Properties

module _ (isSetS : isSet S) (isSetA : isSet A) where
  isSetChain⁺′ : isSet (Chain⁺′ A)
  isSetChain⁺′ = isSetΣ (isSetΠ  _  isOfHLevelMaybe 0 (isSet× isSetS isSetA))) λ f  isProp→isSet (isProp-final f)

  isSetChain : isSet (Chain A)
  isSetChain =
    subst
      isSet
      (isoToPath (sym-⇔ chain-iso))
      (isSet× isSetA (subst isSet (isoToPath (sym-⇔ chain⁺-iso)) isSetChain⁺′))