module lps.Tactics where

open import Bag-equivalence
open import Equality.Propositional

import Relation.Nullary as RN
import Relation.Binary.PropositionalEquality as RBEq
open import lib.Maybe

open import lib.Context
open Context
module BT = BelongsTo
module Il = Interleaving

module MyFin where

  open import Data.Nat hiding (_≟_)
  open import Data.Fin as Fin
  open import lib.Nullary
  open import Function

  suc-inj : {n : ℕ} {k l : Fin n} (eq : Fin.suc k RBEq.≡ suc l) → k RBEq.≡ l
  suc-inj RBEq.refl = RBEq.refl

  _≟_ : {n : ℕ} (k l : Fin n) → RN.Dec (k RBEq.≡ l)
  zero ≟ zero   = RN.yes RBEq.refl
  zero ≟ suc l  = RN.no (λ ())
  suc k ≟ zero  = RN.no (λ ())
  suc k ≟ suc l = dec (k ≟ l) (RN.yes ∘ RBEq.cong suc) (λ p → RN.no $ p ∘ suc-inj)

module TacticsBasics (Pr : Set) where

  open import Prelude
  open import lps.IMLL Pr
  open Type
  open Pointwise

  toContext : List Pr → Con ty
  toContext = foldr (λ p Γ → Γ ∙ `κ p) ε

  toGoal : Pr → List Pr → ty
  toGoal p []       = `κ p
  toGoal p (q ∷ qs) = `κ p `⊗ toGoal q qs

  data isAtom : (σ : ty) → Set where
    `κ : (p : Pr) → isAtom $ `κ p

  infixl 40 _`⊗_
  data isProduct : (σ : ty) → Set where
    `κ   : (p : Pr) → isProduct $ `κ p
    _`⊗_ : {σ τ : ty} (S : isProduct σ) (T : isProduct τ) → isProduct $ σ `⊗ τ

  isProductToGoal : (x : Pr) (xs : List Pr) → isProduct $ toGoal x xs
  isProductToGoal x []       = `κ x
  isProductToGoal x (y ∷ xs) = `κ x `⊗ isProductToGoal y xs

  fromProduct : {σ : ty} (S : isProduct σ) → List Pr
  fromProduct (`κ p)   = p ∷ []
  fromProduct (S `⊗ T) = fromProduct S ++ fromProduct T

  isAtoms : (Γ : Con ty) → Set
  isAtoms = PCon isAtom

  isAtomsToContext : (xs : List Pr) → isAtoms $ toContext xs
  isAtomsToContext []       = ε
  isAtomsToContext (x ∷ xs) = isAtomsToContext xs ∙ `κ x

  fromAtoms : {γ : Con ty} (Γ : isAtoms γ) → List Pr
  fromAtoms ε          = []
  fromAtoms (Γ ∙ `κ p) = p ∷ fromAtoms Γ 

  noWith : {γ : Con ty} {σ τ : ty} (Γ : isAtoms γ) (var : σ `& τ BT.∈ γ) → ⊥ {lzero}
  noWith (Γ ∙ ()) BT.zro
  noWith (Γ ∙ _)  (BT.suc var) = noWith Γ var

  noTensor : {γ : Con ty} {σ τ : ty} (Γ : isAtoms γ) (var : σ `⊗ τ BT.∈ γ) → ⊥ {lzero}
  noTensor (Γ ∙ ()) BT.zro
  noTensor (Γ ∙ _) (BT.suc var) = noTensor Γ var

  isAtoms-merge⁻¹ : {γ δ e : Con ty} (Γ : isAtoms γ) (mg : γ Interleaving.≡ δ ⋈ e) →
                       isAtoms δ × isAtoms e
  isAtoms-merge⁻¹ ε       Il.ε         = ε , ε
  isAtoms-merge⁻¹ (Γ ∙ S) (mg Il.∙ʳ σ) = Σ-map id (λ E → E ∙ S) $ isAtoms-merge⁻¹ Γ mg
  isAtoms-merge⁻¹ (Γ ∙ S) (mg Il.∙ˡ σ) = Σ-map (λ Δ → Δ ∙ S) id $ isAtoms-merge⁻¹ Γ mg

  fromToContext : (xs : List Pr) → xs RBEq.≡ fromAtoms (isAtomsToContext xs)
  fromToContext []       = RBEq.refl
  fromToContext (x ∷ xs) = RBEq.cong₂ _∷_ RBEq.refl $ fromToContext xs

  fromToGoal : (x : Pr) (xs : List Pr) → x ∷ xs RBEq.≡ fromProduct (isProductToGoal x xs)
  fromToGoal x []       = RBEq.refl
  fromToGoal x (y ∷ ys) = RBEq.cong₂ _∷_ RBEq.refl $ fromToGoal y ys

open import Algebra
open CommutativeMonoid
open import Level
import Data.Product as Prod
import Data.Empty   as Zero

module TacticsAbMon
  (Mon : CommutativeMonoid zero zero)
  (_≟_ : (x y : Carrier Mon) → RN.Dec (x RBEq.≡ y))
  where

  open import Algebra.Structures
  module M  = CommutativeMonoid Mon

  open import Data.Nat as Nat hiding (_≟_)
  open import Prelude hiding (ℕ; Fin)
  open import Data.Fin
  open import Data.Vec as Vec

  infixr 6 _`∙_
  data Term (n : ℕ) : Set where
    `v   : (k : Fin n)        → Term n 
    `c   : (el : Carrier Mon) → Term n
    _`∙_ : (t u : Term n)     → Term n

  ⟦_⟧ : {n : ℕ} (t : Term n) (ρ : Vec M.Carrier n) → M.Carrier
  ⟦ `v k   ⟧ ρ = Vec.lookup k ρ
  ⟦ `c el  ⟧ ρ = el
  ⟦ t `∙ u ⟧ ρ = ⟦ t ⟧ ρ M.∙ ⟦ u ⟧ ρ

  _∙∙_ : {n : ℕ} → M.Carrier × List (Fin n) → M.Carrier × List (Fin n) → M.Carrier × List (Fin n)
  (e , ks) ∙∙ (f , ls) = e M.∙ f , ks Prelude.++ ls

  norm : {n : ℕ} (t : Term n) → M.Carrier × List (Fin n)
  norm (`v k)   = M.ε , k ∷ []
  norm (`c el)  = el  , []
  norm (t `∙ u) = norm t ∙∙ norm u

  ⟦_⟧s : {n : ℕ} (ks : List $ Fin n) (ρ : Vec M.Carrier n) → M.Carrier
  ⟦ ks ⟧s ρ = Prelude.foldr M._∙_ M.ε $ Prelude.map (flip Vec.lookup ρ) ks

  open import Relation.Binary.EqReasoning M.setoid

  ⟦_`++_⟧s : {n : ℕ} (ks ls : List $ Fin n) (ρ : Vec M.Carrier n) →
            ⟦ ks Prelude.++ ls ⟧s ρ M.≈ ⟦ ks ⟧s ρ M.∙ ⟦ ls ⟧s ρ
  ⟦ []     `++ ls ⟧s ρ = M.sym $ M.identityˡ _
  ⟦ k ∷ ks `++ ls ⟧s ρ =
    begin
      ⟦ k ∷ ks Prelude.++ ls ⟧s ρ                  ≈⟨ M.∙-cong M.refl $ ⟦ ks `++ ls ⟧s ρ ⟩
      Vec.lookup k ρ M.∙ (⟦ ks ⟧s ρ M.∙ ⟦ ls ⟧s ρ) ≈⟨ M.sym $ M.assoc (Vec.lookup k ρ) (⟦ ks ⟧s ρ) (⟦ ls ⟧s ρ) ⟩
      ⟦ k ∷ ks ⟧s ρ M.∙ ⟦ ls ⟧s ρ
    ∎

  ⟦_⟧′ : {n : ℕ} (t : M.Carrier × List (Fin n)) (ρ : Vec M.Carrier n) → M.Carrier
  ⟦ el , ks ⟧′ ρ = el M.∙ ⟦ ks ⟧s ρ

  flip23 : (e f g h : M.Carrier) → (e M.∙ f) M.∙ (g M.∙ h) M.≈ (e M.∙ g) M.∙ (f M.∙ h)
  flip23 e f g h =
    begin
      (e M.∙ f) M.∙ (g M.∙ h) ≈⟨ M.assoc e f (g M.∙ h) ⟩
      e M.∙ (f M.∙ (g M.∙ h)) ≈⟨ M.∙-cong M.refl (M.sym $ M.assoc f g h) ⟩
      e M.∙ (f M.∙ g M.∙ h)   ≈⟨ M.∙-cong M.refl (M.∙-cong (M.comm f g) M.refl) ⟩
      e M.∙ (g M.∙ f M.∙ h)   ≈⟨ M.∙-cong M.refl (M.assoc g f h) ⟩
      e M.∙ (g M.∙ (f M.∙ h)) ≈⟨ M.sym $ M.assoc e g (f M.∙ h) ⟩
      (e M.∙ g) M.∙ (f M.∙ h)
    ∎

  ∙∙-sound : {n : ℕ} (t u : M.Carrier × List (Fin n)) (ρ : Vec M.Carrier n) →
             ⟦ t ⟧′ ρ M.∙ ⟦ u ⟧′ ρ M.≈ ⟦ t ∙∙ u ⟧′ ρ
  ∙∙-sound (e , ks) (f , ls) ρ =
    let F ks = ⟦ ks ⟧s ρ in
    begin
      (e M.∙ F ks) M.∙ (f M.∙ F ls)        ≈⟨ flip23 e (F ks) f (F ls) ⟩
      (e M.∙ f) M.∙ (F ks M.∙ F ls)        ≈⟨ M.∙-cong M.refl (M.sym $ ⟦ ks `++ ls ⟧s ρ) ⟩
      (e M.∙ f) M.∙ (F $ ks Prelude.++ ls)
    ∎

  norm-sound : {n : ℕ} (t : Term n) (ρ : Vec M.Carrier n) → ⟦ t ⟧ ρ M.≈ ⟦ norm t ⟧′ ρ
  norm-sound (`v k)   ρ =
    begin
      Vec.lookup k ρ                   ≈⟨ M.sym $ Prod.proj₂ M.identity _ ⟩
      Vec.lookup k ρ M.∙ M.ε           ≈⟨ M.sym $ Prod.proj₁ M.identity _ ⟩
      M.ε M.∙ (Vec.lookup k ρ M.∙ M.ε)
    ∎
  norm-sound (`c el)  ρ = M.sym $ Prod.proj₂ M.identity _
  norm-sound (t `∙ u) ρ =
    begin
      ⟦ t ⟧ ρ M.∙ ⟦ u ⟧ ρ             ≈⟨ M.∙-cong (norm-sound t ρ) (norm-sound u ρ) ⟩
      ⟦ norm t ⟧′ ρ M.∙ ⟦ norm u ⟧′ ρ ≈⟨ ∙∙-sound (norm t) (norm u) ρ ⟩
      ⟦ norm t ∙∙ norm u ⟧′ ρ
    ∎

  module FixedVars (n : ℕ) (ρ : Vec M.Carrier n) where

    open import lps.IMLL (Fin n)
    open Type
    open Pointwise
    open TacticsBasics (Fin n)

    sound⋈ : {γ δ e : Con ty} (Γ : isAtoms γ) (Δ : isAtoms δ) (E : isAtoms e)
             (mg : γ Interleaving.≡ δ ⋈ e) → ⟦ fromAtoms Γ ⟧s ρ M.≈ ⟦ fromAtoms Δ ⟧s ρ M.∙ ⟦ fromAtoms E ⟧s ρ
    sound⋈ ε ε ε Il.ε = M.sym $ M.identityˡ M.ε
    sound⋈ (Γ PCon.∙ (`κ p)) Δ (E PCon.∙ (`κ .p)) (mg Il.∙ʳ .(`κ p)) =
      begin
        ⟦ p ∷ fromAtoms Γ ⟧s ρ                                         ≈⟨ M.∙-cong M.refl $ sound⋈ Γ Δ E mg ⟩
        Vec.lookup p ρ M.∙ (⟦ fromAtoms Δ ⟧s ρ M.∙ ⟦ fromAtoms E ⟧s ρ) ≈⟨ M.sym $ M.assoc _ _ _ ⟩
        ⟦ p ∷ fromAtoms Δ ⟧s ρ M.∙ ⟦ fromAtoms E ⟧s ρ                  ≈⟨ M.∙-cong (M.comm _ _) M.refl ⟩
        ⟦ fromAtoms Δ ⟧s ρ M.∙ Vec.lookup p ρ M.∙ ⟦ fromAtoms E ⟧s ρ   ≈⟨ M.assoc _ _ _ ⟩
        ⟦ fromAtoms Δ ⟧s ρ M.∙ ⟦ p ∷ fromAtoms E ⟧s ρ
      ∎ 
    sound⋈ (Γ PCon.∙ `κ p) (Δ PCon.∙ `κ .p) E (mg Il.∙ˡ .(`κ p)) =
      begin
        ⟦ p ∷ fromAtoms Γ ⟧s ρ                                         ≈⟨ M.∙-cong M.refl $ sound⋈ Γ Δ E mg ⟩
        Vec.lookup p ρ M.∙ (⟦ fromAtoms Δ ⟧s ρ M.∙ ⟦ fromAtoms E ⟧s ρ) ≈⟨ M.sym $ M.assoc _ _ _ ⟩
        ⟦ p ∷ fromAtoms Δ ⟧s ρ M.∙ ⟦ fromAtoms E ⟧s ρ
      ∎

    sound : {γ : Con ty} {σ : ty} (Γ : isAtoms γ) (S : isProduct σ) →
            γ ⊢ σ → ⟦ fromAtoms Γ ⟧s ρ M.≈  ⟦ fromProduct S ⟧s ρ
    sound (ε PCon.∙ `κ p) (`κ .p) `v = M.refl
    sound Γ (S `⊗ T) (s `⊗ʳ t by mg) =
      let (Δ , E) = isAtoms-merge⁻¹ Γ mg
          ih₁     = sound Δ S s
          ih₂     = sound E T t
      in begin
        ⟦ fromAtoms Γ ⟧s ρ                             ≈⟨ sound⋈ Γ Δ E mg ⟩
        ⟦ fromAtoms Δ ⟧s ρ M.∙ ⟦ fromAtoms E ⟧s ρ      ≈⟨ M.∙-cong ih₁ ih₂ ⟩
        ⟦ fromProduct S ⟧s ρ M.∙  ⟦ fromProduct T ⟧s ρ ≈⟨ M.sym $ ⟦ fromProduct S `++ fromProduct T ⟧s ρ ⟩
        ⟦ fromProduct $ S `⊗ T ⟧s ρ
      ∎
    sound Γ _ (var `⊗ˡ _)  = ⊥-elim $ noTensor Γ var
    sound Γ _ (var `&ˡ₁ _) = ⊥-elim $ noWith   Γ var
    sound Γ _ (var `&ˡ₂ _) = ⊥-elim $ noWith   Γ var

  open import lps.ProofSearch
  open import lib.Nullary as LN
  open Maybe
  import Data.Empty as Zero
  import lps.Equivalence as Equivalence

  eq-const : ∀ {n e f} (eq : e RBEq.≡ f) (ρ : Vec M.Carrier n) → ⟦ e , [] ⟧′ ρ M.≈ ⟦ f , [] ⟧′ ρ
  eq-const RBEq.refl ρ = M.refl

  proveM : {n : ℕ} (t u : Term n) (ρ : Vec M.Carrier n) → Maybe $ ⟦ t ⟧ ρ M.≈ ⟦ u ⟧ ρ
  proveM {n} t u ρ with norm t | norm u | norm-sound t ρ | norm-sound u ρ
  ... | (e , k ∷ ks) | (f , [])     | sndt | sndu = none
  ... | (e , [])     | (f , [])     | sndt | sndu = dec (e ≟ f) (some ∘ p₁) (const none)
    where
      p₁ : (ef : e RBEq.≡ f) → ⟦ t ⟧ ρ M.≈ ⟦ u ⟧ ρ
      p₁ ef =
        begin
          ⟦ t ⟧ ρ       ≈⟨ sndt ⟩
          ⟦ e , [] ⟧′ ρ ≈⟨ eq-const ef ρ ⟩
          ⟦ f , [] ⟧′ ρ ≈⟨ M.sym sndu ⟩
          ⟦ u ⟧ ρ
        ∎
  ... | (e , ks)     | (f , l ∷ ls) | sndt | sndu =
    flip (dec (e ≟ f)) (const none) $ λ eq →
    let open FixedVars n ρ
        open TacticsBasics (Fin n)
        open Prove (Fin n) (MyFin._≟_ {n})
        open Equivalence (Fin n) (MyFin._≟_ {n})
        okContext = isAtomsToContext ks
        okGoal    = isProductToGoal l ls
        result    = dec (⊢-dec (toContext ks) (toGoal l ls)) some (const none)
        mon-eq    = sound okContext okGoal <$> result
        fromTo l  = fromProduct ∘ isProductToGoal l
        rew-eqks  = RBEq.subst (λ e → ⟦ e ⟧s ρ M.≈ _) (RBEq.sym $ fromToContext ks)
        rew-eqls  = RBEq.subst (λ e → ⟦ e ⟧s ρ M.≈ ⟦ l ∷ ls ⟧s ρ) (fromToGoal l ls)
    in (λ pr →
        begin
          ⟦ t ⟧ ρ                  ≈⟨ sndt ⟩
          ⟦ e , ks ⟧′ ρ            ≈⟨ M.∙-cong (RBEq.subst (M._≈_ e) eq M.refl) (rew-eqks pr) ⟩
          f M.∙ ⟦ fromTo l ls ⟧s ρ ≈⟨ M.∙-cong M.refl (rew-eqls M.refl) ⟩
          ⟦ f , l ∷ ls ⟧′ ρ        ≈⟨ M.sym sndu ⟩
          ⟦ u ⟧ ρ
        ∎) <$> mon-eq

  proveMonEq : {n : ℕ} (t u : Term n) (ρ : Vec M.Carrier n) {_ : maybe (const ⊤) ⊥ $ proveM t u ρ} →
               ⟦ t ⟧ ρ M.≈ ⟦ u ⟧ ρ
  proveMonEq t u ρ {pr} = Maybe.induction P ⊥-elim const (proveM t u ρ) pr
    where P = λ a → ∀ (pr : maybe (const ⊤) ⊥ a) → ⟦ t ⟧ ρ M.≈ ⟦ u ⟧ ρ

module BagEq (Pr : Set) (_≟_ : (x y : Pr) → RN.Dec (x RBEq.≡ y)) where

  open import Algebra.Structures
  open import Prelude
  open import Function-universe equality-with-J as FU
  import Bijection equality-with-J as Bij

  isSg : IsSemigroup _≈-bag_ _++_
  isSg = record { isEquivalence = record { refl  = λ _ → Bij.id
                                         ; sym   = Prelude._∘_ FU.inverse
                                         ; trans = λ f g z → g z Bij.∘ f z }
                ; assoc         = ++-assoc 
                ; ∙-cong        = ++-cong }
    where
      ++-assoc : (xs ys zs : List Pr) → (xs ++ ys) ++ zs ≈-bag xs ++ ys ++ zs
      ++-assoc []       ys zs = λ z → Bij.id
      ++-assoc (x ∷ xs) ys zs = _≡_.refl ∷-cong ++-assoc xs ys zs

  Mon : CommutativeMonoid lzero lzero
  Mon = record
          { Carrier = List Pr
          ; _≈_     = _≈-bag_
          ; _∙_     = _++_
          ; ε       = []
          ; isCommutativeMonoid = record { isSemigroup = isSg
                                         ; identityˡ   = λ _ _ → Bij.id
                                         ; comm        = ++-comm } }

  ∷-inj : {x y : Pr} {xs ys : List Pr} (eq : RBEq._≡_ {_} {List Pr} (x ∷ xs) (y ∷ ys)) →
          x RBEq.≡ y × xs RBEq.≡ ys
  ∷-inj RBEq.refl = RBEq.refl , RBEq.refl

  _≟s_ : (xs ys : List Pr) → RN.Dec (xs RBEq.≡ ys)
  []       ≟s [] = RN.yes RBEq.refl
  []       ≟s (x ∷ ys) = RN.no (λ ())
  (x ∷ xs) ≟s [] = RN.no (λ ())
  (x ∷ xs) ≟s (y ∷ ys) with x ≟ y | xs ≟s ys
  ... | RN.yes p | RN.yes ps = RN.yes $ RBEq.cong₂ _∷_ p ps
  ... | _        | RN.no ¬ps = RN.no (¬ps Prelude.∘ proj₂ Prelude.∘ ∷-inj)
  ... | RN.no ¬p | _         = RN.no (¬p Prelude.∘ proj₁ Prelude.∘ ∷-inj)

  open TacticsAbMon Mon _≟s_ public
