{- | Adequate rings were defined in
 -   Olaf Helmer, The elementary divisor theorem for certain rings without chain condition, 1943
 -
 -   The proof that adequate rings satisfy the Kaplansky condition is taken from
 -   Irving Kaplansky, Elementary divisors and modules, 1949
-}

\import Algebra.Domain
\import Algebra.Domain.Bezout
\import Algebra.Domain.GCD
\import Algebra.Group
\import Algebra.Linear.Matrix.Smith
\import Algebra.Meta
\import Algebra.Monoid
\import Algebra.Monoid.Category
\import Algebra.Monoid.GCD
\import Algebra.Ring.Ideal
\import Algebra.Ring.Noetherian
\import Function
\import Function.Meta ($)
\import Logic
\import Logic.Meta
\import Meta
\import Paths
\import Paths.Meta
\import Relation.Equivalence
\import Set
\open DivQuotient \hiding (~)
\open Monoid

\class PID \extends SmithDomain, NoetherianCRing {
  | divChain : CMonoid.DivChain {DivQuotientMonoid nonZeroMonoid}
  | isNoetherian I Ifg => Ideal.fromMonoidChainCondition (IntegralDomain.Dec.fromNonZeroDivChain divChain) I \lam n => bezout_finitelyGenerated_principal.1 isBezout (Ifg n)
  | isKaplansky => adequate_kaplansky \lam a/=0 => adequate'_adequate (domain_adequate' divChain a/=0)

  \lemma is1Dim : Dim<= 1
    => bezout_1Dim<->adequate'.2 \lam a/=0 => domain_adequate' divChain a/=0
} \where {
  \type IsAdequate {M : CMonoid} (a : M) => \Pi (b : M) ->  (c : E) (LDiv c a) (IsCoprime c b)  d (LDiv d a -> IsCoprime d b -> TruncP (LDiv d c))

  \type IsAdequate' {M : CMonoid} (a : M) => \Pi (b : M) ->  (a1 a2 : M) (n : Nat) (a = a1 * a2) (LDiv a1 (pow b n)) (IsCoprime a2 b)

  \lemma adequate'_adequate {M : GCDMonoid} {a : M} (adeq : IsAdequate' a) : IsAdequate a
    => \lam b => \case adeq b \with {
      | inP s => inP (s.2, LDiv.make s.1 (*-comm *> inv s.4), s.6, \lam d d|a d_b => coprime_*_div (rewrite s.4 in d|a) $ IsCoprime.factor-right s.5 $ M.IsCoprime_pow-right d_b)
    }

  \open UnitlessGCDMonoid

  \lemma unitless_adequate' {M : UnitlessGCDMonoid} (dc : M.DivChain) (a : M) : IsAdequate' a
    => \lam b =>
        \let | g x => GCD.gcd|val1 {gcdC x b}
             | f x => LDiv.inv {g x}
             | (inP (n, d : LDiv)) => dc (iterr f __ a) \lam i => inP $ LDiv.swap $ g (iterr f i a)
             | s => iterr-index-ind {_} {\lam n x => \Sigma (d : M) (a = d * x) (LDiv d (pow b n))} (1, inv ide-left, LDiv.id-div) (\lam {m} {x} r => (r.1 * gcd x b, r.2 *> pmap (r.1 *) (inv $ LDiv.inv-right {g x}) *> inv *-assoc, LDiv.product r.3 (GCD.gcd|val2 {gcdC x b}))) n
             | c => iterr f n a
        \in inP (s.1, c, n, s.2, s.3, IsCoprime.<=gcd $ transport (GCD c b) (uniqueUnit $ Inv.lmake d.inv $ cancel_*-left c $ inv *-assoc *> pmap (`* g c) d.inv-right *> *-comm *> LDiv.inv-right {g c} *> inv ide-right) (gcdC c b))

  \lemma monoid_adequate' {M : CancelGCDMonoid} (dc : DivChain {DivQuotientMonoid M}) (a : M) : IsAdequate' a
    => \lam b => \case unitless_adequate' dc (inD a) (inD b) \with {
      | inP (in~ a1, in~ a2, n, a=a1a2, a1|b^n, a2_b) =>
        \have (c : Inv, p) => equivalent-associates a (a1 * a2) (make~ a=a1a2)
        \in inP (c * a1, a2, n, p *> inv *-assoc, LDiv.trans (LDiv.make c.inv $ *-comm *> inv *-assoc *> pmap (`* _) c.inv-left *> ide-left) (div-from~ $ transportInv (LDiv _) (MonoidHom.func-pow {inDHom M}) a1|b^n), IsCoprime.<=gcd $ gcd-from~ $ IsCoprime.=>gcd a2_b)
    }

  \lemma domain_adequate' {M : GCDDomain.Dec} (dc : DivChain {DivQuotientMonoid M.nonZeroMonoid}) {a : M} (a/=0 : a /= 0) : IsAdequate' a
    => \lam b => \case decideEq b 0 \with {
      | yes b=0 => inP (a, 1, 1, inv ide-right, rewrite b=0 $ simplify zero-div, IsCoprime.IsCoprime_ide-left)
      | no b/=0 => TruncP.map (monoid_adequate' dc (a, nonZeroApart a/=0) (b, nonZeroApart b/=0)) \lam (a1,a2,n,a=a1a2,a1|b^n,a2_b) =>
          (a1.1, a2.1, n, pmap __.1 a=a1a2, transport (LDiv _) subMonoid.embed.func-pow (nonZero_ldiv a1|b^n),
           \lam z z|a2 z|b => nonZero_inv $ a2_b (z, nonZeroApart $ ldiv/=0 b/=0 z|b) (ldiv_nonZero z|a2) (ldiv_nonZero z|b))
    }

  \lemma oneDim_adequate' {R : GCDDomain.Dec} (d : R.Dim<= 1) {a : R} (a/=0 : a /= 0) : IsAdequate' a
    => \lam b =>
        \have | (inP (v,n,d)) => R.oneDimensional-char.1 d a b a/=0
              | (inP (a1,a2,a=a1a2,a1|b^n,(c,p))) => split_* d
        \in inP (a1, a2, n, a=a1a2, a1|b^n, BezoutRing.bezout_coprime {_} {c} {v} equation)

  \lemma bezout_1Dim<->adequate' {R : BezoutDomain.Dec} : R.Dim<= 1 <-> (\Pi {a : R} -> a /= 0 -> IsAdequate' a)
    => (oneDim_adequate', \lam f => oneDimensional-char.2 \lam a u a/=0 =>
        \have | (inP (a1,a2,n,a=a1a2,a1|u^n,a2_u)) => f a/=0 u
              | (inP (s,v,p)) => gcd_bezout (IsCoprime.=>gcd a2_u)
        \in inP (v, n, rewrite a=a1a2 $ LDiv.product a1|u^n $ LDiv.make s equation))

  \lemma adequate_kaplansky {R : BezoutDomain.Dec} (adeq : \Pi {a : R} -> a /= 0 -> IsAdequate a) : SmithRing.IsKaplansky R
    => \lam a b c a_b_c => \case coprime_bezoutArray a_b_c, decideEq a 0 \with {
      | inP (s,p), yes a=0 => inP (s 1, s 2, BezoutRing.bezout_coprime {_} {0} {1} equation)
      | _, no a/=0 =>
        \have | (inP R) => adeq a/=0 c
              | (inP (c',c*,cp)) => gcd_bezout (IsCoprime.=>gcd R.3)
        \in inP (1, c* * (1 - b), simplify \lam d d1 (y,p) =>
            \have | d_c : IsCoprime d c => \lam z (x1,p1) (x2,p2) => a_b_c z
                            (LDiv.trans (LDiv.make x1 p1) d1, LDiv.make (x1 * y - x2 * c* * (1 - b)) equation, LDiv.make x2 p2)
                  | d_R : IsCoprime d R.1 => BezoutRing.bezout_coprime {_} {y} {(1 - b) * c'} equation
            \in d_R d LDiv.id-div $ LDiv_TruncP $ R.4 d d1 d_c)
    }
}