{-# OPTIONS --cubical --no-import-sorts --safe --experimental-lossy-unification #-}

{-
  Kan extension of a functor C → D to a functor PreShv C ℓ → PreShv D ℓ left or right adjoint to
  precomposition.
-}

module Cubical.Categories.Presheaf.KanExtension where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Function
open import Cubical.Foundations.HLevels
open import Cubical.Functions.FunExtEquiv

open import Cubical.HITs.SetQuotients

open import Cubical.Categories.Category
open import Cubical.Categories.Functor
open import Cubical.Categories.NaturalTransformation
open import Cubical.Categories.Adjoint
open import Cubical.Categories.Presheaf.Base
open import Cubical.Categories.Instances.Functors
open import Cubical.Categories.Instances.Sets

{-
  Left Kan extension of a functor C → D to a functor PreShv C ℓ → PreShv D ℓ left adjoint to precomposition.
-}

module Lan {ℓC ℓC' ℓD ℓD'} ℓS
  {C : Precategory ℓC ℓC'} {D : Precategory ℓD ℓD'}
  (F : Functor C D)
  where

  open Functor
  open NatTrans

  private
    module C = Precategory C
    module D = Precategory D

    {-
      We want the category SET ℓ we're mapping into to be large enough that the coend will take presheaves
      Cᵒᵖ → Set ℓ to presheaves Dᵒᵖ → Set ℓ, otherwise we get no adjunction with precomposition.
      So we must have ℓC,ℓC',ℓD' ≤ ℓ; the parameter ℓS allows ℓ to be larger than their maximum.
    -}
    ℓ = ℓ-max (ℓ-max (ℓ-max ℓC ℓC') ℓD') ℓS

  module _ (G : Functor (C ^op) (SET ℓ)) where

    -- Definition of the coend

    module _ (d : D.ob) where

      Raw : Type ℓ
      Raw = Σ[ c ∈ C.ob ] Σ[ g ∈ D.Hom[ d , F ⟅ c ⟆ ] ] G .F-ob c .fst

      data _≈_ : (u v : Raw) → Type ℓ where
        shift : {c c' : C.ob} (g : D.Hom[ d , F ⟅ c ⟆ ]) (f : C.Hom[ c , c' ]) (a : (G ⟅ c' ⟆) .fst)
          → (c' , (g D.⋆ F ⟪ f ⟫) , a) ≈ (c , g , (G ⟪ f ⟫) a)

      Quo = Raw / _≈_

    pattern shift/ g f a i = eq/ _ _ (shift g f a) i

    -- Action of Quo on arrows in D

    mapR : {d d' : D.ob} (h : D.Hom[ d' , d ]) → Quo d → Quo d'
    mapR h [ c , g , a ] = [ c , h D.⋆ g , a ]
    mapR h (shift/ g f a i) =
      hcomp
        (λ j → λ
          { (i = i0) → [ _ , D.⋆Assoc h g (F ⟪ f ⟫) j , a ]
          ; (i = i1) → [ _ , h D.⋆ g , (G ⟪ f ⟫) a ]
          })
        (shift/ (h D.⋆ g) f a i)
    mapR h (squash/ t u p q i j) =
      squash/ (mapR h t) (mapR h u) (cong (mapR h) p) (cong (mapR h) q) i j

    mapRId : (d : D.ob) → mapR (D.id d) ≡ (idfun _)
    mapRId d =
      funExt (elimProp (λ _ → squash/ _ _) (λ (c , g , a) i → [ c , D.⋆IdL g i , a ]))

    mapR∘ : {d d' d'' : D.ob}
      (h' : D.Hom[ d'' , d' ]) (h : D.Hom[ d' , d ])
      → mapR (h' D.⋆ h) ≡ mapR h' ∘ mapR h
    mapR∘ h' h =
      funExt (elimProp (λ _ → squash/ _ _) (λ (c , g , a) i → [ c , D.⋆Assoc h' h g i , a ]))

  LanOb : Functor (C ^op) (SET ℓ) → Functor (D ^op) (SET _)
  LanOb G .F-ob d .fst = Quo G d
  LanOb G .F-ob d .snd = squash/
  LanOb G .F-hom = mapR G
  LanOb G .F-id {d} = mapRId G d
  LanOb G .F-seq h h' = mapR∘ G h' h

  -- Action of Quo on arrows in Cᵒᵖ → Set

  module _ {G G' : Functor (C ^op) (SET ℓ)} (α : NatTrans G G') where

    mapL : (d : D.ob) → Quo G d → Quo G' d
    mapL d [ c , g , a ] = [ c , g , α .N-ob c a ]
    mapL d (shift/ g f a i) =
      hcomp
        (λ j → λ
          { (i = i0) → [ _ , (g D.⋆ F ⟪ f ⟫) , α .N-ob _ a ]
          ; (i = i1) → [ _ , g , funExt⁻ (α .N-hom f) a (~ j) ]
          })
        (shift/ g f ((α ⟦ _ ⟧) a) i)
    mapL d (squash/ t u p q i j) =
      squash/ (mapL d t) (mapL d u) (cong (mapL d) p) (cong (mapL d) q) i j

    mapLR : {d d' : D.ob} (h : D.Hom[ d' , d ])
      → mapL d' ∘ mapR G h ≡ mapR G' h ∘ mapL d
    mapLR h = funExt (elimProp (λ _ → squash/ _ _) (λ _ → refl))

  mapLId : (G : Functor (C ^op) (SET ℓ))
    (d : D.ob) → mapL (idTrans G) d ≡ idfun (Quo G d)
  mapLId G d = funExt (elimProp (λ _ → squash/ _ _) (λ _ → refl))

  mapL∘ : {G G' G'' : Functor (C ^op) (SET ℓ)}
    (β : NatTrans G' G'') (α : NatTrans G G')
    (d : D.ob) → mapL (seqTrans α β) d ≡ mapL β d ∘ mapL α d
  mapL∘ β α d = funExt (elimProp (λ _ → squash/ _ _) (λ _ → refl))

  LanHom : {G G' : Functor (C ^op) (SET ℓ)}
    → NatTrans G G' → NatTrans (LanOb G) (LanOb G')
  LanHom α .N-ob = mapL α
  LanHom α .N-hom = mapLR α

  -- Definition of the left Kan extension functor

  Lan : Functor (FUNCTOR (C ^op) (SET ℓ)) (FUNCTOR (D ^op) (SET ℓ))
  Lan .F-ob = LanOb
  Lan .F-hom = LanHom
  Lan .F-id {G} = makeNatTransPath (funExt (mapLId G))
  Lan .F-seq α β = makeNatTransPath (funExt (mapL∘ β α))

  -- Adjunction between the left Kan extension and precomposition

  private
    F* = precomposeF (SET ℓ) (F ^opF)

  open UnitCounit

  η : 𝟙⟨ FUNCTOR (C ^op) (SET ℓ) ⟩ ⇒ funcComp F* Lan
  η .N-ob G .N-ob c a = [ c , D.id _ , a ]
  η .N-ob G .N-hom {c'} {c} f =
    funExt λ a →
    [ c , D.id _ , (G ⟪ f ⟫) a ]
      ≡⟨ sym (shift/ (D.id _) f a) ⟩
    [ c' , ((D.id _) D.⋆ F ⟪ f ⟫) , a ]
      ≡[ i ]⟨ [ c' , lem i , a ] ⟩
    [ c' , (F ⟪ f ⟫ D.⋆ (D.id _)) , a ]
    ∎
    where
    lem : (D.id _) D.⋆ F ⟪ f ⟫ ≡ F ⟪ f ⟫ D.⋆ (D.id _)
    lem = D.⋆IdL (F ⟪ f ⟫) ∙ sym (D.⋆IdR (F ⟪ f ⟫))
  η .N-hom f = makeNatTransPath refl

  ε : funcComp Lan F* ⇒ 𝟙⟨ FUNCTOR (D ^op) (SET ℓ) ⟩
  ε .N-ob H .N-ob d =
    elim
      (λ _ → (H ⟅ d ⟆) .snd)
      (λ (c , g , a) → (H ⟪ g ⟫) a)
      (λ {_ _ (shift g f a) i → H .F-seq (F ⟪ f ⟫) g i a})
  ε .N-ob H .N-hom g' =
    funExt (elimProp (λ _ → (H ⟅ _ ⟆) .snd _ _) (λ (c , g , a) → funExt⁻ (H .F-seq g g') a))
  ε .N-hom {H} {H'} α =
    makeNatTransPath
      (funExt₂ λ d →
         elimProp (λ _ → (H' ⟅ _ ⟆) .snd _ _)
          (λ (c , g , a) → sym (funExt⁻ (α .N-hom g) a)))

  Δ₁ : ∀ G → seqTrans (Lan ⟪ η ⟦ G ⟧ ⟫) (ε ⟦ Lan ⟅ G ⟆ ⟧) ≡ idTrans _
  Δ₁ G =
    makeNatTransPath
      (funExt₂ λ d →
        elimProp (λ _ → squash/ _ _)
          (λ (c , g , a) →
            [ c , g D.⋆ D.id _ , a ]
              ≡[ i ]⟨ [ c , (g D.⋆ F .F-id (~ i)) , a ] ⟩
            [ c , g D.⋆ (F ⟪ C.id _ ⟫) , a ]
              ≡⟨ shift/ g (C.id _) a ⟩
            [ c , g , (G ⟪ C.id _ ⟫) a ]
              ≡[ i ]⟨ [ c , g , G .F-id i a ] ⟩
            [ c , g , a ]
            ∎))

  Δ₂ : ∀ H → seqTrans (η ⟦ F* ⟅ H ⟆ ⟧) (F* ⟪ ε ⟦ H ⟧ ⟫) ≡ idTrans _
  Δ₂ H = makeNatTransPath (funExt λ c → H .F-id)

  adj : Lan ⊣ F*
  adj = make⊣ η ε Δ₁ Δ₂

{-
  Right Kan extension of a functor C → D to a functor PreShv C ℓ → PreShv D ℓ right adjoint to precomposition.
-}

module Ran {ℓC ℓC' ℓD ℓD'} ℓS
  {C : Precategory ℓC ℓC'} {D : Precategory ℓD ℓD'}
  (F : Functor C D)
  where

  open Functor
  open NatTrans

  private
    module C = Precategory C
    module D = Precategory D

    {-
      We want the category SET ℓ we're mapping into to be large enough that the coend will take presheaves
      Cᵒᵖ → Set ℓ to presheaves Dᵒᵖ → Set ℓ, otherwise we get no adjunction with precomposition.
      So we must have ℓC,ℓC',ℓD' ≤ ℓ; the parameter ℓS allows ℓ to be larger than their maximum.
    -}
    ℓ = ℓ-max (ℓ-max (ℓ-max ℓC ℓC') ℓD') ℓS

  module _ (G : Functor (C ^op) (SET ℓ)) where

    -- Definition of the end

    record End (d : D.ob) : Type ℓ where
      field
        fun : (c : C.ob) (g : D.Hom[ F ⟅ c ⟆ , d ]) → G .F-ob c .fst
        coh : {c c' : C.ob} (f : C.Hom[ c , c' ]) (g : D.Hom[ F ⟅ c' ⟆ , d ])
          → fun c (F ⟪ f ⟫ ⋆⟨ D ⟩ g) ≡ (G ⟪ f ⟫) (fun c' g)

    open End

    end≡ : {d : D.ob} {x x' : End d} → (∀ c g → x .fun c g ≡ x' .fun c g) → x ≡ x'
    end≡ h i .fun c g = h c g i
    end≡ {_} {x} {x'} h i .coh f g =
      isSet→isSet' (G .F-ob _ .snd)
        (x .coh f g)
        (x' .coh f g)
        (h _ (F ⟪ f ⟫ ⋆⟨ D ⟩ g))
        (cong (G ⟪ f ⟫) (h _ g))
        i

    -- Action of End on arrows in D

    mapR : {d d' : D.ob} (h : D.Hom[ d' , d ]) → End d → End d'
    mapR h x .fun c g = x .fun c (g ⋆⟨ D ⟩ h)
    mapR h x .coh f g = cong (x .fun _) (D.⋆Assoc (F ⟪ f ⟫) g h) ∙ x .coh f (g ⋆⟨ D ⟩ h)

    mapRId : (d : D.ob) → mapR (D.id d) ≡ (idfun _)
    mapRId h = funExt λ x → end≡ λ c g → cong (x .fun c) (D.⋆IdR g)

    mapR∘ : {d d' d'' : D.ob}
      (h' : D.Hom[ d'' , d' ]) (h : D.Hom[ d' , d ])
      → mapR (h' D.⋆ h) ≡ mapR h' ∘ mapR h
    mapR∘ h' h = funExt λ x → end≡ λ c g → cong (x .fun c) (sym (D.⋆Assoc g h' h))

  open End

  RanOb : Functor (C ^op) (SET ℓ) → Functor (D ^op) (SET _)
  RanOb G .F-ob d .fst = End G d
  RanOb G .F-ob d .snd =
    -- We use that End is equivalent to a Σ-type to prove its HLevel more easily
    isOfHLevelRetract 2
      {B =
        Σ[ z ∈ ((c : C.ob) (g : D.Hom[ F ⟅ c ⟆ , d ]) → G .F-ob c .fst) ]
        ({c c' : C.ob} (f : C.Hom[ c , c' ]) (g : D.Hom[ F ⟅ c' ⟆ , d ])
          → z c (F ⟪ f ⟫ ⋆⟨ D ⟩ g) ≡ (G ⟪ f ⟫) (z c' g))
      }
      (λ x → λ where .fst → x .fun; .snd → x .coh)
      (λ σ → λ where .fun → σ .fst; .coh → σ .snd)
      (λ _ → refl)
      (isSetΣ
        (isSetΠ2 λ _ _ → G .F-ob _ .snd)
        (λ _ → isProp→isSet
          (isPropImplicitΠ λ _ → isPropImplicitΠ λ _ → isPropΠ2 λ _ _ → G .F-ob _ .snd _ _)))
  RanOb G .F-hom = mapR G
  RanOb G .F-id {d} = mapRId G d
  RanOb G .F-seq h h' = mapR∘ G h' h

  -- Action of End on arrows in Cᵒᵖ → Set

  module _ {G G' : Functor (C ^op) (SET ℓ)} (α : NatTrans G G') where

    mapL : (d : D.ob) → End G d → End G' d
    mapL d x .fun c g = (α ⟦ c ⟧) (x .fun c g)
    mapL d x .coh f g =
      cong (α ⟦ _ ⟧) (x .coh f g)
      ∙ funExt⁻ (α .N-hom f) (x .fun _ g)

    mapLR : {d d' : D.ob} (h : D.Hom[ d' , d ])
      → mapL d' ∘ mapR G h ≡ mapR G' h ∘ mapL d
    mapLR h = funExt λ _ → end≡ _ λ _ _ → refl

  mapLId : (G : Functor (C ^op) (SET ℓ))
    (d : D.ob) → mapL (idTrans G) d ≡ idfun (End G d)
  mapLId G d = funExt λ _ → end≡ _ λ _ _ → refl

  mapL∘ : {G G' G'' : Functor (C ^op) (SET ℓ)}
    (β : NatTrans G' G'') (α : NatTrans G G')
    (d : D.ob) → mapL (seqTrans α β) d ≡ mapL β d ∘ mapL α d
  mapL∘ β α d = funExt λ _ → end≡ _ λ _ _ → refl

  RanHom : {G G' : Functor (C ^op) (SET ℓ)}
    → NatTrans G G' → NatTrans (RanOb G) (RanOb G')
  RanHom α .N-ob = mapL α
  RanHom α .N-hom = mapLR α

  -- Definition of the right Kan extension functor

  Ran : Functor (FUNCTOR (C ^op) (SET ℓ)) (FUNCTOR (D ^op) (SET ℓ))
  Ran .F-ob = RanOb
  Ran .F-hom = RanHom
  Ran .F-id {G} = makeNatTransPath (funExt (mapLId G))
  Ran .F-seq α β = makeNatTransPath (funExt (mapL∘ β α))

  -- Adjunction between precomposition and right Kan extension

  private
    F* = precomposeF (SET ℓ) (F ^opF)

  open UnitCounit

  η : 𝟙⟨ FUNCTOR (D ^op) (SET ℓ) ⟩ ⇒ (funcComp Ran F*)
  η .N-ob G .N-ob d a .fun c g = (G ⟪ g ⟫) a
  η .N-ob G .N-ob d a .coh f g = funExt⁻ (G .F-seq g (F ⟪ f ⟫)) a
  η .N-ob G .N-hom h = funExt λ a → end≡ _ λ c g → sym (funExt⁻ (G .F-seq h g) a)
  η .N-hom {G} {G'} α =
    makeNatTransPath (funExt₂ λ d a → end≡ _ λ c g → sym (funExt⁻ (α .N-hom g) a))

  ε : funcComp F* Ran ⇒ 𝟙⟨ FUNCTOR (C ^op) (SET ℓ) ⟩
  ε .N-ob H .N-ob c x = x .fun c (D.id _)
  ε .N-ob H .N-hom {c} {c'} g =
    funExt λ x →
    cong (x .fun c') (D.⋆IdL _ ∙ sym (D.⋆IdR _)) ∙ x .coh g (D.id _)
  ε .N-hom {H} {H'} α = makeNatTransPath refl

  Δ₁ : ∀ G → seqTrans (F* ⟪ η ⟦ G ⟧ ⟫) (ε ⟦ F* ⟅ G ⟆ ⟧) ≡ idTrans _
  Δ₁ G = makeNatTransPath (funExt₂ λ c a → funExt⁻ (G .F-id) a)

  Δ₂ : ∀ H → seqTrans (η ⟦ Ran ⟅ H ⟆ ⟧) (Ran ⟪ ε ⟦ H ⟧ ⟫) ≡ idTrans _
  Δ₂ H = makeNatTransPath (funExt₂ λ c x → end≡ _ λ c' g → cong (x .fun c') (D.⋆IdL g))

  adj : F* ⊣ Ran
  adj = make⊣ η ε Δ₁ Δ₂


------------------------------------------------------------------------
-- Some substitution lemmas
------------------------------------------------------------------------

open import Atom

module Substitution (atoms : χ-atoms) where

open import Equality.Propositional
open import Prelude hiding (const)

open import Bag-equivalence equality-with-J using (_∈_)

open import Chi atoms

open χ-atoms atoms

private
  variable
    c       : Const
    e e′ e″ : Exp
    x y     : Var
    xs      : List Var

-- Some simplification lemmas for the substitution functions.

var-step-≡ : x ≡ y → var y [ x ← e ] ≡ e
var-step-≡ {x = x} {y = y} x≡y with x V.≟ y
… | yes _  = refl
… | no x≢y = ⊥-elim (x≢y x≡y)

var-step-≢ : x ≢ y → var y [ x ← e ] ≡ var y
var-step-≢ {x = x} {y = y} x≢y with x V.≟ y
… | no _    = refl
… | yes x≡y = ⊥-elim (x≢y x≡y)

lambda-step-≡ : x ≡ y → lambda y e [ x ← e′ ] ≡ lambda y e
lambda-step-≡ {x = x} {y = y} x≡y with x V.≟ y
… | yes _  = refl
… | no x≢y = ⊥-elim (x≢y x≡y)

lambda-step-≢ :
  x ≢ y → lambda y e [ x ← e′ ] ≡ lambda y (e [ x ← e′ ])
lambda-step-≢ {x = x} {y = y} x≢y with x V.≟ y
… | no _    = refl
… | yes x≡y = ⊥-elim (x≢y x≡y)

rec-step-≡ : x ≡ y → rec y e [ x ← e′ ] ≡ rec y e
rec-step-≡ {x = x} {y = y} x≡y with x V.≟ y
… | yes _  = refl
… | no x≢y = ⊥-elim (x≢y x≡y)

rec-step-≢ :
  x ≢ y → rec y e [ x ← e′ ] ≡ rec y (e [ x ← e′ ])
rec-step-≢ {x = x} {y = y} x≢y with x V.≟ y
… | no _    = refl
… | yes x≡y = ⊥-elim (x≢y x≡y)

branch-step-∈ :
  x ∈ xs →
  branch c xs e [ x ← e′ ]B ≡ branch c xs e
branch-step-∈ {x = x} {xs = xs} x∈xs with V.member x xs
… | yes _   = refl
… | no x∉xs = ⊥-elim (x∉xs x∈xs)

branch-step-∉ :
  ¬ x ∈ xs →
  branch c xs e [ x ← e′ ]B ≡ branch c xs (e [ x ← e′ ])
branch-step-∉ {x = x} {xs = xs} x∉xs with V.member x xs
… | no _     = refl
… | yes x∈xs = ⊥-elim (x∉xs x∈xs)

-- A "fusion" lemma.

mutual

  fusion : ∀ e → e [ x ← e′ ] [ x ← e″ ] ≡ e [ x ← e′ [ x ← e″ ] ]
  fusion (apply e₁ e₂) =
    cong₂ apply (fusion e₁) (fusion e₂)

  fusion {x = x} {e′ = e′} {e″ = e″} (lambda y e) with x V.≟ y
  … | yes _ = refl
  … | no _  =
    lambda y (e [ x ← e′ ] [ x ← e″ ])  ≡⟨ cong (lambda _) $ fusion e ⟩∎
    lambda y (e [ x ← e′ [ x ← e″ ] ])  ∎

  fusion (case e bs) =
    cong₂ case (fusion e) (fusion-B⋆ bs)

  fusion {x = x} {e′ = e′} {e″ = e″} (rec y e) with x V.≟ y
  … | yes _ = refl
  … | no _  =
    rec y (e [ x ← e′ ] [ x ← e″ ])  ≡⟨ cong (rec _) $ fusion e ⟩∎
    rec y (e [ x ← e′ [ x ← e″ ] ])  ∎

  fusion {x = x} {e″ = e″} (var y) with x V.≟ y
  … | yes _  = refl
  … | no x≢y =
    var y [ x ← e″ ]  ≡⟨ var-step-≢ x≢y ⟩∎
    var y             ∎

  fusion (const c es) =
    cong (const c) (fusion-⋆ es)

  fusion-B :
    ∀ b → b [ x ← e′ ]B [ x ← e″ ]B ≡ b [ x ← e′ [ x ← e″ ] ]B
  fusion-B {x = x} {e′ = e′} {e″ = e″} (branch c xs e)
    with V.member x xs
  … | yes x∈xs =
    branch c xs e [ x ← e″ ]B  ≡⟨ branch-step-∈ x∈xs ⟩∎
    branch c xs e              ∎
  … | no x∉xs =
    branch c xs (e [ x ← e′ ]) [ x ← e″ ]B  ≡⟨ branch-step-∉ x∉xs ⟩
    branch c xs (e [ x ← e′ ] [ x ← e″ ])   ≡⟨ cong (branch _ _) $ fusion e ⟩∎
    branch c xs (e [ x ← e′ [ x ← e″ ] ])   ∎

  fusion-⋆ :
    ∀ es → es [ x ← e′ ]⋆ [ x ← e″ ]⋆ ≡ es [ x ← e′ [ x ← e″ ] ]⋆
  fusion-⋆ []       = refl
  fusion-⋆ (e ∷ es) = cong₂ _∷_ (fusion e) (fusion-⋆ es)

  fusion-B⋆ :
    ∀ bs → bs [ x ← e′ ]B⋆ [ x ← e″ ]B⋆ ≡ bs [ x ← e′ [ x ← e″ ] ]B⋆
  fusion-B⋆ []       = refl
  fusion-B⋆ (b ∷ bs) = cong₂ _∷_ (fusion-B b) (fusion-B⋆ bs)


{- Formal verification of authenticated append-only skiplists in Agda, version 1.0.

   Copyright (c) 2021 Victor C Miraldo and Oracle and/or its affiliates.
   Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.oracle.com/licenses/upl
-}
open import Data.Unit.NonEta
open import Data.Empty
open import Data.Sum
open import Data.Product
open import Data.Product.Properties
open import Data.Fin hiding (_<_; _≤_)
open import Data.Fin.Properties using () renaming (_≟_ to _≟Fin_)
open import Data.Nat renaming (_≟_ to _≟ℕ_; _≤?_ to _≤?ℕ_)
open import Data.Nat.Properties
open import Data.List renaming (map to List-map)
open import Data.List.Properties using (∷-injective; length-map)
open import Data.List.Relation.Unary.Any renaming (map to Any-map)
open import Data.List.Relation.Unary.All renaming (lookup to All-lookup; map to All-map)
open import Data.List.Relation.Unary.All.Properties hiding (All-map)
open import Data.List.Relation.Unary.Any.Properties renaming (map⁺ to Any-map⁺)
open import Data.List.Relation.Binary.Pointwise using (decidable-≡)
open import Data.Bool hiding (_<_; _≤_)
open import Data.Maybe renaming (map to Maybe-map)
open import Function

open import Relation.Binary.PropositionalEquality
open import Relation.Binary.Definitions
open import Relation.Nullary

open import AAOSL.Lemmas
open import AAOSL.Abstract.Hash
open import AAOSL.Abstract.DepRel

module AAOSL.Abstract.EvoCR
   -- A Hash function maps a bytestring into a hash.
   (hash    : ByteString → Hash)
   -- And is collision resistant
   (hash-cr : ∀{x y} → hash x ≡ hash y → Collision hash x y ⊎ x ≡ y)

   -- Indexes can be encoded in an injective way
   (encodeI     : ℕ → ByteString)
   (encodeI-inj : (m n : ℕ) → encodeI m ≡ encodeI n → m ≡ n)

   (dep  : DepRel)
 where

  open WithCryptoHash hash hash-cr
  open import AAOSL.Abstract.Advancement hash hash-cr encodeI encodeI-inj dep
  open DepRel dep

  -- Returns the last element on path a that is smaller than k
  last-bef : ∀{j i k}(a : AdvPath j i)(i<k : i < k)(k≤j : k ≤′ j) → ℕ
  last-bef {j} a i<k ≤′-refl = j
  -- TODO-1 : The same or similar proof is repeated numerous times below; refactor for clarity
  last-bef AdvDone i<k (≤′-step k≤j) = ⊥-elim (1+n≰n (≤-unstep (≤-trans i<k (≤′⇒≤ k≤j))))
  last-bef {k = k} (AdvThere d h a) i<k (≤′-step k≤j)
    with hop-tgt h ≤?ℕ k
  ...| yes th≤k = hop-tgt h
  ...| no th>k = last-bef a i<k (≤⇒≤′ (≰⇒≥ th>k))

  last-bef-correct : ∀{j i k}(a : AdvPath j i)(i<k : i < k)(k≤j : k ≤′ j)
                   → last-bef a i<k k≤j ∈AP a
  last-bef-correct {j} a i<k ≤′-refl = ∈AP-src
  last-bef-correct AdvDone i<k (≤′-step k≤j) = ⊥-elim (1+n≰n (≤-unstep (≤-trans i<k (≤′⇒≤ k≤j))))
  last-bef-correct {k = k} (AdvThere d h a) i<k (≤′-step k≤j)
    with hop-tgt h ≤?ℕ k
  ...| yes th≤k = step (<⇒≢ (hop-< h)) ∈AP-src
  ...| no th>k
    with last-bef-correct a i<k (≤⇒≤′ (≰⇒≥ th>k))
  ...| ind = step (<⇒≢ (≤-trans (s≤s (∈AP-≤ ind)) (hop-< h))) ind

  lemma5-hop : ∀{j i}(a : AdvPath j i)
             → ∀{k} → j < k
             → (h : HopFrom k) → hop-tgt h ≤ j → i ≤ hop-tgt h → hop-tgt h ∈AP a
  lemma5-hop {j} a j<k h th≤j i≤th
    with hop-tgt h ≟ℕ j
  ...| yes th≡j rewrite th≡j = ∈AP-src
  ...| no th≢j
    with a
  ...| AdvDone rewrite ≤-antisym th≤j i≤th = hereTgtDone
  ...| (AdvThere x h' a')
    with hop-tgt h' ≟ℕ hop-tgt h
  ...| yes th'≡th rewrite sym th'≡th = step (<⇒≢ (hop-< h')) ∈AP-src
  ...| no th'≢th
    with hop-tgt h' ≤?ℕ hop-tgt h
  ...| yes th'≤th = ⊥-elim (1+n≰n (≤-trans j<k (hops-nested-or-nonoverlapping (≤∧≢⇒< th'≤th th'≢th) (≤∧≢⇒< th≤j th≢j))))
  ...| no th'>th = step th≢j (lemma5-hop a' (≤-trans (hop-< h') (≤-unstep j<k)) h (≰⇒≥ th'>th) i≤th)

  lemma5 : ∀{j i k}(a : AdvPath j i)(i<k : i < k)(k≤j : k ≤′ j)
         → ∀{i₀}(b : AdvPath k i₀) → i₀ ≤ i
         → last-bef a i<k k≤j ∈AP b
  lemma5 a i<k ≤′-refl b i₀≤i = ∈AP-src
  lemma5 AdvDone i<k (≤′-step k≤j) b i₀≤i = ⊥-elim (1+n≰n (≤-unstep (≤-trans i<k (≤′⇒≤ k≤j))))
  lemma5 {k = k} (AdvThere d h a) i<k (≤′-step k≤j) b i₀≤i
       with hop-tgt h ≤?ℕ k
  ...| yes th≤k = lemma5-hop b (s≤s (≤′⇒≤ k≤j)) h th≤k (≤-trans i₀≤i (lemma1 a))
  ...| no th>k = lemma5 a i<k (≤⇒≤′ (≰⇒≥ th>k)) b i₀≤i

  -- returns the first element on path a that is greather than k
  first-aft : ∀{j i k}(a : AdvPath j i)(i≤k : i ≤′ k)(k<j : k < j) → ℕ
  first-aft {i = i} a ≤′-refl k<j = i
  first-aft AdvDone (≤′-step i≤k) k<j = ⊥-elim (1+n≰n (≤-unstep (≤-trans k<j (≤′⇒≤ i≤k))))
  first-aft {j} {i} {k} (AdvThere d h a) (≤′-step i≤k) k<j
    with hop-tgt h ≟ℕ k
  ...| yes _ = k
  ...| no th≢k
    with hop-tgt h ≤?ℕ k
  ...| yes th≤k = j
  ...| no th≥k = first-aft a (≤′-step i≤k) (≰⇒> th≥k)

  first-aft-correct : ∀{j i k}(a : AdvPath j i)(i≤k : i ≤′ k)(k<j : k < j)
                    → first-aft a i≤k k<j ∈AP a
  first-aft-correct a ≤′-refl k<j = ∈AP-tgt
  first-aft-correct AdvDone (≤′-step i≤k) k<j = ⊥-elim (1+n≰n (≤-unstep (≤-trans k<j (≤′⇒≤ i≤k))))
  first-aft-correct {j} {i} {k} (AdvThere d h a) (≤′-step i≤k) k<j
    with hop-tgt h ≟ℕ k
  ...| yes th≡k rewrite sym th≡k = step (<⇒≢ k<j) ∈AP-src
  ...| no th≢k
    with hop-tgt h ≤?ℕ k
  ...| yes th≤k = ∈AP-src
  ...| no th≥k
    with first-aft-correct a (≤′-step i≤k) (≰⇒> th≥k)
  ...| ind = step (<⇒≢ (≤-trans (s≤s (∈AP-≤ ind)) (hop-< h))) ind

  lemma5'-hop
    : ∀{j j₁ k}(h : HopFrom j)
    → hop-tgt h < k → k < j → (b : AdvPath j₁ k) → j ≤ j₁ → j ∈AP b
  lemma5'-hop {j} {j₁} h th<k k≤j b j≤j₁
    with j ≟ℕ j₁
  ...| yes refl = ∈AP-src
  ...| no j≢j₁
    with b
  ...| AdvDone = ⊥-elim (1+n≰n (≤-trans k≤j j≤j₁))
  ...| (AdvThere x hb b')
    with hop-tgt hb ≟ℕ j
  ...| yes refl = step (<⇒≢ (hop-< hb)) ∈AP-src
  ...| no tb≢j
    with hop-tgt hb ≤?ℕ j
  ...| no tb≰j = step j≢j₁ (lemma5'-hop h th<k k≤j b' (≰⇒≥ tb≰j))
  ...| yes tb≤j
    with hops-nested-or-nonoverlapping (≤-trans th<k (lemma1 b')) (≤∧≢⇒< tb≤j tb≢j)
  ...| j₁≤j rewrite ≤-antisym j≤j₁ j₁≤j = ∈AP-src

  lemma5' : ∀{j i k}(a : AdvPath j i)(i≤k : i ≤′ k)(k<j : k < j)
          → ∀{j₁}(b : AdvPath j₁ k) → j ≤ j₁
          → first-aft a i≤k k<j ∈AP b
  lemma5' a ≤′-refl k<j b j≤j₁ = ∈AP-tgt
  lemma5' AdvDone (≤′-step i≤k) k<j b j≤j₁ = ⊥-elim (1+n≰n (≤-unstep (≤-trans k<j (≤′⇒≤ i≤k))))
  lemma5' {j} {i} {k} (AdvThere d h a) (≤′-step i≤k) k<j b j≤j₁
    with hop-tgt h ≟ℕ k
  ...| yes _ = ∈AP-tgt
  ...| no th≢k
    with hop-tgt h ≤?ℕ k
  ...| yes th≤k = lemma5'-hop h (≤∧≢⇒< th≤k th≢k) k<j b j≤j₁
  ...| no th≥k = lemma5' a (≤′-step i≤k) (≰⇒> th≥k) b (≤-unstep (≤-trans (hop-< h) j≤j₁))

  ∈AP-⊕-intro-l : ∀{j k i m}
                → {a₂  : AdvPath j k}{a₁ : AdvPath k i}
                → m ∈AP a₂
                → m ∈AP (a₂ ⊕ a₁)
  ∈AP-⊕-intro-l hereTgtThere = hereTgtThere
  ∈AP-⊕-intro-l (step prog m∈a) = step prog (∈AP-⊕-intro-l m∈a)
  ∈AP-⊕-intro-l {a₁ = AdvDone} hereTgtDone = hereTgtDone
  ∈AP-⊕-intro-l {a₁ = AdvThere d h a} hereTgtDone = hereTgtThere

  ∈AP-⊕-intro-r : ∀{j k i m}
                → {a₂  : AdvPath j k}{a₁ : AdvPath k i}
                → m ∈AP a₁
                → m ∈AP (a₂ ⊕ a₁)
  ∈AP-⊕-intro-r {a₂ = AdvDone} hyp = hyp
  ∈AP-⊕-intro-r {k = k} {a₂ = AdvThere d h a} hyp =
      step (<⇒≢ (≤-trans (s≤s (∈AP-≤ hyp)) (≤-trans (s≤s (lemma1 a)) (hop-< h))))
           (∈AP-⊕-intro-r {a₂ = a} hyp)

  ∈AP-⊕-≤-r : ∀{j k i m}{a₂  : AdvPath j k}{a₁ : AdvPath k i}
            → m ∈AP (a₂ ⊕ a₁)
            → m ≤ k
            → m ∈AP a₁
  ∈AP-⊕-≤-r {a₂ = AdvDone} m∈a12 m≤k = m∈a12
  ∈AP-⊕-≤-r {a₂ = AdvThere d h a₂} hereTgtThere m≤k = ⊥-elim (1+n≰n (≤-trans (≤-trans (s≤s (lemma1 a₂)) (hop-< h)) m≤k))
  ∈AP-⊕-≤-r {a₂ = AdvThere d h a₂} (step x m∈a12) m≤k
    = ∈AP-⊕-≤-r m∈a12 m≤k

  findM : ∀ {j i₂ s₁ s₂ tgt}
          → (a₁₁ : AdvPath j s₁)
          → (a₂₁ : AdvPath j s₂)
          → (a₂₂ : AdvPath s₂ i₂)
          → (m₂ : MembershipProof s₂ tgt)
          → i₂ ≤ s₁
          → tgt ≤ s₁
          → s₁ ≤ s₂
          → ∃[ M ] (M ∈AP a₂₂ × M ∈AP mbr-proof m₂ × M ∈AP a₁₁)
  findM {s₁ = s₁} {s₂} a₁₁ a₂₁ a₂₂ m₂ i₂≤s₁ t≤s₁ s₁≤s₂
     with <-cmp s₁ s₂
  ...| tri> _ _ s₂<s₁ = ⊥-elim (<⇒≢ s₂<s₁ (sym (≤-antisym s₁≤s₂ (≤-unstep s₂<s₁))))
  ...| tri≈ _ refl _  = s₁ , ∈AP-src , ∈AP-src , ∈AP-tgt
  ...| tri< s₁<s₂ _ _ = last-bef a₁₁ s₁<s₂ (≤⇒≤′ (lemma1 a₂₁))
                      , lemma5 a₁₁ s₁<s₂ (≤⇒≤′ (lemma1 a₂₁)) a₂₂ i₂≤s₁
                      , lemma5 a₁₁ s₁<s₂ (≤⇒≤′ (lemma1 a₂₁)) (mbr-proof m₂) t≤s₁
                      , last-bef-correct a₁₁ s₁<s₂ (≤⇒≤′ (lemma1 a₂₁))

  findR : ∀{j i₁ s₁ s₂ tgt}
          → (a₁₁ : AdvPath j s₁)
          → (a₁₂ : AdvPath s₁ i₁)
          → (a₂₁ : AdvPath j s₂)
          → (m₁ : MembershipProof s₁ tgt)(m₂ : MembershipProof s₂ tgt)
          → i₁ ≤ tgt
          → tgt ≤ s₁
          → s₁ ≤ s₂ -- wlog
          → ∃[ R ] (R ∈AP mbr-proof m₁ × R ∈AP mbr-proof m₂ × R ∈AP a₁₂)
  findR {s₁ = s₁} {tgt = tgt} a₁₁ a₁₂ a₂₁ m₁ m₂ i₁≤t t≤s₁ s₁≤s₂
    with <-cmp tgt s₁
  ...| tri> _ _ s₁<t = ⊥-elim (<⇒≢ s₁<t (sym (≤-antisym t≤s₁ (≤-unstep s₁<t))))
  ...| tri≈ _ refl _ = s₁ , ∈AP-src , ∈AP-tgt , ∈AP-src
  ...| tri< t<s₁ _ _ = first-aft a₁₂ (≤⇒≤′ i₁≤t) t<s₁
                       , lemma5' a₁₂ (≤⇒≤′ i₁≤t) t<s₁ (mbr-proof m₁) ≤-refl
                       , lemma5' a₁₂ (≤⇒≤′ i₁≤t) t<s₁ (mbr-proof m₂) s₁≤s₂
                       , first-aft-correct a₁₂ (≤⇒≤′ i₁≤t) t<s₁

  -- check Figure 4 (page 12) in: https://arxiv.org/pdf/cs/0302010.pdf
  --
  -- a₁ is dashed black line
  -- a₂ is dashed gray line
  -- m₁ is thick black line
  -- m₂ is thick gray line
  -- s₁ is j
  -- s₂ is k
  -- j is n
  -- tgt is i
  evo-cr : ∀{j i₁ i₂}{t₁ t₂ : View}
          → (a₁ : AdvPath j i₁)
          → (a₂ : AdvPath j i₂)
          → rebuild a₁ t₁ j ≡ rebuild a₂ t₂ j
          → ∀{s₁ s₂ tgt}{u₁ u₂ : View}
          → (m₁ : MembershipProof s₁ tgt)(m₂ : MembershipProof s₂ tgt)
          → s₁ ∈AP a₁ → s₂ ∈AP a₂
          → s₁ ≤ s₂ -- wlog
          → i₁ ≤ tgt
          → i₂ ≤ tgt
          → rebuildMP m₁ u₁ s₁ ≡ rebuild a₁ t₁ s₁
          → rebuildMP m₂ u₂ s₂ ≡ rebuild a₂ t₂ s₂
          → HashBroke ⊎ (mbr-datum m₁ ≡ mbr-datum m₂)
  evo-cr {t₁ = t₁} {t₂} a₁ a₂ hyp {s₁} {s₂} {tgt} {u₁} {u₂}
      m₁ m₂ s₁∈a₁ s₂∈a₂ s₁≤s₂ i₁≤t i₂≤t c₁ c₂
    with ∈AP-cut a₁ s₁∈a₁ | ∈AP-cut a₂ s₂∈a₂
  ...| ((a₁₁ , a₁₂) , refl) | ((a₂₁ , a₂₂) , refl)
    with lemma1 (mbr-proof m₁)
  ...| t≤s₁
  -- The first part of the proof is find some points common to three
  -- of the provided proofs. This is given in Figure 4 of Maniatis and Baker,
  -- and they are called M and R too, to help make it at least a little clear.
  -- First we find a point that belongs in a₂, m₁ and a₁.
    with findM a₁₁ a₂₁ a₂₂ m₂ (≤-trans i₂≤t t≤s₁) t≤s₁ s₁≤s₂
  ...| M , M∈a₂₂ , M∈m₂ , M∈a₁₁
  -- Next, we find a point that belongs in m₁, m₂ and a₁.
    with findR a₁₁ a₁₂ a₂₁ m₁ m₂ i₁≤t t≤s₁ s₁≤s₂
  ...| R , R∈m₁ , R∈m₂ , R∈a₁₂
  -- Now, since a₁ and a₂ rebuild to the same hash and M belongs
  -- to both these proofs, the hash for M is the same.
    with AgreeOnCommon a₁ a₂ hyp (∈AP-⊕-intro-l M∈a₁₁) (∈AP-⊕-intro-r M∈a₂₂)
  ...| inj₁ hb = inj₁ hb
  ...| inj₂ M-a1a2
  -- Similarly, for a₂₂ and m₂
    with AgreeOnCommon a₂₂ (mbr-proof m₂) (trans (sym (rebuild-⊕ a₂₁ a₂₂ ∈AP-src)) (sym c₂)) M∈a₂₂ M∈m₂
  ...| inj₁ hb = inj₁ hb
  ...| inj₂ M-a22m2
  -- Which brings us to: rebuild a1 M == rebuild m2 M
    with trans (trans M-a1a2 (rebuild-⊕ a₂₁ a₂₂ M∈a₂₂)) M-a22m2
  ...| M-a1m2
  -- If a1 and m2 agree on one point, they agree on all points. In particular, they
  -- agree on R!
    with ∈AP-cut (mbr-proof m₂) M∈m₂
  ...| ((m₂₁ , m₂₂) , refl)
    with trans M-a1m2 (rebuild-⊕ m₂₁ m₂₂ ∈AP-src)
  ...| M-a1m22
    with AgreeOnCommon-∈ a₁ m₂₂ (∈AP-⊕-intro-l M∈a₁₁) M-a1m22
           (∈AP-⊕-intro-r R∈a₁₂) (∈AP-⊕-≤-r R∈m₂ (≤-trans (∈AP-≤ R∈a₁₂) (∈AP-≥ M∈a₁₁)))
  ...| inj₁ hb = inj₁ hb
  ...| inj₂ R-a1m22
    with AgreeOnCommon a₁₂ (mbr-proof m₁) (trans (sym (rebuild-⊕ a₁₁ a₁₂ ∈AP-src)) (sym c₁)) R∈a₁₂ R∈m₁
  ...| inj₁ hb = inj₁ hb
  ...| inj₂ R-a12m1
  -- Which finally lets us argue that m1 and m2 also agree on R. Similarly, if they agree
  -- on one point they agree on all points.
    with ∈AP-cut (mbr-proof m₁) R∈m₁
  ...| ((m₁₁ , m₁₂) , refl)
    with trans (trans (trans (sym R-a1m22) (rebuild-⊕ a₁₁ a₁₂ R∈a₁₂)) R-a12m1) (rebuild-⊕ m₁₁ m₁₂ ∈AP-src)
  ...| R-m22m12
    with AgreeOnCommon-∈ m₂₂ m₁₂ (∈AP-⊕-≤-r R∈m₂ (≤-trans (∈AP-≤ R∈a₁₂) (∈AP-≥ M∈a₁₁))) R-m22m12 ∈AP-tgt ∈AP-tgt
  ...| inj₁ hb = inj₁ hb
  ...| inj₂ tgt-m22m12
    with trans (rebuild-⊕ m₂₁ m₂₂ ∈AP-tgt) (trans tgt-m22m12 (sym (rebuild-⊕ m₁₁ m₁₂ ∈AP-tgt)))
  ...| tgt-m1m2 with rebuild-tgt-lemma (mbr-proof m₁)
                       {u₁ ∪₁ (tgt , auth tgt (mbr-datum m₁) u₁) }
                   | rebuild-tgt-lemma (mbr-proof m₂)
                       {u₂ ∪₁ (tgt , auth tgt (mbr-datum m₂) u₂) }
  ...| l1 | l2
    with trans (sym l1) (trans (sym tgt-m1m2) l2)
  ...| auths≡
    rewrite ≟ℕ-refl tgt = auth-inj-1 {tgt} {mbr-datum m₁} {mbr-datum m₂} (mbr-not-init m₁) auths≡


------------------------------------------------------------------------
-- The Agda standard library
--
-- Non-empty lists
------------------------------------------------------------------------

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

module Data.List.NonEmpty where

open import Category.Monad
open import Data.Bool.Base using (Bool; false; true; not; T)
open import Data.Bool.Properties
open import Data.List as List using (List; []; _∷_)
open import Data.Maybe.Base using (Maybe ; nothing; just)
open import Data.Nat as Nat
open import Data.Product as Prod using (∃; _×_; proj₁; proj₂; _,_; -,_)
open import Data.These as These using (These; this; that; these)
open import Data.Sum as Sum using (_⊎_; inj₁; inj₂)
open import Data.Unit
open import Data.Vec as Vec using (Vec; []; _∷_)
open import Function
open import Function.Equality using (_⟨$⟩_)
open import Function.Equivalence
  using () renaming (module Equivalence to Eq)
open import Relation.Binary.PropositionalEquality as P using (_≡_; refl)
open import Relation.Nullary.Decidable using (⌊_⌋)

------------------------------------------------------------------------
-- Non-empty lists

infixr 5 _∷_

record List⁺ {a} (A : Set a) : Set a where
  constructor _∷_
  field
    head : A
    tail : List A

open List⁺ public

-- Basic combinators

module _ {a} {A : Set a} where

  uncons : List⁺ A → A × List A
  uncons (hd ∷ tl) = hd , tl

  [_] : A → List⁺ A
  [ x ] = x ∷ []

  infixr 5 _∷⁺_

  _∷⁺_ : A → List⁺ A → List⁺ A
  x ∷⁺ y ∷ xs = x ∷ y ∷ xs

  length : List⁺ A → ℕ
  length (x ∷ xs) = suc (List.length xs)

------------------------------------------------------------------------
-- Conversion

module _ {a} {A : Set a} where

  toList : List⁺ A → List A
  toList (x ∷ xs) = x ∷ xs

  fromList : List A → Maybe (List⁺ A)
  fromList []       = nothing
  fromList (x ∷ xs) = just (x ∷ xs)

  fromVec : ∀ {n} → Vec A (suc n) → List⁺ A
  fromVec (x ∷ xs) = x ∷ Vec.toList xs

  toVec : (xs : List⁺ A) → Vec A (length xs)
  toVec (x ∷ xs) = x ∷ Vec.fromList xs

lift : ∀ {a b} {A : Set a} {B : Set b} →
       (∀ {m} → Vec A (suc m) → ∃ λ n → Vec B (suc n)) →
       List⁺ A → List⁺ B
lift f xs = fromVec (proj₂ (f (toVec xs)))

------------------------------------------------------------------------
-- Other operations

map : ∀ {a b} {A : Set a} {B : Set b} → (A → B) → List⁺ A → List⁺ B
map f (x ∷ xs) = (f x ∷ List.map f xs)

-- Right fold. Note that s is only applied to the last element (see
-- the examples below).

foldr : ∀ {a b} {A : Set a} {B : Set b} →
        (A → B → B) → (A → B) → List⁺ A → B
foldr {A = A} {B} c s (x ∷ xs) = foldr′ x xs
  where
  foldr′ : A → List A → B
  foldr′ x []       = s x
  foldr′ x (y ∷ xs) = c x (foldr′ y xs)

-- Right fold.

foldr₁ : ∀ {a} {A : Set a} → (A → A → A) → List⁺ A → A
foldr₁ f = foldr f id

-- Left fold. Note that s is only applied to the first element (see
-- the examples below).

foldl : ∀ {a b} {A : Set a} {B : Set b} →
        (B → A → B) → (A → B) → List⁺ A → B
foldl c s (x ∷ xs) = List.foldl c (s x) xs

-- Left fold.

foldl₁ : ∀ {a} {A : Set a} → (A → A → A) → List⁺ A → A
foldl₁ f = foldl f id

-- Append (several variants).

module _ {a} {A : Set a} where

  infixr 5 _⁺++⁺_ _++⁺_ _⁺++_

  _⁺++⁺_ : List⁺ A → List⁺ A → List⁺ A
  (x ∷ xs) ⁺++⁺ (y ∷ ys) = x ∷ (xs List.++ y ∷ ys)

  _⁺++_ : List⁺ A → List A → List⁺ A
  (x ∷ xs) ⁺++ ys = x ∷ (xs List.++ ys)

  _++⁺_ : List A → List⁺ A → List⁺ A
  xs ++⁺ ys = List.foldr _∷⁺_ ys xs

  concat : List⁺ (List⁺ A) → List⁺ A
  concat (xs ∷ xss) = xs ⁺++ List.concat (List.map toList xss)

concatMap : ∀ {a b} {A : Set a} {B : Set b} → (A → List⁺ B) → List⁺ A → List⁺ B
concatMap f = concat ∘′ map f

-- Reverse

reverse : ∀ {a} {A : Set a} → List⁺ A → List⁺ A
reverse = lift (-,_ ∘′ Vec.reverse)

-- Align and Zip

module _ {a b c} {A : Set a} {B : Set b} {C : Set c} where

  alignWith : (These A B → C) → List⁺ A → List⁺ B → List⁺ C
  alignWith f (a ∷ as) (b ∷ bs) = f (these a b) ∷ List.alignWith f as bs

  zipWith : (A → B → C) → List⁺ A → List⁺ B → List⁺ C
  zipWith f (a ∷ as) (b ∷ bs) = f a b ∷ List.zipWith f as bs

  unalignWith : (A → These B C) → List⁺ A → These (List⁺ B) (List⁺ C)
  unalignWith f = foldr (These.alignWith mcons mcons ∘′ f)
                        (These.map [_] [_] ∘′ f)

    where mcons : ∀ {e} {E : Set e} → These E (List⁺ E) → List⁺ E
          mcons = These.fold [_] id _∷⁺_

  unzipWith : (A → B × C) → List⁺ A → List⁺ B × List⁺ C
  unzipWith f (a ∷ as) = Prod.zip _∷_ _∷_ (f a) (List.unzipWith f as)

module _ {a b} {A : Set a} {B : Set b} where

  align : List⁺ A → List⁺ B → List⁺ (These A B)
  align = alignWith id

  zip : List⁺ A → List⁺ B → List⁺ (A × B)
  zip = zipWith _,_

  unalign : List⁺ (These A B) → These (List⁺ A) (List⁺ B)
  unalign = unalignWith id

  unzip : List⁺ (A × B) → List⁺ A × List⁺ B
  unzip = unzipWith id

-- Snoc.

infixl 5 _∷ʳ_ _⁺∷ʳ_

_∷ʳ_ : ∀ {a} {A : Set a} → List A → A → List⁺ A
[]       ∷ʳ y = [ y ]
(x ∷ xs) ∷ʳ y = x ∷ (xs List.∷ʳ y)

_⁺∷ʳ_ : ∀ {a} {A : Set a} → List⁺ A → A → List⁺ A
xs ⁺∷ʳ x = toList xs ∷ʳ x

-- A snoc-view of non-empty lists.

infixl 5 _∷ʳ′_

data SnocView {a} {A : Set a} : List⁺ A → Set a where
  _∷ʳ′_ : (xs : List A) (x : A) → SnocView (xs ∷ʳ x)

snocView : ∀ {a} {A : Set a} (xs : List⁺ A) → SnocView xs
snocView (x ∷ xs)              with List.initLast xs
snocView (x ∷ .[])             | []            = []       ∷ʳ′ x
snocView (x ∷ .(xs List.∷ʳ y)) | xs List.∷ʳ' y = (x ∷ xs) ∷ʳ′ y

-- The last element in the list.

last : ∀ {a} {A : Set a} → List⁺ A → A
last xs with snocView xs
last .(ys ∷ʳ y) | ys ∷ʳ′ y = y

-- Groups all contiguous elements for which the predicate returns the
-- same result into lists.

split : ∀ {a} {A : Set a}
        (p : A → Bool) → List A →
        List (List⁺ (∃ (T ∘ p)) ⊎ List⁺ (∃ (T ∘ not ∘ p)))
split p []       = []
split p (x ∷ xs) with p x | P.inspect p x | split p xs
... | true  | P.[ px≡t ] | inj₁ xs′ ∷ xss = inj₁ ((x , Eq.from T-≡     ⟨$⟩ px≡t) ∷⁺ xs′) ∷ xss
... | true  | P.[ px≡t ] | xss            = inj₁ [ x , Eq.from T-≡     ⟨$⟩ px≡t ]        ∷ xss
... | false | P.[ px≡f ] | inj₂ xs′ ∷ xss = inj₂ ((x , Eq.from T-not-≡ ⟨$⟩ px≡f) ∷⁺ xs′) ∷ xss
... | false | P.[ px≡f ] | xss            = inj₂ [ x , Eq.from T-not-≡ ⟨$⟩ px≡f ]        ∷ xss

-- If we flatten the list returned by split, then we get the list we
-- started with.

flatten : ∀ {a p q} {A : Set a} {P : A → Set p} {Q : A → Set q} →
          List (List⁺ (∃ P) ⊎ List⁺ (∃ Q)) → List A
flatten = List.concat ∘
          List.map Sum.[ toList ∘ map proj₁ , toList ∘ map proj₁ ]

flatten-split :
  ∀ {a} {A : Set a}
  (p : A → Bool) (xs : List A) → flatten (split p xs) ≡ xs
flatten-split p []       = refl
flatten-split p (x ∷ xs)
  with p x | P.inspect p x | split p xs | flatten-split p xs
... | true  | P.[ _ ] | []         | hyp = P.cong (_∷_ x) hyp
... | true  | P.[ _ ] | inj₁ _ ∷ _ | hyp = P.cong (_∷_ x) hyp
... | true  | P.[ _ ] | inj₂ _ ∷ _ | hyp = P.cong (_∷_ x) hyp
... | false | P.[ _ ] | []         | hyp = P.cong (_∷_ x) hyp
... | false | P.[ _ ] | inj₁ _ ∷ _ | hyp = P.cong (_∷_ x) hyp
... | false | P.[ _ ] | inj₂ _ ∷ _ | hyp = P.cong (_∷_ x) hyp

-- Groups all contiguous elements /not/ satisfying the predicate into
-- lists. Elements satisfying the predicate are dropped.

wordsBy : ∀ {a} {A : Set a} → (A → Bool) → List A → List (List⁺ A)
wordsBy p =
  List.mapMaybe Sum.[ const nothing , just ∘′ map proj₁ ] ∘ split p

------------------------------------------------------------------------
-- Examples

-- Note that these examples are simple unit tests, because the type
-- checker verifies them.

private
 module Examples {A B : Set}
                 (_⊕_ : A → B → B)
                 (_⊗_ : B → A → B)
                 (_⊙_ : A → A → A)
                 (f : A → B)
                 (a b c : A)
                 where

  hd : head (a ∷⁺ b ∷⁺ [ c ]) ≡ a
  hd = refl

  tl : tail (a ∷⁺ b ∷⁺ [ c ]) ≡ b ∷ c ∷ []
  tl = refl

  mp : map f (a ∷⁺ b ∷⁺ [ c ]) ≡ f a ∷⁺ f b ∷⁺ [ f c ]
  mp = refl

  right : foldr _⊕_ f (a ∷⁺ b ∷⁺ [ c ]) ≡ (a ⊕ (b ⊕ f c))
  right = refl

  right₁ : foldr₁ _⊙_ (a ∷⁺ b ∷⁺ [ c ]) ≡ (a ⊙ (b ⊙ c))
  right₁ = refl

  left : foldl _⊗_ f (a ∷⁺ b ∷⁺ [ c ]) ≡ ((f a ⊗ b) ⊗ c)
  left = refl

  left₁ : foldl₁ _⊙_ (a ∷⁺ b ∷⁺ [ c ]) ≡ ((a ⊙ b) ⊙ c)
  left₁ = refl

  ⁺app⁺ : (a ∷⁺ b ∷⁺ [ c ]) ⁺++⁺ (b ∷⁺ [ c ]) ≡
          a ∷⁺ b ∷⁺ c ∷⁺ b ∷⁺ [ c ]
  ⁺app⁺ = refl

  ⁺app : (a ∷⁺ b ∷⁺ [ c ]) ⁺++ (b ∷ c ∷ []) ≡
          a ∷⁺ b ∷⁺ c ∷⁺ b ∷⁺ [ c ]
  ⁺app = refl

  app⁺ : (a ∷ b ∷ c ∷ []) ++⁺ (b ∷⁺ [ c ]) ≡
          a ∷⁺ b ∷⁺ c ∷⁺ b ∷⁺ [ c ]
  app⁺ = refl

  conc : concat ((a ∷⁺ b ∷⁺ [ c ]) ∷⁺ [ b ∷⁺ [ c ] ]) ≡
         a ∷⁺ b ∷⁺ c ∷⁺ b ∷⁺ [ c ]
  conc = refl

  rev : reverse (a ∷⁺ b ∷⁺ [ c ]) ≡ c ∷⁺ b ∷⁺ [ a ]
  rev = refl

  snoc : (a ∷ b ∷ c ∷ []) ∷ʳ a ≡ a ∷⁺ b ∷⁺ c ∷⁺ [ a ]
  snoc = refl

  snoc⁺ : (a ∷⁺ b ∷⁺ [ c ]) ⁺∷ʳ a ≡ a ∷⁺ b ∷⁺ c ∷⁺ [ a ]
  snoc⁺ = refl

  split-true : split (const true) (a ∷ b ∷ c ∷ []) ≡
               inj₁ ((a , tt) ∷⁺ (b , tt) ∷⁺ [ c , tt ]) ∷ []
  split-true = refl

  split-false : split (const false) (a ∷ b ∷ c ∷ []) ≡
                inj₂ ((a , tt) ∷⁺ (b , tt) ∷⁺ [ c , tt ]) ∷ []
  split-false = refl

  split-≡1 :
    split (λ n → ⌊ n Nat.≟ 1 ⌋) (1 ∷ 2 ∷ 3 ∷ 1 ∷ 1 ∷ 2 ∷ 1 ∷ []) ≡
    inj₁ [ 1 , tt ] ∷ inj₂ ((2 , tt) ∷⁺ [ 3 , tt ]) ∷
    inj₁ ((1 , tt) ∷⁺ [ 1 , tt ]) ∷ inj₂ [ 2 , tt ] ∷ inj₁ [ 1 , tt ] ∷
    []
  split-≡1 = refl

  wordsBy-true : wordsBy (const true) (a ∷ b ∷ c ∷ []) ≡ []
  wordsBy-true = refl

  wordsBy-false : wordsBy (const false) (a ∷ b ∷ c ∷ []) ≡
                  (a ∷⁺ b ∷⁺ [ c ]) ∷ []
  wordsBy-false = refl

  wordsBy-≡1 :
    wordsBy (λ n → ⌊ n Nat.≟ 1 ⌋) (1 ∷ 2 ∷ 3 ∷ 1 ∷ 1 ∷ 2 ∷ 1 ∷ []) ≡
    (2 ∷⁺ [ 3 ]) ∷ [ 2 ] ∷ []
  wordsBy-≡1 = refl


module pretty where
open import general-util

-- Adapted from A Prettier Printer (Philip Wadler)
-- https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf

-- The pretty printer
infixr 5 _:<|>_
infixr 6 _:<>_
infixr 6 _<>_

data DOC : Set where
  NIL : DOC
  _:<>_ : DOC → DOC → DOC
  NEST : ℕ → DOC → DOC
  TEXT : rope → DOC
  LINE : DOC
  _:<|>_ : DOC → DOC → DOC

data Doc : Set where
  Nil : Doc
  _Text_ : rope → Doc → Doc
  _Line_ : ℕ → Doc → Doc

nil = NIL
_<>_ = _:<>_
nest = NEST
text = TEXT
line = LINE

flatten : DOC → DOC
flatten NIL = NIL
flatten (x :<> y) = flatten x :<> flatten y
flatten (NEST i x) = NEST i (flatten x)
flatten (TEXT s) = TEXT s
flatten LINE = TEXT [[ " " ]]
flatten (x :<|> y) = flatten x

flatten-out : DOC → rope
flatten-out NIL = [[]]
flatten-out (x :<> y) = flatten-out x ⊹⊹ flatten-out y
flatten-out (NEST i x) = flatten-out x
flatten-out (TEXT s) = s
flatten-out LINE = [[ " " ]]
flatten-out (x :<|> y) = flatten-out x

group = λ x → flatten x :<|> x

fold : ∀ {ℓ} {X : Set ℓ} → ℕ → X → (X → X) → X
fold 0 z s = z
fold (suc n) z s = s (fold n z s)

copy : ∀ {ℓ} {X : Set ℓ} → ℕ → X → 𝕃 X
copy i x = fold i [] (x ::_)

layout : Doc → rope
layout Nil = [[]]
layout (s Text x) = s ⊹⊹ layout x
layout (i Line x) = [[ 𝕃char-to-string ('\n' :: copy i ' ') ]] ⊹⊹ layout x

_∸'_ : ℕ → ℕ → maybe ℕ
m ∸' n with suc m ∸ n
...| zero = nothing
...| suc o = just o

fits : maybe ℕ → Doc → 𝔹
fits nothing x  = ff
fits (just w) Nil = tt
fits (just w) (s Text x) = fits (w ∸' rope-length s) x
fits (just w) (i Line x) = tt

{-# TERMINATING #-}
be : ℕ → ℕ → 𝕃 (ℕ × DOC) → Doc
better : ℕ → ℕ → Doc → Doc → Doc
best : ℕ → ℕ → DOC → Doc

better w k x y = if fits (w ∸' k) x then x else y
best w k x = be w k [ 0 , x ]

be w k [] = Nil
be w k ((i , NIL) :: z) = be w k z
be w k ((i , x :<> y) :: z) = be w k ((i , x) :: (i , y) :: z)
be w k ((i , NEST j x) :: z) = be w k ((i + j , x) :: z)
be w k ((i , TEXT s) :: z) = s Text be w (k + rope-length s) z
be w k ((i , LINE) :: z) = i Line be w i z
be w k ((i , x :<|> y) :: z) = better w k (be w k ((i , x) :: z)) (be w k ((i , y) :: z))

pretty : ℕ → DOC → rope
pretty w x = layout (best w 0 x)

-- Utility functions

infixr 6 _<+>_ _</>_ _<+/>_
_<+>_ : DOC → DOC → DOC
x <+> y = x <> text [[ " " ]] <> y
_</>_ : DOC → DOC → DOC
x </> y = x <> line <> y

folddoc : (DOC → DOC → DOC) → 𝕃 DOC → DOC
folddoc f [] = nil
folddoc f (x :: []) = x
folddoc f (x :: xs) = f x (folddoc f xs)

spread = folddoc _<+>_

stack = folddoc _</>_

bracket : string → DOC → string → DOC
bracket l x r = group (text [[ l ]] <> nest 2 (line <> x) <> line <> text [[ r ]])

_<+/>_ : DOC → DOC → DOC
x <+/> y = x <> (text [[ " " ]] :<|> line) <> y

{-# TERMINATING #-}
fill : 𝕃 DOC → DOC
fill [] = nil
fill (x :: []) = x
fill (x :: y :: zs) = (flatten x <+> fill (flatten y :: zs)) :<|> (x </> fill (y :: zs))

{-# TERMINATING #-}
filln : 𝕃 (ℕ × DOC) → DOC
filln [] = nil
filln ((i , x) :: []) = nest i x
filln ((i , x) :: (j , y) :: zs) =
  (flatten x <+> filln ((j , flatten y) :: zs))
    :<|> (nest i x <> nest j line <> filln ((j , y) :: zs))

{-# TERMINATING #-}
fill-last : ℕ → 𝕃 DOC → DOC
fill-last i [] = nil
fill-last i (x :: []) = nest i x
fill-last i (x :: y :: []) = (flatten x <+> flatten y) :<|> (nest i (x </> y))
fill-last i (x :: y :: zs) = (flatten x <+> fill-last i (flatten y :: zs)) :<|> (x </> fill-last i (y :: zs))


------------------------------------------------------------------------
-- The actual maximum stack size of the compiled program matches the
-- maximum stack size of the instrumented source-level semantics
------------------------------------------------------------------------

open import Prelude

import Lambda.Syntax

module Lambda.Compiler-correctness.Sizes-match
  {Name : Type}
  (open Lambda.Syntax Name)
  (def : Name → Tm 1)
  where

open import Equality.Propositional as E using (refl)
open import Logical-equivalence using (_⇔_)
open import Tactic.By.Propositional using (by)
open import Prelude.Size

open import Colist E.equality-with-J hiding (_++_; length)
import Conat E.equality-with-J as Conat
open import Function-universe E.equality-with-J hiding (id; _∘_)
open import List E.equality-with-J using (_++_; length)
open import Monad E.equality-with-J
open import Nat E.equality-with-J
open import Vec.Data E.equality-with-J

open import Upper-bounds

open import Lambda.Compiler def
open import Lambda.Interpreter.Stack-sizes def as I
open import Lambda.Delay-crash-trace as DCT
  using (Delay-crash-trace)
open import Lambda.Virtual-machine.Instructions Name
open import Lambda.Virtual-machine comp-name as VM

private
  module C = Closure Code
  module T = Closure Tm

open Delay-crash-trace using (tell)

------------------------------------------------------------------------
-- A lemma

-- A rearrangement lemma for ⟦_⟧⁺.

⟦⟧-· :
  ∀ {A n} (t₁ t₂ : Tm n)
    {ρ} tc {k : T.Value → Delay-crash-trace (ℕ → ℕ) A ∞} →
  DCT.[ ∞ ] ⟦ t₁ · t₂ ⟧ ρ tc >>= k ∼
            do v₁ ← ⟦ t₁ ⟧ ρ false
               v₂ ← ⟦ t₂ ⟧ ρ false
               [ pred , pred ] v₁ ∙ v₂ >>= k
⟦⟧-· t₁ t₂ {ρ} tc {k} =
  ⟦ t₁ · t₂ ⟧ ρ tc >>= k                   DCT.∼⟨⟩

  (do v₁ ← ⟦ t₁ ⟧ ρ false
      v₂ ← ⟦ t₂ ⟧ ρ false
      [ pred , pred ] v₁ ∙ v₂) >>= k       DCT.∼⟨ DCT.symmetric (DCT.associativity (⟦ t₁ ⟧ _ _) _ _) ⟩

  (do v₁ ← ⟦ t₁ ⟧ ρ false
      (do v₂ ← ⟦ t₂ ⟧ ρ false
          [ pred , pred ] v₁ ∙ v₂) >>= k)  DCT.∼⟨ ((⟦ t₁ ⟧ _ _ DCT.∎) DCT.>>=-cong λ _ → DCT.symmetric (DCT.associativity (⟦ t₂ ⟧ _ _) _ _)) ⟩

  (do v₁ ← ⟦ t₁ ⟧ ρ false
      v₂ ← ⟦ t₂ ⟧ ρ false
      [ pred , pred ] v₁ ∙ v₂ >>= k)       DCT.∎

------------------------------------------------------------------------
-- Well-formed continuations and stacks

-- A continuation is OK with respect to a certain state if the
-- following property is satisfied.

Cont-OK :
  Size → State → (T.Value → Delay-crash-trace (ℕ → ℕ) C.Value ∞) → Type
Cont-OK i ⟨ c , s , ρ ⟩ k =
  ∀ v → [ i ] VM.stack-sizes ⟨ c , val (comp-val v) ∷ s , ρ ⟩ ≂
              numbers (k v) (1 + length s)

-- A workaround for what might be an Agda bug.

castC :
  ∀ {i} {j : Size< i} {s k} →
  Cont-OK i s k → Cont-OK j s k
castC {s = ⟨ _ , _ , _ ⟩} c-ok = cast-≂ ∘ c-ok

-- If the In-tail-context parameter indicates that we are in a tail
-- context, then the stack must have a certain shape, and it must be
-- related to the continuation in a certain way.

data Stack-OK (i : Size)
              (k : T.Value → Delay-crash-trace (ℕ → ℕ) C.Value ∞) :
              In-tail-context → Stack → Type where
  unrestricted : ∀ {s} → Stack-OK i k false s
  restricted   :
    ∀ {s n} {c : Code n} {ρ : C.Env n} →
    (∀ v → [ i ] 2 + length s ∷′
                 VM.stack-sizes ⟨ c , val (comp-val v) ∷ s , ρ ⟩ ≂
                 numbers (k v) (2 + length s)) →
    Stack-OK i k true (ret c ρ ∷ s)

-- Stacks that are OK in a tail context are OK in any context.

any-OK :
  ∀ {tc i k s} →
  Stack-OK i k true s →
  Stack-OK i k tc   s
any-OK {false} = const unrestricted
any-OK {true}  = id

------------------------------------------------------------------------
-- The stack sizes match

mutual

  -- Some lemmas making up the main part of the correctness result.

  ⟦⟧-correct :
    ∀ {i n} (t : Tm n) (ρ : T.Env n) {tc c s}
      {k : T.Value → Delay-crash-trace (ℕ → ℕ) C.Value ∞} →
    Stack-OK i k tc s →
    Cont-OK i ⟨ c , s , comp-env ρ ⟩ k →
    [ i ] VM.stack-sizes ⟨ comp tc t c , s , comp-env ρ ⟩ ≂
          numbers (⟦ t ⟧ ρ tc >>= k) (length s)

  ⟦⟧-correct (var x) ρ {tc} {c} {s} {k} _ c-ok =
    VM.stack-sizes ⟨ var x ∷ c , s , comp-env ρ ⟩                         ∼⟨ ∷∼∷′ ⟩≂

    (length s ∷′
     VM.stack-sizes ⟨ c , val (index (comp-env ρ) x) ∷ s , comp-env ρ ⟩)  ≡⟨ by (comp-index ρ x) ⟩≂

    (length s ∷′
     VM.stack-sizes ⟨ c , val (comp-val (index ρ x)) ∷ s , comp-env ρ ⟩)  ≂⟨ cons″-≂ (c-ok (index ρ x)) ⟩∼

    (length s ∷′ numbers (k (index ρ x)) (1 + length s))                  ∼⟨ symmetric-∼ ∷∼∷′ ⟩

    numbers (⟦ var x ⟧ ρ tc >>= k) (length s)                             ∎

  ⟦⟧-correct (lam t) ρ {tc} {c} {s} {k} _ c-ok =
    VM.stack-sizes ⟨ clo (comp-body t) ∷ c , s , comp-env ρ ⟩             ∼⟨ ∷∼∷′ ⟩≂

    (length s ∷′
     VM.stack-sizes ⟨ c , val (comp-val (T.lam t ρ)) ∷ s , comp-env ρ ⟩)  ≂⟨ cons″-≂ (c-ok (T.lam t ρ)) ⟩∼

    (length s ∷′ numbers (k (T.lam t ρ)) (1 + length s))                  ∼⟨ symmetric-∼ ∷∼∷′ ⟩

    numbers (⟦ lam t ⟧ ρ tc >>= k) (length s)                             ∎

  ⟦⟧-correct (t₁ · t₂) ρ {tc} {c} {s} {k} _ c-ok =
    VM.stack-sizes ⟨ comp false t₁ (comp false t₂ (app ∷ c))
                   , s
                   , comp-env ρ
                   ⟩                                                ≂⟨ (⟦⟧-correct t₁ _ unrestricted λ v₁ →

      VM.stack-sizes ⟨ comp false t₂ (app ∷ c)
                     , val (comp-val v₁) ∷ s
                     , comp-env ρ
                     ⟩                                                    ≂⟨ (⟦⟧-correct t₂ _ unrestricted λ v₂ →

        VM.stack-sizes ⟨ app ∷ c
                       , val (comp-val v₂) ∷ val (comp-val v₁) ∷ s
                       , comp-env ρ
                       ⟩                                                        ≂⟨ ∙-correct v₁ v₂ c-ok ⟩∼

        numbers ([ pred , pred ] v₁ ∙ v₂ >>= k) (2 + length s)                  ∎) ⟩∼

      numbers (do v₂ ← ⟦ t₂ ⟧ ρ false
                  [ pred , pred ] v₁ ∙ v₂ >>= k)
              (1 + length s)                                              ∎) ⟩∼

    numbers (do v₁ ← ⟦ t₁ ⟧ ρ false
                v₂ ← ⟦ t₂ ⟧ ρ false
                [ pred , pred ] v₁ ∙ v₂ >>= k)
            (length s)                                              ∼⟨ symmetric-∼ (numbers-cong (⟦⟧-· t₁ t₂ tc)) ⟩

    numbers (⟦ t₁ · t₂ ⟧ ρ tc >>= k) (length s)                     ∎

  ⟦⟧-correct (call f t) ρ {false} {c} {s} {k} unrestricted c-ok =
    VM.stack-sizes ⟨ comp false t (cal f ∷ c) , s , comp-env ρ ⟩        ≂⟨ (⟦⟧-correct t _ unrestricted λ v →

      VM.stack-sizes ⟨ cal f ∷ c , val (comp-val v) ∷ s , comp-env ρ ⟩        ∼⟨ ∷∼∷′ ⟩≂

      1 + length s ∷′
      VM.stack-sizes ⟨ comp-name f
                     , ret c (comp-env ρ) ∷ s
                     , comp-val v ∷ []
                     ⟩                                                        ≂⟨ cons′-≂ (_⇔_.from ≂′⇔≂″ λ { .force →
                                                                                body-lemma (def f) [] (castC c-ok) }) ⟩∼
      1 + length s ∷′
      numbers (⟦ def f ⟧ (v ∷ []) true >>= tell pred ∘ return >>= k)
              (1 + length s)                                                  ∼⟨ symmetric-∼ ∷∼∷′ ⟩

      numbers ([ id , pred ] T.lam (def f) [] ∙ v >>= k)
              (1 + length s)                                                  ∎) ⟩∼

    numbers (⟦ t ⟧ ρ false >>= λ v →
             [ id , pred ] T.lam (def f) [] ∙ v >>= k)
            (length s)                                                  ∼⟨ numbers-cong (DCT.associativity (⟦ t ⟧ _ _) _ _) ⟩

    numbers ((⟦ t ⟧ ρ false >>=
              [ id , pred ] T.lam (def f) [] ∙_) >>= k)
            (length s)                                                  ∼⟨⟩

    numbers (⟦ call f t ⟧ ρ false >>= k) (length s)                     ∎

  ⟦⟧-correct {i} (call f t) ρ {true} {c} {ret c′ ρ′ ∷ s} {k}
             s-ok@(restricted c-ok) _ =
    VM.stack-sizes ⟨ comp false t (tcl f ∷ c)
                   , ret c′ ρ′ ∷ s
                   , comp-env ρ
                   ⟩                                                    ≂⟨ (⟦⟧-correct t _ unrestricted λ v →

      VM.stack-sizes ⟨ tcl f ∷ c
                     , val (comp-val v) ∷ ret c′ ρ′ ∷ s
                     , comp-env ρ
                     ⟩                                                        ∼⟨ ∷∼∷′ ⟩≂

      2 + length s ∷′
      VM.stack-sizes ⟨ comp-name f
                     , ret c′ ρ′ ∷ s
                     , comp-val v ∷ []
                     ⟩                                                        ≡⟨⟩≂

      2 + length s ∷′
      VM.stack-sizes ⟨ comp-name f
                     , ret c′ ρ′ ∷ s
                     , comp-env (v ∷ [])
                     ⟩                                                        ≂⟨ cons′-≂ (_⇔_.from ≂′⇔≂″ λ { .force →
                                                                                ⟦⟧-correct (def f) (_ ∷ []) (restricted lemma) (λ v′ →
        VM.stack-sizes ⟨ ret ∷ []
                       , val (comp-val v′) ∷ ret c′ ρ′ ∷ s
                       , comp-env (v ∷ [])
                       ⟩                                                             ∼⟨ ∷∼∷′ ⟩≂

        2 + length s ∷′
        VM.stack-sizes ⟨ c′ , val (comp-val v′) ∷ s , ρ′ ⟩                           ≂⟨ lemma v′ ⟩∼

        numbers (tell id (k v′)) (2 + length s)                                      ∎) }) ⟩∼

      2 + length s ∷′
      numbers (⟦ def f ⟧ (v ∷ []) true >>= tell id ∘ k) (1 + length s)        ∼⟨ (refl ∷ λ { .force →
                                                                                numbers-cong (DCT.associativity (⟦ def f ⟧ _ _) _ _) }) ⟩
      2 + length s ∷′
      numbers (⟦ def f ⟧ (v ∷ []) true >>= tell id ∘ return >>= k)
              (1 + length s)                                                  ∼⟨ symmetric-∼ ∷∼∷′ ⟩

      numbers ([ pred , id ] T.lam (def f) [] ∙ v >>= k)
              (2 + length s)                                                  ∎) ⟩∼

    numbers (⟦ t ⟧ ρ false >>= λ v →
             [ pred , id ] T.lam (def f) [] ∙ v >>= k)
            (1 + length s)                                              ∼⟨ numbers-cong (DCT.associativity (⟦ t ⟧ _ _) _ _) ⟩

    numbers ((⟦ t ⟧ ρ false >>=
              [ pred , id ] T.lam (def f) [] ∙_) >>= k)
            (1 + length s)                                              ∼⟨⟩

    numbers (⟦ call f t ⟧ ρ true >>= k) (1 + length s)                  ∎
    where
    lemma : {j : Size< i} → _
    lemma = λ v′ →
      2 + length s ∷′
      VM.stack-sizes ⟨ c′ , val (comp-val v′) ∷ s , ρ′ ⟩  ≂⟨ consʳ-≂ (inj₁ (here ≤-refl)) (cast-≂ (c-ok v′)) ⟩∼

      2 + length s ∷′
      numbers (k v′) (2 + length s)                       ∼⟨ symmetric-∼ ∷∼∷′ ⟩

      numbers (tell id (k v′)) (2 + length s)             ∎

  ⟦⟧-correct (con b) ρ {tc} {c} {s} {k} _ c-ok =
    VM.stack-sizes ⟨ con b ∷ c , s , comp-env ρ ⟩                       ∼⟨ ∷∼∷′ ⟩≂

    (length s ∷′
     VM.stack-sizes ⟨ c , val (comp-val (T.con b)) ∷ s , comp-env ρ ⟩)  ≂⟨ cons″-≂ (c-ok (T.con b)) ⟩∼

    (length s ∷′ numbers (k (T.con b)) (1 + length s))                  ∼⟨ symmetric-∼ ∷∼∷′ ⟩

    numbers (⟦ con b ⟧ ρ tc >>= k) (length s)                           ∎

  ⟦⟧-correct (if t₁ t₂ t₃) ρ {tc} {c} {s} {k} s-ok c-ok =
    VM.stack-sizes ⟨ comp false t₁
                       (bra (comp tc t₂ []) (comp tc t₃ []) ∷ c)
                   , s
                   , comp-env ρ
                   ⟩                                                ≂⟨ (⟦⟧-correct t₁ _ unrestricted λ v₁ → ⟦if⟧-correct v₁ t₂ t₃ s-ok c-ok) ⟩∼

    numbers (⟦ t₁ ⟧ ρ false >>= λ v₁ → ⟦if⟧ v₁ t₂ t₃ ρ tc >>= k)
            (length s)                                              ∼⟨ numbers-cong (DCT.associativity (⟦ t₁ ⟧ _ _) _ _) ⟩

    numbers ((⟦ t₁ ⟧ ρ false >>= λ v₁ → ⟦if⟧ v₁ t₂ t₃ ρ tc) >>= k)
            (length s)                                              ∎

  body-lemma :
    ∀ {i n n′} (t : Tm (1 + n)) ρ {ρ′ : C.Env n′} {c s v}
      {k : T.Value → Delay-crash-trace (ℕ → ℕ) C.Value ∞} →
    Cont-OK i ⟨ c , s , ρ′ ⟩ k →
    [ i ] VM.stack-sizes ⟨ comp-body t
                         , ret c ρ′ ∷ s
                         , comp-val v ∷ comp-env ρ
                         ⟩ ≂
          numbers (⟦ t ⟧ (v ∷ ρ) true >>= tell pred ∘ return >>= k)
                  (1 + length s)
  body-lemma t ρ {ρ′} {c} {s} {v} {k} c-ok =
    VM.stack-sizes ⟨ comp-body t
                   , ret c ρ′ ∷ s
                   , comp-val v ∷ comp-env ρ
                   ⟩                                               ≡⟨⟩≂

    VM.stack-sizes ⟨ comp-body t
                   , ret c ρ′ ∷ s
                   , comp-env (v ∷ ρ)
                   ⟩                                               ≂⟨ (⟦⟧-correct t (_ ∷ _) (any-OK (restricted lemma)) λ v′ →

      VM.stack-sizes ⟨ ret ∷ []
                     , val (comp-val v′) ∷ ret c ρ′ ∷ s
                     , comp-env (v ∷ ρ)
                     ⟩                                                   ∼⟨ ∷∼∷′ ⟩≂

      2 + length s ∷′
      VM.stack-sizes ⟨ c , val (comp-val v′) ∷ s , ρ′ ⟩                  ≂⟨ lemma v′ ⟩∼

      numbers (tell pred (k v′)) (2 + length s)                          ∎) ⟩∼

    numbers (⟦ t ⟧ (v ∷ ρ) true >>= tell pred ∘ k) (1 + length s)  ∼⟨ numbers-cong (DCT.associativity (⟦ t ⟧ _ _) _ _) ⟩

    numbers (⟦ t ⟧ (v ∷ ρ) true >>= tell pred ∘ return >>= k)
            (1 + length s)                                         ∎
    where
    lemma = λ v′ →
      2 + length s ∷′
      VM.stack-sizes ⟨ c , val (comp-val v′) ∷ s , ρ′ ⟩  ≂⟨ cons″-≂ (c-ok v′) ⟩∼

      2 + length s ∷′ numbers (k v′) (1 + length s)      ∼⟨ symmetric-∼ ∷∼∷′ ⟩

      numbers (tell pred (k v′)) (2 + length s)          ∎

  ∙-correct :
    ∀ {i n} v₁ v₂ {ρ : C.Env n} {c s}
      {k : T.Value → Delay-crash-trace (ℕ → ℕ) C.Value ∞} →
    Cont-OK i ⟨ c , s , ρ ⟩ k →
    [ i ] VM.stack-sizes ⟨ app ∷ c
                         , val (comp-val v₂) ∷ val (comp-val v₁) ∷ s
                         , ρ
                         ⟩ ≂
          numbers ([ pred , pred ] v₁ ∙ v₂ >>= k) (2 + length s)

  ∙-correct (T.lam t₁ ρ₁) v₂ {ρ} {c} {s} {k} c-ok =
    VM.stack-sizes
      ⟨ app ∷ c
      , val (comp-val v₂) ∷ val (comp-val (T.lam t₁ ρ₁)) ∷ s
      , ρ
      ⟩                                                              ∼⟨ ∷∼∷′ ⟩≂

    2 + length s ∷′
    VM.stack-sizes ⟨ comp-body t₁
                   , ret c ρ ∷ s
                   , comp-val v₂ ∷ comp-env ρ₁
                   ⟩                                                 ≂⟨ cons′-≂ (_⇔_.from ≂′⇔≂″ λ { .force → body-lemma t₁ _ (castC c-ok) }) ⟩∼

    2 + length s ∷′
    numbers (⟦ t₁ ⟧ (v₂ ∷ ρ₁) true >>= tell pred ∘ return >>= k)
            (1 + length s)                                           ∼⟨ symmetric-∼ ∷∼∷′ ⟩

    numbers ([ pred , pred ] T.lam t₁ ρ₁ ∙ v₂ >>= k) (2 + length s)  ∎

  ∙-correct (T.con b) v₂ {ρ} {c} {s} {k} _ =
    VM.stack-sizes ⟨ app ∷ c
                   , val (comp-val v₂) ∷ val (C.con b) ∷ s
                   , ρ
                   ⟩                                             ∼⟨ ∷∼∷′ ⟩≂

    2 + length s ∷′ []                                           ∼⟨ symmetric-∼ ∷∼∷′ ⟩≂

    numbers ([ pred , pred ] T.con b ∙ v₂ >>= k) (2 + length s)  □≂

  ⟦if⟧-correct :
    ∀ {i n} v₁ t₂ t₃ {ρ : T.Env n} {tc c s}
      {k : T.Value → Delay-crash-trace (ℕ → ℕ) C.Value ∞} →
    Stack-OK i k tc s →
    Cont-OK i ⟨ c , s , comp-env ρ ⟩ k →
    [ i ] VM.stack-sizes ⟨ bra (comp tc t₂ []) (comp tc t₃ []) ∷ c
                         , val (comp-val v₁) ∷ s
                         , comp-env ρ
                         ⟩ ≂
          numbers (⟦if⟧ v₁ t₂ t₃ ρ tc >>= k) (1 + length s)

  ⟦if⟧-correct (T.lam t₁ ρ₁) t₂ t₃ {ρ} {tc} {c} {s} {k} _ _ =
    VM.stack-sizes ⟨ bra (comp tc t₂ []) (comp tc t₃ []) ∷ c
                   , val (comp-val (T.lam t₁ ρ₁)) ∷ s
                   , comp-env ρ
                   ⟩                                              ∼⟨ ∷∼∷′ ⟩≂

    1 + length s ∷′ []                                            ∼⟨ symmetric-∼ ∷∼∷′ ⟩≂

    numbers (⟦if⟧ (T.lam t₁ ρ₁) t₂ t₃ ρ tc >>= k) (1 + length s)  □≂

  ⟦if⟧-correct (T.con true) t₂ t₃ {ρ} {tc} {c} {s} {k} s-ok c-ok =
    VM.stack-sizes ⟨ bra (comp tc t₂ []) (comp tc t₃ []) ∷ c
                   , val (comp-val (T.con true)) ∷ s
                   , comp-env ρ
                   ⟩                                             ∼⟨ ∷∼∷′ ⟩≂

    1 + length s ∷′
    VM.stack-sizes ⟨ comp tc t₂ [] ++ c , s , comp-env ρ ⟩       ≡⟨ by (comp-++ _ t₂) ⟩≂

    1 + length s ∷′
    VM.stack-sizes ⟨ comp tc t₂ c , s , comp-env ρ ⟩             ≂⟨ cons″-≂ (⟦⟧-correct t₂ _ s-ok c-ok) ⟩∼

    1 + length s ∷′ numbers (⟦ t₂ ⟧ ρ tc >>= k) (length s)       ∼⟨ symmetric-∼ ∷∼∷′ ⟩

    numbers (⟦if⟧ (T.con true) t₂ t₃ ρ tc >>= k) (1 + length s)  ∎

  ⟦if⟧-correct (T.con false) t₂ t₃ {ρ} {tc} {c} {s} {k} s-ok c-ok =
    VM.stack-sizes ⟨ bra (comp tc t₂ []) (comp tc t₃ []) ∷ c
                   , val (comp-val (T.con false)) ∷ s
                   , comp-env ρ
                   ⟩                                              ∼⟨ ∷∼∷′ ⟩≂

    1 + length s ∷′
    VM.stack-sizes ⟨ comp tc t₃ [] ++ c , s , comp-env ρ ⟩        ≡⟨ by (comp-++ _ t₃) ⟩≂

    1 + length s ∷′
    VM.stack-sizes ⟨ comp tc t₃ c , s , comp-env ρ ⟩              ≂⟨ cons″-≂ (⟦⟧-correct t₃ _ s-ok c-ok) ⟩∼

    1 + length s ∷′ numbers (⟦ t₃ ⟧ ρ tc >>= k) (length s)        ∼⟨ symmetric-∼ ∷∼∷′ ⟩

    numbers (⟦if⟧ (T.con false) t₂ t₃ ρ tc >>= k) (1 + length s)  ∎

-- The virtual machine and the interpreter produce related lists of
-- stack sizes.
--
-- (However, the traces are not necessarily bisimilar, see
-- Lambda.Interpreter.Stack-sizes.Counterexample.stack-sizes-not-bisimilar.)

stack-sizes-related :
  (t : Tm 0) →
  [ ∞ ] VM.stack-sizes ⟨ comp₀ t , [] , [] ⟩ ≂ I.stack-sizes t
stack-sizes-related t =
  VM.stack-sizes ⟨ comp false t [] , [] , [] ⟩  ≂⟨ ⟦⟧-correct t [] unrestricted (λ _ → cons″-≂ (_ □≂)) ⟩∼
  numbers (comp-val ⟨$⟩ ⟦ t ⟧ [] false) 0       ∼⟨ scanl-cong (DCT.trace-⟨$⟩ _) ⟩
  numbers (⟦ t ⟧ [] false) 0                    ∼⟨⟩
  I.stack-sizes t                               ∎

-- The maximum stack sizes match.

maximum-stack-sizes-match :
  ∀ (t : Tm 0) {i v} →
  LUB (I.stack-sizes t) i →
  LUB (VM.stack-sizes ⟨ comp₀ t , [] , [] ⟩) v →
  Conat.[ ∞ ] i ∼ v
maximum-stack-sizes-match t {i} {v} i-lub =
  LUB (VM.stack-sizes ⟨ comp₀ t , [] , [] ⟩) v  ↝⟨ LUB-≂ (stack-sizes-related t) ⟩
  LUB (I.stack-sizes t) v                       ↝⟨ lub-unique i-lub ⟩□
  Conat.[ ∞ ] i ∼ v                             □


module Haskell.Prim.Enum where

open import Agda.Builtin.Nat as Nat hiding (_==_; _<_; _+_; _*_; _-_)
open import Agda.Builtin.Char
open import Agda.Builtin.Equality
open import Agda.Builtin.List
open import Agda.Builtin.Unit
open import Agda.Builtin.Int using (pos; negsuc)

open import Haskell.Prim
open import Haskell.Prim.Bool
open import Haskell.Prim.Bounded
open import Haskell.Prim.Either
open import Haskell.Prim.Eq
open import Haskell.Prim.Functor
open import Haskell.Prim.Int
open import Haskell.Prim.Integer
open import Haskell.Prim.List
open import Haskell.Prim.Maybe
open import Haskell.Prim.Num
open import Haskell.Prim.Ord
open import Haskell.Prim.Tuple
open import Haskell.Prim.Word

--------------------------------------------------
-- Enum
--    Assumptions: unbounded enums have no constraints on their
--    operations and bounded enums should work on all values between
--    minBound and maxBound. Unbounded enums do not support enumFrom
--    and enumFromThen (since they return infinite lists).

IfBoundedBelow : Maybe (BoundedBelow a) → (⦃ BoundedBelow a ⦄ → Set) → Set
IfBoundedBelow Nothing  k = ⊤
IfBoundedBelow (Just i) k = k ⦃ i ⦄

IfBoundedAbove : Maybe (BoundedAbove a) → (⦃ BoundedAbove a ⦄ → Set) → Set
IfBoundedAbove Nothing  k = ⊤
IfBoundedAbove (Just i) k = k ⦃ i ⦄

record Enum (a : Set) : Set₁ where
  field
    BoundedBelowEnum : Maybe (BoundedBelow a)
    BoundedAboveEnum : Maybe (BoundedAbove a)
    fromEnum         : a → Int

  private
    IsBoundedBelow : Set
    IsBoundedBelow = maybe ⊥ (λ _ → ⊤) BoundedBelowEnum

    IsBoundedAbove : Set
    IsBoundedAbove = maybe ⊥ (λ _ → ⊤) BoundedAboveEnum

    TrueIfLB : (⦃ BoundedBelow a ⦄ → Bool) → Set
    TrueIfLB C = IfBoundedBelow BoundedBelowEnum (IsTrue C)

    TrueIfUB : (⦃ BoundedAbove a ⦄ → Bool) → Set
    TrueIfUB C = IfBoundedAbove BoundedAboveEnum (IsTrue C)

    FalseIfLB : (⦃ BoundedBelow a ⦄ → Bool) → Set
    FalseIfLB C = IfBoundedBelow BoundedBelowEnum (IsFalse C)

    FalseIfUB : (⦃ BoundedAbove a ⦄ → Bool) → Set
    FalseIfUB C = IfBoundedAbove BoundedAboveEnum (IsFalse C)

    minInt : ⦃ BoundedBelow a ⦄ → Int
    minInt ⦃ _ ⦄ = fromEnum minBound

    maxInt : ⦃ BoundedAbove a ⦄ → Int
    maxInt ⦃ _ ⦄ = fromEnum maxBound

  field
    toEnum : (n : Int) → ⦃ TrueIfLB (minInt <= n) ⦄ → ⦃ TrueIfUB (n <= maxInt) ⦄ → a
    succ   : (x : a) → ⦃ FalseIfUB (fromEnum x == maxInt) ⦄ → a
    pred   : (x : a) → ⦃ FalseIfLB (fromEnum x == minInt) ⦄ → a

    enumFrom       : ⦃ IsBoundedAbove ⦄ → a → List a
    enumFromTo     : a → a → List a
    -- In the Prelude Enum instances `enumFromThenTo x x y` gives the
    -- infinite list of `x`s. The constraint is a little bit stronger than it needs to be,
    -- since it rules out different x and x₁ that maps to the same Int, but this saves us
    -- requiring an Eq instance for `a`, and it's not a terrible limitation to not be able to
    -- write [0, 2^64 .. 2^66].
    enumFromThenTo : (x x₁ : a) → ⦃ IsFalse (fromEnum x == fromEnum x₁) ⦄ → a → List a
    enumFromThen   : ⦃ IsBoundedBelow ⦄ → ⦃ IsBoundedAbove ⦄ → (x x₁ : a) → ⦃ IsFalse (fromEnum x == fromEnum x₁) ⦄ → List a

open Enum ⦃ ... ⦄ public

private
  divNat : Nat → Nat → Nat
  divNat a 0       = 0
  divNat a (suc b) = div-helper 0 b a b

  diff : Integer → Integer → Maybe Nat
  diff a b =
    case a - b of λ where
      (pos n)    → Just n
      (negsuc _) → Nothing

  unsafeIntegerToNat : Integer → Nat
  unsafeIntegerToNat (pos n) = n
  unsafeIntegerToNat (negsuc _) = 0

  integerFromCount : Integer → Integer → Nat → List Integer
  integerFromCount a step 0       = []
  integerFromCount a step (suc n) = a ∷ integerFromCount (a + step) step n

  integerFromTo : Integer → Integer → List Integer
  integerFromTo a b = maybe [] (integerFromCount a 1 ∘ suc) (diff b a)

  integerFromThenTo : (a a₁ : Integer) → ⦃ IsFalse (integerToInt a == integerToInt a₁) ⦄ → Integer → List Integer
  integerFromThenTo a a₁ b =
    case compare a a₁ of λ where
      LT → maybe [] (λ d → integerFromCount a (a₁ - a) (suc (divNat d (unsafeIntegerToNat (a₁ - a))))) (diff b a)
      EQ → [] -- impossible
      GT → maybe [] (λ d → integerFromCount a (a₁ - a) (suc (divNat d (unsafeIntegerToNat (a - a₁))))) (diff a b)

instance
  iEnumInteger : Enum Integer
  iEnumInteger .BoundedBelowEnum  = Nothing
  iEnumInteger .BoundedAboveEnum  = Nothing
  iEnumInteger .fromEnum          = integerToInt
  iEnumInteger .toEnum          n = intToInteger n
  iEnumInteger .succ              = _+ 1
  iEnumInteger .pred              = _- 1
  iEnumInteger .enumFromTo        = integerFromTo
  iEnumInteger .enumFromThenTo    = integerFromThenTo

module _ (from : a → Integer) (to : Integer → a) where
  private
    fromTo : a → a → List a
    fromTo a b = map to (enumFromTo (from a) (from b))

    fromThenTo : (x x₁ : a) → ⦃ IsFalse (fromEnum (from x) == fromEnum (from x₁)) ⦄ → a → List a
    fromThenTo a a₁ b = map to (enumFromThenTo (from a) (from a₁) (from b))

  unboundedEnumViaInteger : Enum a
  unboundedEnumViaInteger .BoundedBelowEnum      = Nothing
  unboundedEnumViaInteger .BoundedAboveEnum      = Nothing
  unboundedEnumViaInteger .fromEnum              = integerToInt ∘ from
  unboundedEnumViaInteger .toEnum         n      = to (intToInteger n)
  unboundedEnumViaInteger .succ           x      = to (from x + 1)
  unboundedEnumViaInteger .pred           x      = to (from x - 1)
  unboundedEnumViaInteger .enumFromTo     a b    = fromTo a b
  unboundedEnumViaInteger .enumFromThenTo a a₁ b = fromThenTo a a₁ b

  boundedBelowEnumViaInteger : ⦃ Ord a ⦄ → ⦃ BoundedBelow a ⦄ → Enum a
  boundedBelowEnumViaInteger .BoundedBelowEnum      = Just it
  boundedBelowEnumViaInteger .BoundedAboveEnum      = Nothing
  boundedBelowEnumViaInteger .fromEnum              = integerToInt ∘ from
  boundedBelowEnumViaInteger .toEnum         n      = to (intToInteger n)
  boundedBelowEnumViaInteger .succ           x      = to (from x + 1)
  boundedBelowEnumViaInteger .pred           x      = to (from x - 1)
  boundedBelowEnumViaInteger .enumFromTo     a b    = fromTo a b
  boundedBelowEnumViaInteger .enumFromThenTo a a₁ b = fromThenTo a a₁ b

  boundedAboveEnumViaInteger : ⦃ Ord a ⦄ → ⦃ BoundedAbove a ⦄ → Enum a
  boundedAboveEnumViaInteger .BoundedBelowEnum      = Nothing
  boundedAboveEnumViaInteger .BoundedAboveEnum      = Just it
  boundedAboveEnumViaInteger .fromEnum              = integerToInt ∘ from
  boundedAboveEnumViaInteger .toEnum         n      = to (intToInteger n)
  boundedAboveEnumViaInteger .succ           x      = to (from x + 1)
  boundedAboveEnumViaInteger .pred           x      = to (from x - 1)
  boundedAboveEnumViaInteger .enumFrom       a      = fromTo a maxBound
  boundedAboveEnumViaInteger .enumFromTo     a b    = fromTo a b
  boundedAboveEnumViaInteger .enumFromThenTo a a₁ b = fromThenTo a a₁ b

  boundedEnumViaInteger : ⦃ Ord a ⦄ → ⦃ Bounded a ⦄ → Enum a
  boundedEnumViaInteger .BoundedBelowEnum      = Just it
  boundedEnumViaInteger .BoundedAboveEnum      = Just it
  boundedEnumViaInteger .fromEnum              = integerToInt ∘ from
  boundedEnumViaInteger .toEnum         n      = to (intToInteger n)
  boundedEnumViaInteger .succ           x      = to (from x + 1)
  boundedEnumViaInteger .pred           x      = to (from x - 1)
  boundedEnumViaInteger .enumFromTo     a b    = fromTo a b
  boundedEnumViaInteger .enumFromThenTo a a₁ b = fromThenTo a a₁ b
  boundedEnumViaInteger .enumFrom       a      = fromTo a maxBound
  boundedEnumViaInteger .enumFromThen   a a₁   =
    if a < a₁ then fromThenTo a a₁ maxBound
              else fromThenTo a a₁ minBound

instance
  iEnumNatural : Enum Nat
  iEnumNatural = boundedBelowEnumViaInteger pos unsafeIntegerToNat

  iEnumInt : Enum Int
  iEnumInt = boundedEnumViaInteger intToInteger integerToInt

  iEnumWord : Enum Word
  iEnumWord = boundedEnumViaInteger wordToInteger integerToWord

  iEnumBool : Enum Bool
  iEnumBool = boundedEnumViaInteger (if_then 1 else 0) (_/= 0)

  iEnumOrdering : Enum Ordering
  iEnumOrdering = boundedEnumViaInteger (λ where LT → 0; EQ → 1; GT → 2)
                                        (λ where (pos 0) → LT; (pos 1) → EQ; _ → GT)

  iEnumUnit : Enum (Tuple [])
  iEnumUnit = boundedEnumViaInteger (λ _ → 0) (λ _ → [])

  iEnumChar : Enum Char
  iEnumChar = boundedEnumViaInteger (pos ∘ primCharToNat)
                                    λ where (pos n) → primNatToChar n; _ → '_'

  -- Missing:
  --  Enum Double  (can't go via Integer)


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

-- In this module, we define the usual definition of D<: -- the one having typing and
-- subtyping mutually defined.
module DsubFull where

open import Data.List as List
open import Data.List.All as All
open import Data.Nat as ℕ
open import Data.Maybe as Maybe
open import Data.Product
open import Data.Sum
open import Data.Empty renaming (⊥ to False)
open import Data.Vec as Vec renaming (_∷_ to _‼_ ; [] to nil) hiding (_++_)
open import Function

open import Data.Maybe.Properties as Maybeₚ
open import Data.Nat.Properties as ℕₚ
open import Relation.Nullary
open import Relation.Binary.PropositionalEquality as ≡

open import DsubDef
open import Dsub renaming (_⊢_<:_ to _⊢′_<:_)
open import Utils

infix 4 _⊢_<:_ _⊢_∶_

mutual
  data _⊢_∶_ : Env → Trm → Typ → Set where
    var∶  : ∀ {Γ n T} →
      (T∈Γ : env-lookup Γ n ≡ just T) →
      Γ ⊢ var n ∶ T
    Π-I   : ∀ {Γ T t U} →
      Γ ‣ T ! ⊢ t ∶ U →
      Γ ⊢ val (Λ T ∙ t) ∶ Π T ∙ U
    Π-E   : ∀ {Γ n m S U} →
      Γ ⊢ var n ∶ Π S ∙ U →
      Γ ⊢ var m ∶ S →
      Γ ⊢ n $$ m ∶ U [ m / 0 ]
    ⟨A⟩-I : ∀ {Γ T} →
      Γ ⊢ val ⟨A= T ⟩ ∶ ⟨A: T ⋯ T ⟩
    ltinn : ∀ {Γ t u T U} →
      Γ ⊢ t ∶ T →
      Γ ‣ T ! ⊢ u ∶ U ↑ 0 →
      Γ ⊢ lt t inn u ∶ U
    <∶   : ∀ {Γ t S U} →
      Γ ⊢ t ∶ S →
      Γ ⊢ S <: U →
      Γ ⊢ t ∶ U

  data _⊢_<:_ : Env → Typ → Typ → Set where
    <:⊤   : ∀ {Γ T} →
      Γ ⊢ T <: ⊤
    ⊥<:   : ∀ {Γ T} →
      Γ ⊢ ⊥ <: T
    refl  : ∀ {Γ T} →
      Γ ⊢ T <: T
    tran : ∀ {Γ S U} T →
      (S<:T : Γ ⊢ S <: T) →
      (T<:U : Γ ⊢ T <: U) →
      Γ ⊢ S <: U
    bnd   : ∀ {Γ S U S′ U′} →
      (S′<:S : Γ ⊢ S′ <: S) →
      (U<:U′ : Γ ⊢ U <: U′) →
      Γ ⊢ ⟨A: S ⋯ U ⟩ <: ⟨A: S′ ⋯ U′ ⟩
    Π<:   : ∀ {Γ S U S′ U′} →
      (S′<:S : Γ ⊢ S′ <: S) →
      (U<:U′ : (S′ ∷ Γ) ⊢ U <: U′) →
      Γ ⊢ Π S ∙ U <: Π S′ ∙ U′
    sel₁  : ∀ {Γ n S U} →
      Γ ⊢ var n ∶ ⟨A: S ⋯ U ⟩ →
      Γ ⊢ S <: n ∙A
    sel₂  : ∀ {Γ n S U} →
      Γ ⊢ var n ∶ ⟨A: S ⋯ U ⟩ →
      Γ ⊢ n ∙A <: U

-- The following function shows that indeed the subtyping defined in Lemma 2 is
-- equivalent to the original subtyping.
mutual
  <:⇒<:′ : ∀ {Γ S U} → Γ ⊢ S <: U → Γ ⊢′ S <: U
  <:⇒<:′ <:⊤                = dtop
  <:⇒<:′ ⊥<:                = dbot
  <:⇒<:′ refl               = drefl
  <:⇒<:′ (tran T S<:T T<:U) = dtrans T (<:⇒<:′ S<:T) (<:⇒<:′ T<:U)
  <:⇒<:′ (bnd S′<:S U<:U′)  = dbnd (<:⇒<:′ S′<:S) (<:⇒<:′ U<:U′)
  <:⇒<:′ (Π<: S′<:S U<:U′)  = dall (<:⇒<:′ S′<:S) (<:⇒<:′ U<:U′)
  <:⇒<:′ (sel₁ n∶SU) with var∶⇒<: n∶SU
  ... | T′ , T′∈Γ , T′<:SU  = dsel₁ T′∈Γ (dtrans _ T′<:SU (dbnd drefl dtop))
  <:⇒<:′ (sel₂ n∶SU) with var∶⇒<: n∶SU
  ... | T′ , T′∈Γ , T′<:SU  = dsel₂ T′∈Γ (dtrans _ T′<:SU (dbnd dbot drefl))

  var∶⇒<: : ∀ {Γ x T} → Γ ⊢ var x ∶ T → ∃ λ T′ → env-lookup Γ x ≡ just T′ × Γ ⊢′ T′ <: T
  var∶⇒<: (var∶ T∈Γ)      = -, T∈Γ , drefl
  var∶⇒<: (<∶ x∶T T<:U) with var∶⇒<: x∶T
  ... | T′ , T′∈Γ , T′<:T = T′ , T′∈Γ , dtrans _ T′<:T (<:⇒<:′ T<:U)

<:′⇒<: : ∀ {Γ S U} → Γ ⊢′ S <: U → Γ ⊢ S <: U
<:′⇒<: dtop                 = <:⊤
<:′⇒<: dbot                 = ⊥<:
<:′⇒<: drefl                = refl
<:′⇒<: (dtrans T S<:T T<:U) = tran T (<:′⇒<: S<:T) (<:′⇒<: T<:U)
<:′⇒<: (dbnd S′<:S U<:U′)   = bnd (<:′⇒<: S′<:S) (<:′⇒<: U<:U′)
<:′⇒<: (dall S′<:S U<:U′)   = Π<: (<:′⇒<: S′<:S) (<:′⇒<: U<:U′)
<:′⇒<: (dsel₁ T∈Γ T<:SU)    = sel₁ (<∶ (var∶ T∈Γ) (<:′⇒<: T<:SU))
<:′⇒<: (dsel₂ T∈Γ T<:SU)    = sel₂ (<∶ (var∶ T∈Γ) (<:′⇒<: T<:SU))


module Haskell.RangedSets.Boundaries where

open import Agda.Builtin.Nat as Nat hiding (_==_; _<_; _+_; _-_)
open import Agda.Builtin.Char
open import Agda.Builtin.Int using (pos; negsuc)

open import Haskell.Prim
open import Haskell.Prim.Ord
open import Haskell.Prim.Bool
open import Haskell.Prim.Char
open import Haskell.Prim.Maybe
open import Haskell.Prim.Enum
open import Haskell.Prim.Num
open import Haskell.Prim.Eq
open import Haskell.Prim.Functor
open import Haskell.Prim.Monoid
open import Haskell.Prim.Monad
open import Haskell.Prim.Applicative
open import Haskell.Prim.Int
open import Haskell.Prim.List
open import Haskell.Prim.Integer
open import Haskell.Prim.Real
open import Haskell.Prim.Tuple
open import Haskell.Prim.Double
open import Haskell.Prim.Bounded

open import Agda.Builtin.Char

{-# FOREIGN AGDA2HS
import Data.Ratio
#-}

record DiscreteOrdered (a : Set) ⦃ _ : Ord a ⦄ : Set where
    field
        adjacent : a → a → Bool
        adjacentBelow : a → Maybe a

open DiscreteOrdered ⦃ ... ⦄ public
{-# COMPILE AGDA2HS DiscreteOrdered class #-}

orderingFromInt : Int → Ordering
orderingFromInt n = if_then_else_ (n == 0) LT (if_then_else_ (n == 1) EQ GT)
{-# COMPILE AGDA2HS orderingFromInt #-}

boundedAdjacentBool : (x y : Bool) → Bool
boundedAdjacentBool x y = if_then_else_ (x < y) true false
{-# COMPILE AGDA2HS boundedAdjacentBool #-}

boundedBelowBool : (x : Bool) → Maybe Bool
boundedBelowBool x = if_then_else_ (x == false) Nothing (Just false)
{-# COMPILE AGDA2HS boundedBelowBool #-}

boundedAdjacentOrdering : (x y : Ordering) → Bool
boundedAdjacentOrdering x y = if_then_else_ (x < y && x < GT) ((fromEnum x) + 1 == (fromEnum y)) false
{-# COMPILE AGDA2HS boundedAdjacentOrdering #-}

boundedBelowOrdering : (x : Ordering) → Maybe Ordering
boundedBelowOrdering x = if_then_else_ (x == LT) Nothing (Just (orderingFromInt ((fromEnum x) - 1)))
{-# COMPILE AGDA2HS boundedBelowOrdering #-}

boundedAdjacentChar : (x y : Char) → Bool
boundedAdjacentChar x y = if_then_else_ (x < y && x /= '\1114111') (((fromEnum x) + 1) == fromEnum y) false
{-# COMPILE AGDA2HS boundedAdjacentChar #-}

boundedBelowChar : (x : Char) → Maybe Char
boundedBelowChar x = if_then_else_ (x == '\0') Nothing (Just (chr ((ord x) - 1)))
{-# COMPILE AGDA2HS boundedBelowChar #-}

boundedAdjacentInt : (x y : Int) → Bool
boundedAdjacentInt x y = if_then_else_ (x < y && x /= 9223372036854775807) (x + 1 == y) false
{-# COMPILE AGDA2HS boundedAdjacentInt #-}

boundedBelowInt : (x : Int) → Maybe Int
boundedBelowInt x = if_then_else_ (x == -9223372036854775808) Nothing (Just (x - 1))
{-# COMPILE AGDA2HS boundedBelowInt #-}

boundedAdjacentInteger : (x y : Integer) → Bool
boundedAdjacentInteger x y = ((fromEnum x) + 1 == (fromEnum y))
{-# COMPILE AGDA2HS boundedAdjacentInteger #-}

boundedBelowInteger : (x : Integer) → Maybe Integer
boundedBelowInteger x = Just (x - (toInteger 1))
{-# COMPILE AGDA2HS boundedBelowInteger #-}

constructTuple : ⦃ o : Ord a ⦄ → ⦃ o2 : Ord b ⦄ → ⦃ DiscreteOrdered b ⦃ o2 ⦄ ⦄ → a → Maybe b → Maybe (a × b)
constructTuple _ Nothing = Nothing
constructTuple a (Just value) = Just (a , value)
{-# COMPILE AGDA2HS constructTuple #-}

constructTriple : ⦃ Ord a ⦄ → ⦃ Ord b ⦄ → ⦃ o : Ord c ⦄ → ⦃ DiscreteOrdered c ⦃ o ⦄ ⦄ → a → b → Maybe c → Maybe (a × b × c)
constructTriple _ _ Nothing = Nothing
constructTriple a b (Just value) = Just (a , b , value)
{-# COMPILE AGDA2HS constructTriple #-}

constructQuadtruple : ⦃ Ord a ⦄ → ⦃ Ord b ⦄ → ⦃ Ord c ⦄ → ⦃ o : Ord d ⦄ → ⦃ DiscreteOrdered d ⦃ o ⦄ ⦄
                        → a → b → c → Maybe d → Maybe (Tuple (a ∷ b ∷ c ∷ d ∷ []))
constructQuadtruple _ _ _ Nothing = Nothing
constructQuadtruple a b c (Just value) = Just (a ∷ b ∷ c ∷ value ∷ [])
{-# COMPILE AGDA2HS constructQuadtruple #-}

instance
    isDiscreteOrderedBool : DiscreteOrdered Bool
    isDiscreteOrderedBool . adjacent = boundedAdjacentBool
    isDiscreteOrderedBool . adjacentBelow = boundedBelowBool
{-# COMPILE AGDA2HS isDiscreteOrderedBool #-}

instance
    isDiscreteOrderedOrdering : DiscreteOrdered Ordering
    isDiscreteOrderedOrdering . adjacent = boundedAdjacentOrdering
    isDiscreteOrderedOrdering . adjacentBelow = boundedBelowOrdering
{-# COMPILE AGDA2HS isDiscreteOrderedOrdering #-}

instance
    isDiscreteOrderedChar : DiscreteOrdered Char
    isDiscreteOrderedChar . adjacent = boundedAdjacentChar
    isDiscreteOrderedChar . adjacentBelow = boundedBelowChar
{-# COMPILE AGDA2HS isDiscreteOrderedChar #-}

instance
    isDiscreteOrderedInt : DiscreteOrdered Int
    isDiscreteOrderedInt . adjacent = boundedAdjacentInt
    isDiscreteOrderedInt . adjacentBelow = boundedBelowInt
{-# COMPILE AGDA2HS isDiscreteOrderedInt #-}

instance
    isDiscreteOrderedInteger : DiscreteOrdered Integer
    isDiscreteOrderedInteger . adjacent = boundedAdjacentInteger
    isDiscreteOrderedInteger . adjacentBelow = boundedBelowInteger
{-# COMPILE AGDA2HS isDiscreteOrderedInteger #-}

instance
    isDiscreteOrderedDouble : DiscreteOrdered Double
    isDiscreteOrderedDouble . adjacent x y = false
    isDiscreteOrderedDouble . adjacentBelow x = Nothing
{-# COMPILE AGDA2HS isDiscreteOrderedDouble #-}

instance
    isDiscreteOrderedList : ⦃ o : Ord a ⦄ → ⦃ ol : Ord (List a) ⦄ → DiscreteOrdered (List a) ⦃ ol ⦄
    isDiscreteOrderedList . adjacent x y = false
    isDiscreteOrderedList . adjacentBelow x = Nothing
{-# COMPILE AGDA2HS isDiscreteOrderedList #-}

instance
    isDiscreteOrderedRatio : ⦃ o : Ord a ⦄ → ⦃ Integral a ⦄ → ⦃ or : Ord (Ratio a )⦄ → DiscreteOrdered (Ratio a) ⦃ or ⦄
    isDiscreteOrderedRatio . adjacent x y = false
    isDiscreteOrderedRatio . adjacentBelow x = Nothing
{-# COMPILE AGDA2HS isDiscreteOrderedRatio #-}

instance
    isDiscreteOrderedTuple : ⦃ Ord a ⦄ → ⦃ o : Ord b ⦄ → ⦃ DiscreteOrdered b ⦃ o ⦄ ⦄
                            →  ⦃ ot : Ord (a × b) ⦄ → DiscreteOrdered (a × b) ⦃ ot ⦄
    isDiscreteOrderedTuple . adjacent (x1 , x2) (y1 , y2) = (x1 == y1) && (adjacent x2 y2)
    isDiscreteOrderedTuple . adjacentBelow (x1 , x2) = constructTuple x1 (adjacentBelow x2)
{-# COMPILE AGDA2HS isDiscreteOrderedTuple #-}

instance
    isDiscreteOrderedTriple : ⦃ Ord a ⦄ → ⦃ Ord b ⦄ → ⦃ o : Ord c ⦄ → ⦃ DiscreteOrdered c ⦃ o ⦄ ⦄
                                → ⦃ ot : Ord (a × b × c) ⦄ → DiscreteOrdered (a × b × c) ⦃ ot ⦄
    isDiscreteOrderedTriple . adjacent (x1 , x2 , x3) (y1 , y2 , y3) = (x1 == y1) && (x2 == y2) && (adjacent x3 y3)
    isDiscreteOrderedTriple . adjacentBelow (x1 , x2 , x3) = constructTriple x1 x2 (adjacentBelow x3)
{-# COMPILE AGDA2HS isDiscreteOrderedTriple #-}

instance
    isDiscreteOrderedQuadtruple : ⦃ Ord a ⦄ → ⦃ Ord b ⦄ → ⦃ Ord c ⦄
                            → ⦃ o : Ord d ⦄ → ⦃ DiscreteOrdered d ⦃ o ⦄ ⦄
                            → ⦃ ot : Ord (Tuple (a ∷ b ∷ c ∷ d ∷ [])) ⦄ → DiscreteOrdered (Tuple (a ∷ b ∷ c ∷ d ∷ [])) ⦃ ot ⦄
    isDiscreteOrderedQuadtruple . adjacent (x1 ∷ x2 ∷ x3 ∷ x4 ∷ []) (y1 ∷ y2 ∷ y3 ∷ y4 ∷ []) = (x1 == y1) && (x2 == y2) && (x3 == y3) && (adjacent x4 y4)
    isDiscreteOrderedQuadtruple . adjacentBelow (x1 ∷ x2 ∷ x3 ∷ x4 ∷ []) = constructQuadtruple x1 x2 x3 (adjacentBelow x4)
{-# COMPILE AGDA2HS isDiscreteOrderedQuadtruple #-}

data Boundary (a : Set) ⦃ b : Ord a ⦄ ⦃ _ : DiscreteOrdered a ⦃ b ⦄ ⦄ : Set where
    BoundaryAbove    : a → Boundary a
    BoundaryBelow    : a → Boundary a
    BoundaryAboveAll : Boundary a
    BoundaryBelowAll : Boundary a

{-# COMPILE AGDA2HS Boundary #-}

above : ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄
        → Boundary a ⦃ o ⦄ → a → Bool
above (BoundaryAbove b) a    = a > b
above (BoundaryBelow b) a    = a >= b
above BoundaryAboveAll _     = false
above BoundaryBelowAll _     = true
{-# COMPILE AGDA2HS above #-}

infixr 4 _/>/_
_/>/_ : ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄
    → a → Boundary a ⦃ o ⦄ → Bool
_/>/_ x (BoundaryAbove b) = x > b
_/>/_ x (BoundaryBelow b) = x >= b
_/>/_ _ BoundaryAboveAll = false
_/>/_ _ BoundaryBelowAll = true
{-# COMPILE AGDA2HS _/>/_ #-}

instance
    isBoundaryEq : ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Eq (Boundary a)
    isBoundaryEq . _==_ (BoundaryAbove b1) (BoundaryAbove b2) = (b1 == b2)
    isBoundaryEq . _==_ (BoundaryAbove b1) (BoundaryBelow b2) = if_then_else_ (b1 < b2 && (adjacent b1 b2)) true false
    isBoundaryEq . _==_ (BoundaryBelow b1) (BoundaryBelow b2) = (b1 == b2)
    isBoundaryEq . _==_ (BoundaryBelow b1) (BoundaryAbove b2) = if_then_else_ (b1 > b2 && (adjacent b2 b1)) true false
    isBoundaryEq . _==_ BoundaryAboveAll BoundaryAboveAll = true
    isBoundaryEq . _==_ BoundaryBelowAll BoundaryBelowAll = true
    isBoundaryEq . _==_ _ _ = false
{-# COMPILE AGDA2HS isBoundaryEq #-}

instance
    isBoundaryOrd : ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Ord (Boundary a)
    isBoundaryOrd . compare (BoundaryAbove b1) (BoundaryAbove b2) = compare b1 b2
    isBoundaryOrd . compare (BoundaryAbove b1) (BoundaryBelow b2) = if_then_else_ (b1 < b2) (if_then_else_ (adjacent b1 b2) EQ LT) GT
    isBoundaryOrd . compare (BoundaryAbove b1) BoundaryAboveAll = LT
    isBoundaryOrd . compare (BoundaryAbove b1) BoundaryBelowAll = GT
    isBoundaryOrd . compare (BoundaryBelow b1) (BoundaryBelow b2) = compare b1 b2
    isBoundaryOrd . compare (BoundaryBelow b1) (BoundaryAbove b2) = if_then_else_ (b1 > b2) (if_then_else_ (adjacent b2 b1) EQ GT) LT
    isBoundaryOrd . compare (BoundaryBelow b1) BoundaryAboveAll = LT
    isBoundaryOrd . compare (BoundaryBelow b1) BoundaryBelowAll = GT
    isBoundaryOrd . compare BoundaryAboveAll BoundaryAboveAll = EQ
    isBoundaryOrd . compare BoundaryAboveAll _ = GT
    isBoundaryOrd . compare BoundaryBelowAll BoundaryBelowAll = EQ
    isBoundaryOrd . compare BoundaryBelowAll _ = LT
    isBoundaryOrd ._<_ x y = compare x y == LT
    isBoundaryOrd ._>_ x y = compare x y == GT
    isBoundaryOrd ._<=_ x BoundaryAboveAll = true
    isBoundaryOrd ._<=_ BoundaryBelowAll y = true
    isBoundaryOrd ._<=_ x y = (compare x y == LT) || (compare x y == EQ)
    isBoundaryOrd ._>=_ x y = (compare x y == GT) || (compare x y == EQ)
    isBoundaryOrd .max x y = if (compare x y == GT) then x else y
    isBoundaryOrd .min x y = if (compare x y == LT) then x else y
{-# COMPILE AGDA2HS isBoundaryOrd #-}