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

  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⋈ (Γ   p) Δ (E   .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⋈ (Γ   p) (Δ   .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 (ε   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