\import Algebra.Field
\import Algebra.Group
\import Algebra.Meta
\import Algebra.Monoid
\import Algebra.Monoid.Category
\import Algebra.Monoid.GCD
\import Algebra.Monoid.Sub
\import Algebra.Ring
\import Algebra.Ring.Localization
\import Algebra.Ring.Reduced
\import Algebra.Semiring
\import Arith.Nat
\import Function
\import Function.Meta ($)
\import Logic
\import Logic.Meta
\import Meta
\import Order.StrictOrder
\import Paths
\import Paths.Meta
\import Relation.Equivalence
\import Set
\import Set.Fin
\open Monoid

{- | Domain is a ring with a tight apartness relation such that elements apart from {zro} are closed under finite products.
 -   Classically, this is equivalent to the usual definition since there is a unique apartness relation on a set assuming LEM.
 -}
\class Domain \extends Ring.With#, ImpotentRing, NonZeroRing {
  | zro#ide : ide `#0
  | #0-* {x y : E} : x `#0 -> y `#0 -> (x * y) `#0

  | isReduced p => #0-tight \lam a#0 => #0-zro $ transport #0 p (#0-* a#0 a#0)
  | isImpotent {a} p => \have q : a * (1 - a) = 0 => ldistr_- *> toZero (ide-right *> inv p)
                        \in \case #0-+ (transportInv #0 (+-comm *> +-assoc *> pmap (1 +) negative-left *> zro-right) zro#ide) \with {
                          | byLeft a#0 => byRight $ inv $ fromZero $ #0-tight \lam zro-a#0 => apartNonZero (#0-* a#0 zro-a#0) q
                          | byRight zro-a#0 => byLeft $ #0-tight \lam a#0 => apartNonZero (#0-* a#0 zro-a#0) q
                        }

  \default zro/=ide zro=ide => #0-zro (transportInv #0 zro=ide zro#ide)

  \lemma nonZero_* {x y : E} (x/=0 : x /= zro) (y/=0 : y /= zro) : x * y /= zro => \lam x*y=0 =>
    x/=0 (#0-tight (\lam x#0 =>
    y/=0 (#0-tight (\lam y#0 =>
        #0-zro (transport #0 x*y=0 (#0-* x#0 y#0))))))

  \lemma nonZero-left {x y : E} (x/=0 : x /= zro) (x*y=0 : x * y = zro) : y = zro
    => separatedEq (nonZero_* x/=0 __ x*y=0)

  \lemma nonZero-right {x y : E} (y/=0 : y /= zro) (x*y=0 : x * y = zro) : x = zro
    => separatedEq (nonZero_* __ y/=0 x*y=0)

  \lemma nonZero-cancel-left {x y z : E} (x/=0 : x /= zro) (x*y=x*z : x * y = x * z) : y = z
    => fromZero (nonZero-left x/=0 (ldistr_- *> toZero x*y=x*z))

  \lemma nonZero-cancel-right {x y z : E} (z/=0 : z /= zro) (x*z=y*z : x * z = y * z) : x = y
    => fromZero (nonZero-right z/=0 (rdistr_- *> toZero x*z=y*z))

  \func nonZeroMonoid : CancelMonoid \cowith
    | E => \Sigma (x : E) (x `#0)
    | ide => (ide, zro#ide)
    | * x y => (x.1 * y.1, #0-* x.2 y.2)
    | ide-left => ext ide-left
    | ide-right => ext ide-right
    | *-assoc => ext *-assoc
    | cancel_*-left x {y} {z} x*y=x*z => ext (nonZero-cancel-left (apartNonZero x.2) (pmap __.1 x*y=x*z))
    | cancel_*-right z {x} {y} x*z=y*z => ext (nonZero-cancel-right (apartNonZero z.2) (pmap __.1 x*z=y*z))

  \func subMonoid : SubMonoid \this \cowith
    | contains => #0
    | contains_ide => zro#ide
    | contains_* => #0-*

  \lemma ldiv_nonZero {a b : nonZeroMonoid} (a|b : Monoid.LDiv a.1 b.1) : Monoid.LDiv {nonZeroMonoid} a b \cowith
    | inv => (a|b.inv, #0-*-right (transportInv #0 a|b.inv-right b.2))
    | inv-right => ext a|b.inv-right

  \func nonZero_ldiv {a b : nonZeroMonoid} (a|b : Monoid.LDiv {nonZeroMonoid} a b) : Monoid.LDiv a.1 b.1 \cowith
    | inv => a|b.inv.1
    | inv-right => pmap __.1 a|b.inv-right

  \lemma inv_nonZero {a : nonZeroMonoid} (i : Monoid.Inv a.1) : Monoid.Inv {nonZeroMonoid} a \cowith
    | inv => (i.inv, #0-*-right (transportInv #0 i.inv-right zro#ide))
    | inv-left => ext i.inv-left
    | inv-right => ext i.inv-right

  \lemma nonZero_inv {a : nonZeroMonoid} (i : Monoid.Inv {nonZeroMonoid} a) : Monoid.Inv a.1 \cowith
    | inv => i.inv.1
    | inv-left => pmap __.1 i.inv-left
    | inv-right => pmap __.1 i.inv-right

  \lemma pow_#0 {a : E} {n : Nat} (a#0 : a `#0) : pow a n `#0 \elim n
    | 0 => zro#ide
    | suc n => #0-* (pow_#0 a#0) a#0
} \where {
  \class Dec \extends Domain, Ring.Dec, StrictDomain {
    \default zeroProduct {x} {y} xy=0 => \case decideEq x zro, decideEq y zro \with {
      | yes x=0, _ => byLeft x=0
      | _, yes y=0 => byRight y=0
      | no x/=0, no y/=0 => absurd $ #0-zro $ transport #0 xy=0 $ #0-* (nonZeroApart x/=0) (nonZeroApart y/=0)
    }
    \default zro#ide => nonZeroApart (\lam x => zro/=ide (inv x))
    \default #0-* x#0 y#0 => nonZeroApart (\lam xy=0 => ||.rec' (apartNonZero x#0) (apartNonZero y#0) (zeroProduct xy=0))

    \func LDiv_TruncP {a b : E} (p : TruncP (LDiv a b)) : LDiv a b
      => \case decideEq a 0 \with {
        | yes a=0 => LDiv.make 1 \case p \with {
          | inP (_,q) => later (rewrite a=0 simplify) *> q
        }
        | no a/=0 => TruncP.remove (\lam d d' => ext $ nonZero-cancel-left a/=0 $ LDiv.inv-right {d} *> inv (LDiv.inv-right {d'})) p
      }
  }
}

-- | An integral domain is a commutative domain.
\class IntegralDomain \extends Domain, CRing.With#, ImpotentCRing, NonZeroCRing {
  \func nonZeroMonoid : CancelCMonoid \cowith
    | CancelMonoid => Domain.nonZeroMonoid
    | *-comm => ext *-comm

  \lemma div-inv (x y : E) (x/=0 : x /= zro) (x|y : LDiv x y) (y|x : LDiv y x) : Inv x|y.inv
    => Inv.rmake y|x.inv (Domain.nonZero-cancel-left x/=0 (equation {Monoid}))

  \func gcd_nonZero {a b : nonZeroMonoid} (gcd : GCD a.1 b.1) : GCD {nonZeroMonoid} a b \cowith
    | gcd => (gcd.gcd, #0-*-left (transportInv #0 gcd.gcd|val1.inv-right a.2))
    | gcd|val1 => ldiv_nonZero gcd.gcd|val1
    | gcd|val2 => ldiv_nonZero gcd.gcd|val2
    | gcd-univ g g|a g|b => ldiv_nonZero $ gcd.gcd-univ g.1 (nonZero_ldiv g|a) (nonZero_ldiv g|b)

  \func nonZero_gcd {a b : nonZeroMonoid} (gcd : GCD {nonZeroMonoid} a b) : GCD a.1 b.1 \cowith
    | gcd => gcd.gcd.1
    | gcd|val1 => nonZero_ldiv gcd.gcd|val1
    | gcd|val2 => nonZero_ldiv gcd.gcd|val2
    | gcd-univ g (g|a : LDiv g a.1) g|b => nonZero_ldiv {_} {g, #0-*-left $ transportInv #0 g|a.inv-right a.2} $ gcd.gcd-univ _ (ldiv_nonZero g|a) (ldiv_nonZero g|b)

  \lemma divQuotient_LDiv {x y : DivQuotient nonZeroMonoid} : TruncP (LDiv {DivQuotientMonoid \this} (map subMonoid.embed x) (map subMonoid.embed y)) <-> TruncP (LDiv {DivQuotientMonoid nonZeroMonoid} x y) \elim x, y
    | in~ x, in~ y => (\case __ \with {
      | inP (in~ z, p) => TruncP.map (make~ p).2 \lam s => div-to~ $ ldiv_nonZero $ LDiv.trans (LDiv.factor-left LDiv.id-div) s
    }, \case __ \with {
      | inP (in~ z, p) => TruncP.map (make~ p).2 \lam (xz|y : LDiv (x nonZeroMonoid.* z) y) =>
          \new LDiv (map subMonoid.embed (inD {nonZeroMonoid} x)) (map subMonoid.embed (inD {nonZeroMonoid} y)) (inD (z.1 * xz|y.inv.1)) $ pmap inD $ inv *-assoc *> pmap __.1 xz|y.inv-right
    })

  \lemma toNonZeroDivChain (cc : CMonoid.DivChain {DivQuotientMonoid \this}) : CMonoid.DivChain {DivQuotientMonoid nonZeroMonoid}
    => \lam a d => \have | (inP s) => cc (\lam i => map subMonoid.embed (a i)) \lam i => divQuotient_LDiv.2 (d i)
                         | (inP d) => divQuotient_LDiv.1 (inP s.2)
                   \in inP (s.1, d)

  \lemma IsRegular_/=0 {a : E} : IsRegular a <-> a /= 0
    => (\lam ar a=0 => zro/=ide $ ar (rewrite a=0 simplify), \lam a/=0 p => nonZero-cancel-left a/=0 p)
} \where {
  \open DivQuotient

  \class Dec \extends IntegralDomain, Domain.Dec, CRing.Dec, StrictIntegralDomain, PPRing {
    | isPPRing a => inP \case decideEq a 0 \with {
      | yes a=0 => (0, rewrite a=0 (inv zro_*-left), \lam _ => zro_*-left)
      | no a/=0 => (1, inv ide-left, \lam p => ide-left *> nonZero-left a/=0 p)
    }

    \lemma fromNonZeroDivChain (cc : CMonoid.DivChain {DivQuotientMonoid nonZeroMonoid}) : CMonoid.DivChain {DivQuotientMonoid \this}
      => \lam a ac =>
          \have | f => map {SubMonoid.cStruct subMonoid} subMonoid.embed
                | f-inj : isInj f => \lam {x} {y} => \case \elim x, \elim y \with {
                  | in~ x, in~ y => \lam p => \have t => make~ p \in ~-pequiv (TruncP.map t.1 ldiv_nonZero, TruncP.map t.2 ldiv_nonZero)
                }
                | r n (q : Not ( (j : Fin (suc (suc n))) (a (suc j) = in~ 0))) => TruncP.rec {_} {\Sigma (x : DivQuotient nonZeroMonoid) (f x = a (suc n))} (\lam s s' => ext $ f-inj $ s.2 *> inv s'.2) (Quotient.in-surj (a (suc n)))
                   \lam s => (in~ (s.1, nonZeroApart \lam s=0 => q $ inP (n, pmap (\lam x => a (suc x)) (toFin'=id $ id<suc <∘ id<suc) *> inv s.2 *> pmap in~ s=0)), s.2)
                | t => cc (\lam n => \case FinSet.search {FinFin (suc (suc n))} _ (\lam j => divQuotient_dec0 (a (suc j))) \with {
                         | yes e => in~ (1,zro#ide)
                         | no q => (r n q).1
                       }) (\lam n => mcases {1} \with {
                         | yes e => inP LDiv.ide-div
                         | no q => rewrite (FinSet.search_no_reduce (\lam (j : Fin (suc (suc n))) => divQuotient_dec0 (a (suc j))) (\lam p => q p)) \case ac (suc n) \with {
                           | inP d => divQuotient_LDiv.1 $ inP $ transport2 LDiv (inv (r (suc n) q).2) (inv (r n (\lam p => q p)).2) d
                         }
                       })
          \in \case t \with {
            | inP s => mcases {2} s.2 _ \with {
                | yes e', s2 => TruncP.map e' \lam e => (e.1, \new LDiv {
                  | inv => in~ 0
                  | inv-right => cases (a e.1) \with {
                    | in~ b => pmap in~ zro_*-right *> inv e.2
                  }
                })
                | no q, s2 => rewrite (FinSet.search_no_reduce (\lam (j : Fin (suc (suc s.1))) => divQuotient_dec0 (a (suc j))) (\lam p => q p)) at s2 $
                                inP (suc s.1, transport2 LDiv (r s.1 (\lam p => q p)).2 (r (suc s.1) q).2 $ MonoidHom.func-LDiv {f} s2)
              }
          }

    \lemma oneDimensional-char : Dim<= 1 <-> (\Pi (a u : E) -> a /= 0 ->  (v : E) (n : Nat) (LDiv a (pow u n * (1 - u * v))))
      => (\lam d a u a/=0 => \case d (a,u) \with {
        | inP (b,m,p) => \have q => nonZero-cancel-left (apartNonZero $ pow_#0 $ nonZeroApart a/=0) (p *> inv zro_*-right)
                         \in inP (b 1, m 1, LDiv.make (b 0) equation)
      }, \lam f x => \case decideEq (x 0) 0 \with {
        | yes x0=0 => inP ((1,1), (1,1), rewrite x0=0 simplify)
        | no x0/=0 => \case f (x 0) (x 1) x0/=0 \with {
          | inP (v,n,(y,p)) => inP ((y, v), (1, n), equation)
        }
      })
  }
}

\instance FieldOfQuotients (D : IntegralDomain.Dec) : DiscreteField \cowith
  | CRing => LocRing D.subMonoid
  | zro/=ide p => \case LocRing.unequals p \with {
    | inP (c,c#0,q) => D.#0-zro (transport D.#0 equation c#0)
  }
  | eitherZeroOrInv => \case \elim __ \with {
    | in~ x => \case decideEq x.1 0 \with {
      | yes e => byLeft $ LocRing.equals1 $ ide-right *> e *> inv zro_*-left
      | no q => byRight $ Monoid.Inv.lmake (inl~ (x.2, x.1, AddGroup.nonZeroApart q)) $ LocRing.equals1 (simplify *-comm)
    }
  }

\class StrictDomain \extends Ring, NonZeroSemiring, ImpotentRing {
  | zeroProduct {x y : E} : x * y = zro -> (x = zro) || (y = zro)
  | isReduced => \case zeroProduct __ \with {
    | byLeft r => r
    | byRight r => r
  }
  | isImpotent {a} p => \have q : a * (a - 1) = 0 => ldistr_- *> toZero (p *> inv ide-right)
                        \in \case zeroProduct q \with {
                          | byLeft r => byLeft r
                          | byRight r => byRight (fromZero r)
                        }

  \lemma zeroBigProd {l : Array E} (p : BigProd l = zro) :  (j : Fin l.len) (l j = 0) \elim l
    | nil => absurd $ zro/=ide (inv p)
    | a :: l => \case zeroProduct p \with {
      | byLeft r => inP (0,r)
      | byRight r => TruncP.map (zeroBigProd r) \lam s => (suc s.1, s.2)
    }

  \lemma zeroOrCancel-left {x y z : E} (p : x * y = x * z) : (x = 0) || (y = z)
    => \case zeroProduct $ ldistr_- *> toZero p \with {
      | byLeft x=0 => byLeft x=0
      | byRight y-z=0 => byRight (fromZero y-z=0)
    }

  \lemma zeroOrCancel-right {x y z : E} (p : x * z = y * z) : (x = y) || (z = 0)
    => \case zeroProduct $ rdistr_- *> toZero p \with {
      | byLeft x-y=0 => byLeft (fromZero x-y=0)
      | byRight z=0 => byRight z=0
    }

  \lemma nonZero-cancel-left {x y z : E} (x/=0 : x /= 0) (p : x * y = x * z) : y = z
    => \case zeroOrCancel-left p \with {
      | byLeft x=0 => absurd (x/=0 x=0)
      | byRight r => r
    }

  \lemma nonZero-cancel-right {x y z : E} (z/=0 : z /= 0) (p : x * z = y * z) : x = y
    => \case zeroOrCancel-right p \with {
      | byLeft r => r
      | byRight z=0 => absurd (z/=0 z=0)
    }
}

\class StrictIntegralDomain \extends StrictDomain, ImpotentCRing {
  \lemma div-associates {a b : E} (a|b : LDiv a b) (b|a : LDiv b a) : TruncP (associates a b)
    => \case zeroOrCancel-left $ inv *-assoc *> pmap (`* _) a|b.inv-right *> b|a.inv-right *> inv ide-right \with {
      | byLeft a=0 => inP (Inv.ide-isInv, a=0 *> inv (ldiv=0 a|b a=0) *> inv ide-left)
      | byRight r => inP (Inv.lmake _ r, inv b|a.inv-right *> *-comm)
    }
}