open import Data.Empty
open import Data.Nat using (ℕ; _+_)
open import Data.Fin hiding (_+_)
open import Data.Fin.Properties
open import Data.Vec
open import Algebra
import Algebra.Morphism.Definitions as MorphDefs
open import Relation.Binary
open import Relation.Binary.Morphism.Structures
open import Relation.Binary.PropositionalEquality as ≡ using (_≢_)
import Data.Vec.Relation.Binary.Equality.Setoid as VecSetoid

open import Vec.Permutation

module Algebra.Morphism.vecCommMonoid {c} {ℓ} {a} {ℓ′}
  (S : Setoid a ℓ′)
  (let open module S = Setoid S renaming (Carrier to A; _≈_ to _≈₁_))
  (open VecSetoid S)
  (cMonoid : CommutativeMonoid c ℓ)
  (let open module CM = CommutativeMonoid cMonoid renaming (Carrier to B; _≈_ to _≈₂_))
  (let open module MD {n} = MorphDefs (Vec A n) B _≈₂_)
  (⟦_⟧ : ∀ {n} → Vec A n → B)
  (ε-homo : Homomorphic₀ ⟦_⟧ [] ε)
  (isRelHomomorphism : ∀ {n} → IsRelHomomorphism (_≋_ {n}) _≈₂_ ⟦_⟧)
  (homo : ∀ {m n} → (xs : Vec A m) (ys : Vec A n) → ⟦ xs ++ ys ⟧ ≈₂ ⟦ xs ⟧ ∙ ⟦ ys ⟧)
  where

open module IRL {n} = IsRelHomomorphism (isRelHomomorphism {n})

open import Vec.View S
open import Relation.Binary.Reasoning.Setoid setoid
open import Algebra.Solver.CommutativeMonoid cMonoid hiding (⟦_⟧)

private variable
  m n : ℕ
  xs ys ws zs vs : Vec A n
  p q : A
  x y w z v : B

⦅_⦆ : A → B
⦅ x ⦆ = ⟦ [ x ] ⟧

↭⇒≈ : (_↭_ {n = n}) =[ ⟦_⟧ ]⇒ _≈₂_
↭⇒≈ _↭_.refl = CM.refl
↭⇒≈ (prep {n} {xs} {ys} x xs↔ys) = begin
  ⟦ [ x ] ++  xs ⟧ ≈⟨ homo _ _ ⟩
  ⦅ x ⦆ ∙ ⟦ xs ⟧   ≈⟨ ∙-congˡ (↭⇒≈ xs↔ys) ⟩
  ⦅ x ⦆ ∙ ⟦ ys ⟧  ≈˘⟨ homo _ _ ⟩
  ⟦ [ x ] ++ ys ⟧   ∎
↭⇒≈ (swap {n} {xs} {ys} x y xs↔ys) = begin
  ⟦ [ x ] ++ [ y ] ++ xs ⟧  ≈⟨ homo _ _ ⟩
  ⦅ x ⦆ ∙ ⟦ [ y ] ++ xs ⟧   ≈⟨ ∙-congˡ (homo _ _) ⟩
  ⦅ x ⦆ ∙ (⦅ y ⦆ ∙ ⟦ xs ⟧) ≈˘⟨ assoc _ _ _ ⟩
  ⦅ x ⦆ ∙ ⦅ y ⦆ ∙ ⟦ xs ⟧    ≈⟨ ∙-cong (comm _ _) (↭⇒≈ xs↔ys) ⟩
  ⦅ y ⦆ ∙ ⦅ x ⦆ ∙ ⟦ ys ⟧    ≈⟨ assoc _ _ _ ⟩
  ⦅ y ⦆ ∙ (⦅ x ⦆ ∙ ⟦ ys ⟧) ≈˘⟨ ∙-congˡ (homo _ _) ⟩
  ⦅ y ⦆ ∙ ⟦ [ x ] ++ ys ⟧  ≈˘⟨ homo _ _ ⟩
  ⟦ [ y ] ++ [ x ] ++ ys ⟧   ∎
↭⇒≈ (_↭_.trans {n} {xs} {ys} {zs} xs↔ys ys↔zs) = begin
  ⟦ xs ⟧ ≈⟨ ↭⇒≈ xs↔ys ⟩
  ⟦ ys ⟧ ≈⟨ ↭⇒≈ ys↔zs ⟩
  ⟦ zs ⟧ ∎

cong-vec : {xs : Vec A m} {ys : Vec A n} → xs ≋ ys → ⟦ xs ⟧ ≈₂ ⟦ ys ⟧
cong-vec eqn with length-equal eqn
... | ≡.refl = cong eqn

cong-⦅⦆ : p ≈₁ q → ⦅ p ⦆ ≈₂ ⦅ q ⦆
cong-⦅⦆ eqn = cong (eqn VecSetoid.∷ VecSetoid.[])

homo5 : ⟦ xs ++ ys ++ ws ++ zs ++ vs ⟧ ≈₂ ⟦ xs ⟧ ∙ (⟦ ys ⟧ ∙ (⟦ ws ⟧ ∙ (⟦ zs ⟧ ∙ ⟦ vs ⟧)))
homo5 {xs = xs} {ys = ys} {ws = ws} {zs = zs} {vs = vs} = begin
 ⟦ xs ++ ys ++ ws ++ zs ++ vs ⟧ ≈⟨ homo xs _ ⟩
 ⟦ xs ⟧ ∙  ⟦ ys ++ ws ++ zs ++ vs ⟧ ≈⟨ ∙-congˡ (homo ys _) ⟩
 ⟦ xs ⟧ ∙ (⟦ ys ⟧ ∙ ⟦ ws ++ zs ++ vs ⟧) ≈⟨ ∙-congˡ (∙-congˡ (homo ws _)) ⟩
 ⟦ xs ⟧ ∙ (⟦ ys ⟧ ∙ (⟦ ws ⟧ ∙ ⟦ zs ++ vs ⟧)) ≈⟨ ∙-congˡ (∙-congˡ (∙-congˡ (homo _ _))) ⟩
 ⟦ xs ⟧ ∙ (⟦ ys ⟧ ∙ (⟦ ws ⟧ ∙ (⟦ zs ⟧ ∙ ⟦ vs ⟧))) ∎


swap5 : x ∙ (y ∙ (w ∙ (z ∙ v))) ≈₂ (y ∙ z) ∙ (x ∙ (w ∙ v))
swap5 = solve 5 (λ x y w z v → x ⊕ (y ⊕ (w ⊕ (z ⊕ v))) ⊜ (y ⊕ z) ⊕ (x ⊕ (w ⊕ v))) CM.refl _ _ _ _ _

swap5' : x ∙ (y ∙ (w ∙ (z ∙ v))) ≈₂ (z ∙ y) ∙ (x ∙ (w ∙ v))
swap5' = solve 5 (λ x y w z v → x ⊕ (y ⊕ (w ⊕ (z ⊕ v))) ⊜ (z ⊕ y) ⊕ (x ⊕ (w ⊕ v))) CM.refl _ _ _ _ _

swapSameVecI<j : ∀ {xs : Vec A (2 + n)} {i j p q}
  → i < j
  → ⦅ lookup xs i ⦆ ∙ ⦅ lookup xs j ⦆ ≈₂ ⦅ p ⦆ ∙ ⦅ q ⦆
  → ⟦ xs ⟧ ≈₂ ⟦ xs [ i ]≔ p [ j ]≔ q ⟧
swapSameVecI<j {xs = xs} {i} {j} {p} {q} i<j eqn with vec→VecView2 xs i<j
... | view2 {ys = ys} {x} {zs} {w} {ws} vec≡xs = begin
  ⟦ xs ⟧ ≈˘⟨ cong-vec vec≡xs ⟩
  ⟦ ys ++ [ x ] ++ zs ++ [ w ] ++ ws ⟧ ≈⟨ homo5 ⟩
  ⟦ ys ⟧ ∙ (⦅ x ⦆ ∙ (⟦ zs ⟧ ∙ (⦅ w ⦆ ∙ ⟦ ws ⟧))) ≈⟨ swap5 ⟩
  ⦅ x ⦆ ∙ ⦅ w ⦆ ∙ (⟦ ys ⟧ ∙ (⟦ zs ⟧ ∙ ⟦ ws ⟧)) ≈⟨ ∙-congʳ xw≡pq ⟩
  ⦅ p ⦆ ∙ ⦅ q ⦆ ∙ (⟦ ys ⟧ ∙ (⟦ zs ⟧ ∙ ⟦ ws ⟧)) ≈˘⟨ swap5 ⟩
  ⟦ ys ⟧ ∙ (⦅ p ⦆ ∙ (⟦ zs ⟧ ∙ (⦅ q ⦆ ∙ ⟦ ws ⟧))) ≈˘⟨ homo5 ⟩
  ⟦ ys ++ [ p ] ++ zs ++ [ q ] ++ ws ⟧
    ≈⟨ cong-vec (changeVecView2ʳ i<j q (changeVecView2ˡ i<j p vec≡xs)) ⟩
  ⟦ xs [ i ]≔ p [ j ]≔ q ⟧ ∎ where

  lXsI : lookup xs i ≈₁ x
  lXsI = lookupVecView2ˡ i<j vec≡xs

  lXsJ : lookup xs j ≈₁ w
  lXsJ = lookupVecView2ʳ i<j vec≡xs

  xw≡pq = begin
    ⦅ x ⦆ ∙ ⦅ w ⦆ ≈˘⟨ ∙-cong (cong-⦅⦆ lXsI) (cong-⦅⦆ lXsJ) ⟩
    ⦅ lookup xs i ⦆ ∙ ⦅ lookup xs j ⦆ ≈⟨ eqn ⟩
    ⦅ p ⦆ ∙ ⦅ q ⦆ ∎

swapSameVecI>j : ∀ {xs : Vec A (2 + n)} {i j p q}
  → i > j
  → ⦅ lookup xs i ⦆ ∙ ⦅ lookup xs j ⦆ ≈₂ ⦅ p ⦆ ∙ ⦅ q ⦆
  → ⟦ xs ⟧ ≈₂ ⟦ xs [ i ]≔ p [ j ]≔ q ⟧
swapSameVecI>j {xs = xs} {i} {j} {p} {q} i>j eqn with vec→VecView2 xs i>j
... | view2 {ys = ys} {x} {zs} {w} {ws} vec≡xs = begin
  ⟦ xs ⟧ ≈˘⟨ cong-vec vec≡xs ⟩
  ⟦ ys ++ [ x ] ++ zs ++ [ w ] ++ ws ⟧ ≈⟨ homo5 ⟩
  ⟦ ys ⟧ ∙ (⦅ x ⦆ ∙ (⟦ zs ⟧ ∙ (⦅ w ⦆ ∙ ⟦ ws ⟧))) ≈⟨ swap5' ⟩
  ⦅ w ⦆ ∙ ⦅ x ⦆ ∙ (⟦ ys ⟧ ∙ (⟦ zs ⟧ ∙ ⟦ ws ⟧)) ≈⟨ ∙-congʳ xw≡pq ⟩
  ⦅ p ⦆ ∙ ⦅ q ⦆ ∙ (⟦ ys ⟧ ∙ (⟦ zs ⟧ ∙ ⟦ ws ⟧)) ≈˘⟨ swap5' ⟩
  ⟦ ys ⟧ ∙ (⦅ q ⦆ ∙ (⟦ zs ⟧ ∙ (⦅ p ⦆ ∙ ⟦ ws ⟧))) ≈˘⟨ homo5 ⟩
  ⟦ ys ++ [ q ] ++ zs ++ [ p ] ++ ws ⟧
    ≈⟨ cong-vec (changeVecView2ˡ i>j q (changeVecView2ʳ i>j p vec≡xs)) ⟩
  ⟦ xs [ i ]≔ p [ j ]≔ q ⟧ ∎ where

  lXsI : lookup xs i ≈₁ w
  lXsI = lookupVecView2ʳ i>j vec≡xs

  lXsJ : lookup xs j ≈₁ x
  lXsJ = lookupVecView2ˡ i>j vec≡xs

  xw≡pq = begin
    ⦅ w ⦆ ∙ ⦅ x ⦆ ≈˘⟨ ∙-cong (cong-⦅⦆ lXsI) (cong-⦅⦆ lXsJ) ⟩
    ⦅ lookup xs i ⦆ ∙ ⦅ lookup xs j ⦆ ≈⟨ eqn ⟩
    ⦅ p ⦆ ∙ ⦅ q ⦆ ∎


swapSameVecLookup : ∀ {xs : Vec A n} {i j p q}
  → i ≢ j
  → ⦅ lookup xs i ⦆ ∙ ⦅ lookup xs j ⦆ ≈₂ ⦅ p ⦆ ∙ ⦅ q ⦆
  → ⟦ xs ⟧ ≈₂ ⟦ xs [ i ]≔ p [ j ]≔ q ⟧
swapSameVecLookup {n = ℕ.suc ℕ.zero} {xs} {zero} {zero} i≢j eqn = ⊥-elim (i≢j ≡.refl)
swapSameVecLookup {n = ℕ.suc (ℕ.suc n)} {xs = xs} {i} {j} i≢j eqn with <-cmp i j
... | tri≈ _ i≡j _ = ⊥-elim (i≢j i≡j)
... | tri< i<j _ _ = swapSameVecI<j i<j eqn
... | tri> _ _ i>j = swapSameVecI>j i>j eqn