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₀