{-# OPTIONS --cubical --safe #-}

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Function
open import Cubical.Foundations.Path
open import Cubical.Foundations.Isomorphism renaming (Iso to _≅_)
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Univalence
open import Cubical.Foundations.HLevels
open import Cubical.Functions.FunExtEquiv
open import Cubical.Data.Unit
open import Cubical.Data.Sigma
open import Cubical.Data.Nat

module Cubical.Data.W.Indexed where

open _≅_

private
  variable
    ℓX ℓS ℓP : Level

module Types {X : Type ℓX} (S : X → Type ℓS) (P : ∀ x → S x → Type ℓP) (inX : ∀ x (s : S x) → P x s → X) where
  data IW (x : X) : Type (ℓ-max ℓX (ℓ-max ℓS ℓP)) where
    node : (s : S x) → (subtree : (p : P x s) → IW (inX x s p)) → IW x

  Subtree : ∀ {x} → (s : S x) → Type (ℓ-max (ℓ-max ℓX ℓS) ℓP)
  Subtree {x} s = (p : P x s) → IW (inX x s p)

  RepIW : (x : X) → Type (ℓ-max (ℓ-max ℓX ℓS) ℓP)
  RepIW x = Σ[ s ∈ S x ] Subtree s

open Types public

module _ {X : Type ℓX} {S : X → Type ℓS} {P : ∀ x → S x → Type ℓP} {inX : ∀ x (s : S x) → P x s → X} where

  getShape : ∀ {x} → IW S P inX x → S x
  getShape (node s subtree) = s

  getSubtree : ∀ {x} → (w : IW S P inX x) → (p : P x (getShape w)) → IW S P inX (inX x (getShape w) p)
  getSubtree (node s subtree) = subtree

  wExt : ∀ {x} (w w' : IW S P inX x)
    → (ps : getShape w ≡ getShape w')
    → (pw : PathP (λ i → Subtree S P inX (ps i)) (getSubtree w) (getSubtree w'))
    → w ≡ w'
  wExt (node s subtree) (node s' subtree') ps psubtree = cong₂ node ps psubtree

  isoRepIW : (x : X) → IW S P inX x ≅ RepIW S P inX x
  fun (isoRepIW x) (node s subtree) = s , subtree
  inv (isoRepIW x) (s , subtree) = node s subtree
  rightInv (isoRepIW x) (s , subtree) = refl
  leftInv (isoRepIW x) (node s subtree) = refl

  equivRepIW : (x : X) → IW S P inX x ≃ RepIW S P inX x
  equivRepIW x = isoToEquiv (isoRepIW x)

  pathRepIW : (x : X) → IW S P inX x ≡ RepIW S P inX x
  pathRepIW x = ua (equivRepIW x)

  isPropIW : (∀ x → isProp (S x)) → ∀ x → isProp (IW S P inX x)
  isPropIW isPropS x (node s subtree) (node s' subtree') =
    cong₂ node (isPropS x s s') (toPathP (funExt λ p → isPropIW isPropS _ _ (subtree' p)))

module IWPathTypes {X : Type ℓX} (S : X → Type ℓS) (P : ∀ x → S x → Type ℓP) (inX : ∀ x (s : S x) → P x s → X) where

  --somewhat inspired by https://github.com/jashug/IWTypes , but different.

  IndexCover : Type (ℓ-max (ℓ-max ℓX ℓS) ℓP)
  IndexCover = Σ[ x ∈ X ] IW S P inX x × IW S P inX x

  ShapeCover : IndexCover → Type ℓS
  ShapeCover (x , w , w') = getShape w ≡ getShape w'

  ArityCover : ∀ xww' → ShapeCover xww' → Type ℓP
  ArityCover (x , w , w') ps = P x (getShape w')

  inXCover : ∀ xww' → (ps : ShapeCover xww') → ArityCover xww' ps → IndexCover
  inXCover (x , w , w') ps p = (inX x (getShape w') p) , (subst (Subtree S P inX) ps (getSubtree w) p , getSubtree w' p)

  Cover : ∀ {x : X} → (w w' : IW S P inX x) → Type (ℓ-max (ℓ-max ℓX ℓS) ℓP)
  Cover {x} w w' = IW ShapeCover ArityCover inXCover (x , w , w')

module IWPath {X : Type ℓX} {S : X → Type ℓS} {P : ∀ x → S x → Type ℓP} {inX : ∀ x (s : S x) → P x s → X} where
  open IWPathTypes S P inX

  isoEncode : ∀ {x} (w w' : IW S P inX x) → (w ≡ w') ≅ Cover w w'
  isoEncodeSubtree : ∀ {x} (w w' : IW S P inX x) (ps : ShapeCover (x , w , w'))
    → (PathP (λ i → Subtree S P inX (ps i)) (getSubtree w) (getSubtree w'))
       ≅
       (∀ (p : P x (getShape w')) → IW ShapeCover ArityCover inXCover (inXCover (x , w , w') ps p))

  isoEncodeSubtree w w'@(node s' subtree') ps =
    PathPIsoPath (λ i → Subtree S P inX (ps i)) (getSubtree w) (getSubtree w') ⟫
    invIso (funExtIso) ⟫
    codomainIsoDep (λ p → isoEncode _ (subtree' p))
    where _⟫_ = compIso
          infixr 10 _⟫_
  fun (isoEncode w@(node s subtree) w'@(node s' subtree')) pw =
    node (cong getShape pw) (fun (isoEncodeSubtree w w' (cong getShape pw)) (cong getSubtree pw))
  inv (isoEncode w@(node s subtree) w'@(node s' subtree')) cw@(node ps csubtree) =
    cong₂ node ps (inv (isoEncodeSubtree w w' ps) csubtree)
  rightInv (isoEncode w@(node s subtree) w'@(node s' subtree')) cw@(node ps csubtree) =
    cong (node ps) (
      fun (isoEncodeSubtree w w' ps) (inv (isoEncodeSubtree w w' ps) csubtree)
        ≡⟨ rightInv (isoEncodeSubtree w w' ps) csubtree ⟩
      csubtree ∎
    )
  leftInv (isoEncode w@(node s subtree) w'@(node s' subtree')) pw =
    cong₂ node (cong getShape pw)
      (inv (isoEncodeSubtree w w' (cong getShape pw))
        (fun (isoEncodeSubtree w w' (cong getShape pw))
          (cong getSubtree pw)
        )
      )
      ≡⟨ cong (cong₂ node (cong getShape pw)) (leftInv (isoEncodeSubtree w w' (cong getShape pw)) (cong getSubtree pw)) ⟩
    cong₂ node (cong getShape pw) (cong getSubtree pw)
      ≡⟨ flipSquare (λ i → wExt (node (getShape (pw i)) (getSubtree (pw i))) (pw i) refl refl) ⟩
    pw ∎

  encode : ∀ {x} (w w' : IW S P inX x) → w ≡ w' → Cover w w'
  encode w w' = fun (isoEncode w w')

  decode : ∀ {x} (w w' : IW S P inX x) → Cover w w' → w ≡ w'
  decode w w' = inv (isoEncode w w')

  decodeEncode : ∀ {x} (w w' : IW S P inX x) → (pw : w ≡ w') → decode w w' (encode w w' pw) ≡ pw
  decodeEncode w w' = leftInv (isoEncode w w')

  encodeDecode : ∀ {x} (w w' : IW S P inX x) → (cw : Cover w w') → encode w w' (decode w w' cw) ≡ cw
  encodeDecode w w' = rightInv (isoEncode w w')

  equivEncode : ∀ {x} (w w' : IW S P inX x) → (w ≡ w') ≃ Cover w w'
  equivEncode w w' = isoToEquiv (isoEncode w w')

  pathEncode : ∀ {x} (w w' : IW S P inX x) → (w ≡ w') ≡ Cover w w'
  pathEncode w w' = ua (equivEncode w w')

open IWPathTypes
open IWPath

isOfHLevelSuc-IW : {X : Type ℓX} {S : X → Type ℓS} {P : ∀ x → S x → Type ℓP} {inX : ∀ x (s : S x) → P x s → X} →
  (n : HLevel) → (∀ x → isOfHLevel (suc n) (S x)) → ∀ x → isOfHLevel (suc n) (IW S P inX x)
isOfHLevelSuc-IW zero isHS x = isPropIW isHS x
isOfHLevelSuc-IW (suc n) isHS x w w' =
  subst (isOfHLevel (suc n)) (λ i → pathEncode w w' (~ i))
    (isOfHLevelSuc-IW n
      (λ (y , v , v') → isHS y (getShape v) (getShape v'))
      (x , w , w')
    )