"""
    CoefficientsQadicReversed(k::Int, z::T, q::Int, n::Int) where {T<:Integer}

Return an array ``[ v_1, v_2, \\ldots, v_n ]`` of length `n`
such that ``k*(q^n-1)/z`` has the form
``v_n q^0 + v_{n-1} q^1 + \\cdots + v_1 q^{n-1}``,
that is, the result contains the `q`-adic coefficients of ``k*(q^n-1)/z``
in reversed order.

We assume that ``0 \\leq k \\leq z``, ``q > 1``, and ``n > 0``.

The (perhaps large) number ``k*(q^n-1)/z`` need not be created
in the computations.
"""
function CoefficientsQadicReversed(k::Int, z::T, q::Int, n::Int) where {T<:Integer}
    v::Vector{Int} = zeros(Int, n)
    if k == z
      for i in 1:n
        v[i] = q-1
      end
    else
      r = k
      for i in 1:n
        d, r = divrem(r * q, z)
        v[i] = d
      end
    end

    return v
end

# The following function is needed only for the sake of efficiency
# comparisons.
function CoefficientsQadic(ke::T, q::Int, n::Int) where {T<:Integer}
    v::Vector{Int} = zeros(Int, n)
    for i in 1:n
        ke, r = divrem(ke, q)
        v[i] = r
    end

    return v
end

function islessorequal(mon1::Vector{Int}, mon2::Vector{Int}, n::Int)
  for i in 1:n
    if mon2[i] < mon1[i]
      return false
    end
  end

  return true
end

"""
    LoewyStructureInfo(q::Int, n::Int, z::T) where {T<:Integer}

Return a dictionary with the following keys.

`:monomials`
    the array of reversed `q`-adic expansions for multiples of
    `e = (q^n-1)/z`, each of length `n`,
    see [`CoefficientsQadicReversed`](@ref),

`:layers`
    the array of the Loewy layers to which the monomials belong,

`:chain`
    an array of positions of monomials of a longest ascending chain,

`:m`
    the value `m(q,e)`,

`:LL`
    the Loewy length of `A[q,n,z]`,
    equal to the length of the `:layers` value plus 1,

`:parameters`
    the array `[ q, n, z ]`.
"""
function LoewyStructureInfo(q::Int, n::Int, z::T) where {T<:Integer}
    if (z != 1 && powermod(q, n, z) != 1)
      error("<z> must divide <q>^<n> - 1")
    end

    monomials = [zeros(Int, n)]
    layers = Int[0]
    degrees = Int[0]    # just for speedup: avoid comparisons
    predecessors = Int[0]

    m = n * (q-1)
    for i in 1:z
      mon = CoefficientsQadicReversed(i, z, q, n)
      lambda = 0
      pred = 1
      mm = Base.sum(mon)
      for j in 2:i
        if lambda < layers[j] && degrees[j] < mm &&
                                 islessorequal(monomials[j], mon, n)
          lambda = layers[j]
          pred = j
        end
      end
      push!(monomials, mon)
      push!(layers, lambda + 1)
      push!(degrees, mm)
      push!(predecessors, pred)

      if lambda == 0
        if mm < m
          m = mm
        end
      end
    end

    # Extract information about one longest chain.
    i = length(monomials)
    pred = Int[]
    while i > 0
      push!(pred, i)
      i = predecessors[i]
    end

    return Dict(:monomials => monomials,
                :layers => layers,
                :chain => pred,
                :m => m,
                :LL => layers[z+1] + 1,
                :parameters => [q, n, z])
end;

"""
    LoewyVector(data::Dict)

Return an array that stores at position `i+1` the dimension of the `i`-th
Loewy layer of the Singer algebra described by the dictionary `data`.
"""
function LoewyVector(data::Dict) Vector{Int}
    v = zeros(Int, data[:LL])
    for i in data[:layers]
      v[i+1] = v[i+1] + 1
    end

    return v
end;

function SingerAlgE(q::Int, n::Int, z::T) where {T <: Integer}
    return div(BigInt(q)^n-1, z)
end;

"""
    LoewyLength(q::Int, z::T) where {T <: Integer}
    LoewyLength(q::Int, n::Int, z::T, m::Int) where {T <: Integer}
    LoewyLength(q::Int, n::Int, z::T, A::GAP.GapObj) where {T <: Integer}
    LoewyLength(q::Int, n::Int, z::T, m::Int, A::GAP.GapObj) where {T <: Integer}

Return the Loewy length of the Singer Algebra ``A[q, z]``.

If `n` and `m` are given then `n` must be the multiplicative order of `q`
modulo `z`, and `m` must be the value ``m(q, e)``.

If `A` is given then it must be the GAP object that represents ``A[q, z]``;
in this case, stored attributes of `A` are used.
"""
function LoewyLength(q::Int, z::T) where {T <: Integer}
    n = OrderMod(q, z)
    return LoewyLength(q, n, z,
               MinimalDegree(q, SingerAlgE(q, n, z)))
end;

function LoewyLength(q::Int, n::Int, z::Int, m::Int)
    l = LoewyLengthCheap(q, n, z, m)
    if l == 0
      l = LoewyLengthHard(q, n, z, m)
    end

    return l
end;

function LoewyLength(q::Int, n::Int, z::T, A::GAP.GapObj) where {T <: Integer}
    # First we need m; we store this attribute in `A`.
    if GAP.Globals.HasMinimalDegreeOfSingerAlgebraJulia(A)
      m = GAP.Globals.MinimalDegreeOfSingerAlgebraJulia(A)
    else
      m = MinimalDegree(q, n, SingerAlgE(q, n, z), A)
    end

    if GAP.Globals.HasLoewyStructureInfoJulia(A)
      # The Loewy info is now known.
      l = GAP.Globals.LoewyStructureInfoJulia(A)[:LL]
    else
      l = LoewyLength(q, n, z, m, A)
    end

    return l
end;

function LoewyLength(q::Int, n::Int, z::T, m::Int, A::GAP.GapObj) where {T <: Integer}
    # Compute the value.
    l = LoewyLengthCheap(q, n, z, m)
    if l == 0
      info = LoewyStructureInfo(q, n, z)
      # Store the attribute.
      GAP.Globals.SetLoewyStructureInfoJulia(A, info)
      l = info[:LL]
    end

    return l
end;

"""
    SufficientCriterionForLoewyBoundAttained(q::Int, n::Int, z::T, m::Int) where {T <: Integer}

Return a string that describes the criterion from
[BHHK1](https://doi.org/10.1016/j.jalgebra.2019.05.004) or
[BHHK2](http://arxiv.org/abs/1912.03065v1)
from which it follows that the Loewy length of the algebra ``A[q,z]``
is equal to the upper bound ``\\lfloor n (q-1) / m \\rfloor + 1``,
where ``n = \\ord_z(q)``, ``e = (q^n-1) / z``, and ``m = m(q, e)``;
if no such criterion was found then the returned string is empty.
"""
function SufficientCriterionForLoewyBoundAttained(q::Int, n::Int, z::T, m::Int) where {T <: Integer}

    if n <= 3
      return "Cor. I.7.1 (n <= 3)"
    elseif z < 70
      return "z < 70"
    elseif m == 2
      return "La. I.6.3"
    elseif mod(q - 1, m) == 0
      return "Thm. I.7.1"
    elseif n * (q-1) < 3 * m
      return "La. I.7.1 (iii)"
    end

    qm = mod(q, m)
    if 1 < qm && div(n * (qm-1), m) == 1
      return "Prop. II.3.15"
    end

    bq = BigInt(q)
    qnm1 = bq^n - 1
    e = div(qnm1, z)

    if e <= 32
      return "Prop. II.6.1 (e <= 32)"
    elseif e <= 3 * m
      return "Prop. II.6.4"
    elseif n <= 5 && mod(div(qnm1, q-1), e) == 0
      return "Prop. II.5.3, II.5.6 (e | (q^n-1)/(q-1), n <= 5)"
    elseif mod(n, div(m * OrderMod(q, e, n), gcd(m, q-1))) == 0
      return "La. II.5.2 (ii)"
    end

    if mod(div(qnm1, q-1), e) == 0
      # we know m <= n
      if m >= n-1
        return "Prop. II.5.1 (ii)"
      elseif m == n-2 && mod(q, m) in 1:div(m+1, 2)
        return "Prop. II.5.1 (iii)"
      elseif mod(div((n-m) * (q-1), m), n-m) == 0
        return "Prop. II.5.1 (i)"
      end
    end

    if mod(n, m) == 0
      if mod(Base.sum([bq^div(i*n, m) for i in 0:(m-1)]), e) == 0
        return "La. II.4.1 for r = 1"
      end
      for r in DivisorsInt(n)
        if mod(div(n, r), m) == 0 &&
           mod(Base.sum([bq^div(n*i, m*r) for i in 0:(m-1)]), e) == 0
          return "La. II.4.1"
        end
      end
    end

    p = SmallestRootInt(e)

    if p == 2
      return "Thm. II.4.3 (iii)"
    elseif PrimesGAP.isprime(p)
      # (Here we know that 'q mod p <> 1' holds.)
      # Check whether 'p' is a Pierpont prime.
      p = p - 1
      while iseven(p)
        p = div(p, 2)
      end
      while mod(p, 3) == 0
        p = div(p, 3)
      end
      if p == 1
        return "Thm. II.4.3 (ii)"
      end
    end

    # Concerning Remark II.5.4,
    # we need not check \Phi_2 (since m = e if e divides q-1,
    # and we have checked whether m divides q-1),
    # \Phi_4, \Phi_8 (because then m = 2).
    phi6 = bq^2 - q + 1     # this is \Phi_6(q)
    if mod(phi6, e) == 0 ||
       mod(bq^3*(bq^3+1) + 1, e) == 0 ||  # this is \Phi_9(q)
       mod(bq^3*(q-1) + phi6, e) == 0     # this is \Phi_{10}(q)
      return "Rem. II.5.4"
    end

    # We give up.
    return ""
end;

function LoewyLengthCheap(q::Int, n::Int, z::T, m::Int) where {T <: Integer}
    if SufficientCriterionForLoewyBoundAttained(q, n, z, m) != ""
      # The bound is attained.
      return div(n*(q-1), m) + 1
    else
      return 0
    end
end;

function LoewyLengthHard(q::Int, n::Int, z::T, m::Int) where {T <: Integer}
    return LoewyStructureInfo(q, n, z)[:LL]
end;

function OverflowTable(data::Dict)
    if ! haskey(data, :overflowTable)
      monoms = data[ :monomials ]
      d = length(monoms)
      mat = falses(d, d)
      q = data[:parameters][1]
      n = data[:parameters][2]
      for i in 1:d
        for j in 1:(i-1)
          res = all(x -> x < q, monoms[i] + monoms[j])
          mat[i, j] = res
          mat[j, i] = res
        end
        mat[i, i] = all(x -> x < q, monoms[i] + monoms[i])
      end

      data[:overflowTable] = mat
    end

    return data[:overflowTable]
end

function MultTable(data::Dict)
    if ! haskey(data, :multtable)
      monoms = data[:monomials]
      n = length(monoms)
      nn = div(n+1, 2)
      mat = zeros(Int, n, n)
      q = data[:parameters][1]
      if isodd(q)
        qhalf = div(q, 2) + 1
      else
        qhalf = div(q, 2)
      end
      for i in 1:nn
        for j in 1:(i-1)
          if all(x -> x < q, monoms[i] + monoms[j])
            mat[i, j] = i+j-1
            mat[j, i] = i+j-1
          end
        end
        if all(x -> x < qhalf, monoms[i])
          mat[i, i] = i+i-1
        end
      end
      for i in (nn+1):n
        # The [i,j] entry can be nonzero only if i+j <= z+2 holds.
        for j in 1:(n+1-i)
          if all(x -> x < q, monoms[i] + monoms[j])
            mat[i,j] = i+j-1
            mat[j,i] = i+j-1
          end
        end
      end
      data[:multtable] = mat
    end

    return data[:multtable]
end

function OverflowCount(data::Dict)
    if ! haskey(data, :overflowCount)
      mat = OverflowTable(data)
      list = [count(mat[i,:]) for i in 1:size(mat, 1)]
      data[:overflowCount] = list
    end

    return data[:overflowCount]
end

function BasisOfSum(data::Dict, list1::Vector{Int}, list2::Vector{Int})
    return sort(union(list1, list2))
end

function BasisOfIntersection(data::Dict, list1::Vector{Int}, list2::Vector{Int})
    return intersect(list1, list2)
end

function BasisOfProductSpace(data::Dict, list1::Vector{Int}, list2::Vector{Int})
    mat = MultTable(data)
    basis = Int[]

    for i in list1
      for j in list2
        if mat[i,j] != 0
          push!(basis, i+j-1)
        end
      end
    end

    return unique(sort(basis))
end

function BasisOfIdeal(data::Dict, list::Vector{Int})
    return BasisOfProductSpace(data, list, collect(1:length(data[:monomials])))
end

function BasisOfAnnihilator(data::Dict, list::Vector{Int})
    mat = MultTable(data)
    return filter(i -> all(j -> mat[i,j] == 0, list), 1:size(mat, 1))
end;

function BasesOfRadicalSeries(data::Dict)
    lay = data[:layers]
    ll = data[:LL]
    jbylayer = map(i -> findall(isequal(i), lay), 1:(ll-1))
    J = [jbylayer[ll-1]]
    for i in 2:(ll-1)
      push!(J, sort(union(J[i-1], jbylayer[ll-i])))
    end

    return reverse(J)
end

function BasesOfSocleSeries(data::Dict)
    lay = reverse(data[:layers])
    ll = data[:LL]
    sbylayer = map(i -> findall(isequal(i), lay), 0:(ll-2))
    S = [sbylayer[1]]
    for i in 2:(ll-1)
      push!(S, sort(union(S[i-1], sbylayer[i])))
    end

    return S
end

function BasisOfPowers(data::Dict, I::Vector{Int}, p::Int, m::Int)
    result = Int[]
    q = data[:parameters][1]
    pm = BigInt(p)^m
    quo = div(q-1, pm)
    for k in I
      if maximum(data[:monomials][k]) <= quo
        push!(result, pm * (k-1) + 1)
      end
    end

    return result
end

function BasisOfPMRoots(data::Dict, I::Vector{Int}, p::Int, m::Int)
    q= data[:parameters][1]
    pm = BigInt(p)^m
    quo = div(q-1, pm)

    # The first condition means that B_k^{p^m} is zero
    # and hence inside the given subspace.
    # For the second condition, we may assume that B_k^{p^m}
    # is nonzero and hence equal to B_{p^m (k-1)+1}.
    return filter(k -> maximum(data[:monomials][k]) > quo ||
                        pm * (k-1) + 1 in I,
                  1:length(data[:monomials]))
end

IsSubset(I::Vector{Int}, J::Vector{Int}) = all(x -> x in I, J)

function BasisOfPC(data::Dict, I::Vector{Int}, J::Vector{Int})
    # Note that we are interested in subspaces of the radical.
    return filter(k -> IsSubset(J, BasisOfProductSpace(data, [k], I)),
                  2:length(data[:monomials]))
end


"""
    RationalFunction(p::AbstractPolynomial, q::AbstractPolynomial)
    p // q

Create a rational expression (`p/q`) from the two polynomials.

There is no attempt to cancel common factors. The [`lowest_terms`](@ref) function attempts to do that.

For purposes of iteration, a rational function is treated like a tuple.

## Examples
```
julia> using Polynomials

julia> p,q = fromroots(Polynomial, [1,2,3]), fromroots(Polynomial, [2,3,4])
(Polynomial(-6 + 11*x - 6*x^2 + x^3), Polynomial(-24 + 26*x - 9*x^2 + x^3))

julia> pq = p // q
(-6 + 11*x - 6*x^2 + x^3) // (-24 + 26*x - 9*x^2 + x^3)

julia> lowest_terms(pq)
(-0.333333 + 0.333333*x) // (-1.33333 + 0.333333*x)

julia> pq(2.5)
-1.0

julia> pq(2) # uses first non-`0/0` ratio of `p⁽ᵏ⁾/q⁽ᵏ⁾`
-0.5

julia> pq^2
(36 - 132*x + 193*x^2 - 144*x^3 + 58*x^4 - 12*x^5 + x^6) // (576 - 1248*x + 1108*x^2 - 516*x^3 + 133*x^4 - 18*x^5 + x^6)

julia> derivative(pq)
(-108 + 180*x - 111*x^2 + 30*x^3 - 3*x^4) // (576 - 1248*x + 1108*x^2 - 516*x^3 + 133*x^4 - 18*x^5 + x^6)
```

!!! Note:
    The [RationalFunctions.jl](https://github.com/aytekinar/RationalFunctions.jl) was a helpful source of ideas.

"""
struct RationalFunction{T, X, P<:Polynomials.AbstractPolynomial{T,X}} <: AbstractRationalFunction{T,X,P}
    num::P
    den::P
    function RationalFunction(p::P, q::P) where {T,X, P<:Polynomials.AbstractPolynomial{T,X}}
        new{T,X,P}(p, q)
    end
    function RationalFunction(p::P, q::T) where {T,X, P<:Polynomials.AbstractPolynomial{T,X}}
        new{T,X,P}(p, q*one(P))
    end
    function RationalFunction(p::T, q::Q) where {T,X, Q<:Polynomials.AbstractPolynomial{T,X}}
        new{T,X,Q}(p*one(Q), q)
    end
end

RationalFunction(p,q)  = RationalFunction(promote(p,q)...)

# evaluation
(pq::RationalFunction)(x) = eval_rationalfunction(x, pq)

# Look like rational numbers
function Base.://(p::Polynomials.AbstractPolynomial,q::Polynomials.AbstractPolynomial)
    RationalFunction(p,q)
end

function Base.promote_rule(::Type{PQ}, ::Type{Q}) where {T,X,P,PQ<:RationalFunction{T,X,P},
                                                         S,Y,Q<:AbstractPolynomial{S,Y}}
    assert_same_variable(X,Y)
    R = promote_type(T,S)
    P′ = constructorof(P)
    RationalFunction{R,X,P′{R,X}}
end
function Base.promote_rule(::Type{PQ}, ::Type{PQ′}) where {T,X,P,PQ<:RationalFunction{T,X,P},
                                                           S,Y,Q,PQ′<:RationalFunction{S,Y,Q}}
    Polynomials.assert_same_variable(X,Y)
    R = promote_type(T,S)
    P′ = constructorof(promote_type(P,Q))
    RationalFunction{R,X,P′{R,X}}
end

function Base.promote_type(::Type{R}, ::Type{RR}) where {T,X,P,R<:RationalFunction{T,X,P},
                                                         S,  Q,RR<:RationalFunction{S,X,Q}}
    PQ = promote_type(P,Q)
    TS = promote_type(T,S)
    RationalFunction{TS, X, PQ}
end

function Base.convert(::Type{R}, pq::RR) where {T,X,P,R<:RationalFunction{T,X,P},
                                                S,  Q,RR<:RationalFunction{S,X,Q}}
    p,q = pq
    p′, q′ = convert(P, p), convert(P, q')
    RationalFunction(p′, q′)
end

function variable(::Type{PQ}) where {T,X,PQ <: RationalFunction{T,X}}
    x = variable(Polynomial)
    rational_function(PQ, x, one(x))
end


using RecipesBase

function poly_interval(p::Poly)
    # Find points of interest
    zero_pts = roots(p)
    crit_pts = roots(polyder(p, 1))
    infl_pts = roots(polyder(p, 2))
    pts = sort([ real(pt) for pt in [zero_pts; crit_pts; infl_pts] if isreal(pt) ])

    # Choose a range that shows all interesting points with some margin
    min_x, max_x = length(pts) > 0 ? (pts[1], pts[end]) : (-1, 1)
    diff = max(max_x - min_x, 1)
    a = min_x - diff/2
    b = max_x + diff/2

    return a : diff/50 : b
end

function poly_label(p::Poly)
    buff = IOBuffer()
    printpoly(buff, p)
    String(take!(buff))
end

@recipe function poly_recipe(p::Poly, range = poly_interval(p))
    label --> poly_label(p)
    range, map(x -> polyval(p, x), range)
end

@recipe function poly_recipe(p::Poly, a, b)
    label --> poly_label(p)
    step = (b-a)/100
    xs = a:step:b
    ys = map(x -> polyval(p, x), xs)
    xs, ys
end


"""
This  package, which depends only  on the packages `LaurentPolynomials` and
`ModuleElts`,  implements Puiseux polynomials,  that is linear combinations
of  monomials of the  type `x₁^{a₁}… xₙ^{aₙ}`  where `xᵢ` are variables and
`aᵢ`  are exponents which can be arbitrary rational numbers.

Recall  that  on  an  algebraically  closed coefficient field, the quotient
field  of the Puiseux polynomials is  the algebraic closure of the quotient
field  of  the  multivariate  polynomials.  We use Puiseux polynomials with
cyclotomic coefficients as splitting fields of cyclotomic Hecke algebras.

This  package is in particular  a perfectly usable (and  quite good I hope)
implementation   of  multivariate  polynomials  and  multivariate  rational
fractions if you are only interested in that.

Some  functions described below work  only with polynomials where variables
are  raised to integral powers;  we will refer to  such objects as "Laurent
polynomials"; some functions require further that variables are raised only
to  positive powers: we refer then to "true polynomials".

Puiseux  polynomials have the  parametric type `Mvp{C,E}`  where `C` is the
type  of the coefficients and `E` is the type of the exponents (`E=Int` for
Laurent   polynomials;   `E=Rational{Int}`   for   more   general   Puiseux
polynomials).  When printing only `C` is  printed if `E==Int`. The rational
fractions  have type  `Frac{Mvp{C,Int}}`(and in  addition the numerator and
denominator should be true polynomials).

We first look at how to make Puiseux polynomials.

`@Mvp x₁,…,xₙ`

assigns  to each  Julia name  `xᵢ` an  `Mvp` representing  an indeterminate
suitable   to  build   multivariate  polynomials   or  rational  fractions.
`Mvp(:x₁)` creates the same `Mvp` without assigning it to variable `x₁`.

```julia-repl
julia> @Mvp x,y

julia> (x+y^-1)^3
Mvp{Int64}: x³+3x²y⁻¹+3xy⁻²+y⁻³

julia> x+Mvp(:z)
Mvp{Int64}: x+z

julia> x^(1//2)
Mvp{Int64,Rational{Int64}}: x½

julia> Mvp(3)
Mvp{Int64}: 3
```

`Mvp(x::Number)`  returns the multivariate polynomial  with only a constant
term, equal to `x`.

It  is convenient to create `Mvp`s using  variables such as `x,y` above. To
create  them more  directly, `Monomial(:x=>1,:y=>-2)`  creates the monomial
`xy⁻²`, and then `Mvp(Monomial(:x=>1,:y=>-2)=>3,Monomial()=>4)` creates the
`Mvp`  `3xy⁻²+4`. This is the way `Mvp` are printed in another context than
the repl, IJulia or Pluto (where they display nicely as shown above).

```julia-rep1
julia> print(3x*y^-2+4)
Mvp(Monomial(:x,:y => -2) => 3,Monomial() => 4) # :x is shorthand for :x=>1

julia> print(x^(1//2))
Mvp(Monomial(:x => 1//2) => 1)
```

Only  monomials and one-term `Mvp`s can  be raised to a non-integral power;
the  `Mvp` with one term constant `c`  times the monomial `m` can be raised
to  a fractional  power of  denominator `d`  if and  only if `root(c,d)` is
defined (this is equivalent to `c^{1//d}` for floats);

```julia-repl
julia> (4x)^(1//2)
Mvp{Int64,Rational{Int64}}: 2x½

julia> (2.0x)^(1//2)
Mvp{Float64,Rational{Int64}}: 1.4142135623730951x½

julia> root(2.0x)
Mvp{Float64,Rational{Int64}}: 1.4142135623730951x½
```

One  may  want  to  define  `root`  differently;  for instance, in my other
package   `CylotomicNumbers`  I   define  square   roots  of  rationals  as
cyclotomics;  I also have implemented in `CylotomicNumbers` arbitrary roots
of roots of unity). Using `CylotomicNumbers`:

```julia-rep1
julia> (2x)^(1//2)
Mvp{Cyc{Int64},Rational{Int64}}: √2x½

julia> (E(3)*x)^(2//3)
Mvp{Cyc{Int64},Rational{Int64}}: ζ₉²x⅔
```

There  are various ways to take an  `Mvp` apart. Below are the most direct;
look   also  at  the   functions  `coefficient`,  `coefficients`,  `pairs`,
`monomials`, `variables` and `powers`.

```julia-repl
julia> p=3x*y^-2+4
Mvp{Int64}: 3xy⁻²+4

julia> term(p,1)
xy⁻² => 3

julia> term(p,2)
 => 4

julia> length(p) # the number of terms
2

julia> term.(p,1:length(p)) # same as pairs(p)
2-element Vector{Pair{Monomial{Int64}, Int64}}:
 xy⁻² => 3
      => 4

julia> m=first(term(p,1)) # same as first(monomials(p))
Monomial{Int64}:xy⁻²

julia> length(m) # how many variables in m
2

julia> m[:x] # power of x in m
1

julia> m[:y] # power of y in m
-2

julia> map((x,y)->x=>y,variables(m),powers(m)) # same as pairs(m)
2-element Vector{Pair{Symbol, Int64}}:
 :x => 1
 :y => -2
```

The valuation and degree of an Mvp can be inspected globally or variable by
variable.

```julia-repl
julia> p
Mvp{Int64}: 3xy⁻²+4

julia> variables(p)
2-element Vector{Symbol}:
 :x
 :y

julia> degree(p),degree(p,:x),degree(p,:y)
(0, 1, 0)

julia> valuation(p),valuation(p,:x),valuation(p,:y)
(-1, 0, -2)
```

Terms  are totally ordered in an `Mvp`  by a monomial ordering (that is, an
ordering  on  monomials  so  that  `x<y`  implies `xz<yz` for any monomials
`x,y,z`).  By default, the  ordering is `lex`.  The terms are in decreasing
order,  so that the  first term is  the highest. The  orderings `grlex` and
`grevlex` are also implemented (see `grobner_basis` for how to use them).

An  `Mvp` is a *scalar*  if the valuation and  degree are `0`. The function
`scalar`  returns the  constant coefficient  if the  `Mvp` is a scalar, and
`nothing` otherwise.

Usual  arithmetic (`+`, `-`,  `*`, `^`, `/`,  `//`, `one`, `isone`, `zero`,
`iszero`,  `==`)  works.  Elements  of  type  `<:Number`  are considered as
scalars for scalar operations on the coefficients.

```julia-repl
julia> p
Mvp{Int64}: 3xy⁻²+4

julia> p^2
Mvp{Int64}: 9x²y⁻⁴+24xy⁻²+16

julia> p/2
Mvp{Float64}: 1.5xy⁻²+2.0

julia> p//2
Mvp{Rational{Int64}}: (3//2)xy⁻²+2//1
```

When  converting an `Mvp` one needs to specify the two type parameters (the
type of the coefficients and the type of the exponents).

```julia-repl
julia> Mvp{Float64,Rational{Int}}(p)
Mvp{Float64,Rational{Int64}}: 3.0xy⁻²+4.0
```

One can evaluate an `Mvp` when setting the value of some variables by using
the  function call syntax (actually, the keyword syntax for the object used
as a function). There is also a more explicit `value` function.

```julia-repl
julia> p=x+y
Mvp{Int64}: x+y

julia> value(p,:x=>2)
Mvp{Int64}: y+2

julia> p(x=2)
Mvp{Int64}: y+2

julia> p(x=2,y=x)
Mvp{Int64}: x+2

julia> value(p,:x=>2,:y=>x)
Mvp{Int64}: x+2
```

Note  that  an  `Mvp`  always  evaluates  to an `Mvp`, for consistency. You
should  use `scalar`  on the  result of  evaluating all  variables to get a
number.

```julia-repl
julia> p(x=1,y=2)
Mvp{Int64}: 3

julia> scalar(p(x=1,y=2))
3

julia> v=(x^(1//2))(x=2.0)
Mvp{Float64,Rational{Int64}}: 1.4142135623730951

julia> scalar(v)
1.4142135623730951
```

One  can divide an `Mvp` by another when the division is exact, compute the
`gcd` and `lcm` of two `Mvp`.

```julia-repl
julia> exactdiv(x^2-y^2,x-y)
Mvp{Int64}: x+y

julia> (x+y)/(2x^2)   # or by a monomial
Mvp{Float64}: 0.5x⁻¹+0.5x⁻²y

julia> (x+y)//(2x^2)
Mvp{Rational{Int64}}: (1//2)x⁻¹+(1//2)x⁻²y

julia> (x+y)/(x-y)    # otherwise one gets a rational fraction
Frac{Mvp{Int64, Int64}}: (x+y)/(x-y)
```

Raising  a non-monomial  Laurent polynomial  to a  negative power returns a
rational   fraction.  Rational  fractions  are  normalized  such  that  the
numerator  and denominators are true polynomials  prime to each other. They
have  the  arithmetic  operations  `+`,  `-`  , `*`, `/`, `//`, `^`, `inv`,
`one`,  `isone`, `zero`, `iszero` (these  operations can operate between an
`Mvp` or a `Number` and a `Frac{<:Mvp}`).

```julia-repl
julia> (x+1)^-2
Frac{Mvp{Int64, Int64}}: 1/(x²+2x+1)

julia> x+1/(y+1)
Frac{Mvp{Int64, Int64}}: (xy+x+1)/(y+1)

julia> 1-1/(y+1)
Frac{Mvp{Int64, Int64}}: y/(y+1)
```

One  can evaluate a `Frac` when setting  the value of some
variables by using the function call syntax or the `value` function:

```julia-repl
julia> ((x+y)/(x-y))(x=y+1)
Mvp{Float64}: 2.0y+1.0

julia> value((x+y)/(x-y),:x=>y+1;Rational=true) # use // for division
Mvp{Int64}: 2y+1
```

`Frac`  can  be  dissected  using  `numerator`  and `denominator`. They are
scalars  for  broadcasting  and  can  be  sorted  (have  `cmp` and `isless`
methods).

```julia-repl
julia> m=[x+y x-y;x+1 y+1]
2×2 Matrix{Mvp{Int64, Int64}}:
 x+y  x-y
 x+1  y+1

julia> n=inv(Frac.(m))
2×2 Matrix{Frac{Mvp{Int64, Int64}}}:
 (-y-1)/(x²-2xy-y²-2y)  (x-y)/(x²-2xy-y²-2y)
 (x+1)/(x²-2xy-y²-2y)   (-x-y)/(x²-2xy-y²-2y)

julia> lcm(denominator.(n))
Mvp{Int64}: x²-2xy-y²-2y
```

Finally,   `Mvp`s  have   methods  `conj`,   `adjoint`  which   operate  on
coefficients,   a   `derivative`   method,   and  methods  `positive_part`,
`negative_part` and `bar` (useful for Kazhdan-Lusztig theory).

```julia_repl
julia> @Mvp z
Mvp{Int64}: z

julia> hessian(p,vars)=[derivative(derivative(p,x),y) for x in vars, y in vars]
hessian (generic function with 1 method)

julia> hessian(x^2*y^2*z^2,[:x,:y,:z])
3×3 Matrix{Mvp{Int64, Int64}}:
 2y²z²  4xyz²  4xy²z
 4xyz²  2x²z²  4x²yz
 4xy²z  4x²yz  2x²y²

julia> jacobian(pols,vars)=[derivative(p,v) for p in pols, v in vars]
jacobian (generic function with 1 method)

julia> jacobian([x,y,z],[:x,:y,:z])
3×3 Matrix{Mvp{Int64, Int64}}:
 1  0  0
 0  1  0
 0  0  1

julia> p=(x+y^-1)^4
Mvp{Int64}: x⁴+4x³y⁻¹+6x²y⁻²+4xy⁻³+y⁻⁴

julia> positive_part(p)
Mvp{Int64}: x⁴

julia> negative_part(p)
Mvp{Int64}: y⁻⁴

julia> bar(p)
Mvp{Int64}: y⁴+4x⁻¹y³+6x⁻²y²+4x⁻³y+x⁻⁴
```

Despite  the degree of generality of our  polynomials, the speed is not too
shabby. For the Fateman test f(f+1) where f=(1+x+y+z+t)^15, we take 3sec.
According to the Nemo paper, Sagemath takes 10sec and Nemo takes 1.6sec.
"""
module PuiseuxPolynomials
using ModuleElts
using LaurentPolynomials
export coefficient, monomials, powers
export Mvp, Monomial, @Mvp, variables, value, laurent_denominator, term,
       lex, grlex, grevlex, grobner_basis, rename_variables
#------------------ Monomials ---------------------------------------------
"""
`Monomials` are implemented as a list of pairs `:variable=>degree` sorted
in the alphabetic order of vraiables.
"""
struct Monomial{T} # T is Int or Rational{Int}
  d::ModuleElt{Symbol,T}
end

Monomial(a::Union{Pair,Symbol}...)=Monomial(ModuleElt(map(x->x isa Symbol ?
          x=>1 : x,a)...)) # to be avoided when preformance needed
Monomial()=one(Monomial{Int})

Base.convert(::Type{Monomial{T}},v::Symbol) where T=Monomial(v=>T(1))
Base.convert(::Type{Monomial{T}},m::Monomial{N}) where {T,N}=
  Monomial(convert(ModuleElt{Symbol,T},m.d))

# we need a promote rule to mix fractional momonials with others in an Mvp
Base.promote_rule(::Type{Monomial{N}},::Type{Monomial{M}}) where {N,M}=
  Monomial{promote_type(N,M)}

Base.:*(a::Monomial, b::Monomial)=Monomial(a.d+b.d)
Base.isone(a::Monomial)=iszero(a.d)
#Base.iszero(a::Monomial)=false
Base.one(::Type{Monomial{T}}) where T=Monomial(zero(ModuleElt{Symbol,T}))
Base.one(m::Monomial)=Monomial(zero(m.d))
Base.inv(a::Monomial)=Monomial(-a.d)
LaurentPolynomials.exactdiv(a::Monomial, b::Monomial)=a*inv(b)
Base.:/(a::Monomial, b::Monomial)=a*inv(b)
Base.://(a::Monomial, b::Monomial)=a*inv(b)
Base.:^(x::Monomial,p)=Monomial(x.d*p)
Base.getindex(a::Monomial,k)=getindex(a.d,k)
Base.length(a::Monomial)=length(a.d)
"`variables(a::Monomial)` iterator on the variables of `a` (a sorted list of `Symbol`s)"
variables(a::Monomial)=keys(a.d)
"`powers(a::Monomial)` iterator on the powers of variables in `a`"
powers(a::Monomial)=values(a.d)
"""
`pairs(a::Monomial)`

returns the pairs `:variable=>power` in `a`.
"""
Base.pairs(a::Monomial)=pairs(a.d)
ispositive(a::Monomial)=all(>=(0),powers(a))

const unicodeFrac=Dict((1,2)=>'½',(1,3)=>'⅓',(2,3)=>'⅔',
  (1,4)=>'¼',(3,4)=>'¾',(1,5)=>'⅕',(2,5)=>'⅖',(3,5)=>'⅗',
  (4,5)=>'⅘',(1,6)=>'⅙',(5,6)=>'⅚',(1,8)=>'⅛',(3,8)=>'⅜',
  (5,8)=>'⅝',(7,8)=>'⅞',(1,9)=>'⅑',(1,10)=>'⅒',(1,7)=>'⅐')

const subvec=['₀','₁','₂','₃','₄','₅','₆','₇','₈','₉']

function LaurentPolynomials.stringexp(io::IO,r::Rational{<:Integer})
  d=denominator(r); n=numerator(r)
  if isone(d) return LaurentPolynomials.stringexp(io,n) end
  if get(io,:TeX,false) return "^{\\frac{$n}{$d}}" end
  res=Char[]
  if n<0 push!(res,'⁻'); n=-n end
  if haskey(unicodeFrac,(n,d)) push!(res,unicodeFrac[(n,d)])
  else
   if isone(n) push!(res,'\U215F')
   else append!(res,map(x->LaurentPolynomials.supvec[x+1],reverse(digits(n))))
     push!(res,'⁄')
   end
   append!(res,map(x->subvec[x+1],reverse(digits(d))))
  end
  String(res)
end

function Base.show(io::IO,m::Monomial)
  replorTeX=get(io,:TeX,false) || get(io,:limit,false)
  if !replorTeX
    print(io,"Monomial(")
    join(io,map(m.d)do (s,c)
      if c==1 repr(s)
      elseif c==2 join([repr(s),repr(s)],",")
      else repr(s=>c)
      end
    end,",")
    print(io,")")
    return
  end
  if isone(m) return end
  start=true
  for (v,d) in m.d
    if !(start || replorTeX) print(io,"*") end
    print(io,string(v))
    if !isone(d)
      if isone(denominator(d)) d=numerator(d) end
      if replorTeX print(io,LaurentPolynomials.stringexp(io,d))
      elseif d isa Integer print(io,"^$d")
      else print(io,"^($d)")
      end
    end
    start=false
  end
end

function Base.show(io::IO, ::MIME"text/plain", m::Monomial)
  if !haskey(io,:typeinfo)
    print(io,typeof(m),":")
    io=IOContext(io,:typeinfo=>typeof(m))
  end
  show(io,m)
end

"""
`lex(a::Monomial, b::Monomial)`
The  "lex" ordering,  where `a<b`  if the  first variable  in `a/b`
occurs to a positive power.
"""
function lex(a::Monomial, b::Monomial)
  for ((va,pa),(vb,pb)) in zip(a.d,b.d)
    if va!=vb return va<vb ? pa>0 : pb<0 end
    if pa!=pb return isless(pb,pa) end
  end
  la=length(a.d)
  lb=length(b.d)
  @inbounds la>lb ? last(a.d.d[lb+1])>0 : lb>la ? last(b.d.d[la+1])<0 : false
end

"""
`grlex(a::Monomial, b::Monomial)`
The "grlex" ordering, where `a<b̀` if `degree(a)>degree(b)` or the degrees
are equal but `lex(a,b)`.
"""
function grlex(a::Monomial, b::Monomial)
  da=degree(a);db=degree(b)
  if da!=db return isless(db,da) end
  lex(a,b)
end

"""
`grevlex(a::Monomial, b::Monomial)`
The "grevlex" ordering, where `a<b̀` if `degree(a)>degree(b)` or the degrees
are equal but the last variable in `a/b` occurs to a negative power.
"""
function grevlex(a::Monomial, b::Monomial)
  da=degree(a);db=degree(b)
  if da!=db return isless(db,da) end
  lex(Monomial(ModuleElt(reverse(b.d.d))),
      Monomial(ModuleElt(reverse(a.d.d))))
end

"""
`isless(a::Monomial,b::Monomial)`

For  our implementation of `Mvp`s to  work, `isless` must define a monomial
order (that is, for monomials `m,a,b` we have `a<b => a*m<b*m`). By default
we  use the  "lex" ordering.
"""
@inline Base.isless(a::Monomial, b::Monomial)=lex(a,b)

Base.:(==)(a::Monomial, b::Monomial)=a.d==b.d

#monomial d of greatest degree in each variable such that (m/d,n/d) positive
Base.gcd(m::Monomial,n::Monomial)=Monomial(ModuleElts.merge2(min,m.d,n.d))
Base.gcd(v::AbstractArray{<:Monomial})=reduce(gcd,v)

#monomial l of smallest degree in each variable such that (l/m,l/n) positive
Base.lcm(m::Monomial,n::Monomial)=Monomial(ModuleElts.merge2(max,m.d,n.d))
Base.lcm(v::AbstractArray{<:Monomial})=reduce(lcm,v)

Base.hash(a::Monomial, h::UInt)=hash(a.d,h)

LaurentPolynomials.degree(m::Monomial)=sum(powers(m);init=0)
LaurentPolynomials.degree(m::Monomial,var::Symbol)=m.d[var]

function LaurentPolynomials.root(m::Monomial,n::Integer=2)
  if all(x->iszero(x%n),powers(m))
       Monomial(ModuleElt(k=>div(v,n) for (k,v) in m.d))
  else Monomial(ModuleElt(k=>v//n for (k,v) in m.d))
  end
end

#------------------------------------------------------------------------------
"""
`Mvp`s are implemented as a list of pairs `monomial=>coefficient` sorted
by the monomial order `lex`.
"""
struct Mvp{T,N} # N=type of exponents T=type of coeffs
  d::ModuleElt{Monomial{N},T}
end

Mvp(a::Pair...;c...)=Mvp(ModuleElt(a...;c...))
Mvp(;c...)=zero(Mvp{Int,Int}) # for some calls to map() to work

"""
 `@Mvp x,y`

 is  equivalent to `x=Mvp(:x);y=Mvp(:y)`  excepted it creates  `x,y` in the
 global scope of the current module, since it uses `eval`.
"""
macro Mvp(t)
  if t isa Expr
    for v in t.args
      Base.eval(Main,:($v=Mvp($(Core.QuoteNode(Symbol(v))))))
    end
  elseif t isa Symbol
    Base.eval(Main,:($t=Mvp($(Core.QuoteNode(t)))))
  end
end

Base.broadcastable(p::Mvp)=Ref(p)
Base.cmp(a::Mvp,b::Mvp)=cmp(a.d,b.d)
Base.isless(a::Mvp,b::Mvp)=cmp(a,b)==-1
Base.hash(a::Mvp,h::UInt)=hash(a.d,h)

function Base.show(io::IO, ::MIME"text/html", a::Mvp)
  print(IOContext(io,:TeX=>true),"\$",a,"\$")
end

function Base.show(io::IO, ::MIME"text/plain", a::Mvp{T,N}) where{T,N}
  if !haskey(io,:typeinfo)
    print(io,N==Int ? "Mvp{$T}" : "Mvp{$T,$N}",": ")
    io=IOContext(io,:typeinfo=>typeof(a))
  end
  show(io,a)
end

function Base.show(io::IO, x::Mvp)
  if get(io,:limit,false) || get(io,:TeX,false)
    show(IOContext(io,:showbasis=>nothing),x.d)
    # :showbasis=>nothing necessary if called when already showing a ModuleElt
  else
    print(io,"Mvp(")
    join(io,repr.(pairs(x)),",")
    print(io,")")
  end
end

Base.length(x::Mvp)=length(x.d)
term(x::Mvp,i)=x.d.d[i]
ismonomial(x::Mvp)=length(x.d)==1 # x is a non-zero monomial
Base.zero(p::Mvp)=Mvp(zero(p.d))
Base.zero(::Type{Mvp{T,N}}) where {T,N}=Mvp(zero(ModuleElt{Monomial{N},T}))
Base.one(::Type{Mvp{T,N}}) where {T,N}=Mvp(one(Monomial{N})=>one(T);check=false)
Base.one(::Type{Mvp{T}}) where T=one(Mvp{T,Int})
Base.one(::Type{Mvp})=Mvp(1)
Base.one(p::Mvp{T,N}) where {T,N}=iszero(p) ? one(Mvp{T,N}) : Mvp(one(Monomial{N})=>one(first(coefficients(p)));check=false)
Base.isone(x::Mvp)=ismonomial(x) && isone(first(term(x,1)))&&isone(last(term(x,1)))
Base.copy(p::Mvp)=Mvp(p.d)
Base.iszero(p::Mvp)=iszero(p.d)
Base.convert(::Type{Mvp},a::Number)=convert(Mvp{typeof(a),Int},a)
Base.convert(::Type{Mvp{T,N}},a::Number) where {T,N}=iszero(a) ?
 zero(Mvp{T,N}) : Mvp(one(Monomial{N})=>convert(T,a);check=false)
(::Type{Mvp{T,N}})(a::Number) where {T,N}=convert(Mvp{T,N},a)
(::Type{Mvp{T,N}})(a::Mvp) where {T,N}=convert(Mvp{T,N},a)
Base.convert(::Type{Mvp{T,N}},a::Mvp{T1,N1}) where {T,T1,N,N1}=
  Mvp(convert(ModuleElt{Monomial{N},T},a.d))
Base.convert(::Type{Mvp},x::Mvp)=x
Base.convert(::Type{Mvp},v::Symbol)=Mvp(Monomial(v)=>1;check=false)
"""
`Mvp(x::Symbol)` creates the `Mvp` with one term of degree one and coefficient
1 with variable `x`
"""
Mvp(x::Symbol)=convert(Mvp,x)
Mvp(x::Number)=convert(Mvp,x)
Mvp(x::Mvp)=x
# stupid stuff to make LU work
Base.adjoint(a::Mvp)=conj(a)
Base.transpose(a::Mvp)=a
Base.abs(a::Mvp)=a

Base.:(==)(a::Mvp, b::Mvp)=a.d==b.d
Base.:(==)(a::Mvp,x::Number)=(iszero(a) && iszero(x)) || (!isnothing(x) && x==scalar(a))
Base.:(==)(x::Number,a::Mvp)= a==x

function Base.convert(::Type{T},a::Mvp) where T<:Number
  if iszero(a) return zero(T) end
  x=scalar(a)
  if isnothing(x) throw(InexactError(:convert,T,a)) end
  T(x)
end
(::Type{T})(a::Mvp) where T<: Number=convert(T,a)

Base.isinteger(p::Mvp)=scalar(p)!==nothing && isinteger(scalar(p))

# we need a promote rule to handle Vectors of Mvps of different types
Base.promote_rule(::Type{Mvp{T1,N1}},::Type{Mvp{T2,N2}}) where {T1,T2,N1,N2} =
  Mvp{promote_type(T1,T2),promote_type(N1,N2)}

# and this rule to handle Vectors mixing Mvps with numbers
Base.promote_rule(::Type{Mvp{T1,N}},::Type{T2}) where {T1,N,T2<:Number} =
  Mvp{promote_type(T1,T2),N}

Base.:+(a::Mvp, b::Mvp)=Mvp(a.d+b.d) # ModuleElts.+ takes care of promotion
Base.:+(a::Number, b::Mvp)=+(promote(a,b)...)
Base.:+(a::Mvp, b::Number)=+(promote(a,b)...)

Base.:-(a::Mvp)=Mvp(-a.d)
Base.:-(a::Mvp, b::Mvp)=Mvp(a.d-b.d)
Base.:-(a::Mvp, b::Number)=-(promote(a,b)...)
Base.:-(b::Number, a::Mvp)=-(promote(b,a)...)

Base.:*(a::Number, b::Mvp)=Mvp(b.d*a)
Base.:*(b::Mvp, a::Number)=a*b
# we have a monomial order so there is no ordering check in next line
Base.:*(a::Monomial, b::Mvp)=Mvp(ModuleElt(m*a=>c for (m,c) in pairs(b);check=false))
Base.:*(b::Mvp,a::Monomial)=a*b

function Base.:*(a::Mvp, b::Mvp)
  if length(a)>length(b) a,b=(b,a) end
  a,b=promote(a,b)
  if iszero(a) return a
  elseif iszero(b) return b
  elseif isone(a) return b
  elseif isone(b) return a
  end
  let b=b # needed !!!!
    sum(b*m*c for (m,c) in pairs(a))
  end
end

Base.:/(p::Mvp,q::Number)=Mvp(p.d/q)
Base.://(p::Mvp,q::Number)=Mvp(p.d//q)
Base.div(a::Mvp,b::Number)=Mvp(merge(div,a.d,b))
LaurentPolynomials.exactdiv(a::Mvp,b::Number)=Mvp(merge(exactdiv,a.d,b;check=false))

"""
`conj(p::Mvp)` acts on the coefficients of `p`

```julia-repl
julia> @Mvp x;conj(im*x+1)
Mvp{Complex{Int64}}: (0 - 1im)x+1 + 0im
```
"""
Base.conj(a::Mvp)=Mvp(merge(conj,a.d;check=false))

function Base.:^(x::Mvp, p::Union{Integer,Rational})
  if isinteger(p) p=Int(p) end
  if iszero(p) return one(x)
  elseif iszero(x) || isone(p) return x
  elseif p isa Rational return root(x,denominator(p))^numerator(p)
  elseif ismonomial(x)
    (m,c)=term(x,1);return isone(c) ? Mvp(m^p=>c;check=false) : Mvp(m^p=>c^p;check=false)
  elseif p>=0 return Base.power_by_squaring(x,p)
  else return Base.power_by_squaring(inv(x),-p)
  end
end

"""
`degree(m::Mvp[,v::Symbol])`

The `degree` of a monomial is the sum of  the exponents of the variables.
The `degree` of an `Mvp` is the largest degree of a monomial.

With  second argument a  variable name, `degree`  returns the degree of the
polynomial in that variable.

```julia-repl
julia> a=x^2+x*y
Mvp{Int64}: x²+xy

julia> degree(a), degree(a,:y), degree(a,:x)
(2, 1, 2)
```
"""
LaurentPolynomials.degree(p::Mvp)=iszero(p) ? 0 : maximum(degree,monomials(p))
LaurentPolynomials.degree(p::Mvp,v::Symbol)=iszero(p) ? 0 : maximum(x->degree(x,v),monomials(p))

"""
`valuation(m::Mvp[,v::Symbol])`

The `valuation` of an `Mvp` is the minimal degree of a monomial.

With  second argument a variable name, `valuation` returns the valuation of
the polynomial in that variable.

```julia-repl
julia> @Mvp x,y; a=x^2+x*y
Mvp{Int64}: x²+xy

julia> valuation(a), valuation(a,:y), valuation(a,:x)
(2, 0, 1)
```

"""
LaurentPolynomials.valuation(p::Mvp)=iszero(p) ? 0 : minimum(degree,monomials(p))
LaurentPolynomials.valuation(p::Mvp,v::Symbol)=iszero(p) ? 0 : minimum(x->degree(x,v),monomials(p))

"""
`coefficient(p::Mvp,m::Monomial)`

The coefficient of the polynomial `p` on the monomial `m`.

```julia-repl
julia> @Mvp x,y; p=(x-y)^3
Mvp{Int64}: x³-3x²y+3xy²-y³

julia> coefficient(p,Monomial(:x,:x,:y)) # coefficient on x²y
-3

julia> coefficient(p,Monomial()) # constant coefficient
0
```
"""
coefficient(p::Mvp,m::Monomial)=p.d[m]

"""
`pairs(p::Mvp)`

returns the pairs monomial=>coefficient in `p`.
"""
Base.pairs(p::Mvp)=pairs(p.d)

"""
`monomials(p::Mvp)`

is an efficient iterator over the monomials of `p`.
"""
monomials(p::Mvp)=keys(p.d)

"""
`coefficients(p::Mvp)`

is an efficient iterator over the coefficients of the monomials in `p`.
"""
LaurentPolynomials.coefficients(p::Mvp)=values(p.d)

"""
  `coefficients(p::Mvp, var::Symbol)`

returns  a Dict with keys the degree  in `var` and values the corresponding
coefficient of `p` with respect to `var`.

```julia-repl
julia> p=(x+y+inv(y))^4
Mvp{Int64}: x⁴+4x³y+4x³y⁻¹+6x²y²+12x²+6x²y⁻²+4xy³+12xy+12xy⁻¹+4xy⁻³+y⁴+4y²+6+4y⁻²+y⁻⁴

julia> coefficients(p,:x)
Dict{Int64, Mvp{Int64, Int64}} with 5 entries:
  0 => y⁴+4y²+6+4y⁻²+y⁻⁴
  4 => 1
  2 => 6y²+12+6y⁻²
  3 => 4y+4y⁻¹
  1 => 4y³+12y+12y⁻¹+4y⁻³

julia> coefficients(p,:y)
Dict{Int64, Mvp{Int64, Int64}} with 9 entries:
  0  => x⁴+12x²+6
  4  => 1
  -1 => 4x³+12x
  2  => 6x²+4
  -3 => 4x
  -2 => 6x²+4
  -4 => 1
  3  => 4x
  1  => 4x³+12x
```

The  same  caveat  is  applicable  to  `coefficients` as to evaluating: the
values  are always `Mvp`s. To get a list of scalars for the coefficients of
a  univariate polynomial represented as a `Mvp`, one should use `scalar` on
the values of `coefficients`.
"""
function LaurentPolynomials.coefficients(p::Mvp{T,N},v::Symbol)where {T,N}
  if iszero(p) return Dict{Int,typeof(p)}() end
  d=Dict{N,typeof(p.d.d)}()
  for (m,c) in pairs(p)
    found=false
    for (i,(v1,deg)) in enumerate(m.d)
      if v1==v
        found=true
        d[deg]=push!(get!(d,deg,empty(p.d.d)),
                Monomial(ModuleElt(deleteat!(copy(m.d.d),i);check=false))=>c)
      end
    end
    if !found  d[0]=push!(get!(d,0,empty(p.d.d)),m=>c) end
  end
  Dict(dg=>Mvp(c...;check=false) for (dg,c) in d) # c... is sorted by defn of monomial order
end

"""
  `coefficient(p::Mvp, var::Symbol, d)`

returns  the coefficient of degree `d` in the variable `var` in the `Mvp` `p`.

```julia-repl
julia> @Mvp x,y; p=(x+y^(1//2)+1)^3
Mvp{Int64,Rational{Int64}}: x³+3x²y½+3x²+3xy+6xy½+3x+y³⁄₂+3y+3y½+1

julia> coefficient(p,:y,1//2)
Mvp{Int64,Rational{Int64}}: 3x²+6x+3

julia> coefficient(p,:x,1)
Mvp{Int64,Rational{Int64}}: 3y+6y½+3
```
"""
function coefficient(p::Mvp,var::Symbol,d)
  res=empty(p.d.d)
  for (m,c) in pairs(p)
    found=false
    for (i,(v1,deg)) in enumerate(m.d)
      if v1==var && deg==d
        found=true
        push!(res,Monomial(ModuleElt(deleteat!(copy(m.d.d),i);check=false))=>c)
        break
      elseif v1>var break
      end
    end
    if d==0 && !found push!(res,m=>c) end
  end
  Mvp(ModuleElt(res;check=false))
end

"""
  `Pol(p::Mvp{T}) where T`

  converts the one-variable `Mvp{T}` `p` to a `Pol{T}`. It is an error if `p`
  has more than one variable.

```julia-repl
julia> @Mvp x; @Pol q; Pol(x^2+x)
Pol{Int64}: q²+q
```
"""
function LaurentPolynomials.Pol(x::Mvp{T})where T
  l=variables(x)
  if isempty(l) return Pol(scalar(x)) end
  if length(l)>1 error("cannot convert $(length(l))-variate Mvp to Pol") end
  val=Int(valuation(x))
  p=zeros(T,Int(degree(x))-val+1)
  for (mon,coeff) in pairs(x) p[Int(degree(mon))-val+1]=coeff end
  Pol(p,val)
end

"""
  `Pol(p::Mvp,v::Symbol)`

returns  a polynomial whose coefficients are  the coefficients of the `Mvp`
`p`  with respect to the variable `v`.  The variable `v` should appear only
with integral powers in `p`.

```julia-repl
julia> p=(x+y^(1//2))^3
Mvp{Int64,Rational{Int64}}: x³+3x²y½+3xy+y³⁄₂

julia> Pol(:q); Pol(p,:x)
Pol{Mvp{Int64, Rational{Int64}}}: q³+3y½q²+3yq+y³⁄₂
```
"""
function LaurentPolynomials.Pol(p::Mvp{T,N},var::Symbol)where{T,N}
  v=Int(valuation(p,var))
  res=[Pair{Monomial{N},T}[] for i in v:Int(degree(p,var))]
  for (m,c) in pairs(p)
    found=false
    for (i,(v1,deg)) in enumerate(m.d)
      if v1==var
        found=true
        push!(res[Int(deg)-v+1],
             Monomial(ModuleElt(deleteat!(copy(m.d.d),i);check=false))=>c)
        break
      elseif v1>var break
      end
    end
    if !found push!(res[-v+1],m=>c) end
  end
  Pol(map(x->Mvp(ModuleElt(x;check=false)),res),v)
end

"""
`Mvp(p::Pol)` converts `p` to  an  `Mvp` (with the same variable name).

```julia-repl
julia> @Pol q
Pol{Int64}: q

julia> Mvp(q^2+q)
Mvp{Int64}: q²+q
```
"""
Mvp(x::Pol)=convert(Mvp,x)

Base.convert(::Type{Mvp{T,N}},p::Pol) where{T,N}=
                     p(Mvp(convert(Monomial{N},LaurentPolynomials.varname[])=>one(T)))
Base.convert(::Type{Mvp},p::Pol)=p(Mvp(LaurentPolynomials.varname[]))

"""
`variables(p::Mvp)`

`variables(v::Array{Mvp})`

returns  the list of  variables of `p`  (resp. all `p`  in `v`) as a sorted
list of `Symbol`s.

```julia-repl
julia> @Mvp x,y,z

julia> variables([x+y+1,z])
3-element Vector{Symbol}:
 :x
 :y
 :z
```
"""
variables(pp::AbstractArray{<:Mvp})=sort(unique(k1 for p in pp for k in monomials(p) for k1 in variables(k)))

variables(p::Mvp)=sort(unique(k1 for k in monomials(p) for k1 in variables(k)))

"""
`scalar(p::Mvp)`

If `p` is a scalar, return that scalar, otherwise return `nothing`.

```julia-repl
julia> p=Mvp(:x)+1
Mvp{Int64}: x+1

julia> w=p(x=4)
Mvp{Int64}: 5

julia> scalar(w)
5

julia> typeof(scalar(w))
Int64
```
if  `p` is an array, then apply `scalar` to its elements and return `nothing`
if it contains any `Mvp` which is not a scalar.
"""
function LaurentPolynomials.scalar(p::Mvp{T})where T
  if iszero(p) return zero(T) end
  if ismonomial(p)
    (m,c)=term(p,1)
    if isone(m) return c end
  end
end

function LaurentPolynomials.scalar(m::AbstractArray{<:Mvp})
  p=scalar.(m)
  if !any(isnothing,p) return p end
end

"""
    `value(p::Mvp,:x₁=>v₁,:x₂=>v₂,...)`
     ̀(p::Mvp)(x₁=v₁,…,xₙ=vₙ)`

returns  the value of  `p` when doing  the simultaneous substitution of the
variable `:x1` by `v1`, of `x2` by `v2`, …

```julia-repl
julia> p=-2+7x^5*inv(y)
Mvp{Int64}: 7x⁵y⁻¹-2

julia> p(x=2)
Mvp{Int64}: -2+224y⁻¹

julia> p(y=1)
Mvp{Int64}: 7x⁵-2

julia> p(x=2,y=1)
Mvp{Int64}: 222
```

One should pay attention to the fact that the last value is not an integer,
but  a constant `Mvp` (for consistency).  See the function `scalar` for how
to convert such constants to their base ring.

```julia-repl
julia> p(x=y)
Mvp{Int64}: 7y⁴-2

julia> p(x=y,y=x)
Mvp{Int64}: -2+7x⁻¹y⁵
```
Evaluating an `Mvp` which is a Puiseux polynomial may cause calls to `root`

```julia-repl
julia> p=x^(1//2)*y^(1//3)
Mvp{Int64,Rational{Int64}}: x½y⅓

julia> p(;x=y)
Mvp{Int64,Rational{Int64}}: y⅚

julia> p(;x=4)
Mvp{Int64,Rational{Int64}}: 2y⅓

julia> p(;y=2.0)
Mvp{Float64,Rational{Int64}}: 1.2599210498948732x½
```
"""
function value(p::Mvp,k::Pair...)
  res1=badi=nothing
  if length(k)==1 (key,val)=k[1]
  # special code when length(k)==1 and !(key∈ variables(p)) is 10x faster
  elseif isempty(k) return p
  else vv=Dict(k)
  end
  for (i,(m,c1)) in enumerate(pairs(p))
    res=badj=nothing
    for (j,(v,c)) in enumerate(m.d)
      if length(k)==1 && key!=v continue end
      if length(k)>1
        if !haskey(vv,v) continue
        else val=vv[v]
        end
      end
      if badj==nothing
        badj=Int[]
        res=Mvp(c1)
      end
      if isinteger(c) res*=val^Int(c)
      else res*=root(val,denominator(c))^numerator(c)
      end
      push!(badj,j)
    end
    if badj!==nothing
      res*=Monomial(ModuleElt(deleteat!(copy(m.d.d),badj);check=false))
      badj=nothing
      if badi===nothing res1=res
        badi=Int[]
      else res1+=res
      end
      push!(badi,i)
    end
 #  println("badi=$badi m=$m c=$c res1=$res1")
  end
  if badi===nothing return p end
  Mvp(deleteat!(copy(pairs(p)),badi)...;check=false)+res1
end

(p::Mvp)(;arg...)=value(p,arg...)

function LaurentPolynomials.root(a::Integer,n=2)
  if n!=2 return end
  r=isqrt(a)
  if r^2==a return r end
end

function LaurentPolynomials.root(p::Mvp,n=2)
  if iszero(p) return p end
  n=Int(n)
  if !ismonomial(p)
    throw(DomainError("root($p,$n) non-monomial not implemented"))
  end
  (m,c)=term(p,1)
  isone(c) ? Mvp(root(m,n)=>c;check=false) : Mvp(root(m,n)=>root(c,n))
end

"""
`Base.:^(p,m;vars=variables(p))`

Implements  the action of  a matrix on  `Mvp`s. `vars` should  be a list of
symbols   representing  variables.   The  polynomial   `p`  is  changed  by
simultaneous  substitution in it of  `vᵢ` by `(v×m)ᵢ` where  `v` is the row
vector  of  the  `Mvp(vᵢ)`.  If  `vars`  is  omitted,  it  is  taken  to be
`variables(p)`.

```julia-repl
julia> @Mvp x,y

julia> (x+y)^[1 2;3 1]
Mvp{Int64}: 3x+4y
```
"""
Base.:^(p::Mvp,m::AbstractMatrix;vars=variables(p))=
  p(;map(Pair,vars,permutedims(Mvp.(vars))*m)...)

LaurentPolynomials.positive_part(p::Mvp)=
  Mvp(ModuleElt(m=>c for (m,c) in pairs(p) if ispositive(m);check=false))

LaurentPolynomials.negative_part(p::Mvp)=
  Mvp(ModuleElt(m=>c for (m,c) in pairs(p) if all(<(0),powers(m));check=false))

LaurentPolynomials.bar(p::Mvp)=Mvp(ModuleElt(inv(m)=>c for (m,c) in pairs(p)))

"""
The  function 'derivative(p,v₁,…,vₙ)' returns the  derivative of 'p' with
respect to  the variable given by the symbol 'v₁', then `v₂`, ...

```julia-repl
julia> @Mvp x,y;p=7x^5*y^-1-2
Mvp{Int64}: 7x⁵y⁻¹-2

julia> derivative(p,:x)
Mvp{Int64}: 35x⁴y⁻¹

julia> derivative(p,:y)
Mvp{Int64}: -7x⁵y⁻²

julia> derivative(p,:x,:y)
Mvp{Int64}: -35x⁴y⁻²

julia> p=x^(1//2)*y^(1//3)
Mvp{Int64,Rational{Int64}}: x½y⅓

julia> derivative(p,:x)
Mvp{Rational{Int64},Rational{Int64}}: (1//2)x⁻½y⅓

julia> derivative(p,:y)
Mvp{Rational{Int64},Rational{Int64}}: (1//3)x½y⁻⅔

julia> derivative(p,:z)
Mvp{Rational{Int64},Rational{Int64}}: 0
```
"""
function LaurentPolynomials.derivative(p::Mvp,vv...)
  # check needed because 0 could appear in coeffs
  for v in vv
    p=Mvp(ModuleElt(m*Monomial(v=>-1)=>c*degree(m,v) for (m,c) in pairs(p)))
  end
  p
end

# returns p/q when the division is exact, nothing otherwise
# Arguments must be true polynomials
function LaurentPolynomials.exactdiv(p::Mvp,q::Mvp)
  if isone(q) return p end
  if iszero(q) error("cannot divide by 0")
  elseif iszero(p) || isone(q) return p
  elseif ismonomial(q)
    m,c=term(q,1)
    return Mvp(ModuleElt(inv(m)*m1=>exactdiv(c1,c) for (m1,c1) in pairs(p);check=false))
   elseif ismonomial(p) error(q," does not exactly divide ",p)
  end
  var=first(variables(first(monomials(p))))
  res=zero(p)
  mq=degree(q,var)
  cq=coefficient(q,var,mq)
  while !iszero(p)
#   if length(p)<length(q) return nothing end
    mp=degree(p,var)
    t=exactdiv(coefficient(p,var,mp),cq)
    if mp!=mq t=Monomial(ModuleElt(var=>mp-mq))*t end
    res+=t
    p-=t*q
  end
  res
end

Base.gcd(a::AbstractFloat,b::AbstractFloat)=one(a)*one(b)

"""
`gcd(p::Mvp,  q::Mvp)`  computes  the  `gcd`  of  the  'Mvp' arguments. The
arguments must be true polynomials.

```julia-repl
julia> gcd(x^2-y^2,(x+y)^2)
Mvp{Int64}: -x-y
```
"""
function Base.gcd(a::Mvp,b::Mvp)
  if iszero(a) return b
  elseif iszero(b) return a
  elseif ismonomial(a)
    (m,c)=term(a,1)
    return Mvp(gcd(m,reduce(gcd,monomials(b)))=>gcd(c,reduce(gcd,coefficients(b)));check=false)
  elseif ismonomial(b) return gcd(b,a)
  end
  va=variables(a)
  vb=variables(b)
  vars=intersect(va,vb)
  if isempty(vars)
    if iszero(a) return b
    elseif iszero(b) return a
    else
      return Mvp(gcd(reduce(gcd,coefficients(a)),reduce(gcd,coefficients(b))))
    end
  end
  v=first(vars)
  if length(union(va,vb))==1 srgcd(Pol(a),Pol(b))(Mvp(v))
  else              srgcd(Pol(a,v),Pol(b,v))(Mvp(v))
  end
end

Base.gcd(v::AbstractArray{<:Mvp})=reduce(gcd,v;init=Mvp(0))

"""
`laurent_denominator(p1,p2,…)`

returns  the unique true monomial  `m` of minimal degree  such that for all
the Laurent polynomials `p1,p2,…` the product `m*pᵢ` is a true polynomial.

```julia-repl
julia> laurent_denominator(x^-1,y^-2+x^4)
Monomial{Int64}:xy²
```
"""
laurent_denominator(pp::Mvp...)=inv(gcd([m for p in pp for (m,c) in pairs(p)]))

"""
`lcm(p1,p2,...)`

Returns  the Lcm  of the  `Mvp` arguments.  The arguments  must be  true
polynomials.

```julia-repl
julia> lcm(x^2-y^2,(x+y)^2)
Mvp{Int64}: -x³-x²y+xy²+y³
```
"""
Base.lcm(a::Mvp,b::Mvp)=exactdiv(a*b,gcd(a,b))
Base.lcm(a::AbstractArray{<:Mvp})=reduce(lcm,a)

Base.eltype(p::Mvp{T,N}) where{T,N} =T

Base.denominator(p::Mvp)=lcm(denominator.(coefficients(p)))
Base.numerator(p::Mvp{<:Rational{T},N}) where{T,N} =convert(Mvp{T,N},p*denominator(p))
#----------------------------- Frac{Mvp{T,Int}} -------------------------------
# make both pols positive without common monomial factor
function make_positive(a::Mvp,b::Mvp)
  d=laurent_denominator(a,b)
  isone(d) ? (a,b) : (a*d,b*d)
end

LaurentPolynomials.Frac(a::Mvp,b::Mvp;k...)=Frac(promote(a,b)...;k...)

"""
`Frac(a::Mvp,b::Mvp;pol=false,prime=false)`

`Mvp`s  `a` and `b` are promoted to  same coefficient type, and checked for
being  true polynomials  without common  monomial factor (unless `pol=true`
asserts  that this  is already  the case)  and unless `prime=true` they are
made prime to each other by dividing by their gcd.
"""
function LaurentPolynomials.Frac(a::T,b::T;pol=false,prime=false)::Frac{T} where T<:Mvp
  if iszero(a) return LaurentPolynomials.Frac_(a,one(a))
  elseif iszero(b) error("division by 0")
  end
  if !pol
    (a,b)=make_positive(a,b)
  end
  if !prime && !ismonomial(b) && !ismonomial(a)
    d=gcd(a,b)
    a,b=exactdiv(a,d),exactdiv(b,d)
  end
  if scalar(b)==-1 a,b=(-a,-b) end
  return LaurentPolynomials.Frac_(a,b)
end

LaurentPolynomials.Frac(a::Mvp)=Frac(a,one(a);prime=true)

function LaurentPolynomials.Frac(a::Mvp{<:Rational,Int},b::Mvp{<:Rational,Int};k...)
  Frac(numerator(a)*denominator(b),numerator(b)*denominator(a);k...)
end

function Mvp(p::Frac{<:Mvp})
  if isone(p.den) return p.num
  elseif ismonomial(p.den)
    (m,c)=term(p.den,1)
    return p.num*inv(m)*inv(c)
  end
  error("cannot convert ",p," to Mvp")
end

Base.convert(::Type{Mvp{T,Int}},p::Frac{<:Mvp}) where T=convert(Mvp{T,Int},Mvp(p))

function Base.convert(::Type{Frac{T}},p::Mvp) where T<:Mvp
  q=convert(T,p)
  Frac(q,one(q);prime=true)
end

function Base.convert(::Type{Frac{T}},p::Number) where T<:Mvp
  Frac(convert(T,p),convert(T,1);pol=true,prime=true)
end

function Base.promote_rule(a::Type{T1},b::Type{Frac{T2}})where {T1<:Mvp,T2<:Mvp}
  Frac{promote_type(T1,T2)}
end

function Base.promote_rule(a::Type{Frac{T1}},b::Type{Frac{T2}})where {T1<:Mvp,T2<:Mvp}
  Frac{promote_type(T1,T2)}
end

function Base.promote_rule(a::Type{T1},b::Type{Frac{T2}})where {T1<:Number,T2<:Mvp}
  Frac{promote_type(Mvp{T1,Int},T2)}
end

function Base.inv(p::Mvp)
  if ismonomial(p)
    (m,c)=term(p,1)
    return Mvp(inv(m)=>c^2==1 ? c : 1/c)
  end
  Frac(Mvp(1),p;prime=true)
end

function Base.://(a::Mvp,b::Mvp)
  if iszero(a) return a end
  if ismonomial(b)
    (m,c)=term(b,1)
    return Mvp(ModuleElt(m1/m=>c^2==1 ? c1*c : c1//c for (m1,c1) in pairs(a);check=false))
  end
  Frac(a,b)
end

function Base.:/(a::Mvp,b::Mvp)
  if iszero(a) return a end
  if ismonomial(b)
    (m,c)=term(b,1)
    return Mvp(ModuleElt(m1/m=>c1/c for (m1,c1) in pairs(a);check=false))
  end
  Frac(a,b)
end

Base.://(a::Frac{<:Mvp},b::Number)=Frac(a.num,a.den*b;pol=true,prime=true)
Base.://(a::Frac{<:Mvp},b::Mvp)=Frac(a.num,a.den*b)
Base.:/(a::Frac{<:Mvp},b::Union{Frac{<:Mvp},Number,Mvp})=a//b
Base.:/(a::Union{Number,Mvp},b::Frac{<:Mvp})=a*inv(b)
Base.://(p::Number,q::Mvp)=Mvp(p)//q
Base.:/(p::Number,q::Mvp)=Mvp(p)/q

Base.:*(a::Frac{<:Mvp},b::Number)=Frac(a.num*b,a.den;pol=true,prime=true)
Base.:*(b::Number,a::Frac{<:Mvp})=a*b

value(p::Frac{<:Mvp},k::Pair...;Rational=false)=Rational ?
  value(p.num,k...)//value(p.den,k...) : value(p.num,k...)/value(p.den,k...)

(p::Frac{<:Mvp})(;arg...)=value(p,arg...)

#--------------------  Grobner bases -------------------------------------
# the reference is Cox, Little, O'Shea chapter 2

# minimum term and its index for monomial order lt
# findmin does not have the keywords lt and by so I must spin my own
function fmin(l;lt=lex,by=first)
  res=(first(l),1)
  for i in 2:length(l)
   if lt(by(l[i]),by(first(res))) res=(l[i],i) end
  end
  res
end

# Leading term for monomial order lt
LT(p;lt=lex)=lt==lex ? first(pairs(p)) : fmin(pairs(p);lt)[1]

# drop from p leading term for monomial order lt
function dropLT(p;lt=lex)
  if lt==lex return Mvp(ModuleElt(pairs(p)[2:end];check=false)) end
  Mvp(ModuleElt(deleteat!(pairs(p),fmin(pairs(p);lt)[2]);check=false))
end

# quotient of leading terms
function quotientLT(p,q;lt=lex)
  pm,pc=LT(p;lt)
  qm,qc=LT(q;lt)
  t=pm/qm
  if ispositive(t) Mvp(t=>exactdiv(pc,qc)) end
end

# remainder on division of p by list F
# Cox-Little-O'Shea Th. 3 §3 chap.2
function remainder(p,F;lt=lex)
  q=zero(F)
  r=zero(p)
  while !iszero(p)
    gotquotient=false
    for i in eachindex(F)
      t=quotientLT(p,F[i];lt)
      if t!==nothing
        q[i]+=t
#       xprintln("p=",p," F[i]=",F[i]," t=",t)
        p-=t*F[i]
        gotquotient=true
        break
      end
    end
    if !gotquotient
      r+=Mvp(LT(p;lt))
      p=dropLT(p;lt)
    end
  end
  (q,r)
end

# Cox-Little-O'Shea def. 4.(ii) §6 chap.2
function S_polynomial(p,q;lt=lex)
  pm,pc=LT(p;lt)
  qm,qc=LT(q;lt)
  c=lcm(pm,qm)
  (c/pm)*exactdiv(p,pc)-(c/qm)*exactdiv(q,qc)
end

function reduce_basis(F;lt=lex)
# F=sort(F,by=length)
  F=copy(F)
  i=1
  while i<=length(F)
    if any(j->j!=i && quotientLT(F[i],F[j];lt)!==nothing,eachindex(F))
      deleteat!(F,i)
    else i+=1
    end
  end
  F
end

"""
`grobner_basis(F;lt=lex)`

computes  a Gröbner basis  of the polynomial  ideal generated by the `Mvp`s
given  by the vector `F`. The  keyword `lt` describes the monomial ordering
to use.
```julia-repl
julia> @Mvp x,y,z; F=[x^2+y^2+z^2-1,x^2-y+z^2,x-z]
3-element Vector{Mvp{Int64, Int64}}:
 x²+y²+z²-1
 x²-y+z²
 x-z

julia> grobner_basis(F)
3-element Vector{Mvp{Int64, Int64}}:
 x-z
 -y+2z²
 4z⁴+2z²-1

julia> grobner_basis(F;lt=grlex)
3-element Vector{Mvp{Int64, Int64}}:
 x-z
 y²+y-1
 -y+2z²

julia> grobner_basis(F;lt=grevlex)
3-element Vector{Mvp{Int64, Int64}}:
 x-z
 y²+y-1
 2x²-y
```

There is no keyword to change the ordering of the variables. We suggest
to use `rename_variables` for this purpose.
"""
function grobner_basis(F;lt=lex)
# Cox-Little-O'Shea Th. 9 §10 chap.2
  F=copy(F)
  B=[(i,j) for j in 1:length(F) for i in 1:j-1]
  t=length(F)
  while !isempty(B)
    i,j=popfirst!(B)
    li,_=LT(F[i];lt)
    lj,_=LT(F[j];lt)
    lij=lcm(li,lj)
    if lij==li*lj continue end
    ll=filter(l->l!=i && l!=j && !((i,l) in B) && !((j,l) in B)
               && !((l,i) in B) && !((l,j) in B),1:length(F))
    if any(l->ispositive(lij/first(LT(F[l];lt))),ll) continue end
    s=S_polynomial(F[i],F[j];lt)
    r=remainder(s,F;lt)[2]
    if !iszero(r)
      t+=1
      push!(F,r)
      append!(B,tuple.(1:t-1,t))
    end
  end
  reduce_basis(F;lt)
end

"""
`rename_variables(p,v)` renames `variables(p)` to `v`

`rename_variables(p,l::Pair{Symbol,Symbol}...)` renames the
variables in `p` as indicated by the pairs in `l`.

```julia-repl
julia> @Mvp x,y,z; p=x+y+z
Mvp{Int64}: x+y+z

julia> rename_variables(p,Symbol.('A':'Z'))
Mvp{Int64}: A+B+C

julia> rename_variables(p,[:U,:V])
Mvp{Int64}: U+V+z

julia> rename_variables(p,:x=>:U,:z=>:V) # faster than p(;x=Mvp(:U),z=Mvp(:V))
Mvp{Int64}: U+V+y
```
"""
function rename_variables(p::Mvp,l::Pair{Symbol,Symbol}...)
  d=Dict(l)
  Mvp(ModuleElt(map(pairs(p)) do (m,c)
    Monomial(ModuleElt(map(((v,i),)->(haskey(d,v) ? d[v] : v)=>i,m.d.d)))=>c
  end))
end

rename_variables(p::Mvp,l::AbstractVector{Symbol})=
     rename_variables(p,map((x,y)->x=>y,variables(p),l)...)
#--------------------  benchmarks -------------------------------------
#julia> @btime (x+y+z)^3;
#  2.292 μs (49 allocations: 5.42 KiB)

#julia1.6.3> @btime PuiseuxPolynomials.fateman(15)
# 4.040 s (15219390 allocations: 5.10 GiB)
function fateman(n)
  f=(1+sum(Mvp.((:x,:y,:z,:t))))*one(n)
  f=f^n
  length(f*(f+1))
end

# @btime inv(Frac.([x+y x-y;x+1 y+1])) setup=(x=Mvp(:x);y=Mvp(:y))
# 195.739 μs (4483 allocations: 296.94 KiB)
end


# visualize the final states of vocab charlm model after the roots of words

using TSne, Statistics

include("charlm.jl")

# load model
if !@isdefined(model)
    include(Knet.dir("src/models21/deepmap.jl"))
    include(Knet.dir("src/models21/fileio.jl"))
    model = charlm_model()
    charlm_loss(model, tst) # necessary to initialize weights, fix this
    setweights!(model, "charlm.jld2")
end
#@show charlm_loss(model,tst)

# load unique roots from data: 8864 of them
if !@isdefined(rootpos)
    posdict = Dict{String,Int}()
    rootpos = Dict{String,Set{String}}()
    for line in eachline("trmor2018.filtered")
        local sur, lex, root, pos
        sur,lex = split(line,'\t')
        root, pos = split(lex,['+','^'])[1:2]
        if !haskey(posdict, pos)
            posdict[pos] = 1+length(posdict)
        end
        push!(get!(rootpos,root,Set{String}()), pos)
    end
    roots = collect(keys(rootpos))
end

# construct a dataset with batchsize = 1
if !@isdefined(rootdata)
    rootdata = map(roots) do root
        chars = split(root,"")
        ints = map(chars) do char
            if char == "þ"; char = "ş"; end # a bug in dataset
            if char == "ý"; char = "ı"; end
            if char == "ð"; char = "ğ"; end
            surface_vocab.w2i[char]
        end
        permutedims(ints)       # batch needs (B,T) shape
    end
end

# try clustering the final hidden state
# model(charlm_input(tst[1])) |> "37×128×8 CUDA.CuArray{Float32, 3, CUDA.Mem.DeviceBuffer}"
# model[1:end-1](charlm_input(tst[1])) |> "350×128×8 CUDA.CuArray{Float32, 3, CUDA.Mem.DeviceBuffer}"

hidden_states(model, batch) = model[1:end-1](charlm_input(batch)) |> Array{Float64} # Warning: Float32 makes tsne much slower!
final_hidden_states(model, batch) = hidden_states(model, batch)[:,:,end]
final_hidden_states(model, data::Vector{Matrix{Int64}}) = reduce(hcat, (x->final_hidden_states(model,x)).(data))
#@show summary(final_hidden_states(model, tst))
#surface_labels(batch) = reduce(*, surface_vocab.i2w[batch]; dims=2)
#surface_labels(data::Vector{Matrix{Int64}}) = reduce(vcat, surface_labels.(data))

if !@isdefined rootfhs
    rootfhs = final_hidden_states(model, rootdata) |> permutedims # (8864, 350)
end

if !@isdefined root_tsne
    root_tsne = tsne(rootfhs)
end

using Plots, ColorSchemes
plotly()
labels = map(roots) do r; posdict[first(rootpos[r])]; end
poscolor = get(ColorSchemes.tab20, labels, :extrema)
scatter(root_tsne[:,1], root_tsne[:,2]; hover=roots, color=poscolor)

# From: https://github.com/nassarhuda/JuliaTutorials/blob/master/TSNE/TSNE.ipynb
# function my_tsne_plot(Y,d,labels; marker=5)
#     colorsvec = get(ColorSchemes.tab10, labels, :extrema)
#     plot(legend=false,axis=false,grid=false)
#     tsneplot = scatter!(Y[:,1], Y[:,2],
#                         marker=(marker,marker,:auto,stroke(0)),alpha=0.8,
#                         background_color=:black,
#                         color=colorsvec)
#     for i = 0:9
#         mx,my = mean(Y[findall(labels.==i),:],dims=1)
#         s = Shape(mx.+[-d,d,d,-d],my.+[-d,-d,d,d])
#         plot!(s,color=ColorSchemes.tab10[i+1],linewidth=0,alpha=0.9)
#         annotate!(mx,my,text(string(i),color=:white))
#     end
#     return tsneplot
# end

# function tsne_mnist(N=3000; plot=false)
#     # using MLDatasets, ColorSchemes, Plots
#     alldata, labels = MNIST.traindata(Float64)
#     data = reshape(permutedims(alldata[:, :, 1:N], (3, 1, 2)),
#                    N, size(alldata, 1)*size(alldata, 2))
#     labels = labels[1:N]
#     rescale(A; dims=1) = (A .- mean(A, dims=dims)) ./ max.(std(A, dims=dims), eps())
#     # Normalize the data, this should be done if there are large scale differences in the dataset
#     X = rescale(data, dims=1)
#     @show summary(X)
#     Y1 = tsne(X, 2, 50, 1000, 20.0)
#     plot ? my_tsne_plot(Y1, 5, labels) : Y1
# end

# Timing: tsne(tstfhs[1:1000,:], 2, 50, 1000, 20.0);

# mnist tsne:        charlm tsne: (why is it taking so much longer with Float32?)
# N    secs  KL      secs  KL
# 1000 3     0.7470  3     0.3175
# 2000 14    1.0122  13    0.3681
# 3000 31    1.1529  25    0.4125
# 4000 48    1.2079

nothing


using ParameterizedFunctions, Test
using OrdinaryDiffEq, Calculus, ForwardDiff

f = @ode_def begin
    dx = -a
    dy = b
end a b

cb = ContinuousCallback((u,t,i) -> u[1], (integrator)->(println("Stopped.");integrator.p[2]=zero(integrator.p[2])))
function test_f(p)
  prob = ODEProblem(f,eltype(p).([1.0,0.0]),eltype(p).((0.0,1.0)),copy(p))
  integrator = init(prob,Tsit5(),abstol=1e-14,reltol=1e-14,callback=cb)
  step!(integrator)
  solve!(integrator).u[end]
end
p = [2.0, 1.0]
findiff = Calculus.finite_difference_jacobian(test_f,p)
fordiff = ForwardDiff.jacobian(test_f,p)
@test findiff ≈ fordiff

f2 = @ode_def begin
    dx = -x
    dy = b
end a b

function test_f2(p)
  prob = ODEProblem(f2,eltype(p).([1.0,0.0]),eltype(p).((0.0,1.0)),copy(p))
  integrator = init(prob,Tsit5(),abstol=1e-14,reltol=1e-14,callback=cb)
  step!(integrator)
  solve!(integrator).u[end]
end
p = [2.0, 1.0]
findiff = Calculus.finite_difference_jacobian(test_f2,p)
fordiff = ForwardDiff.jacobian(test_f2,p)
@test findiff ≈ fordiff

# Gradients and Hessians

function myobj(θ)
  f(u,p,t) = -θ[1]*u
  u0, _ = promote(10.0, θ[1])
  prob = ODEProblem(f, u0, (0.0, 1.0))
  sol = solve(prob, Tsit5())
  diff = sol.u - 10*exp.(-sol.t)
  return diff'diff
end

ForwardDiff.gradient(myobj, [1.0])
ForwardDiff.hessian(myobj, [1.0])

function myobj2(θ)
  f(du,u,p,t) = (du[1]=-θ[1]*u[1])
  u0, _ = promote(10.0, θ[1])
  prob = ODEProblem(f, [u0], (0.0, 1.0))
  sol = solve(prob, Tsit5())
  diff = sol[:,1] .- 10 .*exp.(-sol.t)
  return diff'diff
end

ForwardDiff.gradient(myobj2, [1.0])
ForwardDiff.hessian(myobj2, [1.0])

function myobj3(θ)
  f(u,p,t) = -θ[1]*u
  u0, _ = promote(10.0, θ[1])
  tspan_end, _ = promote(1.0, θ[1])
  prob = ODEProblem(f, u0, (0.0, tspan_end))
  sol = solve(prob, Tsit5())
  diff = sol.u - 10*exp.(-sol.t)
  return diff'diff
end

ForwardDiff.gradient(myobj3, [1.0])
ForwardDiff.hessian(myobj3, [1.0])

function myobj4(θ)
  f(du,u,p,t) = (du[1] = -θ[1]*u[1])
  u0, _ = promote(10.0, θ[1])
  tspan_end, _ = promote(1.0, θ[1])
  prob = ODEProblem(f, [u0], (0.0, tspan_end))
  sol = solve(prob, Tsit5())
  diff = sol[:,1] .- 10 .* exp.(-sol.t)
  return diff'diff
end

ForwardDiff.gradient(myobj4, [1.0])
ForwardDiff.hessian(myobj4, [1.0])


@doc raw"""
    sigmoid(x)

Elementwise evaluation of the [sigmoid function](https://en.wikipedia.org/wiki/Sigmoid_function)

```math
    \sigma(t) = \frac{1}{1+\exp(t)}
```
"""
sigmoid(x) = @. 1 / (1 + exp(-x))

"""
    allequal(x)::Bool

Inspired by a [Stackoverflow post](https://stackoverflow.com/questions/47564825/check-if-all-the-elements-of-a-julia-array-are-equal/47578613)
"""
@inline function allequal(x)::Bool
    length(x) < 2 && return true
    x1 = x[1]
    @inbounds for i in eachindex(x)
        x[i] == x1 || return false
    end
    return true
end

"""
    normalize_columns!(X::Matrix, p::Real=2)

using `LA.normalize!(x, p)` for each column `x` of `X`.
"""
function normalize_columns!(X::Matrix, p::Real=2)
    for x in eachcol(X)
        LA.normalize!(x, p)
    end
    return X
end

"""
    pairwise_distances(X, Y) = Distances.pairwise(Distances.Euclidean(1e-8), X, Y; dims=2)

Pairwise euclidean distance matrix between columns of `X` and `Y`.
Equivalent to `[norm(x - y) for x in X, y in Y]`.
"""
pairwise_distances(X, Y) = Distances.pairwise(Distances.Euclidean(1e-8), X, Y; dims=2)

"""
    pairwise_distances(X; diag_coeff=Inf) = Distances.pairwise(Distances.Euclidean(1e-8), X; dims=2)

Pairwise euclidean distance matrix between columns of `X`
Equivalent to [`PRS.pairwise_distances`](@ref)`(X, X)` and setting the diagonal elements to `diag_coeff`.
"""
function pairwise_distances(X; diag_coeff=Inf)
    dist = Distances.pairwise(Distances.Euclidean(1e-8), X; dims=2)
    dist[LA.diagind(dist)] .= diag_coeff
    return dist
end

## Graph functions

edgemap(g::LG.AbstractGraph) = Dict(LG.edges(g) .=> 1:LG.ne(g))

sink_nodes(g::LG.SimpleDiGraph) = [v for v in LG.vertices(g) if LG.outdegree(g, v) == 0]

"""
    strong_product(g::G, h::G)::G where {G<:LG.AbstractGraph}

Return the [strong product](https://en.wikipedia.org/wiki/Strong_product_of_graphs) of two graphs

- Section 5 of [BNBLPR19](@cite)
"""
function strong_product(g::G, h::G)::G where {G<:LG.AbstractGraph}
    sp_gh = G(LG.nv(g) * LG.nv(h))
    id(i, j) = (i - 1) * LG.nv(h) + j
    for e1 in LG.edges(g)
        i1, i2 = Tuple(e1)
        for j = 1:LG.nv(h)
            LG.add_edge!(sp_gh, id(i1, j), id(i2, j))
        end
        for e2 in LG.edges(h)
            j1, j2 = Tuple(e2)
            LG.add_edge!(sp_gh, id(i1, j1), id(i2, j2))
            LG.add_edge!(sp_gh, id(i1, j2), id(i2, j1))
        end
    end
    for e in LG.edges(h)
        j1, j2 = Tuple(e)
        for i in LG.vertices(g)
            LG.add_edge!(sp_gh, id(i, j1), id(i, j2))
        end
    end
    return sp_gh
end

"""
    strong_product(g::G)::G where {G<:LG.AbstractGraph}

Return the [strong product](https://en.wikipedia.org/wiki/Strong_product_of_graphs) of a graph with itself

- Section 5 of [BNBLPR19](@cite)
"""
function strong_product(g::G)::G where {G<:LG.AbstractGraph}
    n = LG.nv(g)
    sp_gg = G(n * n)  # nv = n^2, ne = 2n(2n+1)
    id(i, j) = (i - 1) * n + j
    for e1 in LG.edges(g)
        i1, i2 = Tuple(e1)
        for j in LG.vertices(g)
            LG.add_edge!(sp_gg, id(i1, j), id(i2, j))
            LG.add_edge!(sp_gg, id(j, i1), id(j, i2))
        end
        for e2 in LG.edges(g)
            j1, j2 = Tuple(e2)
            LG.add_edge!(sp_gg, id(i1, j1), id(i2, j2))
            LG.add_edge!(sp_gg, id(i1, j2), id(i2, j1))
        end
    end
    return sp_gg
end

king_graph(k::Integer, l::Integer) = strong_product(LG.path_graph(l), LG.path_graph(k))
king_graph(k::Integer) = strong_product(LG.path_graph(k))

"""
    uniform_weighted_graph(
        [rng::Random.AbstractRNG,]
        graph::LG.AbstractGraph
    )::SWG.SimpleWeightedGraph

Return a weighted version of `graph` where each edge is attached an independent uniform random variable.
"""
function uniform_weighted_graph(
    rng::Random.AbstractRNG,
    graph::LG.AbstractGraph
)::SWG.SimpleWeightedGraph
    g = SWG.SimpleWeightedGraph(graph)
    for e in LG.edges(g)
        i, j = Tuple(e)
        @inbounds g.weights[i, j] = g.weights[j, i] = rand(rng, weighttype(g))
    end
    return g
end

uniform_weighted_graph(graph) = uniform_weighted_graph(Random.default_rng(), graph)

"""
    random_edge_orientation(
        rng::Random.AbstractRNG,
        graph::LG.SimpleGraph{T},
        p::Real=0.5
    )::LG.SimpleDiGraph{T} where {T}

Construct an directed version of ``
"""
function random_edge_orientation(
    rng::Random.AbstractRNG,
    graph::LG.SimpleGraph{T},
    p::Real=0.5
)::LG.SimpleDiGraph{T} where {T}
    @assert 0 <= p <= 1
    return LG.SimpleDiGraphFromIterator(flip_edge(rng, e, p) for e in LG.edges(graph))
end

function random_edge_orientation(
    graph::LG.SimpleGraph,
    p::Real=0.5
)
    return random_edge_orientation(Random.default_rng(), graph, p)
end

"""
    flip_edge(
        [rng::Random.AbstractRNG,]
        edge::LG.Edge,
        p::Real=0.5
    )::LG.Edge

return `edge` with probability `p` or `reverse(edge)` with probability `1-p`.
"""
function flip_edge(
    rng::Random.AbstractRNG,
    edge::LG.Edge,
    p::Real=0.5
)::LG.Edge
    return rand(rng) < p ? edge : reverse(edge)
end

flip_edge(edge::LG.Edge, p::Real=0.5) = flip_edge(Random.default_rng(), edge, p)

"""
    random_neighbor_assignment(
        [rng::Random.AbstractRNG,]
        graph::LG.SimpleGraph{T},
        roots=T[]
    )::LG.SimpleDiGraph{T} where {T}

Return a oriented subgraph of `graph` where each vertex except the `roots` is connected to a unique neighbor, i.e., each vertex has outdegree equal to one.
"""
function random_neighbor_assignment(
    rng::Random.AbstractRNG,
    graph::LG.SimpleGraph{T},
    roots=T[]
)::LG.SimpleDiGraph{T} where {T}
    g = LG.SimpleDiGraph(LG.nv(graph))
    for v in LG.vertices(g)
        if v ∉ roots
            w = rand(rng, LG.neighbors(graph, v))
            LG.add_edge!(g, v, w)
        end
    end
    return g
end

function random_neighbor_assignment(
    graph::LG.SimpleGraph{T},
    roots=T[]
) where {T}
    return random_neighbor_assignment(Random.default_rng(), graph, roots)
end

## String methods

function findall_overlap(pat::AbstractString, str::AbstractString)
    found = UnitRange{Int64}[]
    isempty(pat) && return found

    rng = findfirst(pat, str)
    isnothing(rng) && return found

    i, j = first(rng), last(rng)
    k, e = first(rng), lastindex(str)
    while k <= e
        @inbounds k = nextind(str, k)
        rng = findnext(pat, str, k)
        if isnothing(rng)
            push!(found, i:j)
            break
        end
        k = first(rng)
        if j < k
            push!(found, i:j)
            i = k
        end
        j = last(rng)
    end

    return found
end

function has_common_prefix_suffix(s::String)
    n = length(s)
    n <= 1 && return false
    for i in 1:div(n, 2)
        @inbounds s[1:i] == s[n-i+1:n] && return true
    end
    return false
end


module IntervalSets

using Base: @pure
import Base: eltype, convert, show, in, length, isempty, isequal, issubset, ==, hash,
             union, intersect, minimum, maximum, extrema, range, clamp, float, ⊇, ⊊, ⊋

using Statistics
import Statistics: mean

using Dates

export AbstractInterval, Interval, OpenInterval, ClosedInterval,
            ⊇, .., ±, ordered, width, duration, leftendpoint, rightendpoint, endpoints,
            isopenset, isclosedset, isleftclosed, isrightclosed,
            isleftopen, isrightopen, closedendpoints,
            infimum, supremum

"""
A subtype of `Domain{T}` represents a subset of type `T`, that provides `in`.
"""
abstract type Domain{T} end

Base.IteratorSize(::Type{<:Domain}) = Base.SizeUnknown()

"""
A subtype of `AbstractInterval{T}` represents an interval subset of type `T`, that provides
`endpoints`, `closedendpoints`.
"""
abstract type AbstractInterval{T} <: Domain{T} end

"A tuple containing the left and right endpoints of the interval."
endpoints(d::AI) where AI<:AbstractInterval = error("Override endpoints(::$(AI))")

"The left endpoint of the interval."
leftendpoint(d::AbstractInterval) = endpoints(d)[1]

"The right endpoint of the interval."
rightendpoint(d::AbstractInterval) = endpoints(d)[2]

"A tuple of `Bool`'s encoding whether the left/right endpoints are closed."
closedendpoints(d::AI) where AI<:AbstractInterval = error("Override closedendpoints(::$(AI))")

"Is the interval closed at the left endpoint?"
isleftclosed(d::AbstractInterval) = closedendpoints(d)[1]

"Is the interval closed at the right endpoint?"
isrightclosed(d::AbstractInterval) = closedendpoints(d)[2]

# open_left and open_right are implemented in terms of closed_* above, so those
# are the only ones that should be implemented for specific intervals
"Is the interval open at the left endpoint?"
isleftopen(d::AbstractInterval) = !isleftclosed(d)

"Is the interval open at the right endpoint?"
isrightopen(d::AbstractInterval) = !isrightclosed(d)

# Only closed if closed at both endpoints, and similar for open
isclosedset(d::AbstractInterval) = isleftclosed(d) && isrightclosed(d)

"Is the interval open?"
isopenset(d::AbstractInterval) = isleftopen(d) && isrightopen(d)

eltype(::Type{AbstractInterval{T}}) where {T} = T
@pure eltype(::Type{I}) where {I<:AbstractInterval} = eltype(supertype(I))

convert(::Type{AbstractInterval}, i::AbstractInterval) = i
convert(::Type{AbstractInterval{T}}, i::AbstractInterval{T}) where T = i

ordered(a::T, b::T) where {T} = ifelse(a < b, (a, b), (b, a))
ordered(a, b) = ordered(promote(a, b)...)

checked_conversion(::Type{T}, a, b) where {T} = _checked_conversion(T, convert(T, a), convert(T, b))
_checked_conversion(::Type{T}, a::T, b::T) where {T} = a, b
_checked_conversion(::Type{Any}, a, b) = throw(ArgumentError("$a and $b promoted to type Any"))
_checked_conversion(::Type{T}, a, b) where {T} = throw(ArgumentError("$a and $b are not both of type $T"))

function infimum(d::AbstractInterval{T}) where T
    a = leftendpoint(d)
    b = rightendpoint(d)
    a > b && throw(ArgumentError("Infimum not defined for empty intervals"))
    a
end

function supremum(d::AbstractInterval{T}) where T
    a = leftendpoint(d)
    b = rightendpoint(d)
    a > b && throw(ArgumentError("Supremum not defined for empty intervals"))
    b
end

mean(d::AbstractInterval) = (leftendpoint(d) + rightendpoint(d))/2

"""
    w = width(iv)

Calculate the width (max-min) of interval `iv`. Note that for integers
`l` and `r`, `width(l..r) = length(l:r) - 1`.
"""
function width(A::AbstractInterval)
    _width = rightendpoint(A) - leftendpoint(A)
    max(zero(_width), _width)   # this works when T is a Date
end

"""
A subtype of `TypedEndpointsInterval{L,R,T}` where `L` and `R` are `:open` or `:closed`,
that represents an interval subset of type `T`, and provides `endpoints`.
"""
abstract type TypedEndpointsInterval{L,R,T} <: AbstractInterval{T} end

closedendpoints(d::TypedEndpointsInterval{:closed,:closed}) = (true,true)
closedendpoints(d::TypedEndpointsInterval{:closed,:open}) = (true,false)
closedendpoints(d::TypedEndpointsInterval{:open,:closed}) = (false,true)
closedendpoints(d::TypedEndpointsInterval{:open,:open}) = (false,false)

in(v, I::TypedEndpointsInterval{:closed,:closed}) = leftendpoint(I) ≤ v ≤ rightendpoint(I)
in(v, I::TypedEndpointsInterval{:open,:open}) = leftendpoint(I) < v < rightendpoint(I)
in(v, I::TypedEndpointsInterval{:closed,:open}) = leftendpoint(I) ≤ v < rightendpoint(I)
in(v, I::TypedEndpointsInterval{:open,:closed}) = leftendpoint(I) < v ≤ rightendpoint(I)

in(v::Complex, I::TypedEndpointsInterval{:closed,:closed}) = isreal(v) && in(real(v), I)
in(v::Complex, I::TypedEndpointsInterval{:open,:open}) = isreal(v) && in(real(v), I)
in(v::Complex, I::TypedEndpointsInterval{:closed,:open}) = isreal(v) && in(real(v), I)
in(v::Complex, I::TypedEndpointsInterval{:open,:closed}) = isreal(v) && in(real(v), I)

in(::Missing, I::TypedEndpointsInterval{:closed,:closed}) = !isempty(I) && missing
in(::Missing, I::TypedEndpointsInterval{:open,:open}) = !isempty(I) && missing
in(::Missing, I::TypedEndpointsInterval{:closed,:open}) = !isempty(I) && missing
in(::Missing, I::TypedEndpointsInterval{:open,:closed}) = !isempty(I) && missing

isempty(A::TypedEndpointsInterval{:closed,:closed}) = leftendpoint(A) > rightendpoint(A)
isempty(A::TypedEndpointsInterval) = leftendpoint(A) ≥ rightendpoint(A)

isequal(A::TypedEndpointsInterval{L,R}, B::TypedEndpointsInterval{L,R}) where {L,R} = (isequal(leftendpoint(A), leftendpoint(B)) & isequal(rightendpoint(A), rightendpoint(B))) | (isempty(A) & isempty(B))
isequal(A::TypedEndpointsInterval, B::TypedEndpointsInterval) = isempty(A) & isempty(B)

==(A::TypedEndpointsInterval{L,R}, B::TypedEndpointsInterval{L,R}) where {L,R} = (leftendpoint(A) == leftendpoint(B) && rightendpoint(A) == rightendpoint(B)) || (isempty(A) && isempty(B))
==(A::TypedEndpointsInterval, B::TypedEndpointsInterval) = isempty(A) && isempty(B)

function issubset(A::TypedEndpointsInterval, B::TypedEndpointsInterval)
    Al, Ar = endpoints(A)
    Bl, Br = endpoints(B)
    return isempty(A) | ( (Bl ≤ Al) & (Ar ≤ Br) )
end
function issubset(A::TypedEndpointsInterval{:closed,R1} where R1, B::TypedEndpointsInterval{:open,R2} where R2)
    Al, Ar = endpoints(A)
    Bl, Br = endpoints(B)
    return isempty(A) | ( (Bl < Al) & (Ar ≤ Br) )
end
function issubset(A::TypedEndpointsInterval{L1,:closed} where L1, B::TypedEndpointsInterval{L2,:open} where L2)
    Al, Ar = endpoints(A)
    Bl, Br = endpoints(B)
    return isempty(A) | ( (Bl ≤ Al) & (Ar < Br) )
end
function issubset(A::TypedEndpointsInterval{:closed,:closed}, B::TypedEndpointsInterval{:open,:open})
    Al, Ar = endpoints(A)
    Bl, Br = endpoints(B)
    return isempty(A) | ( (Bl < Al) & (Ar < Br) )
end

⊇(A::AbstractInterval, B::AbstractInterval) = issubset(B, A)
⊊(A::AbstractInterval, B::AbstractInterval) = (A ≠ B) & (A ⊆ B)
⊋(A::AbstractInterval, B::AbstractInterval) = (A ≠ B) & (A ⊇ B)

const _interval_hash = UInt == UInt64 ? 0x1588c274e0a33ad4 : 0x1e3f7252

hash(I::TypedEndpointsInterval, h::UInt) = hash(leftendpoint(I), hash(rightendpoint(I), hash(_interval_hash, h)))

minimum(d::TypedEndpointsInterval{:closed}) = infimum(d)
minimum(d::TypedEndpointsInterval{:open}) = throw(ArgumentError("$d is open on the left. Use infimum."))
maximum(d::TypedEndpointsInterval{L,:closed}) where L = supremum(d)
maximum(d::TypedEndpointsInterval{L,:open}) where L = throw(ArgumentError("$d is open on the right. Use supremum."))

extrema(I::TypedEndpointsInterval) = (infimum(I), supremum(I))

# Open and closed at endpoints
isleftclosed(d::TypedEndpointsInterval{:closed}) = true
isleftclosed(d::TypedEndpointsInterval{:open}) = false
isrightclosed(d::TypedEndpointsInterval{L,:closed}) where {L} = true
isrightclosed(d::TypedEndpointsInterval{L,:open}) where {L} = false

# UnitRange construction
# The third is the one we want, but the first two are needed to resolve ambiguities
Base.Slice{T}(i::TypedEndpointsInterval{:closed,:closed,I}) where {T<:AbstractUnitRange,I<:Integer} =
    Base.Slice{T}(minimum(i):maximum(i))
Base.Slice(i::TypedEndpointsInterval{:closed,:closed,I}) where I<:Integer =
    Base.Slice(minimum(i):maximum(i))
function Base.OneTo{T}(i::TypedEndpointsInterval{:closed,:closed,I}) where {T<:Integer,I<:Integer}
    @noinline throwstart(i) = throw(ArgumentError("smallest element must be 1, got $(minimum(i))"))
    minimum(i) == 1 || throwstart(i)
    Base.OneTo{T}(maximum(i))
end
Base.OneTo(i::TypedEndpointsInterval{:closed,:closed,I}) where {I<:Integer} =
    Base.OneTo{I}(i)
UnitRange{T}(i::TypedEndpointsInterval{:closed,:closed,I}) where {T<:Integer,I<:Integer} = UnitRange{T}(minimum(i), maximum(i))
UnitRange(i::TypedEndpointsInterval{:closed,:closed,I}) where {I<:Integer} = UnitRange{I}(i)
range(i::TypedEndpointsInterval{:closed,:closed,I}) where {I<:Integer} = UnitRange{I}(i)

"""
    range(i::ClosedInterval; step, length)
    range(i::ClosedInterval, len::Integer)

Constructs a range of a specified step or length.
"""
range(i::TypedEndpointsInterval{:closed,:closed}; step=nothing, length=nothing) =
    range(leftendpoint(i); stop=rightendpoint(i), step=step, length=length)
range(i::TypedEndpointsInterval{:closed,:closed}, len::Integer) = range(i; length=len)

"""
    range(i::Interval{:closed,:open}; length)
    range(i::Interval{:closed,:open}, len::Integer)

Constructs a range of a specified length with `step=width(i)/length`.
"""
range(i::TypedEndpointsInterval{:closed,:open}; length::Integer) =
    range(leftendpoint(i); step=width(i)/length, length=length)
range(i::TypedEndpointsInterval{:closed,:open}, len::Integer) = range(i; length=len)

"""
    clamp(t, i::ClosedInterval)

Clamp the scalar `t` such that the result is in the interval `i`.
"""
clamp(t, i::TypedEndpointsInterval{:closed,:closed}) =
    clamp(t, leftendpoint(i), rightendpoint(i))

"""
   duration(iv)

calculates the the total number of integers or dates of an integer or date
valued interval. For example, `duration(0..1)` is 2, while `width(0..1)` is 1.
"""
function duration(A::TypedEndpointsInterval{:closed,:closed,T}) where {T<:Integer}
    # TODO: Remove this method in the next breaking release
    Base.depwarn("`duration` will be removed in the next breaking release because it is ill-defined concept. " *
    "If you need 3 from 2..4, replace duration(2..4) with width(2..4)+1", :duration)
    max(0, Int(A.right - A.left) + 1)
end
function duration(A::TypedEndpointsInterval{:closed,:closed,Date})
    # TODO: Remove this method in the next breaking release
    Base.depwarn("`duration` will be removed in the next breaking release because it is ill-defined concept. " *
    "If you need 961 from A=Date(2018,08,08)..Date(2021,03,25), replace duration(A) with Dates.days(width(A))+1", :duration)
    max(0, Dates.days(A.right - A.left) + 1)
end

include("interval.jl")

# convert numbers to intervals
# TODO: These conversions will be removed in the next breaking release (#97)
function convert(::Type{AbstractInterval}, x::Number)
    Base.depwarn("`The conversion number to interval will be removed; construct an interval explicitly, e.g., `x..x`.", :convert)
    x..x
end
function convert(::Type{AbstractInterval{T}}, x::Number) where T
    Base.depwarn("`The conversion number to interval will be removed; construct an interval explicitly, e.g., `x..x`.", :convert)
    convert(AbstractInterval{T}, convert(AbstractInterval, x))
end
function convert(::Type{TypedEndpointsInterval{:closed,:closed}}, x::Number)
    Base.depwarn("`The conversion number to interval will be removed; construct an interval explicitly, e.g., `x..x`.", :convert)
    x..x
end
function convert(::Type{TypedEndpointsInterval{:closed,:closed,T}}, x::Number) where T
    Base.depwarn("`The conversion number to interval will be removed; construct an interval explicitly, e.g., `x..x`.", :convert)
    convert(AbstractInterval{T}, convert(AbstractInterval, x))
end
function convert(::Type{ClosedInterval}, x::Number)
    Base.depwarn("`The conversion number to interval will be removed; construct an interval explicitly, e.g., `x..x`.", :convert)
    x..x
end
function convert(::Type{ClosedInterval{T}}, x::Number) where T
    Base.depwarn("`The conversion number to interval will be removed; construct an interval explicitly, e.g., `x..x`.", :convert)
    convert(AbstractInterval{T}, convert(AbstractInterval, x))
end

include("findall.jl")

end # module


using NetworkFlows
using Base.Test

## Test link.jl ##
#import NetworkFlows.zero_to_one!
@test zero_to_one!([(0,1),(1,2)]) == [(1,2),(2,3)]
@test zero_to_one!([(3,1),(1,2)]) != [(3,1),(1,2)]
@test zero_to_one!([(0,1,1.),(1,2,2.)]) == [(1,2,1.),(2,3,2.)]
@test zero_to_one!([(3,1,0.),(1,2,-2.1)]) != [(3,1,0.),(1,2,-2.1)]

import NetworkFlows.Arc, NetworkFlows.ghostArc, NetworkFlows.simpleArc
@test Arc(0,1,0.).sym == ghostArc(1).sym
@test Arc(0,1,0.).head == ghostArc(1).head
@test Arc(0,1,0.).cap == ghostArc(1).cap
@test Arc(0,2,1.).sym == simpleArc(2).sym
@test Arc(0,2,1.).head == simpleArc(2).head
@test Arc(0,2,1.).cap == simpleArc(2).cap

## Test network.jl
import NetworkFlows.Network, NetworkFlows.findArc
g1 = Network(1,2,[1,2,2],[Arc(0,2,1.)])
g2 = Network(2,1,[1,2,2],[simpleArc(2)])
@test g1.source == g2.target
@test g2.source == g1.target
@test g1.tails == g2.tails
@test length(g1.links) == length(g2.links)
@test findArc(g1,1,2) == findArc(g2,1,2)

## Test io.jl
#import NetworkFlows.print
edges1 = [(0,1,5.),(0,2,7.),(0,5,1.75),(0,6,1.3),(1,3,2.),(2,1,1.),
  (2,3,11.),(2,4,8.),(3,5,6.),(4,5,3.),(6,5,1.25)]
zero_to_one!(edges1)
g3 = Network(edges1, true, 1, 6)
print(g3)

import NetworkFlows.orderedDIMACS
orderedDIMACS(pwd()*"/../ioexample/unordereddimacs",
  pwd()*"/../ioexample/ordereddimacs")
gdimacs = Network(pwd()*"/../ioexample/ordereddimacs",:DIMACS)
gcsv = Network(pwd()*"/../ioexample/csv",:CSV,(1,6))

## Test search.jl
import NetworkFlows.bfs
println("BFS: ",bfs(g3,:Path))

## Test flow.jl
# import NetworkFlows.edmondsKarp, NetworkFlows.connectivity
# import NetworkFlows.kishimoto
@test edmondsKarp(g3)[1] == 12
@test connectivity(g3) == 4
@test edmondsKarp(gdimacs)[1] == 15
@test edmondsKarp(gcsv)[1] == 15
edges2 =
[(1,2,10.),(1,3,20.),(1,4,40.),(1,5,80.),(2,6,1.),(3,6,2.),(4,6,4.),(5,6,8.)]
g4 = Network(edges2,true,1,6)
@test kishimoto(g4,1)[1] == edmondsKarp(g4)[1]
@test kishimoto(g4,2)[1] == 14.
@test kishimoto(g4,3)[1] == 9.
@test kishimoto(g4,4)[1] == 4.

## Test cut.jl
#import NetworkFlows.mincut
@test mincut(g3)[1] == edmondsKarp(g3)[1]
@test mincut(g3)[2] != edmondsKarp(g3)[2]

# Test extmrflow
# import NetworkFlows.breakingPoints
@test breakingPoints(g4) ==
  [(0.,0.,4),(1.0,4.0,3),(2.0,7.0,2),(4.0,11.0,1),(8.0,15.0,0)]
edges3 =
[(1,2,10.),(1,3,20.),(1,4,40.),(1,5,80.),(2,6,2.),(3,6,2.),(4,6,2.),(5,6,8.)]
g5 = Network(edges3,true,1,6)
@test breakingPoints(g5) ==
  [(0.0,0.0,4),(-1.0,-1.0,3),(-1.0,-1.0,2),(2.0,8.0,1),(4.0,14.0,0)]

  # Test mla.jl
  # import NetworkFlows.mixedMLA, NetworkFlows.successMLA
  edges4 = [(1,2,2.),(1,3,3.),(1,4,5.),(2,5,2.),(3,5,3.),(4,5,5.),
    (5,6,3.),(5,7,3.),(5,8,3.),(6,9,3.),(7,9,3.),(8,9,3.),
    (9,10,7.),(9,11,7.),(10,12,7.),(11,12,7.)]
  g6 = Network(edges4,true,1,12)
  @test mixedMLA(g6) == [(4.5,6.0),(9.0,9.0)]
  @test successMLA(g6) == 0.5


using BenchmarkTools

# Get the number of iterations from the command line.
n, = size(ARGS)
if n < 1
    println("Usage: fibonnaci.jl N")
    println("       ---> Please specify the number of iterations.")
    exit()
end

N = parse(Int, ARGS[1])

"""
    recursive_fibonacci(n)

Finds the nth Fibonacci number using recursion.
"""
function recursive_fibonacci(n)
    if n <= 2
        1.0
    else
        recursive_fibonacci(n-1) + recursive_fibonacci(n-2);
    end
end

"""
    iterative_fibonacci(n)

Finds the nth Fibonacci number using iteration.
"""
function iterative_fibonacci(n)
    x, y = (0, 1)
    for i = 1:n x, y = (y, x + y) end
    x
end

println("--------------------------")
println(@sprintf "Iterative - Fibonnaci %d" N)
println("--------------------------")

@btime iterative_fibonacci(N)

println("--------------------------")
println(@sprintf "Recursive - Fibonnaci %d" N)
println("--------------------------")

@btime recursive_fibonacci(N)


using Revise, Distributions, StatsBase, Random, BenchmarkTools, Finometrics

Random.seed!(1111)

#draws a single run of an array
function sampleblockindices(N::Int, runlength::Int)::Vector{Int}
  datrunstart = sample(1:N)
  local out::Vector{Int}
  if datrunstart + runlength - 1 ≤ N
    out = datrunstart:(datrunstart + runlength - 1) |> collect
  else
    toplength = N - datrunstart + 1
    out = [datrunstart:N; 1:(runlength-toplength);]
  end

  #@assert length(out) == runlength "runlength=$runlength but out=$out"
  return out
end

samplerunlength(p::Float64) = rand(Geometric(p)) + 1

#draws a non-parametric bootstrap array
function bootstraparray!(dat::Vector{Tdat}, sim::Vector{Tsim}; p::Float64) where {Tdat, Tsim}
  simrunstart::Int = 1
  N = length(dat[1])

  #need to shift by 1 due to the way the distribution is parametrized
  while simrunstart≤N
    #draw the length of the run from the target distribution
    targetrunlength = samplerunlength(p)
    simrunend = min(simrunstart+targetrunlength-1,N)
    runlength = simrunend - simrunstart + 1

    #draw a set of bootstrap indices
    inds = sampleblockindices(N,runlength)

    #for each vector, copy the relevant indices
    for (v,b) ∈ zip(dat,sim)
      copyto!(b, simrunstart, @view v[inds])
    end

    simrunstart += runlength

  end

  return nothing
end

#run the aggregation functions on the bootstrap array
function bootstrappass!(dat::Vector{Tv}, aggfunc::Taggfunc, sim::Vector{Tv} = deepcopy(dat);
  p::Float64,
  ) where {Tv<:AbstractVector, Taggfunc}

  @assert (length.(dat) .=== length.(sim)) |> all

  bootstraparray!(dat, sim; p)

  return aggfunc(sim)
end

#this runs the full bootstrap
#individual passes are NOT stored
function stationarybootstrap(dat::Vector{Tv}, aggfunc::Taggfunc, B::Int; p) where {Tv, Taggfunc}

  #pre-allocate vectors used for the bootstrap
  sims = [deepcopy(dat) for t ∈ 1:Threads.nthreads()]
  results = Dict(s => Vector{MFloat64}(undef, B) for s ∈ keys(aggfunc(dat)))

  bootstrappass!(dat, aggfunc, sims[1]; p)
  #draw a sample and compute the aggregate function, sotring the reuslts in an array
  Threads.@threads for b ∈ 1:B
    t = Threads.threadid()
    res = bootstrappass!(dat, aggfunc, sims[t]; p)

    for s ∈ keys(res)
      @assert results[s][b] === missing
      results[s][b] = res[s]
    end
  end

  return (; Dict(s=>std(skipmissing(results[s])) for s ∈ keys(results))...)
end

function teststationarybootstrap(N, B; K=2)
  dat = [rand(Normal(1,1), N) |> Vector{Union{Missing, Float64}} for i ∈ 1:K]

  #a placeholder aggregation function
  aggfunc(sim) = Dict{Symbol, Float64}(
    :doublemean => (sum(sim[1]) + sum(sim[2]))/(length(sim[1]) + length(sim[2])),
    :pear=>cor(sim[1],sim[2]),
    :kend=>corkendall(sim[1] |> Vector{Float64}, sim[2] |> Vector{Float64}),
    :spear=>corspearman(sim[1] |> Vector{Float64},sim[2] |> Vector{Float64}))

  p=0.143

  sim = deepcopy(dat)
  for v ∈ sim
    v .= missing
  end

  bootstrappass!(dat, aggfunc, sim; p)
  #@code_warntype bootstrappass!(dat, aggfunc, sim; p)
  for b ∈ sim
    @assert all(b .!== missing)
  end

  results = stationarybootstrap(dat, aggfunc, B; p)
  isapprox(results.doublemean, 1/(2N)^0.5, atol=1e-2) || @warn(
    "!isapprox(results.doublemean, 1/(2N)^0.5, atol=1e-2) - could be an issue if this is unexpected")

  #create an autocorrelated series
  vauto = Vector{MFloat64}(undef, N)
  v = rand(Normal(10,10), N)
  for i ∈ 1:N
    vauto[i] = mean(v[max((i-11),1):i])
  end

  for v ∈ dat
    v .+= vauto
  end

  nwΣ = neweywestΣfunc(4)
  X::Matrix{Float64} = hcat(dat[1] .* std(dat[2]) ./ std(dat[1]), ones(N))
  lm = FMLM(X, dat[2] |> Vector{Float64})
  pearhacz = nwΣ(lm)[1,1]^0.5

  results = stationarybootstrap(dat, aggfunc, B; p)
  @info "newey HAC: $pearhacz,
    stationarybs: $(results.pear),
    delta: $(abs(pearhacz-results.pear))
    cor: $(lm.β[1])
    std: $(((N-2)/(1-lm.β[1]^2))^(-0.5))"
  print("Time per bootstrap:")
  @btime stationarybootstrap($dat, $aggfunc, $B; p=$p)

  return nothing
end

@time teststationarybootstrap(300,10000)


function ikegamida(m_prefs, f_prefs)

    m_num, f_num = size(m_prefs)[2], size(f_prefs)[2]
    m_pool, f_pool = collect(1:m_num), collect(1:f_num)
    match_or_unmatch = Array(Bool, m_num)
    for i in 1:m_num
        match_or_unmatch[i] = true
    end
    m_matched, f_matched = ones(Int64, m_num) + f_num, ones(Int64, f_num) + m_num

    m_rank = Array(Int64, (f_num + 1, m_num))
    f_rank = Array(Int64, (m_num + 1, f_num))

    sorting = Array(Int64, (f_num + 1, 2))
    sorting[:, 1] = collect(1:(f_num + 1))

    for i in 1:m_num
        sorting[:, 2] = m_prefs[:, i]
        m_rank[:,i] = sortrows(sorting, by = x->(x[2]))[:,1]
        sorting = sortrows(sorting, by = x->(x[1]))
    end

    sorting2 = Array(Int64, (m_num + 1, 2))
    sorting2[:, 1] = collect(1:(m_num + 1))

    for i in 1:f_num
        sorting2[:, 2] = f_prefs[:, i]
        f_rank[:,i] = sortrows(sorting2, by = x->(x[2]))[:,1]
        sorting2 = sortrows(sorting2, by = x->(x[1]))
    end

    while sum(match_or_unmatch) > 0
        for i in 1:m_num
            if match_or_unmatch[i] == true

                for j in m_prefs[:, i]

                    if j == 0
                        m_matched[i] = 0
                        match_or_unmatch[i] = false
                        break

                    else
                        no_marriage = f_rank[1, j]

                        if f_rank[i+1, j] < no_marriage

                            if f_matched[j] == m_num + 1
                                f_matched[j] = i
                                m_matched[i] = j
                                match_or_unmatch[i] = false
                                break

                            else

                                if f_rank[f_matched[j]+1, j] > f_rank[i+1, j]
                                    match_or_unmatch[f_matched[j]] = true
                                    f_matched[j] = i
                                    m_matched[i] = j
                                    match_or_unmatch[i] = false
                                    break
                                end
                            end
                        end
                    end
                end
            end
        end
    end

    for (i,t) in enumerate(f_matched)
        if t == m_num + 1
            f_matched[i] = 0
        end
    end

     for (i,t) in enumerate(m_matched)
        if t == f_num + 1
            m_matched[i] = 0
        end
    end

    return m_matched, f_matched
end