module Algebra.Matrix.Properties where

open import Level using (Level)
open import Function
open import Algebra.Bundles
import Algebra.Definitions as ADefinitions
open import Data.Nat as  using ()
open import Data.Nat.Properties as  using ()
open import Data.Fin hiding (_+_)
open import Data.Fin.Properties
open import Data.Sum.Base using (_⊎_; inj₁; inj₂; [_,_])
open import Data.List as L using (List; []; _∷_)
open import Data.Vec.Functional as V
open import Vector.Base as V using (module SetoidProps)
open import Relation.Binary
open import Relation.Binary.PropositionalEquality as  using (_≡_)
open import Relation.Nullary
import Relation.Binary.Reasoning.Setoid as ≈-Reasoning

open import Algebra.Matrix.Base renaming (flip to flipM) using ()
open import Algebra.Matrix.Structures
open import Algebra.BigOps

private variable
  a  : Level
  l m m₁ m₂ n n₁ n₂ p q k : 
  i j h : Fin n
  A : Set 

module MRingProps (ring : Ring a ) where

  open Ring ring hiding (zero)
  open MRing rawRing renaming (Matrix to Matrix′) hiding (flip; ; δ; δss≡δ)
  open import Data.Vec.Functional.Relation.Binary.Equality.Setoid setoid
  open SumRing ring
  open IsLinear
  open module AD' {m} {n} = ADefinitions (_≈ᴹ_ {m} {n})
  open module SProps {n} = SetoidProps (≋-setoid n)

  private
    Matrix = Matrix′ Carrier

  private variable
    M N K P Q : Matrix m n
    x y : Vector Carrier n
    r : Carrier

  ≈ᴹ-refl : Reflexive (_≈ᴹ_ {m = m} {n})
  ≈ᴹ-refl i j = refl

  ≈ᴹ-reflexive : (∀ i j  M i j  N i j)  M ≈ᴹ N
  ≈ᴹ-reflexive p i j rewrite p i j = refl

  ≈ᴹ-sym : Symmetric (_≈ᴹ_ {m = m} {n})
  ≈ᴹ-sym M≈N i j = sym (M≈N i j)

  ≈ᴹ-trans : Transitive (_≈ᴹ_ {m = m} {n})
  ≈ᴹ-trans M≈N N≈P i j = trans (M≈N i j) (N≈P i j)

  ≈ᴹ-isEquivalence : IsEquivalence (_≈ᴹ_ {m = m} {n})
  ≈ᴹ-isEquivalence = record
    { refl  = ≈ᴹ-refl
    ; sym   = ≈ᴹ-sym
    ; trans = ≈ᴹ-trans
    }

  ≈ᴹ-setoid :  {m n}  Setoid _ _
  ≈ᴹ-setoid {m} {n} = record
    { isEquivalence = ≈ᴹ-isEquivalence {m} {n}
    }

  module ≈ᴹ-Reasoning {m} {n} = ≈-Reasoning (≈ᴹ-setoid {m} {n})

  *ᴹ-zero : (M : Matrix m n)  0ᴹ *ᴹ M ≈ᴹ (0ᴹ {m = m})
  *ᴹ-zero {ℕ.zero} M i k = refl
  *ᴹ-zero {ℕ.suc n} M i k = begin
      0# * M zero k + _          ≈⟨ +-congʳ (zeroˡ _) 
      0# + _                     ≈⟨ +-identityˡ _ 
      ( λ j  0# * M (suc j) k) ≈˘⟨ ∑Mulrdist 0#  j  M (suc j) k) 
      0# * ( λ j  M (suc j) k) ≈⟨ zeroˡ _ 
      0#  where open ≈-Reasoning setoid

  *ᴹ-identityˡ : (M : Matrix m n)  1ᴹ *ᴹ M ≈ᴹ M
  *ᴹ-identityˡ {ℕ.suc n} M zero k = begin
    (1ᴹ *ᴹ M) zero k  ≡⟨⟩
    ( λ j  δ zero j * M j k) ≡⟨⟩
    foldr _+_ 0# {ℕ.suc n}  j  δ zero j * M j k) ≡⟨⟩
    δ (zero {n}) zero * M zero k + ( λ j  δ zero (suc j) * M (suc j) k) ≡⟨⟩
    1# * M zero k + ( λ j  0# * M (suc j) k) ≈˘⟨ +-congˡ (∑Mulrdist 0# λ j  M (suc j) k) 
    1# * M zero k + 0# * ( λ j  M (suc j) k) ≈⟨ +-cong (*-identityˡ _) (zeroˡ _) 
    M zero k + 0# ≈⟨ +-identityʳ _ 
    M zero k  where open ≈-Reasoning setoid
  *ᴹ-identityˡ {ℕ.suc n} M (suc i) k = begin
    (0# * M zero k + _) ≈⟨ +-congʳ (zeroˡ _) 
    (0# + _) ≈⟨ +-identityˡ _ 
    ( λ j  δ (suc i) (suc j) * M (suc j) k) ≈⟨ ∑Ext  j  *-congʳ (reflexive (δss≡δ i j)))  
    ( λ j  δ i j * M (suc j) k) ≈⟨ *ᴹ-identityˡ (tail M) i k 
    M (suc i) k  where open ≈-Reasoning setoid

  *ᴹ-congˡ : P ≈ᴹ Q  N *ᴹ P ≈ᴹ N *ᴹ Q
  *ᴹ-congˡ P≈Q _ j = ∑Ext λ k  *-congˡ (P≈Q k j)

  ∑Exchange : (M : Matrix m n)    i    j  M i j))    j    i  M i j))
  ∑Exchange {ℕ.zero} {n}        M = sym (∑0r n)
  ∑Exchange {ℕ.suc m} {ℕ.zero}  M = begin
      i   (M i)) ≈⟨ +-congˡ (∑0r m) 
    0# + 0#           ≈⟨ +-identityˡ 0# 
    0#  where open ≈-Reasoning setoid
  ∑Exchange {ℕ.suc m} {ℕ.suc n} M =
     let a  = M zero zero
         L  =  λ j  M zero (suc j)
         C  =  λ i  M (suc i) zero
         N  =  λ i   λ j  M (suc i) (suc j)
         -- N reindexed
         N' =  λ j   λ i  M (suc i) (suc j)
     in begin
     a + L +   i    j  M (suc i) j)) ≈⟨ +-congˡ (∑Split  i  M (suc i) zero) λ i    j  M (suc i) (suc j))) 
     a + L + (C + N)                         ≈⟨ +-congˡ (+-congˡ (∑Exchange  i j  M (suc i) (suc j)))) 
     a + L + (C + N')                        ≈⟨ +-assoc _ _ _ 
     a + (L + (C + N'))                      ≈˘⟨ +-congˡ (+-assoc _ _ _) 
     a + (L + C + N')                        ≈⟨ +-congˡ (+-congʳ (+-comm _ _)) 
     a + (C + L + N')                        ≈⟨ +-congˡ (+-assoc _ _ _) 
     a + (C + (L + N'))                      ≈˘⟨ +-assoc _ _ _ 
     a + C + (L + N')                        ≈˘⟨ +-congˡ (∑Split  j  M zero (suc j)) λ j    i  M (suc i) (suc j))) 
     a + C +   j    i  M i (suc j))) 
    where open ≈-Reasoning setoid

  *ᴹ-assoc : (M : Matrix m n) (N : Matrix n p) (K : Matrix p q)
     (M *ᴹ N) *ᴹ K ≈ᴹ M *ᴹ (N *ᴹ K)
  *ᴹ-assoc M N K i j = begin
      l    k  M i k * N k l) * K l j)    ≈⟨ ∑Ext  l  ∑Mulldist (K l j)  k  M i k * N k l)) 
      l    k  M i k * N k l * K l j))    ≈⟨ ∑Exchange  l k  M i k * N k l * K l j) 
      k    l  M i k * N k l * K l j))    ≈⟨ ∑Ext  k  ∑Ext  l  *-assoc (M i k) (N k l) (K l j))) 
      k    l  M i k * (N k l * K l j))) ≈˘⟨ ∑Ext  k  ∑Mulrdist (M i k)  l  N k l * K l j)) 
      k  M i k *   l  N k l * K l j)) 
    where open ≈-Reasoning setoid

  ++-split : (M : Matrix m₁ n) (N : Matrix m₂ n) (P : Matrix n p)  (M ++ N) *ᴹ P ≈ᴹ M *ᴹ P ++ N *ᴹ P
  ++-split {m₁} M N P i j with splitAt m₁ i
  ... | inj₁ _ = refl
  ... | inj₂ _ = refl

  ++ⱽ-split : (L : Matrix l m) (M : Matrix m n₁) (N : Matrix m n₂)  L *ᴹ (M ++ⱽ N) ≈ᴹ L *ᴹ M ++ⱽ L *ᴹ N
  ++ⱽ-split {n₁ = n₁} L M N i j with splitAt n₁ j
  ... | inj₁ _ = refl
  ... | inj₂ _ = refl

  take-++ : (M : Matrix m₁ n) (N : Matrix m₂ n)  take m₁ (M ++ N) ≈ᴹ M
  take-++ {m₁} {m₂ = m₂} M N i j rewrite splitAt-↑ˡ m₁ i m₂ = refl

  drop-++ : (M : Matrix m₁ n) (N : Matrix m₂ n)  drop m₁ (M ++ N) ≈ᴹ N
  drop-++ {m₁} {m₂ = m₂} M N i j rewrite splitAt-↑ʳ m₁ m₂ i  = refl

  take-++ⱽ : (M : Matrix m n₁) (N : Matrix m n₂)  takeⱽ n₁ (M ++ⱽ N) ≈ᴹ M
  take-++ⱽ {n₁ = n₁} {n₂} M N i j rewrite splitAt-↑ˡ n₁ j n₂ = refl

  drop-++ⱽ : (M : Matrix m n₁) (N : Matrix m n₂)  dropⱽ n₁ (M ++ⱽ N) ≈ᴹ N
  drop-++ⱽ {n₁ = n₁} {n₂} M N i j rewrite splitAt-↑ʳ n₁ n₂ j = refl

  isLinearId : IsLinear {m = m} {n = n} id
  isLinearId .transMat _ = 1ᴹ
  isLinearId .transEq  _ = ≈ᴹ-sym (*ᴹ-identityˡ _)

  module _
    {T₁ : Matrix m n  Matrix k n}
    {T₂ : Matrix k n  Matrix l n}
    (isLinearT₁ : IsLinear T₁)
    (isLinearT₂ : IsLinear T₂) where

    isLinearComp : IsLinear (T₂  T₁)
    transMat isLinearComp M = isLinearT₂ .transMat (T₁ M) *ᴹ isLinearT₁ .transMat M
    transEq isLinearComp M = begin
      (T₂  T₁) M
        ≈⟨ isLinearT₂ .transEq _ 
      transMat isLinearT₂ (T₁ M) *ᴹ T₁ M
        ≈⟨ *ᴹ-congˡ {N = transMat isLinearT₂ (T₁ M)} (isLinearT₁ .transEq M) 
      transMat isLinearT₂ (T₁ M) *ᴹ (transMat isLinearT₁ M *ᴹ M)
        ≈˘⟨ *ᴹ-assoc (transMat isLinearT₂ (T₁ M)) (transMat isLinearT₁ M) M 
      transMat isLinearComp M *ᴹ M 
      where open ≈ᴹ-Reasoning

  swapIsLinear : (p q : Fin n)  IsLinear {n = m} (swap p q)
  transMat (swapIsLinear p q) _ = swapMatrix p q
  transEq  (swapIsLinear p q) M i j with p  i | q  i
  ... | yes _ | yes _ = sym (∑Mul1r (flip M j) q)
  ... | yes _ | no  _ = sym (∑Mul1r (flip M j) q)
  ... | no  _ | yes _ = sym (∑Mul1r (flip M j) p)
  ... | no  _ | no  _ = sym (∑Mul1r (flip M j) i)

  addConstRowIsLinear : (p q : Fin n) (r : Carrier)
     IsLinear {n = m}  M  M [ p ]≔ r *[ q ])
  transMat (addConstRowIsLinear p q r) _ = addConstRowMatrix p q r
  transEq  (addConstRowIsLinear p q r) M i j with q  i
  ... | yes _ = begin
    M i j + r * M p j ≈˘⟨ +-cong (∑Mul1r  k  M k j) _) α 
      k  δ i k * M k j) +   k  r * δ p k * M k j)  ≈˘⟨ ∑Split _  k  r * δ p k * M k j) 
      k  δ i k * M k j + r * δ p k * M k j) ≈˘⟨ ∑Ext  k  distribʳ (M k j) _ _ ) 
      k  (δ i k + r * δ p k) * M k j) 
    where
      open ≈-Reasoning setoid
      α = begin
          k  r * δ p k * M k j)   ≈⟨ ∑Ext  k  *-assoc _ _ (M k j)) 
          k  r * (δ p k * M k j)) ≈˘⟨ ∑Mulrdist _  k  δ p k * M k j) 
        r *   k  δ p k * M k j)   ≈⟨ *-congˡ (∑Mul1r  k  M k j) _) 
        r * M p j 
  ... | no  _ = sym (∑Mul1r (flip M j) i)

  isLinear→bothLinear : {T : Matrix m n  Matrix k n}
    (isLin : IsLinear T)  MatFunAreLinear (transMat isLin) T
  isLinear→bothLinear isLin = record { transEq = transEq isLin }

  bothLinear→isLinear :
    {transMat : (M : Matrix m n)  Matrix k m}
    {T : Matrix m n  Matrix k n} 
    MatFunAreLinear transMat T  IsLinear T
  bothLinear→isLinear {transMat = transMat} bothLin = record
    { transMat = transMat
    ; transEq  = MF.transEq }
    where module MF = MatFunAreLinear bothLin

  matrixOpsLinear : (mops : MatrixOps m n)
     MatFunAreLinear (const $ matOps→mat mops) (matOps→func mops)
  matrixOpsLinear (swapOp p q)    = isLinear→bothLinear (swapIsLinear p q)
  matrixOpsLinear (addCons p q r) = isLinear→bothLinear (addConstRowIsLinear p q r)

  cong-mopsFunc :  mops  M ≈ᴹ N  matOps→func mops M ≈ᴹ matOps→func mops N
  cong-mopsFunc (swapOp p q) M≈N i j with p  i | q  i
  ... | yes _ | _     = M≈N _ _
  ... | no  _ | yes _ = M≈N _ _
  ... | no  _ | no  _ = M≈N _ _
  cong-mopsFunc (addCons p q r) M≈N i j with q  i
  ... | yes _ = +-cong (M≈N _ _) (*-congˡ (M≈N _ _))
  ... | no  _ = M≈N _ _

  listMatrixOpsLinear : (lMops : List $ MatrixOps m n)
     MatFunAreLinear (const $ listMatOps→mat lMops) (listMatOps→func lMops)
  listMatrixOpsLinear [] = isLinear→bothLinear isLinearId
  listMatrixOpsLinear (x  lMops) = isLinear→bothLinear
    (isLinearComp (bothLinear→isLinear (listMatrixOpsLinear lMops))
    (bothLinear→isLinear (matrixOpsLinear x)))

  ≈⇒list :  (M≈N : M ≈ᴹ N) list  listMatOps→func list M ≈ᴹ listMatOps→func list N
  ≈⇒list M≈N [] = M≈N
  ≈⇒list M≈N (x  xs) = cong-mopsFunc x (≈⇒list M≈N xs)

  swapII≡const :  p i j  swap p p N i j  N i j
  swapII≡const p i j with p  i
  ... | yes ≡.refl = ≡.refl
  ... | no       _ = ≡.refl

  swapCons : swap i j M ≈ᴹ N  swap (suc i) (suc j) (x V.∷ M) ≈ᴹ x V.∷ N
  swapCons {i = p} {q} swapMN zero j = refl
  swapCons {i = p} {q} swapMN (suc i) j
    with p  i | q  i | swapMN i j
  ... | yes _ |     _ | swapIJ = swapIJ
  ... | no  _ | yes _ | swapIJ = swapIJ
  ... | no  _ | no  _ | swapIJ = swapIJ


  ▹⇒list : (M▹N : M  N)  listMatOps→func (▹⇒listMops M▹N) M ≈ᴹ N
  ▹⇒list (idR M≈N) = M≈N
  ▹⇒list {M = M} {N} (rec {xs = M‵} mOps M▹N mys≈zs) = begin
    matOps→func mOps (listMatOps→func (▹⇒listMops M▹N) M)
      ≈⟨ cong-mopsFunc mOps (▹⇒list M▹N) 
    matOps→func mOps _
    ≈⟨ mys≈zs 
    N 
    where open ≈ᴹ-Reasoning

  []▹[] : _▹_ {n = n} V.[] V.[]
  []▹[] = idR  ())

  ∷≈▹ : x  y  M ≈ᴹ N  (x V.∷ M)  (y V.∷ N)
  ∷≈▹ = idR ∘₂ ≋-cons

  add∷ : (M [ i ]≔ r *[ j ]) ≈ᴹ N  (x V.∷ M) [ suc i ]≔ r *[ suc j ] ≈ᴹ x V.∷ N
  add∷ Mcons zero j = refl
  add∷ {j = q} Mcons (suc i) j
    with q  i | Mcons i j
  ... | yes _ | consIJ = consIJ
  ... | no  _ | consIJ = consIJ

  ∷▹▹ : x  y  M  N  (x V.∷ M)  (y V.∷ N)
  ∷▹▹ x≋y (idR M≈N) = ∷≈▹ x≋y M≈N
  ∷▹▹ {x = x} {y} {M = M} {N} x≋y (rec (swapOp p q) M▹N mopsM) =
    rec (swapOp (suc p) (suc q))
    (∷▹▹ x≋y M▹N) (swapCons mopsM)
  ∷▹▹ x≋y (rec (addCons p q r) M▹N mopsM) =
    rec (addCons (suc p) (suc q) r) (∷▹▹ x≋y M▹N) (add∷ mopsM)

  ▹▹∷ :  {xs ys : Matrix 1 n}  xs  ys  M ≈ᴹ N  (xs zero V.∷ M)  (ys zero V.∷ N)
  ▹▹∷ (idR x≈y) = ∷≈▹ (x≈y zero)
  ▹▹∷ {N = N} {ys = zs} (rec {ys = ys} (swapOp zero zero) x▹y mYs≈y) M≈N =
    rec (swapOp zero zero)
      (▹▹∷ x▹y M≈N) α
    where
    α : swap zero zero (ys zero V.∷ N) ≈ᴹ zs zero V.∷ N
    α zero = mYs≈y zero
    α (suc i) = ≋-refl
  ▹▹∷ {N = N} {ys = zs} (rec {ys = ys} (addCons zero zero r) x▹y mYs≈y) M≈N =
    rec (addCons zero zero r) (▹▹∷ x▹y M≈N) α
    where

    α : (ys zero V.∷ N) [ zero ]≔ r *[ zero ] ≈ᴹ zs zero V.∷ N
    α zero = mYs≈y zero
    α (suc i) = ≋-refl

  flip-flip : (xs : Matrix n m)  flipM (flipM xs) ≈ᴹ xs
  flip-flip xs i j rewrite opposite-involutive i | opposite-involutive j = refl

open import Algebra.Matrix.Base

swapMatrixColumns : Matrix A m (n ℕ.+ p)  Matrix A m (p ℕ.+ n)
swapMatrixColumns {n = n} {p} xs i j = xs i (cast (ℕ.+-comm p n) j)