import Syntax.Simple.Signature  as S
import Syntax.BiTyped.Signature as B

import Theory.ModeCorrectness.Signature as MC

module Theory.ModeCorrectness.Synthesis
  {SD : S.SigD} (D : B.SigD SD) (mc : MC.ModeCorrect SD D) where

open import Prelude

import Data.List.Relation.Unary.All as All

open import Syntax.Simple  SD
open import Syntax.Context SD

open import Syntax.BiTyped.Functor     SD
import      Syntax.BiTyped.Pre.Functor SD as P
import      Syntax.Typed.Raw.Functor   SD as R

open import Theory.Erasure

open import Syntax.BiTyped.Term          D
open import Syntax.BiTyped.Pre.Term      D
open import Syntax.Typed.Raw.Term (erase D)

open import Theory.ModeCorrectness.Functor   SD as M

open import Theory.ModeCorrectness.UniqueSynthesised D mc
open import Theory.ModeCorrectness.Properties        D mc

open MC SD
open B  SD
open ≡-Reasoning

private variable
  Ξ     : 
  xs ys : Fins# Ξ

module _ where mutual
  synthesise
    : (Γ : Cxt 0) {r : Raw (length Γ)}
     Pre Syn r  Dec (∃[ A ] Γ  r  A)

  check
    : (Γ : Cxt 0) {r : Raw (length Γ)}
     Pre Chk r  (A : Ty)  Dec (Γ  r  A)

  synthesise Γ (` x) = yes (L.lookup Γ x , var (∈-lookup x) (index-∈-lookup Γ x))

  synthesise Γ (A  t) with check Γ t A
  ... | no  ⊬t = no λ where (A , ._  ⊢t)  ⊬t ⊢t
  ... | yes ⊢t = yes (A , _  ⊢t)

  synthesise Γ (op ts) with synthesiseᶜ (SigD.ar D _) (mc _) Γ ts
  ... | no  ⊬t       = no λ where (A , op ⊢t)  ⊬t (A , ⊢t)
  ... | yes (A , ⊢t) = yes (A , op ⊢t)

  check Γ (t ) A with synthesise Γ t
  ... | no ⊬t = no (why t ⊬t)
    where
      why : {r : Raw (length Γ)}  Pre Syn r  ¬ (∃[ B ] Γ  r  B)  ¬ Γ  r  A
      why _                ⊬t (t  _)          = ⊬t (_ , t)
      why (op (d≡Syn , _)) ⊬t (op (d≡Chk , _)) = Chk≢Syn (trans (sym d≡Chk) d≡Syn)
  ... | yes (B , ⊢t) with A  B
  ... | no ¬A=B = no (¬switch ⊢t ¬A=B)
  ... | yes A=B = yes (⊢t  A=B)

  check Γ (op ts@(d≡Chk , _)) A with checkᶜ (SigD.ar D _) (mc _) Γ ts A
  ... | no ⊬t = no λ where
    (op (d≡Syn , _)  _)  Chk≢Syn (trans (sym d≡Chk) d≡Syn)
    (op ⊢t)               ⊬t ⊢t
  ... | yes ⊢t = yes (op ⊢t)

  checkᶜ
    : (D : OpD)  ModeCorrectᶜ D
     (Γ : Cxt 0) {rs : R.⟦ eraseᶜ D ⟧ᶜ Raw (length Γ)}
     P.⟦ D ⟧ᶜ Raw Pre Chk rs  (A : Ty)  Dec ( D ⟧ᶜ Raw _⊢_[_]_ Γ rs Chk A)
  checkᶜ (ι Chk A₀ Ds) (⊆A∪Ds , SDs) Γ (refl , ts) A with cmp A₀ A
  ... | noₘ   A₀≉A = no λ where
    (_ , σ , eq , ⊢t)  A₀≉A (_ , Sub⇒Sub⊆ σ) ((λ {x} x∈  ⊆enum x) , (begin
      sub⊆ _ A₀ _
        ≡⟨ Sub⇒Sub⊆-= σ A₀ 
      sub σ A₀
        ≡⟨ eq 
      A
        ))
  ... | yesₘ (_ , ρ) A₀≈A@(min-con (A₀⊆ , A₀=A) minρ) with synthesiseⁿ Ds SDs Γ ts (_ , ρ) A₀⊆
  ... | noₘ ¬Pσ   = let ⊆A₀ = domain-cmp A₀ A _ ρ A₀≈A in no λ where
    (_ , σ , eq , ⊢ts)  ¬Pσ (_ , Sub⇒Sub⊆ σ) (ext-con (≤-con  {x} _  ⊆enum x)  {x} x∈  begin
      ρ x∈
        ≡⟨ cong ρ (#∈-uniq _ _) 
      ρ _
        ≡⟨ sub-σ=sub⊆-ρ→σ=ρ ρ σ A₀ A₀⊆ (trans A₀=A (sym eq)) (⊆A₀ x∈) 
      σ x
        )) ((λ {x} _  ⊆enum x) , ⊢ᵃˢ→Sub⊆⊢ᵃˢ ⊢ts))
  ... | yesₘ (_ , ρ̅) (min-con (ext-con (≤-con ρ⊆ρ̅ ρ≤ρ̅) (A₀∪Ds⊆ , ⊢ts)) minρ̅) =
    yes (refl , Sub⊆⇒Sub ρ̅ (A₀∪Ds⊆  ⊆A∪Ds) ,
      (begin _ ≡⟨ Sub⊆⇒Sub-≡ ρ̅ (A₀∪Ds⊆  ⊆A∪Ds) A₀ 
        sub⊆ ρ̅ A₀  {x} _  A₀∪Ds⊆ (⊆A∪Ds x))
         ≡⟨ sym (ρ=σ→subρ=subσ A₀ ρ ρ̅ _ _ λ x∈  trans (ρ≤ρ̅ (A₀⊆ x∈)) (cong ρ̅ (#∈-uniq _ _))) 
        sub⊆ ρ A₀ A₀⊆
           ≡⟨ A₀=A 
        A
         ) , Sub⊆⊢ᵃˢ→⊢ᵃˢ ⊢ts)

  synthesiseᶜ
    : (D : OpD)  ModeCorrectᶜ D
     (Γ : Cxt 0) {rs : R.⟦ eraseᶜ D ⟧ᶜ Raw (length Γ)}
     P.⟦ D ⟧ᶜ Raw Pre Syn rs  Dec (∃[ A ]  D ⟧ᶜ Raw _⊢_[_]_ Γ rs Syn A)
  synthesiseᶜ (ι Syn A Ds) (Ds⊆Ξ , SDs) Γ (refl , ts)
    with synthesiseⁿ Ds SDs Γ ts empty  ())
  ... | noₘ   ¬Pσ = no λ where
    (A , _ , σ , refl , ⊢ts)  ¬Pσ (_ , Sub⇒Sub⊆ σ) (ext-con ∅≤ρ ((λ {x} x∈  ⊆enum x) , ⊢ᵃˢ→Sub⊆⊢ᵃˢ ⊢ts))
  ... | yesₘ ρ (min-con (ext-con []≤ρ (Ds⊆ , ⊢ts)) minρ) =
    let ρ′ = Sub⊆⇒Sub (ρ .proj₂) (Ds⊆  Ds⊆Ξ) in
    yes (A  ρ′  , refl , ρ′ , refl , Sub⊆⊢ᵃˢ→⊢ᵃˢ ⊢ts)

  synthesiseⁿ
    : (Ds : ArgsD Ξ) (mc : ModeCorrectᵃˢ ys Ds)
     (Γ : Cxt 0) {rs : R.⟦ eraseᵃˢ Ds ⟧ᵃˢ Raw (length Γ)}
     P.⟦ Ds ⟧ᵃˢ Raw Pre rs
     ((xs , ρ) : ∃Sub⊆ Ξ) (ys⊆ : ys #⊆ xs)
     MinDec (Ext (_ , ρ) λ ρ̅ 
        Σ[ Ds⊆  ys  known Ds #⊆ _ ] M.⟦ Ds ⟧ᵃˢ ρ̅ ys Ds⊆ mc Raw _⊢_[_]_ Γ rs)
  synthesiseⁿ []       _        _ _        ρ ys⊆ = yesₘ ρ (Pρ→MinExtP (ys⊆  ∪-⊆⁺ #⊆-refl []⊆xs , _))

  synthesiseⁿ {ys = ys} (Δ ⊢[ Syn ] Aₙ  Ds) (Δ⊆Ds , mc) Γ {r , rs} (t , ts) ρ ys⊆ with synthesiseⁿ Ds mc Γ ts ρ ys⊆

  ... | noₘ ¬∃σ = noₘ λ where
    σ (ext-con ρ≤σ (Ds⊆ , ⊢t , ⊢ts))  ¬∃σ σ (ext-con ρ≤σ (Ds⊆  ∪-monotoneʳ ys (∪⁺ʳ (vars Aₙ)) , ⊢ts))

  ... | yesₘ (ys′ , ρ̅) (min-con (ext-con ρ≤ρ̅ (Ds⊆ , ⊢ts)) minρ̅)
    with synthesiseᵃ Δ (_ , ρ̅) (All.map  {B} B⊆ {x} x∈  Ds⊆ (B⊆ x∈)) Δ⊆Ds) Γ t
  ... | no  ¬⊢t = noₘ λ where
    (zs , σ) (ext-con ρ≤σ (Ds⊆′ , ⊢t , ⊢ts)) 
      let ρ̅≤σ = minρ̅ (_ , σ) (ext-con ρ≤σ (Ds⊆′  ∪-monotoneʳ ys (∪⁺ʳ (vars Aₙ)) , ⊢ts)) in
      ¬⊢t (sub⊆ σ Aₙ (Ds⊆′  λ x∈  ∪⁺ʳ ys (∪⁺ˡ x∈)) , ρ≤σ→⊢tᵃ ρ̅≤σ Δ ⊢t)

  ... | yes (A , ⊢t) with acmp Aₙ A (_ , ρ̅)

  ... | noₘ ¬Aₙ≈A  = noₘ λ where
    (zs , σ) (ext-con ρ≤σ (Ds⊆′ , ⊢t′ , ⊢ts′)) 
      let ρ̅≤σ = minρ̅ (_ , σ) (ext-con ρ≤σ (Ds⊆′  ∪-monotoneʳ ys (∪⁺ʳ (vars Aₙ)) , ⊢ts′))
      in ¬Aₙ≈A (_ , σ) (ext-con (minρ̅ (_ , σ) record
        { ext     = ≤-trans ρ≤ρ̅ ρ̅≤σ
        ; witness = (Ds⊆′  ∪-monotoneʳ ys (∪⁺ʳ (vars Aₙ))) , ⊢ts′
        }) (_ , sym (uniqᵐ-⇒ Δ ρ̅≤σ ⊢t ⊢t′)))

  ... | yesₘ (zs , σ) (min-con (ext-con ρ̅≤σ@(≤-con ys⊆zs _) (Aₙ⊆ , refl)) minσ) = yesₘ (_ , σ) (record
    { proof      = record
      { ext     = ≤-trans ρ≤ρ̅ ρ̅≤σ
      ; witness =  ∪-⊆⁺ {xs = ys} ((ys⊆zs  ρ≤ρ̅ .domain-ext)  ys⊆) (∪-⊆⁺ Aₙ⊆ (ys⊆zs  Ds⊆  ∪⁺ʳ ys))  , subst  A 
        M.⟦ Δ ⟧ᵃ (_ , σ) (L.A.map  {A} A⊆ {x} x∈  _ ) Δ⊆Ds) Raw (_⊢_[ Syn ] A) Γ r) (sub⊆-⊆-irrelevant σ Aₙ _ _) (ρ≤σ→⊢tᵃ′ ρ̅≤σ Δ ⊢t) ,
        ρ≤σ→⊢tⁿ′ ρ̅≤σ  Ds ys mc ⊢ts
      }
    ; minimality = λ where
      (zs′ , γ) (ext-con ρ≤γ (Ds⊆′ , ⊢t′ , ⊢ts′)) 
        let ρ̅≤γ = minρ̅ (_ , γ) (ext-con ρ≤γ (Ds⊆′  ∪-monotoneʳ ys (∪⁺ʳ (vars Aₙ)) , ⊢ts′)) in
        minσ (_ , γ) record
        { ext     = ρ̅≤γ
        ; witness = Ds⊆′  ∪⁺ʳ ys  ∪⁺ˡ , sym (uniqᵐ-⇒ Δ ρ̅≤γ ⊢t ⊢t′)
        }
    })

  synthesiseⁿ (Δ ⊢[ Chk ] Aₙ  Ds) (A⊆Ds  Δ⊆Ds , mc) Γ {r , rs} (t , ts) ρ ys⊆ with synthesiseⁿ Ds mc Γ ts ρ ys⊆

  ... | noₘ ¬∃σ = noₘ λ where
    σ (ext-con ρ≤σ (Ds⊆ , ⊢t , ⊢ts))  ¬∃σ σ (ext-con ρ≤σ (Ds⊆ , ⊢ts))

  ... | yesₘ (xs , ρ̅) (min-con (ext-con ρ≤ρ̅ (Ds⊆ , ⊢ts)) minρ̅)
    with checkᵃ Δ (xs , ρ̅) (L.A.map  {B} B⊆ {x} x∈  Ds⊆ (B⊆ x∈)) Δ⊆Ds) Γ t (sub⊆ ρ̅ Aₙ (Ds⊆  A⊆Ds))

  ... | no  ¬⊢t = noₘ λ where
    (zs , σ) (ext-con ρ̅≤σ (Ds⊆′ , ⊢t′ , ⊢ts′)) 
      let ρ̅≤σ = minρ̅ (_ , σ) (ext-con ρ̅≤σ (Ds⊆′ , ⊢ts′))
          eq : sub⊆ σ Aₙ  x  Ds⊆′ (A⊆Ds x))  sub⊆ ρ̅ Aₙ  x  Ds⊆ (A⊆Ds x))
          eq = begin
            sub⊆ σ Aₙ  x  Ds⊆′ (A⊆Ds x))
              ≡⟨ ρ=σ→subρ=subσ Aₙ σ ρ̅ (Ds⊆′  A⊆Ds) (Ds⊆  A⊆Ds)
                 x∈  begin
                  σ (Ds⊆′ (A⊆Ds x∈))
                    ≡⟨ cong σ (#∈-uniq _ _) 
                  σ _
                    ≡⟨ sym (ρ̅≤σ .consistency (Ds⊆ (A⊆Ds x∈))) 
                  ρ̅ (Ds⊆ (A⊆Ds x∈))
                    ) 
            sub⊆ ρ̅ Aₙ _
              ≡⟨ sub⊆-⊆-irrelevant ρ̅ Aₙ _ _ 
            sub⊆ ρ̅ Aₙ  x  Ds⊆ (A⊆Ds x))
              
      in
      ¬⊢t (ρ≤σ→⊢tᵃ ρ̅≤σ Δ (subst  A  M.⟦ Δ ⟧ᵃ (_ , σ) _ Raw (_⊢_[ Chk ] A) Γ r) eq ⊢t′))

  ... | yes ⊢t = yesₘ (_ , ρ̅) (min-con (ext-con ρ≤ρ̅ (Ds⊆ , ⊢t , ⊢ts))
    λ σ (ext-con ρ≤σ (Ds⊆′ , ⊢t , ⊢ts))  minρ̅ σ (ext-con ρ≤σ (Ds⊆′ , ⊢ts)))

  synthesiseᵃ
    : (Δ : TExps Ξ) ((xs , ρ) : ∃Sub⊆ Ξ) (Δ⊆ : Cover xs Δ)
     (Γ : Cxt 0) {r : R.⟦ Δ ⟧ᵃ Raw (length Γ)}
     P.⟦ Δ ⟧ᵃ Raw Pre Syn r
     Dec (∃[ A ] M.⟦ Δ ⟧ᵃ (_ , ρ) Δ⊆ Raw (_⊢_⇒ A) Γ r)
  synthesiseᵃ []      (_ , ρ) Δ⊆        Γ t = synthesise Γ t
  synthesiseᵃ (A  Δ) (_ , ρ) (A⊆  Δ⊆) Γ t =
    synthesiseᵃ Δ (_ , ρ) (Δ⊆) (sub⊆ ρ A (A⊆)  Γ) t

  checkᵃ
    : (Δ : TExps Ξ) ((xs , ρ) : ∃Sub⊆ Ξ) (Δ⊆ : Cover xs Δ)
     (Γ : Cxt 0) {r : R.⟦ Δ ⟧ᵃ Raw (length Γ)}
     P.⟦ Δ ⟧ᵃ Raw Pre Chk r
     (A : Ty)  Dec (M.⟦ Δ ⟧ᵃ (_ , ρ) Δ⊆ Raw (_⊢_⇐ A) Γ r)
  checkᵃ []      (_ , ρ) Δ⊆        Γ t A₀ = check Γ t A₀
  checkᵃ (A  Δ) (_ , ρ) (A⊆  Δ⊆) Γ t A₀ =
    checkᵃ Δ (_ , ρ) Δ⊆ (sub⊆ ρ A A⊆  Γ) t A₀