-- This is a file to normalize a matrix composed by a field
-- it also proved that normalized matrix has the same vector space of the original one

open import Level using (Level; _⊔_) renaming (zero to lzero; suc to lsuc)
open import Function
open import Data.Empty
open import Data.Unit.Polymorphic
open import Data.Product
open import Data.Product.Properties
open import Data.Sum
open import Data.Sum.Properties
open import Data.Sum.Relation.Binary.LeftOrder
open import Data.Sum.Relation.Binary.Pointwise
open import Data.Bool using (false; true)
open import Data.Nat as ℕ hiding (_⊔_; _^_; _*_)
open import Data.Fin as F hiding (_-_)
open import Data.Fin.Properties as F
open import Data.Vec.Functional as VF
open import Data.Vec as V
open import Data.Vec.Properties as V
open import Data.Vec.Recursive as VR using (_^_)
open import Relation.Binary
open import Relation.Binary.Core using (Rel)
open import Relation.Binary.Definitions
open import Relation.Binary.PropositionalEquality as ≡ hiding (setoid)
import Relation.Binary.Reasoning.Setoid as ReasonSetoid
open import Relation.Nullary
open import Relation.Nullary.Decidable
open import Relation.Unary.Properties
open import Algebra
open import Algebra.Module
open import Algebra.Apartness
import Algebra.Properties.Ring as PropRing
open import Relation.Unary hiding (Decidable)

open import lbry
open import MatrixNormalization.Props
open import MatrixNormalization.FinInduction
import Algebra.Module.Instances.FunctionalVector as AMIF
import Algebra.SubModule as ASubMod
import Algebra.SubModule.Vec as ASubModVec
import Algebra.SubModule.Field as ASubModField

open FuncNormAndZeros
open FuncNormAllLines
open module PNorm {n} = PropNorm (F.<-strictTotalOrder n) hiding (_≈_)

module MatrixNormalization.normLinesField {c}
  (field' : HeytingField c c c)
  (let open HeytingField field' renaming (Carrier to F; refl to ≈-refl; sym to ≈-sym; trans to ≈-trans))
  (_≟_ : Decidable _#_)
  (open HeytingCommutativeRing heytingCommutativeRing using (commutativeRing))
  (open CommutativeRing commutativeRing using (ring))
  (open AMIF ring using (leftModule; _≈ᴹ_))
  (≈→∈ : ∀ {n x y} {VS : ASubMod.Cᴹ (leftModule n) c} → x ≈ᴹ y → x ∈ VS → y ∈ VS)
  where

open module ASM {n} = ASubMod (leftModule n)
open module ASMV {n} = ASubModVec (leftModule n) ≈→∈
open module ASMF {n} = ASubModField {ℓm = c} field' (leftModule n)
open PropRing ring
open module LF {n} = LeftModule (leftModule n)
open hFieldProps field'

private variable
  m n : ℕ

Lookup≢0 : (xs : Vector F n) (p : Fin n) → Set _
Lookup≢0 xs p = xs p # 0# × ∀ i → i F.< p → xs i ≈ 0#

AllZeros : (xs : Vector F n) → Set _
AllZeros {n} xs = ∀ (p : Fin n) → xs p ≈ 0#

VecPivots' : (xs : Vector F n) → Set _
VecPivots' {n} xs = (Σ[ p ∈ Fin n ] Lookup≢0 xs p) ⊎ AllZeros xs

VecPivots'→Fin : {xs : Vector F n} → VecPivots' xs → Fin (ℕ.suc n)
VecPivots'→Fin (inj₁ (p , _)) = finSuc p
VecPivots'→Fin (inj₂ _) = fromℕ _

private variable
  p px py : Fin⊤ n
  xs ys : Vector F n

VecPivotPos : (xs : Vector F n) (p : Fin⊤ n) → Set c
VecPivotPos xs (inj₂ _) = AllZeros xs
VecPivotPos xs (inj₁ p) = Lookup≢0 xs p

alwaysSamePivot' : ∀ (xs : Vector F n) p₁ p₂ → Lookup≢0 xs p₁ → Lookup≢0 xs p₂ → p₁ ≡ p₂
alwaysSamePivot' xs p₁ p₂ (vpiv₁≢0 , vpiv₁≡0) (vpiv₂≢0 , vpiv₂≡0) with F.<-cmp p₁ p₂
... | tri< p₁<p₂ _ _ = ⊥-elim (tightʳ vpiv₁≢0 (vpiv₂≡0 p₁ p₁<p₂))
... | tri> _ _ p₁>p₂ = ⊥-elim (tightʳ vpiv₂≢0 (vpiv₁≡0 p₂ p₁>p₂))
... | tri≈ _ p₁≡p₂ _ = p₁≡p₂

alwaysSamePivot : ∀ (xs : Vector F n) p₁ p₂ → VecPivotPos xs p₁ → VecPivotPos xs p₂ → p₁ ≡ p₂
alwaysSamePivot xs (inj₁ _) (inj₂ _) (xsx≢0 , _) vpiv₂ = ⊥-elim (tightʳ xsx≢0 (vpiv₂ _))
alwaysSamePivot xs (inj₂ _) (inj₁ _) vpiv₁ (xsx≢0 , _) = ⊥-elim (tightʳ xsx≢0 (vpiv₁ _))
alwaysSamePivot xs (inj₂ _) (inj₂ _) vpiv₁ vpiv₂ = refl
alwaysSamePivot xs (inj₁ p₁) (inj₁ p₂) vpiv₁ vpiv₂ = cong inj₁ (alwaysSamePivot' xs _ _ vpiv₁ vpiv₂)

VecPiv : Vector F n → Set _
VecPiv xs = Σ[ p ∈ _ ] Lookup≢0 xs p

VecPivots : Vector F n → Set _
VecPivots xs = Σ[ p ∈ _ ] VecPivotPos xs p

findNonZero : (xs : Vector F n) → VecPivots xs
proj₁ (findNonZero {zero} xs) = inj₂ _
proj₂ (findNonZero {zero} xs) ()
findNonZero {suc n} xs with xs F.zero ≟ 0# | findNonZero (VF.tail xs)
... | yes xs0#0 | fn = (inj₁ F.zero) , xs0#0 , λ _ ()
... | no xs0≈0 | inj₁ i , xsI#0 , xs<I≈0 = inj₁ (suc i) , xsI#0 ,
  λ where zero j<si → tight _ _ .proj₁ xs0≈0 ; (suc j) sj<si → xs<I≈0 j (≤-pred sj<si)
... | no xs0≈0 | inj₂ _ , allZeros = inj₂ _ ,
  λ where zero → tight _ _ .proj₁ xs0≈0 ; (suc i) → allZeros i

findNonZeroPos : Vector F n → Fin⊤ _
findNonZeroPos = proj₁ ∘ findNonZero

vecs→zeroPos : Vector F n ^ 2 → Fin⊤ _ ^ 2
vecs→zeroPos = VR.map findNonZeroPos _

findNonZeroEq : VecPivotPos xs p → findNonZeroPos xs ≡ p
findNonZeroEq {xs = xs} = alwaysSamePivot _ _ _ (findNonZero xs .proj₂)

findXs≡inj₁→L≢0 : ∀ {p : Fin n} → findNonZeroPos xs ≡ inj₁ p → Lookup≢0 xs p
findXs≡inj₁→L≢0 {xs = xs} eqn with findNonZero xs
... | inj₁ j , fNonZero = subst (Lookup≢0 xs) (inj₁-injective eqn) fNonZero

vecZeros : ∀ n → Vector F n
vecZeros n = 0ᴹ

allZerosVecZero : AllZeros (vecZeros n)
allZerosVecZero {ℕ.suc n} zero = ≈-refl
allZerosVecZero {ℕ.suc n} (F.suc p) = allZerosVecZero p

vecZerosMax : VecPivotPos (vecZeros n) (inj₂ _)
vecZerosMax zero = ≈-refl
vecZerosMax (F.suc xs) = vecZerosMax xs

vecZero≡inj : findNonZeroPos (vecZeros n) ≡ inj₂ _
vecZero≡inj = findNonZeroEq {xs = vecZeros _} allZerosVecZero

_-v_ : Op₂ $ Vector F n
xs -v ys = xs +ᴹ (-ᴹ ys)

moreZeros≥Piv : ∀ (xs ys : Vector F n)
  {px : Fin n} {py : Fin⊤ n} →
  Lookup≢0 xs px → VecPivotPos ys py
  → (∀ x → x F.< px → ys x ≈ 0#)
  → ys px ≈ 0#
  → inj₁ px ⊎< py
moreZeros≥Piv xs ys {px} {inj₂ _} lx vPivy ys<X≡0 ysX≡0 = ₁∼₂
moreZeros≥Piv xs ys {px} {inj₁ py} lx (pivYs , vPivy) ys<X≡0 ysX≡0 with F.<-cmp px py
... | tri< a ¬b ¬c = ₁∼₁ a
... | tri≈ ¬a refl ¬c = ⊥-elim (tightʳ pivYs ysX≡0)
... | tri> ¬a ¬b c = ⊥-elim (tightʳ pivYs (ys<X≡0 py c))

reduceVector' : (xs ys : Vector F n)
  (fxs : VecPivots xs) (fys : VecPivots ys)
  (xs≡ys : fxs .proj₁ ≡ fys .proj₁)
  → Σ[ res ∈ Vector F n ] (((span xs +ₛ span ys) ≅ (span xs +ₛ span res)) × findNonZeroPos res ⊎> fys .proj₁)
proj₁ (reduceVector' _ _ (inj₂ _ , _) (inj₂ _ , _) _) = vecZeros _
proj₁ (proj₂ (reduceVector' _ _ (inj₂ _ , _) (inj₂ _ , all0ys) refl)) = +ₛ-congˡ (cong-span all0ys)
proj₂ (proj₂ (reduceVector' {n} _ _ (inj₂ _ , pivXs) (inj₂ _ , _) _))
  rewrite vecZero≡inj {n} = ₂∼₂ _
reduceVector' xs ys (inj₁ pxs , pivXs) (inj₁ pys , pivYs) refl =
  ysn' , sameSpace , moreZeros≥Piv xs ysn' pivXs (findNonZero _ .proj₂) ysnXs<≡0 ysnXs≡0
  where
  open ReasonSetoid setoid

  vx = xs pxs
  vy = ys pxs
  xsn = vy *ₗ xs
  ysn = vx *ₗ ys
  ysn' = ysn -v xsn

  sameSpace : (span xs +ₛ span ys) ≅ (span xs +ₛ span ysn')
  sameSpace = sameMulitpliedBoth _ (pivXs .proj₁) _ _ _

  ysnXs<≡0 : ∀ i → i F.< pxs → ysn' i ≈ 0#
  ysnXs<≡0 i i<pxs = begin
    vx * ys i - vy * xs i ≈⟨ +-cong
      (*-congˡ (proj₂ pivYs i i<pxs))
      (-‿cong (*-congˡ (proj₂ pivXs i i<pxs))) ⟩
    vx * 0# - vy * 0# ≈⟨ +-cong (zeroʳ vx) (-‿cong (zeroʳ vy)) ⟩
    0# - 0# ≈⟨ -‿inverseʳ 0# ⟩
    0# ∎

  ysnXs≡0 : ysn' pxs ≈ 0#
  ysnXs≡0 = begin
    vx * vy - vy * vx ≈⟨ +-congˡ (-‿cong (*-comm vy _)) ⟩
    vx * vy - vx * vy ≈⟨ -‿inverseʳ (vx * vy) ⟩
    0# ∎

reduceVector : (xs ys : Vector F n)
  (let fxs = findNonZeroPos xs ; fys = findNonZeroPos ys)
  (xs≡ys : fxs ≡ fys)
  → Σ[ res ∈ Vector F n ] (((span xs +ₛ span ys) ≅ (span xs +ₛ span res)) × findNonZeroPos res ⊎> fys)
reduceVector xs ys xs≡ys = reduceVector' xs ys (findNonZero xs) (findNonZero ys) xs≡ys

V^n→Vec : Vector F m ^ n → Cᴹ {m} c
V^n→Vec {n = ℕ.zero} _ = 0ₛ
V^n→Vec {n = suc ℕ.zero} = span
V^n→Vec {n = suc (suc n)} (v , xs) = span v +ₛ V^n→Vec xs

_≐v_ : Rel (Vector F m ^ n) _
xs ≐v ys = V^n→Vec xs ≅ V^n→Vec ys

normalizeTwoLines' : (vecs : Vector F n ^ 2) →
  Σ[ res ∈ Vector F n ^ 2 ] (vecs ≐v res) ×
    normTwoVectors (vecs→zeroPos vecs) (vecs→zeroPos res)
normalizeTwoLines' tp@(xs , ys) with A.compare (findNonZeroPos xs) (findNonZeroPos ys)
... | tri< _ _ _ = tp , (id , id) , refl , refl
... | tri> _ _ _ = (ys , xs) , +ₛ-comm _ _ , refl , refl
... | tri≈ _ xs≡ys _ = let vred@(zs , same , prop) = reduceVector xs ys (Pointwise-≡⇒≡ xs≡ys) in
  (xs , zs) , same , refl , prop

Matrix : ℕ → ℕ → Set _
Matrix m n = Vec (Vector F m) n

MatrixData : ℕ → ℕ → Set _
MatrixData m n = Vec (Vec F m) n

matrix→matrixData : Matrix m n → MatrixData m n
matrix→matrixData = V.map toVec

matrixData→matrix : MatrixData m n → Matrix m n
matrixData→matrix = V.map fromVec

matrixZeros : Matrix m n → Vec (Fin⊤ m) n
matrixZeros = V.map findNonZeroPos

normMatrix : ∀ (xs : Matrix m n) i j i≢j →
  Σ[ ys ∈ Matrix m n ] (
    (vec→space xs ≅ vec→space ys) ×
    normalizeTwoLines i j i≢j (matrixZeros xs) (matrixZeros ys))
normMatrix {m} {n} xs i j i≢j = yss , swapSameVec {xs = xs} i≢j ysPropMod , lookupXs≢Ys , normVec
  where
  xsi = lookup xs i
  xsj = lookup xs j

  yst' = normalizeTwoLines' (xsi , xsj)
  ysPropMod = yst' .proj₂ .proj₁
  ysProp = yst' .proj₂ .proj₂
  yst = yst' .proj₁
  ysi = yst .proj₁
  ysj = yst .proj₂

  yss = (xs [ i ]≔ ysi) [ j ]≔ ysj

  lookupXs≢Ys : ∀ k → k ≢ i → k ≢ j → lookup (matrixZeros xs) k ≡ lookup (matrixZeros yss) k
  lookupXs≢Ys k k≢i k≢j = begin
    lookup (matrixZeros xs) k     ≡⟨  lookup-map k findNonZeroPos xs  ⟩
    findNonZeroPos (lookup xs k)  ≡˘⟨ cong findNonZeroPos helper      ⟩
    findNonZeroPos (lookup yss k) ≡˘⟨ lookup-map k findNonZeroPos yss ⟩
    lookup (matrixZeros yss) k    ∎ where
    open ≡-Reasoning

    helper = lookup yss k             ≡⟨ lookup∘update′ k≢j (xs [ i ]≔ ysi) _ ⟩
             lookup (xs [ i ]≔ ysi) k ≡⟨ lookup∘update′ k≢i xs _ ⟩
             lookup xs k              ∎

  sameNormTwoVectors : ∀ {p q r s} → p ≡ q → r ≡ s → normTwoVectors {m} p r → normTwoVectors q s
  sameNormTwoVectors refl refl ntv = ntv

  normVec : normTwoVectors _ _
  normVec = sameNormTwoVectors (×-≡,≡→≡ ((projSame _) , (projSame _))) (×-≡,≡→≡ (≡.sym α , ≡.sym β)) ysProp
    where
    open ≡-Reasoning

    projSame : ∀ i → findNonZeroPos (lookup xs i) ≡ lookup (V.map findNonZeroPos xs) i
    projSame i = ≡.sym (lookup-map i findNonZeroPos xs)

    lookupYssI≡ysi = lookup yss i             ≡⟨ lookup∘update′ i≢j (xs [ i ]≔ ysi) ysj ⟩
                     lookup (xs [ i ]≔ ysi) i ≡⟨ lookup∘update i xs _    ⟩
                     ysi ∎

    α = lookup (V.map findNonZeroPos yss) i ≡⟨ lookup-map i findNonZeroPos yss ⟩
        findNonZeroPos (lookup yss i)       ≡⟨ cong findNonZeroPos lookupYssI≡ysi ⟩
        findNonZeroPos ysi ∎

    β = lookup (matrixZeros yss) j    ≡⟨ lookup-map j findNonZeroPos yss ⟩
        findNonZeroPos (lookup yss j) ≡⟨ cong findNonZeroPos {y = ysj} (lookup∘update j (V.updateAt xs i (const ysi)) _) ⟩
        findNonZeroPos ysj            ∎

funcNorm : FuncNormAllLines m n (Matrix m (suc n))
numberZeros funcNorm = matrixZeros
normProps funcNorm = finProps
f (fNormLines funcNorm i j i≢j) xs = normMatrix xs i j i≢j .proj₁
nZerosProp (fNormLines funcNorm i j i≢j) xs = normMatrix xs i j i≢j .proj₂ .proj₂

MatrixS : (xs : Matrix m n) → Set _
MatrixS {m} {n} xs = Σ[ ys ∈ Matrix m n ] (vec→space xs ≅ vec→space ys)

normMatrixS : ∀ (xs : Matrix m n) (xs' : MatrixS xs) i j i≢j →
  Σ[ ys' ∈ MatrixS xs ]
    (normalizeTwoLines i j i≢j (matrixZeros (xs' .proj₁)) (matrixZeros (ys' .proj₁)))
normMatrixS xs (xss , vXs≃VXss) i j i≢j = let
  ys , vXss≅Vys , normed = normMatrix xss i j i≢j
  in (ys , ≐-trans vXs≃VXss vXss≅Vys) , normed

funcNormField : (xs : Matrix m (suc n)) → FuncNormAllLines m n (MatrixS xs)
numberZeros (funcNormField xs) (p , _) = matrixZeros p
normProps (funcNormField xs) = finProps
f (fNormLines (funcNormField xs) i j i≢j) zs@(ys , q) =
  normMatrixS xs zs i j i≢j .proj₁
nZerosProp (fNormLines (funcNormField xs) i j i≢j) zs =
 normMatrixS xs zs i j i≢j .proj₂

normalize : (xs : Matrix m n) → Σ[ ys ∈ Matrix m n ]
  (allLinesNormalized (matrixZeros ys) × (vec→space xs ≅ vec→space ys))
normalize {n = zero} V.[] = V.[] , (λ ()) , ≐-refl
normalize {n = suc n} xs =
  let (ys , normed) , sameVecSpace = normalizeA (funcNormField xs) (xs , ≐-refl)
  in ys , sameVecSpace , normed

normalizeD : MatrixData m n → MatrixData m n
normalizeD = matrix→matrixData ∘ proj₁ ∘ normalize ∘ matrixData→matrix