\import Algebra.Domain
\import Algebra.Domain.IntegrallyClosed
\import Algebra.Group
\import Algebra.Meta
\import Algebra.Monoid
\import Algebra.Monoid.GCD
\import Algebra.Monoid.Sub
\import Algebra.Ring
\import Algebra.Ring.Localization
\import Algebra.Ring.Poly
\import Arith.Nat
\import Data.Or
\import Function.Meta ($)
\import Logic
\import Logic.Meta
\import Meta
\import Order.LinearOrder
\import Paths
\import Paths.Meta
\import Relation.Equivalence
\import Set
\open Monoid(LDiv)

\class GCDDomain \extends IntegralDomain {
  | isGCDDomain (x y : E) : x `#0 -> y `#0 -> TruncP (GCD x y)

  \func nonZeroMonoid : CancelGCDMonoid \cowith
    | CancelCMonoid => IntegralDomain.nonZeroMonoid
    | isGCD x y =>
      \have | (inP (g : GCD x.1 y.1)) => isGCDDomain x.1 y.1 x.2 y.2
            | g*x#0 => transport #0 (inv g.gcd|val1.inv-right) x.2
            | g#0 => #0-*-left g*x#0
      \in inP (\new GCD {
                 | gcd => (g.gcd, g#0)
                 | gcd|val1 => \new LDiv {
                   | inv => (g.gcd|val1.inv, #0-*-right g*x#0)
                   | inv-right => ext g.gcd|val1.inv-right
                 }
                 | gcd|val2 => \new LDiv {
                   | inv => (g.gcd|val2.inv, #0-*-right (transport #0 (inv g.gcd|val2.inv-right) y.2))
                   | inv-right => ext g.gcd|val2.inv-right
                 }
                 | gcd-univ h (h|x : LDiv h x) (h|y : LDiv h y) =>
                   \have h|g : LDiv h.1 g.gcd => g.gcd-univ h.1 (\new LDiv h.1 x.1 h|x.inv.1 (pmap __.1 h|x.inv-right))
                                                                (\new LDiv h.1 y.1 h|y.inv.1 (pmap __.1 h|y.inv-right))
                   \in \new LDiv {
                     | inv => (h|g.inv, #0-*-right (transport #0 (inv h|g.inv-right) g#0))
                     | inv-right => ext h|g.inv-right
                   }
               })

  \func gcd_0 (x : E) : GCD x 0 x \cowith
    | gcd|val1 => \new LDiv {
      | inv => 1
      | inv-right => ide-right
    }
    | gcd|val2 => \new LDiv {
      | inv => 0
      | inv-right => zro_*-right
    }
    | gcd-univ g g|a _ => g|a

  \func gcd_sum (d : E) (g : GCD {\this}) : GCD (g.val1 + g.val2 * d) g.val2 g.gcd \cowith
    | gcd|val1 => \new LDiv {
      | inv => g.gcd|val1.inv + g.gcd|val2.inv * d
      | inv-right => ldistr *> pmap2 (+) g.gcd|val1.inv-right (inv *-assoc *> pmap (`* d) g.gcd|val2.inv-right)
    }
    | gcd|val2 => g.gcd|val2
    | gcd-univ g' (g'|a+bd : LDiv) (g'|b : LDiv) => g.gcd-univ g' (\new LDiv {
      | inv => g'|a+bd.inv - g'|b.inv * d
      | inv-right => ldistr *> pmap2 (+) g'|a+bd.inv-right (Ring.negative_*-right *> pmap negative (inv *-assoc *> pmap (`* d) g'|b.inv-right)) *> simplify
    }) g'|b
} \where {
  \class Dec \extends GCDDomain, GCDMonoid, IntegralDomain.Dec, IntegrallyClosedDomain {
    | isGCD => gcd nonZeroApart
    | gcd-ldistr c {x} {y} g => \case decideEq c 0 \with {
      | yes c=0 => rewrite c=0 $ simplify $ inP \new GCD {
        | gcd|val1 => LDiv.id-div
        | gcd|val2 => LDiv.id-div
        | gcd-univ g g|0 _ => g|0
      }
      | no c/=0 => TruncP.map (gcd nonZeroApart (c * x) (c * y)) $ GCDMonoid.gcd-ldistr_cancel (\lam {a} {b} => nonZero-cancel-left c/=0) g
    }
    | isIntegrallyClosedDomain x xi => \case Dec.coprime-repr-aux x, \elim xi \with {
      | inP c, inP d => \case loc_poly (\lam s p => nonZero-cancel-right (apartNonZero s) p) d.1 d.2 (rewrite c.5 in d.3) \with {
        | inP e => rewrite c.5 \case decide#0 c.1 \with {
          | inl q => inP (0, LocRing.equals1 equation)
          | inr c1#0 => inP (_, inv $ div_loc $ nonZero_ldiv $ nonZeroMonoid.gcd_pow_div {c.2,c.3} {c.1,c1#0} {e.1}
                          (transport (LDiv _) (ext $ inv subMonoid.embed.func-pow) $ ldiv_nonZero {_} {_} {_, pow_#0 c1#0} e.2) $ IsCoprime.<=gcd $ later \new GCD {
                            | gcd|val1 => LDiv.ide-div
                            | gcd|val2 => LDiv.ide-div
                            | gcd-univ g g|c2 g|c1 => ldiv_nonZero $ GCD.gcd-univ {c.4} g.1 (nonZero_ldiv g|c1) (nonZero_ldiv g|c2)
                          })
        }
      }
    }

    \lemma coprime-repr (x : LocRing subMonoid) => coprime-repr-aux x

    \lemma oneDimensional_Bezout (d : Dim<= 1) : IsStrictBezout
      => \lam a b => \case isGCD a b \with {
        | inP (g : GCD) => \case decideEq a 0 \with {
          | yes a=0 => inP (1, 1, 1, 0, equation, simplify)
          | no a/=0 =>
            \let | u => g.gcd|val2.inv
                 | v => g.gcd|val1.inv
                 | g' : GCD v u 1 => g.reduce $ LDiv_IsRegular g.gcd|val1 (IsRegular_/=0.2 a/=0)
                 | (inP (c,n,p)) => oneDimensional-char.1 d v u $ ldiv/=0 a/=0 (LDiv.swap g.gcd|val1)
                 | (inP (d,q)) => coprime_*_div p (IsCoprime_pow-right $ IsCoprime.<=gcd g')
            \in inP (c, d, u, v, rewriteI (g.gcd|val1.inv-right,g.gcd|val2.inv-right) equation, equation)
        }
      }

    \lemma split_* {a b c : E} (a|bc : LDiv a (b * c)) :  (a1 a2 : E) (a = a1 * a2) (LDiv a1 b) (LDiv a2 c)
      => \case decideEq a 0 \with {
        | yes a=0 => inP (b, c, later (rewrite a=0 $ inv zro_*-left) *> a|bc.inv-right, LDiv.id-div, LDiv.id-div)
        | no a/=0 => split-regular (\lam p => nonZero-cancel-left a/=0 p) a|bc
      }

    \lemma div_unit : (\Pi (a b : E) -> Set.Dec (TruncP (LDiv a b))) <-> (\Pi (a : E) -> Set.Dec (Monoid.Inv a))
      => (\lam f a => \case f a 1 \with {
            | yes (inP r) => yes (Monoid.Inv.ldiv r)
            | no r => no \lam p => r (inP p)
          }, \lam f a b => \case decide#0 a, decide#0 b \with {
            | _, inl b=0 => yes $ inP $ LDiv.make 0 $ zro_*-right *> inv b=0
            | inl a=0, inr b#0 => no \lam (inP a|b) => #0-zro $ transport #0 (ldiv=0 a|b a=0) b#0
            | inr a#0, inr b#0 => \case nonZeroMonoid.div_unit.2 (\lam x => \case f x.1 \with {
              | yes e => yes (inv_nonZero e)
              | no q => no \lam e => q (nonZero_inv e)
            }) (a,a#0) (b,b#0) \with {
              | yes e => yes $ inP (nonZero_ldiv e)
              | no q => no \lam (inP a|b) => q (ldiv_nonZero a|b)
            }
      })
  } \where {
    \private \lemma gcd {M : GCDDomain} {D : DecSet M} (d : \Pi {x : M} -> x /= 0 -> x `#0) (a b : M) : TruncP (GCD a b)
      => \case decideEq a 0, decideEq b 0 \with {
        | yes a=0, _ => inP (GCD.swap {transportInv (GCD b __) a=0 (gcd_0 b)})
        | _, yes b=0 => inP (transportInv (GCD a __) b=0 (gcd_0 a))
        | no a/=0, no b/=0 => isGCDDomain a b (d a/=0) (d b/=0)
      }

    \lemma coprime-repr-aux {D : GCDDomain} {dec : IntegralDomain.Dec { | IntegralDomain => D }} (x : LocRing D.subMonoid)
      :  (a b : E) (b#0 : #0 b) (GCD a b 1) (x = inl~ (a, b, b#0))
      | in~ (x,y,y#0) => \case decideEq x 0 \with {
        | yes x=0 => inP (0, 1, zro#ide, GCD.swap {div_gcd LDiv.ide-div}, LocRing.equals1 equation)
        | no x/=0 => TruncP.map (isGCDDomain x y (nonZeroApart x/=0) y#0) \lam (gcd : GCD x y) =>
            (gcd.gcd|val1.inv, gcd.gcd|val2.inv, #0-*-right $ transportInv #0 gcd.gcd|val2.inv-right y#0,
             gcd.reduce \lam {x} {y} => nonZero-cancel-left \lam gcd=0 => x/=0 $ inv gcd.gcd|val1.inv-right *> equation,
             LocRing.equals1 $ equation {usingOnly (gcd.gcd|val1.inv-right, gcd.gcd|val2.inv-right)})
      }

    \lemma loc_poly {R : CRing} {S : SubMonoid R} (nt : \Pi {a : R} -> S a -> \Pi {x y : R} -> x * a = y * a -> x = y)
                    (p : Poly R) (m : isMonic p) {a b : R} {Sb : S b} (e : polyEval (polyMap locMap p) (inl~ (a,b,Sb)) = 0)
      :  (n : Nat) (LDiv b (pow a n)) \elim m
      | inP (n, d, m) => inP (n, \new LDiv {
        | inv => negative $ R.BigSum \new Array R n \lam j => polyCoef p j * pow a j * pow b (n -' suc j)
        | inv-right => \have | s i q : locMap (polyCoef p i * pow a i * pow b (n -' i)) = polyCoef (polyMap locMap p) i * pow (inl~ (a,b,Sb)) i * pow (locMap b) n
                                 => locMap.func-* *> pmap (locMap _ *) locMap.func-pow *> pmap (`* _) (locMap.func-* *> pmap2 (*) (inv (polyCoef_polyMap {locMap})) locMap.func-pow) *> *-assoc *> pmap (_ *) (later $ rewriteI {2} (<=_exists q) $ rewrite pow_+ $ pmap (`* _) (pmap (pow __ i) (LocRing.equals1 simplify) *> pow_*-comm) *> *-assoc) *> inv *-assoc
                             | t => inv (BigSum_suc {_} {n} {\lam j => polyCoef p j * pow a j * pow b (n -' j)}) *> locMap-inj nt (locMap.func-BigSum {\new Array R (suc n) \lam j => polyCoef p j * pow a j * pow b (n -' j)} *> path (\lam i => BigSum (\new Array (LocRing S) (suc n) \lam j => s j (<_suc_<= $ fin_< j) i)) *> inv (BigSum-rdistr {_} {\new Array (LocRing S) (suc n) \lam j => polyCoef (polyMap locMap p) j * pow (inl~ (a,b,Sb)) j}) *> pmap (`* pow (locMap b) n) (inv (polyEval_polyCoef $ degree<=_degree< $ degree<=_polyMap d) *> e) *> zro_*-left)
                             | q (j : Fin n) => *-comm *> pmap (pow b) {suc (n -' suc j)} (NatSemiring.cancel-left j $ <=_exists (suc_<_<= $ fin_< j) *> inv (<=_exists $ LinearOrder.<_<= $ fin_< j))
                       \in negative-unique _ (pmap (`+ _) (negative_*-right *> pmap negative (BigSum-ldistr *> pmap BigSum (exts \lam j => equation {usingOnly (q j)}))) *> negative-left) (pmap (_ +) (later (rewrite (m,-'id) simplify) *> inv (pmap (\lam x => polyCoef p x * pow a x * pow b (n -' x)) (mod_< id<suc))) *> t)
      })
  }
}