\import Algebra.Domain.GCD(GCDDomain)
\import Algebra.Group
\import Algebra.Meta
\import Algebra.Monoid
\import Algebra.Monoid.GCD
\import Algebra.Ring
\import Algebra.Ring.Ideal
\import Algebra.Ring.Reduced
\import Data.Array
\import Data.Fin (fsuc/=)
\import Function.Meta
\import Logic
\import Logic.Meta
\import Meta
\import Paths
\import Paths.Meta
\import Set
\import Set.Countable
\open Monoid(LDiv)

\class BezoutRing \extends CRing, GCDMonoid {
  | isBezout : IsBezout
  | isGCD a b => TruncP.map (isBezout a b) \lam (s,t,d1,d2) => \new GCD a b (s * a + t * b) {
    | gcd|val1 => d1
    | gcd|val2 => d2
    | gcd-univ g (x,p) (y,q) => LDiv.make (s * x + t * y) equation
  }
  | gcd-ldistr c {a} {b} {z} (g : GCD a b z) => TruncP.map (isBezout a b) \lam (s,t,d1,d2) => \new GCD (c * a) (c * b) (c * z) {
    | gcd|val1 => LDiv.product-left c g.gcd|val1
    | gcd|val2 => LDiv.product-left c g.gcd|val2
    | gcd-univ d (x,p) (y,q) => LDiv.trans (later $ rewrite ldistr $ LDiv_+ (LDiv.make (s * x) equation) (LDiv.make (t * y) equation)) $ LDiv.product-left c (g.gcd-univ _ d1 d2)
  }

  \lemma gcd_bezout {a b c : E} (gcd : GCD a b c) :  (s t : E) (s * a + t * b = c)
    => TruncP.map (isBezout a b) \lam (s,t,d1,d2) =>
        \let (w,q) => gcd.gcd-univ (s * a + t * b) d1 d2
        \in (w * s, w * t, equation)

  \lemma bezoutArray_LDiv (l : Array E) :  (c : Array E l.len)  j (LDiv (BigSum (\lam i => c i * l i)) (l j)) \elim l
    | nil => inP (nil, \case __)
    | a :: l =>
      \have | (inP s) => bezoutArray_LDiv l
            | (inP t) => isBezout a (BigSum (\lam j => s.1 j * l j))
      \in inP (t.1 :: map (t.2 *) s.1, \case \elim __ \with {
        | 0 => transport (\lam x => LDiv (t.1 * a + x) a) (BigSum-ldistr *> pmap BigSum (exts \lam j => inv *-assoc)) t.3
        | suc j => LDiv.trans (transport (\lam x => LDiv (t.1 * a + x) _) (BigSum-ldistr *> pmap BigSum (exts \lam j => inv *-assoc)) t.4) (s.2 j)
      })

  \lemma coprime_bezoutArray {a : Array E} (ac : IsCoprimeArray a) :  (s : Array E a.len) (BigSum (\lam j => s j * a j) = 1)
    => TruncP.map (bezoutArray_LDiv a) \lam (c,f) =>
        \have lci : Monoid.Inv => ac (BigSum (\lam j => c j * a j)) f
        \in (\lam j => lci.inv * c j, pmap BigSum (exts \lam j => *-assoc) *> inv BigSum-ldistr *> lci.inv-left)

  \lemma chinese2 (a1 a2 d1 d2 : E) (p : IsCoprime d1 d2) :  (r : E) (LDiv d1 (r - a1)) (LDiv d2 (r - a2))
    => TruncP.map (gcd_bezout (IsCoprime.=>gcd p))
        \lam (s1,s2,s1d1+s2d2=1) => (a1 * s2 * d2 + a2 * s1 * d1, LDiv.make ((a2 - a1) * s1) equation, LDiv.make ((a1 - a2) * s2) equation)

  \lemma chinese {n : Nat} (a d : Array E n) (p : \Pi (i j : Fin n) -> i /= j -> IsCoprime (d i) (d j))
    :  (r : E) (\Pi (j : Fin n) -> LDiv (d j) (r - a j)) \elim n
    | 0 => inP (0, \case __)
    | suc n =>
      \have | (inP (q,f)) => chinese (taild a) (taild d) \lam i j i/=j => p (suc i) (suc j) (fsuc/= i/=j)
            | (inP (r,p1,p2)) => chinese2 (a 0) q (d 0) (BigProd (taild d)) $ IsCoprime_BigProd-right $ later \lam j => p 0 (suc j) (\case __)
      \in inP (r, \case \elim __ \with {
        | 0 => p1
        | suc j => transport (LDiv _) equation (LDiv_+ (LDiv.trans (LDiv_BigProd j) p2) (f j))
      })

  \lemma chinese-unique {n : Nat} (r r' : E) (d : Array E n) (p : \Pi (i j : Fin n) -> i /= j -> IsCoprime (d i) (d j)) (q : \Pi (j : Fin n) -> LDiv (d j) (r - r')) : TruncP (LDiv (BigProd d) (r - r'))
    => coprime_BigProd_div-left p q

  \func maximal-ideal (c : Countable E) (p : \Pi (a b : E) -> Dec (TruncP (LDiv a b))) (l : Array E) (lc : Not (IsCoprimeArray l))
    : \Sigma (M : Ideal \this) M.IsMaximal (\Pi (j : Fin l.len) -> M (l j))
    => \let (M,Mm,Mc) => Ideal.countable-maximal c (\lam I Ig x => \case bezout_finitelyGenerated_principal.1 isBezout Ig \with {
                           | inP (y,yg) => transport Dec (ext (\lam (inP y|x) => Ideal.ldiv y|x yg.1, \lam Ix => yg.2 Ix)) (p y x)
                         }) {Ideal.closure l} Ideal.closure-finGenerated (\lam (inP c) => lc \lam z d => Monoid.Inv.ldiv $ later $ rewrite c.2 $ LDiv_BigSum \lam j => later $ LDiv.factor-right $ d (c.1 j).2)
       \in (M, Mm, \lam j => Mc $ Ideal.closure-superset j)
} \where {
  \lemma bezoutArray_coprime {R : CRing} {n : Nat} (s : Array R n) {u : Array R n} (p : R.BigSum (\lam j => s j * u j) = 1) : IsCoprimeArray u
    => \lam z f => Monoid.Inv.lmake (R.BigSum \lam j => s j * LDiv.inv {f j}) $ BigSum-rdistr *>
          pmap BigSum (exts \lam j => *-assoc *> pmap (_ *) (*-comm *> LDiv.inv-right {f j})) *> p

  \lemma bezoutArrayInv_coprime {R : CRing} {n : Nat} (s : Array R n) {u : Array R n} (p : Monoid.Inv (R.BigSum (\lam j => s j * u j))) : IsCoprimeArray u
    => bezoutArray_coprime (\lam j => p.inv * s j) $ pmap BigSum (exts \lam j => *-assoc) *> inv BigSum-ldistr *> p.inv-left

  \lemma bezout_coprime {R : CRing} {s t a b : R} (p : s * a + t * b = 1) : IsCoprime a b
    => bezoutInv_coprime (transportInv Monoid.Inv p Monoid.Inv.ide-isInv)

  \lemma bezoutInv_coprime {R : CRing} {s t a b : R} (p : Monoid.Inv (s * a + t * b)) : IsCoprime a b
    => \lam z z|a z|b => bezoutArrayInv_coprime (s,t) {a,b} (transport Monoid.Inv simplify p) z (z|a,z|b)
}

\class StrictBezoutRing \extends BezoutRing {
  | isStrictBezout : IsStrictBezout
  \default isBezout a b => TruncP.map (isStrictBezout a b) \lam (s,t,u,v,p,q) => (t, s, \new LDiv {
    | inv => v
    | inv-right => equation
  }, \new LDiv {
    | inv => u
    | inv-right => equation
  })

  \lemma bezoutArray {n : Nat} (a : Array E (suc n)) :  (s u : Array E (suc n)) (BigSum (\lam j => s j * u j) = 1) (d : E) (\Pi (j : Fin (suc n)) -> a j = u j * d) \elim n, a
    | 0, a :: nil => inP (1 :: nil, 1 :: nil, simplify, a, \lam (0) => inv ide-left)
    | suc n, a1 :: a =>
      \have | (inP (s,u,p,d,f)) => bezoutArray a
            | (inP (s1,t1,u1,v1,p1,q1)) => isStrictBezout d a1
      \in inP (s1 :: map (t1 *) s, u1 :: map (v1 *) u, pmap (_ +) (pmap2 (+) equation (cong $ ext \lam j => equation) *> inv (BigSum-ldistr {_} {_} {\lam j => s j * u j}) *> pmap (_ *) p *> ide-right) *> q1, s1 * a1 + t1 * d, \case \elim __ \with {
        | 0 => equation
        | suc j => f j *> equation
      })
}

\class BezoutDomain \extends GCDDomain, BezoutRing {
  | isGCDDomain x y _ _ => TruncP.map (isBezout x y) (\lam p => bezoutGCD p.3 p.4)
} \where {
  \class Dec \extends StrictBezoutDomain, GCDDomain.Dec
    | isStrictBezout => PPRing.bezout->strictBezout isBezout
}

\class StrictBezoutDomain \extends BezoutDomain, StrictBezoutRing