"""
    function exponentiate(A, t::Number, x; kwargs...)
    function exponentiate(A, t::Number, x, algorithm)

Compute ``y = exp(t*A) x``, where `A` is a general linear map, i.e. a `AbstractMatrix` or just
a general function or callable object and `x` is of any Julia type with vector like behavior.

### Arguments:
The linear map `A` can be an `AbstractMatrix` (dense or sparse) or a general function or callable
object that implements the action of the linear map on a vector. If `A` is an `AbstractMatrix`,
`x` is expected to be an `AbstractVector`, otherwise `x` can be of any type that behaves as a
vector and supports the required methods (see KrylovKit docs).

The time parameter `t` can be real or complex, and it is better to choose `t` e.g. imaginary
and `A` hermitian, then to absorb the imaginary unit in an antihermitian `A`. For the former,
the Lanczos scheme is used to built a Krylov subspace, in which an approximation to the exponential
action of the linear map is obtained. The argument `x` can be of any type and should be in the
domain of `A`.

### Return values:
The return value is always of the form `y, info = eigsolve(...)` with
*   `y`: the result of the computation, i.e. `y = exp(t*A)*x`
*   `info`: an object of type [`ConvergenceInfo`], which has the following fields
    -   `info.converged::Int`: 0 or 1 if the solution `y` was approximated up to the requested
        tolerance `tol`.
    -   `info.residual::Nothing`: value `nothing`, there is no concept of a residual in this case
    -   `info.normres::Real`: an estimate (upper bound) of the error between the approximate and exact solution
    -   `info.numops::Int`: number of times the linear map was applied, i.e. number of times
        `f` was called, or a vector was multiplied with `A`
    -   `info.numiter::Int`: number of times the Krylov subspace was restarted (see below)
!!! warning "Check for convergence"
    No warning is printed if not all requested eigenvalues were converged, so always check
    if `info.converged >= howmany`.

### Keyword arguments:
Keyword arguments and their default values are given by:
*   `krylovdim = 30`: the maximum dimension of the Krylov subspace that will be constructed.
    Note that the dimension of the vector space is not known or checked, e.g. `x₀` should not
    necessarily support the `Base.length` function. If you know the actual problem dimension
    is smaller than the default value, it is useful to reduce the value of `krylovdim`, though
    in principle this should be detected.
*   `tol = 1e-12`: the requested accuracy (corresponding to the 2-norm of the residual for
    Schur vectors, not the eigenvectors). If you work in e.g. single precision (`Float32`),
    you should definitely change the default value.
*   `maxiter = 100`: the number of times the Krylov subspace can be rebuilt; see below for
    further details on the algorithms.
*   `issymmetric`: if the linear map is symmetric, only meaningful if `T<:Real`
*   `ishermitian`: if the linear map is hermitian
The default value for the last two depends on the method. If an `AbstractMatrix` is used,
`issymmetric` and `ishermitian` are checked for that matrix, ortherwise the default values are
`issymmetric = false` and `ishermitian = T <: Real && issymmetric`.

### Algorithm
The last method, without default values and keyword arguments, is the one that is finally called,
and can also be used directly. Here, one specifies the algorithm explicitly as either [`Lanczos`](@ref),
for real symmetric or complex hermitian problems, or [`Arnoldi`](@ref), for general problems.
Note that these names refer to the process for building the Krylov subspace.

!!! warning "`Arnoldi` not yet implented"
"""
function exponentiate end

function exponentiate(A, t::Number, v; kwargs...)
    alg = eigselector(A, promote_type(typeof(t), eltype(v)); kwargs...)
    exponentiate(A, t, v, alg)
end

function exponentiate(A, t::Number, v, alg::Lanczos)
    # process initial vector and determine result type
    β = norm(v)
    Av = apply(A, v) # used to determine return type
    numops = 1
    T = promote_type(eltype(Av), typeof(β), typeof(t))
    S = real(T)
    w = mul!(similar(Av, T), v, 1/β)

    # krylovdim and related allocations
    krylovdim = alg.krylovdim
    UU = Matrix{S}(undef, (krylovdim, krylovdim))
    yy1 = Vector{T}(undef, krylovdim)
    yy2 = Vector{T}(undef, krylovdim)

    # initialize iterator
    iter = LanczosIterator(A, w, alg.orth, true)
    fact = initialize(iter)
    numops += 1
    sizehint!(fact, krylovdim)

    # time step parameters
    sgn = sign(t)
    τ::S = abs(t)
    Δτ::S = τ

    # tolerance
    η::S = alg.tol / τ # tolerance per unit step
    if η < length(w)*eps(typeof(η))
        η = length(w)*eps(typeof(η))
        warn("tolerance too small, increasing to $(η*τ)")
    end
    totalerr = zero(η)

    δ::S = 0.9 # safety factor

    # start outer iteration loop
    maxiter = alg.maxiter
    numiter = 0
    while true
        numiter += 1
        Δτ = numiter == maxiter ? τ : min(Δτ, τ)

        # Lanczos or Arnoldi factorization
        while normres(fact) > η && length(fact) < krylovdim
            fact = expand!(iter, fact)
            numops += 1
        end
        K = fact.k # current Krylov dimension
        V = basis(fact)
        m = length(fact)

        # Small matrix exponential and error estimation
        U = copyto!(view(UU, 1:m, 1:m), I)
        H = rayleighquotient(fact) # tridiagonal
        D, U = eig!(H, U)

        # Estimate largest allowed time step
        ϵ::S = zero(η)
        while true
            ϵ₁ = zero(eltype(H))
            ϵ₂ = zero(eltype(H))
            @inbounds for k = 1:K
                ϵ₁ += U[K,k] * exp(sgn * Δτ/2 * D[k]) * conj(U[1,k])
                ϵ₂ += U[K,k] * exp(sgn * Δτ * D[k]) * conj(U[1,k])
            end
            ϵ = normres(fact) * ( 2*abs(ϵ₁)/3 + abs(ϵ₂)/6 ) # error per unit time: see Lubich

            if ϵ < δ * η || numiter == maxiter
                break
            else # reduce time step
                Δτ = round(δ * (η / ϵ)^(1/krylovdim) * Δτ; sigdigits=2)
            end
        end

        # Apply time step
        totalerr += Δτ * ϵ
        y1 = view(yy1, 1:m)
        y2 = view(yy2, 1:m)
        @inbounds for k = 1:m
            y1[k] = exp(sgn*Δτ*D[k])*conj(U[1,k])
        end
        y2 = mul!(y2, U, y1)

        # Finalize step
        w = mul!(w, V, y2)
        τ -= Δτ

        if iszero(τ) # should always be true if numiter == maxiter
            w = rmul!(w, β)
            converged = totalerr < alg.tol ? 1 : 0
            return w, ConvergenceInfo(converged, nothing, totalerr, numiter, numops)
        else
            normw = norm(w)
            β *= normw
            w = rmul!(w, inv(normw))
            fact = initialize!(iter, fact)
        end
    end
end


module SimplexPoly

using ComputedFieldTypes
using DifferentialForms
using LinearAlgebra
using SparseArrays
using StaticArrays

################################################################################

export Term
struct Term{D,T}
    powers::SVector{D,Int}
    coeff::T
    function Term{D,T}(powers::SVector{D,Int}, coeff::T) where {D,T}
        D::Int
        @assert D >= 0
        @assert all(>=(0), powers)
        return new{D,T}(powers, coeff)
    end
end

function Base.show(io::IO, ::MIME"text/plain", x::Term{D,T}) where {D,T}
    skiptype = get(io, :typeinfo, Any) <: Term{D,T}
    if !skiptype
        print(io, "Term(")
    end
    print(io, x.coeff, " * ", x.powers)
    if !skiptype
        print(io, ")")
    end
    return nothing
end

function Base.:(==)(x::Term{D}, y::Term{D}) where {D}
    return x.powers == y.powers && x.coeff == y.coeff
end

function Base.isequal(x::Term, y::Term)
    return isequal((x.powers, x.coeff), (y.powers, y.coeff))
end
Base.isless(x::Term, y::Term) = isless((x.powers, x.coeff), (y.powers, y.coeff))
Base.hash(x::Term, h::UInt) = hash((x.powers, x.coeff), hash(0x11325cd9, h))
export compare
function compare(x::Term, y::Term)
    # Note reversed signs; we want (1,0) < (0,1)
    x.powers > y.powers && return -1
    x.powers < y.powers && return +1
    return 0
end

function Base.map(f, x::Term{D,T}, ys::Term{D,T}...) where {D,T}
    @assert all(x.powers == y.powers for y in ys)
    return Term{D,T}(x.powers, map(f, x.coeff, (y.coeff for y in ys)...))
end

function Base.zero(::Type{Term{D,T}}) where {D,T}
    return Term{D,T}(zero(SVector{D,Int}), zero(T))
end
Base.zero(::Term{D,T}) where {D,T} = zero(Term{D,T})
Base.iszero(x::Term{D,T}) where {D,T} = iszero(x.coeff)

function Base.one(::Type{Term{D,T}}) where {D,T}
    return Term{D,T}(zero(SVector{D,Int}), one(T))
end
Base.one(::Term{D,T}) where {D,T} = one(Term{D,T})
Base.isone(x::Term{D,T}) where {D,T} = iszero(x.powers) && isone(x.coeff)

Base.:+(x::Term{D,T}) where {D,T} = map(+, x)
Base.:-(x::Term{D,T}) where {D,T} = map(-, x)

Base.:+(x::Term{D,T}, y::Term{D,T}) where {D,T} = map(+, x, y)
Base.:-(x::Term{D,T}, y::Term{D,T}) where {D,T} = map(-, x, y)
Base.:*(a, x::Term{D,T}) where {D,T} = map(c -> a * c, x)
Base.:*(x::Term{D,T}, a) where {D,T} = map(c -> c * a, x)
Base.:\(a, x::Term{D,T}) where {D,T} = map(c -> a \ c, x)
Base.:/(x::Term{D,T}, a) where {D,T} = map(c -> c / a, x)
Base.div(x::Term{D,T}, a) where {D,T} = map(c -> div(c, a), x)
Base.mod(x::Term{D,T}, a) where {D,T} = map(c -> mod(c, a), x)

function Base.:*(x::Term{D,T}, y::Term{D,T}) where {D,T}
    return Term{D,T}(x.powers + y.powers, x.coeff * y.coeff)
end
function Base.:^(x::Term{D,T}, n::Integer) where {D,T}
    @assert n >= 0
    r = one(x)
    while n > 0
        if n % 2 != 0
            r *= x
        end
        x *= x
        n ÷= 2
    end
    return r
end

export deriv
function deriv(term::Term{D,T}, dir::Int) where {D,T}
    D::Int
    @assert D >= 0
    @assert 1 <= dir <= D
    p = term.powers[dir]
    p == 0 && return zero(Term{D,T})
    return Term{D,T}(Base.setindex(term.powers, p - 1, dir), p * term.coeff)
end

"""
    koszul

The Koszul operator `κ` (kappa)

See e.g.: Douglas Arnold, Richard Falk, Ragnar Winther, "Finite
element exterior calculus, homological techniques, and applications",
Acta Numerica 15, 1-155 (2006), DOI:10.1017/S0962492906210018.
"""
function koszul end

export koszul
function koszul(term::Term{D,T}, dir::Int) where {D,T}
    D::Int
    @assert D >= 0
    @assert 1 <= dir <= D
    p = term.powers[dir]
    return Term{D,T}(Base.setindex(term.powers, p + 1, dir), term.coeff)
end

################################################################################

export Poly
struct Poly{D,T} <: Number
    terms::Vector{Term{D,T}}
    function Poly{D,T}(terms::Vector{Term{D,T}}) where {D,T}
        D::Int
        @assert D >= 0
        terms = combine(terms)
        return new{D,T}(terms)
    end
end

function combine(terms::Vector{Term{D,T}}) where {D,T}
    terms = sort(terms; by=(t -> t.powers), rev=true)
    isempty(terms) && return terms
    newterms = Term{D,T}[]
    i = 0
    term = terms[i += 1]
    while i < length(terms)
        term2 = terms[i += 1]
        if term2.powers == term.powers
            term += term2
        else
            !iszero(term) && push!(newterms, term)
            term = term2
        end
    end
    !iszero(term) && push!(newterms, term)
    return newterms
end

function Base.show(io::IO, mime::MIME"text/plain", p::Poly{D,T}) where {D,T}
    skiptype = get(io, :typeinfo, Any) <: Poly{D,T}
    if !skiptype
        print(io, "Poly")
    end
    # print(io, "[")
    print(io, "(")
    for (i, term) in enumerate(p.terms)
        # i > 1 && print(io, ", ")
        i > 1 && print(io, " + ")
        show(IOContext(io, :compact => true, :typeinfo => Term{D,T}), mime,
             term)
    end
    # print(io, "]")
    print(io, ")")
    return nothing
end

Base.:(==)(p::Poly{D}, q::Poly{D}) where {D} = p.terms == q.terms

Base.isequal(p::Poly, q::Poly) = isequal(p.terms, q.terms)
Base.isless(p::Poly, q::Poly) = isless(p.terms, q.terms)
Base.hash(p::Poly, h::UInt) = hash(p.terms, hash(0x0b9503a7, h))
export compare
function compare(p::Poly, q::Poly)
    for i in 1:min(length(p.terms), length(q.terms))
        c = compare(p.terms[i], q.terms[i])
        c != 0 && return c
    end
    # Polynomials with more terms come first since a non-zero term
    # comes before the zero term
    length(p.terms) > length(q.terms) && return -1
    length(p.terms) < length(q.terms) && return +1
    return 0
end

Base.map(f, p::Poly{D,T}) where {D,T} = Poly{D,T}(map(t -> map(f, t), p.terms))

function Base.map(f, p::Poly{D,T}, q::Poly{D,T}) where {D,T}
    terms = Term{D,T}[]
    i = j = 1
    ni = length(p.terms)
    nj = length(q.terms)
    while i <= ni || j <= nj
        usei = usej = false
        if i <= ni && j <= nj
            c = cmp(p.terms[i].powers, q.terms[j].powers)
            usei = c <= 0
            usej = c >= 0
        else
            usei = i <= ni
            usej = !usei
        end
        pi = usei ? p.terms[i] : map(zero, q.terms[j])
        qj = usej ? q.terms[j] : map(zero, p.terms[i])
        push!(terms, map(f, pi, qj))
        i += usei
        j += usej
    end
    return Poly{D,T}(terms)
end

function Base.zero(::Type{Poly{D,T}}) where {D,T}
    D::Int
    @assert D >= 0
    return Poly{D,T}(Vector{Term{D,T}}())
end
Base.zero(::Poly{D,T}) where {D,T} = zero(Poly{D,T})
Base.iszero(x::Poly{D,T}) where {D,T} = isempty(x.terms)

function Base.one(::Type{Poly{D,T}}) where {D,T}
    return Poly{D,T}([one(Term{D,T})])
end
Base.one(::Poly{D,T}) where {D,T} = one(Poly{D,T})
Base.isone(x::Poly{D,T}) where {D,T} = length(x.terms) == 1 && isone(x.terms[1])

export unit
function Forms.unit(::Type{Poly{D,T}}, dir::Int, coeff=one(T)) where {D,T}
    D::Int
    @assert D >= 0
    @assert 1 <= dir <= D
    term = Term{D,T}(SVector{D,T}(d == dir for d in 1:D), coeff)
    return Poly{D,T}([term])
end

Base.:+(p::Poly{D,T}) where {D,T} = map(+, p)
Base.:-(p::Poly{D,T}) where {D,T} = map(-, p)

Base.:+(p::Poly{D,T}, q::Poly{D,T}) where {D,T} = map(+, p, q)
Base.:-(p::Poly{D,T}, q::Poly{D,T}) where {D,T} = map(-, p, q)
Base.:*(a::Number, p::Poly{D,T}) where {D,T} = map(t -> a * t, p)
Base.:*(p::Poly{D,T}, a::Number) where {D,T} = map(t -> t * a, p)
Base.:\(a::Number, p::Poly{D,T}) where {D,T} = map(t -> a \ t, p)
Base.:/(p::Poly{D,T}, a::Number) where {D,T} = map(t -> t / a, p)
Base.div(p::Poly{D,T}, a::Number) where {D,T} = map(t -> div(t, a), p)
Base.mod(p::Poly{D,T}, a::Number) where {D,T} = map(t -> mod(t, a), p)

function Base.:*(p::Poly{D,T}, q::Poly{D,T}) where {D,T}
    return Poly{D,T}(Term{D,T}[t * u for t in p.terms for u in q.terms])
end
function Base.:^(x::Poly{D,T}, n::Integer) where {D,T}
    @assert n >= 0
    r = one(x)
    while n > 0
        if n % 2 != 0
            r *= x
        end
        x *= x
        n ÷= 2
    end
    return r
end

function deriv(poly::Poly{D,T}, dir::Int) where {D,T}
    return Poly{D,T}(map(t -> deriv(t, dir), poly.terms))
end

function koszul(poly::Poly{D,T}, dir::Int) where {D,T}
    return Poly{D,T}(map(t -> koszul(t, dir), poly.terms))
end

################################################################################

# Note: PolySpace is unused

export PolySpace
struct PolySpace{D,T}
    polys::Vector{Poly{D,T}}
    function PolySpace{D,T}(polys::Vector{Poly{D,T}}) where {D,T}
        D::Int
        @assert D >= 0
        polys = combine(polys)
        return new{D,T}(polys)
    end
end

function combine(polys::Vector{Poly{D,T}}) where {D,T}
    # TODO: This is wrong -- need to combine polynomials that are
    # multiples of each other
    polys = sort(polys; rev=true)
    unique!(polys)
    filter!(p -> !iszero(p), polys)
    return polys
end

PolySpace{D,T}() where {D,T} = PolySpace{D,T}(Poly{D,T}[])

function Base.show(io::IO, mime::MIME"text/plain",
                   ps::PolySpace{D,T}) where {D,T}
    skiptype = get(io, :typeinfo, Any) <: PolySpace{D,T}
    if !skiptype
        print(io, "PolySpace")
    end
    print(io, "[")
    for (i, poly) in enumerate(ps.polys)
        i > 1 && print(io, ", ")
        show(IOContext(io, :compact => true, :typeinfo => Poly{D,T}), mime,
             poly)
    end
    print(io, "]")
    return nothing
end

function Base.:(==)(ps::PolySpace{D}, qs::PolySpace{D}) where {D}
    return ps.polys == qs.polys
end

Base.isequal(ps::PolySpace, qs::PolySpace) = isequal(ps.polys, qs.polys)
Base.isless(ps::PolySpace, qs::PolySpace) = isless(ps.polys, qs.polys)
Base.hash(ps::PolySpace, h::UInt) = hash(ps.polys, hash(0xa3e4cfbf, h))

Base.map(f, ps::PolySpace{D,T}) where {D,T} = PolySpace{D,T}(map(f, ps.polys))

Base.isempty(ps::PolySpace) = isempty(ps.polys)
Base.length(ps::PolySpace) = length(ps.polys)
function Base.issubset(ps::PolySpace{D,T}, qs::PolySpace{D,T}) where {D,T}
    return issubset(Set(ps.polys), Set(qs.polys))
end
function Base.union(ps::PolySpace{D,T}, qs::PolySpace{D,T}) where {D,T}
    return PolySpace{D,T}([ps.polys; qs.polys])
end
function Base.setdiff(ps::PolySpace{D,T}, qs::PolySpace{D,T}) where {D,T}
    return PolySpace{D,T}(collect(setdiff(Set(ps.polys), Set(qs.polys))))
end

Base.zero(::Type{PolySpace{D,T}}) where {D,T} = PolySpace{D,T}()
Base.iszero(ps::PolySpace) = isempty(ps)
# Base.:+(ps::PolySpace) = ps
# Base.:+(ps::PolySpace{D,T}, qs::PolySpace{D,T}) where {D,T} = union(ps, qs)
# Base.:*(a, ps::PolySpace) = map(p -> a * p, ps)
# Base.:*(ps::PolySpace, a) = map(p -> p * a, ps)
# Base.:\(a, ps::PolySpace) = map(p -> a \ p, ps)
# Base.:/(ps::PolySpace, a) = map(p -> p / a, ps)

deriv(ps::PolySpace, dir::Int) = map(p -> deriv(p, dir), ps)
koszul(ps::PolySpace, dir::Int) = map(p -> koszul(p, dir), ps)

################################################################################

function deriv(f::Form{D,R,Poly{D,T}}) where {D,R,T}
    D::Int
    R::Int
    @assert 0 <= R < D
    r = zero(Form{D,R + 1,Poly{D,T}})
    N = length(f)
    for n in 1:N
        bits = Forms.lin2bit(Val(D), Val(R), n)
        for dir in 1:D
            if !bits[dir]
                parity = false
                for d in 1:(dir - 1)
                    parity ⊻= bits[d]
                end
                s = bitsign(parity)
                rbits = Base.setindex(bits, true, dir)
                rn = Forms.bit2lin(Val(D), Val(R + 1), rbits)
                r = Base.setindex(r, r[rn] + s * deriv(f[n], dir), rn)
            end
        end
    end
    return r::Form{D,R + 1,Poly{D,T}}
end

function koszul(f::Form{D,R,Poly{D,T}}) where {D,R,T}
    D::Int
    R::Int
    @assert 0 < R <= D
    r = zero(Form{D,R - 1,Poly{D,T}})
    N = length(f)
    for n in 1:N
        bits = Forms.lin2bit(Val(D), Val(R), n)
        for dir in 1:D
            if bits[dir]
                parity = false
                for d in 1:(dir - 1)
                    parity ⊻= bits[d]
                end
                s = bitsign(parity)
                rbits = Base.setindex(bits, false, dir)
                rn = Forms.bit2lin(Val(D), Val(R - 1), rbits)
                r = Base.setindex(r, r[rn] + s * koszul(f[n], dir), rn)
            end
        end
    end
    return r::Form{D,R - 1,Poly{D,T}}
end

################################################################################

function powers2ind(powers::SVector{D,Int}, maxp::Int) where {D}
    ind = 0
    for d in D:-1:1
        @assert 0 <= powers[d] <= maxp
        ind = (maxp + 1) * ind + powers[d]
    end
    return ind + 1
end

function poly2vec(poly::Poly{D,T}, maxp::Int) where {D,T}
    nrows = (maxp + 1)^D
    I = Int[]
    V = T[]
    for term in poly.terms
        push!(I, powers2ind(term.powers, maxp))
        push!(V, term.coeff)
    end
    return sparsevec(I, V, nrows)
end

export form2vec
function form2vec(form::Form{D,R,Poly{D,T}}, maxp::Int) where {D,R,T}
    stride = (maxp + 1)^D
    nrows = length(form) * stride
    I = Int[]
    V = T[]
    for (n, poly) in enumerate(form)
        offset = (n - 1) * stride
        vec = poly2vec(poly, maxp)
        Is, Vs = findnz(vec)
        append!(I, Is .+ offset)
        append!(V, Vs)
    end
    return sparsevec(I, V, nrows)
end

export forms2mat
function forms2mat(forms::Vector{<:Form{D,R,Poly{D,T}}},
                   maxp::Int) where {D,R,T}
    nrows = length(Form{D,R}) * (maxp + 1)^D
    ncols = length(forms)
    I = Int[]
    J = Int[]
    V = T[]
    for (j, form) in enumerate(forms)
        vec = form2vec(form, maxp)
        Is, Vs = findnz(vec)
        append!(I, Is)
        append!(J, fill(j, length(Is)))
        append!(V, Vs)
    end
    return sparse(I, J, V, nrows, ncols)
end

export maxpower
maximum0(xs) = isempty(xs) ? 0 : maximum(xs)
maxpower(term::Term) = maximum0(term.powers)
maxpower(poly::Poly) = maximum0(maxpower(term) for term in poly.terms)
maxpower(form::Form) = maximum0(maxpower(elt) for elt in form)
maxpower(forms::Vector{<:Form}) = maximum0(maxpower(form) for form in forms)

################################################################################

export Basis
@computed struct Basis{D,R,T}
    forms::Vector{fulltype(Form{D,R,Poly{D,T}})}
    function Basis{D,R,T}(forms::Vector{<:Form{D,R,Poly{D,T}}}) where {D,R,T}
        D::Int
        R::Int
        @assert 0 ≤ R ≤ D
        forms = combine(forms)
        return new{D,R,T}(forms)
    end
end

export compare
function compare(x::Form{D,R,<:Poly{D}}, y::Form{D,R,<:Poly{D}}) where {D,R}
    for (px, py) in zip(x.elts, y.elts)
        c = compare(px, py)
        c != 0 && return c
    end
    return 0
end

function combine(forms::Vector{<:Form{D,R,Poly{D,T}}}) where {D,R,T}
    forms = sort(forms; rev=true)
    unique!(forms)
    filter!(form -> !iszero(form), forms)
    map!(normalize, forms, forms)
    sort!(forms; lt=(x, y) -> compare(x, y) < 0)
    # TODO: filter out linear combinations
    return forms
end

function normalize(form::Form{D,R,Poly{D,T}}) where {D,R,T<:Integer}
    # coeffs = T[term.coeff for elt in form for poly in elt for term in poly]
    coeffs = T[]
    for poly in form
        poly::Poly{D,T}
        for term in poly.terms
            term::Term{D,T}
            push!(coeffs, term.coeff)
        end
    end
    q = gcd(coeffs)
    return map(x -> x ÷ q, form)
end

# function Basis{D,R,T}() where {D,R,T}
#     return Basis{D,R,T}([])
# end

function Base.show(io::IO, mime::MIME"text/plain",
                   b::Basis{D,R,T}) where {D,R,T}
    skiptype = get(io, :typeinfo, Any) <: Basis{D,R,T}
    if !skiptype
        print(io, "Basis{$D,$R,$T}")
    end
    print(io, "[")
    for (i, form) in enumerate(b.forms)
        i > 1 && print(io, ", ")
        show(IOContext(io, :compact => true, :typeinfo => Form{D,R,Poly{D,T}}),
             mime, form)
    end
    print(io, "]")
    return nothing
end

function Base.:(==)(b1::Basis{D,R}, b2::Basis{D,R}) where {D,R}
    # return b1.forms == b2.forms
    # Fast paths
    b1.forms == b2.forms && return true
    isempty(b1.forms) != isempty(b2.forms) && return false
    # Use subset relations
    return issubset(b1, b2) && issubset(b2, b1)
end

Base.isequal(b1::Basis, b2::Basis) = isequal(b1.forms, b2.forms)
Base.isless(b1::Basis, b2::Basis) = isless(b1.forms, b2.forms)
Base.hash(b::Basis, h::UInt) = hash(b.forms, hash(0xe8439c4b, h))

Base.zero(::Type{<:Basis{D,R,T}}) where {D,R,T} = Basis{D,R,T}([])
Base.zero(::Basis{D,R,T}) where {D,R,T} = zero(Basis{D,R,T})
Base.iszero(basis::Basis) = isempty(basis.forms)

function Forms.unit(form::Form{D,R,Poly{D,T}}) where {D,R,T}
    return Basis{D,R,T}([form])
end

export tensorsum, ⊕
function Forms.tensorsum(basis1::Basis{D,R,T},
                         basis2::Basis{D,R,T}) where {D,R,T}
    return Basis{D,R,T}([basis1.forms; basis2.forms])
end

function tensordiff end
const ⊖ = tensordiff
export tensordiff, ⊖
function tensordiff(basis1::Basis{D,R,T}, basis2::Basis{D,R,T}) where {D,R,T}
    forms2 = Set(basis2.forms)
    forms = fulltype(Form{D,R,Poly{D,T}})[]
    for form in basis1.forms
        if form ∉ forms2
            push!(forms, form)
        end
    end
    return Basis{D,R,T}(forms)
end

Base.isempty(basis::Basis) = isempty(basis.forms)
Base.length(basis::Basis) = length(basis.forms)

function Base.in(form::Form{D,R,Poly{D,T}}, basis::Basis{D,R,T}) where {D,R,T}
    # Fast paths
    isempty(basis.forms) && return false
    any(form == f for f in basis.forms) && return true
    # Convert representation to sparse vectors/matrices
    maxp = max(maxpower(form), maxpower(basis.forms))
    fvec = form2vec(form, maxp)
    bmat = forms2mat(basis.forms, maxp)
    # Sparse solver doesn't handle sparse vectors
    fvec = collect(fvec)
    # Is there a linear combination of the vectors in bmat that yields fvec?
    #     fⁱ = bⁱⱼ aʲ
    if T <: Integer
        # Use floating point numbers for integer polynomials
        avec = float.(bmat) \ float.(fvec)
        avec = rationalize.(avec; tol=sqrt(eps()))
    else
        @assert false
    end
    rvec = fvec - bmat * avec
    return iszero(rvec)
end

function Base.issubset(basis1::Basis{D,R}, basis2::Basis{D,R}) where {D,R}
    isempty(basis1.forms) && return true
    isempty(basis2.forms) && return false
    return all(f in basis2 for f in basis1.forms)
end

# Unused
function make_basis1(::Type{<:Basis{D,R,T}}, p::Int, cond) where {D,R,T}
    D::Int
    R::Int
    @assert 0 <= R <= D
    @assert p >= -1             # -1 is the empty basis
    polys = Poly{D,T}[]
    for i0 in
        CartesianIndex(ntuple(d -> 0, D)):CartesianIndex(ntuple(d -> p, D))
        i = SVector{D,Int}(i0.I)
        if cond(sum(i))
            push!(polys, Poly{D,T}([Term{D,T}(i, one(T))]))
        end
    end
    N = length(Form{D,R})
    forms = fulltype(Form{D,R,Poly{D,T}})[zero(Form{D,R,Poly{D,T}})]
    for n in 1:N
        newforms = fulltype(Form{D,R,Poly{D,T}})[]
        for f in forms
            for p in polys
                push!(newforms, setindex(f, p, n))
            end
        end
        forms = newforms
    end

    return Basis{D,R,T}(forms)
end

# Unused
function make_basis2(::Type{<:Basis{D,R,T}}, p::Int, cond) where {D,R,T}
    D::Int
    R::Int
    @assert 0 <= R <= D
    @assert p >= -1             # -1 is the empty basis
    N = length(Form{D,R})
    forms = fulltype(Form{D,R,Poly{D,T}})[zero(Form{D,R,Poly{D,T}})]
    for i0 in
        CartesianIndex(ntuple(d -> 0, D)):CartesianIndex(ntuple(d -> p, D))
        i = SVector{D,Int}(i0.I)
        if cond(sum(i))
            poly = Poly{D,T}([Term{D,T}(i, one(T))])
            form = Form{D,R,Poly{D,T}}(ntuple(n -> poly, N))
            push!(forms, form)
        end
    end
    return Basis{D,R,T}(forms)
end

function make_basis(::Type{<:Basis{D,R,T}}, p::Int, cond) where {D,R,T}
    D::Int
    R::Int
    @assert 0 <= R <= D
    @assert p >= -1             # -1 is the empty basis
    polys = Poly{D,T}[]
    for i0 in
        CartesianIndex(ntuple(d -> 0, D)):CartesianIndex(ntuple(d -> p, D))
        i = SVector{D,Int}(i0.I)
        if cond(sum(i))
            push!(polys, Poly{D,T}([Term{D,T}(i, one(T))]))
        end
    end
    N = length(Form{D,R})
    forms = fulltype(Form{D,R,Poly{D,T}})[]
    for n in 1:N
        for poly in polys
            form = Form{D,R,Poly{D,T}}(ntuple(m -> m == n ? poly :
                                                   zero(Poly{D,T}), N))
            push!(forms, form)
        end
    end
    return Basis{D,R,T}(forms)
end

export full_basis
function full_basis(::Type{<:Basis{D,R,T}}, p::Int) where {D,R,T}
    return make_basis(Basis{D,R,T}, p, <=(p))
end

export homogeneous_basis
function homogeneous_basis(::Type{<:Basis{D,R,T}}, p::Int) where {D,R,T}
    return make_basis(Basis{D,R,T}, p, ==(p))
end

function deriv(basis::Basis{D,R,T}) where {D,R,T}
    D::Int
    R::Int
    @assert 0 <= R < D
    dforms = fulltype(Form{D,R + 1,Poly{D,T}})[]
    for form in basis.forms
        dform = deriv(form)
        !iszero(dform) && push!(dforms, dform)
    end
    return Basis{D,R + 1,T}(dforms)
end

function koszul(basis::Basis{D,R,T}) where {D,R,T}
    D::Int
    R::Int
    @assert 0 < R <= D
    κforms = fulltype(Form{D,R - 1,Poly{D,T}})[]
    for form in basis.forms
        κform = koszul(form)
        !iszero(κform) && push!(κforms, κform)
    end
    return Basis{D,R - 1,T}(κforms)
end

################################################################################

# function bernstein(::Type{<:Basis{D,0,T}}, p::Int) where {D,T}
#     D::Int
#     R = 0
#     @assert 0 <= R <= D
#     @assert p >= 0
#     polys = fulltype(Form{D,R,Poly{D,T}})[]
#     for i0 in
#         CartesianIndex(ntuple(d -> 0, D)):CartesianIndex(ntuple(d -> p, D))
#         i = SVector{D,Int}(i.I)
#         if sum(i) == p
#             poly = Poly{D,T}([Term{D,T}(i, one(T))])
#             push!(polys, Form{D,R}((poly,)))
#         end
#     end
#     return Basis{D,T}(polys)
# end
#
# function bernstein(::Type{<:Basis{D,R,T}}, p::Int) where {D,R,T}
#     D::Int
#     R::Int
#     @assert 0 <= R <= D
#     @assert p >= 0
#     polys = Poly{D,T}[]
#     for i0 in
#         CartesianIndex(ntuple(d -> 0, D)):CartesianIndex(ntuple(d -> p, D))
#         i = SVector{D,Int}(i.I)
#         if sum(i) == p
#             push!(polys, Poly{D,T}([Term{D,T}(i, one(T))]))
#         end
#     end
#     return Basis{D,T}(polys)
# end

################################################################################

export polynomial_complex
"""
polynomial_complex

Pᵣ Λᵏ
"""
function polynomial_complex(::Val{D}, ::Type{T}, p::Int) where {D,T}
    D::Int
    @assert 0 <= D
    @assert p >= -1

    cc = Dict{Int,Basis{D,R,T} where R}()

    for R in 0:D
        cc[R] = full_basis(Basis{D,R,T}, max(-1, p - R))::Basis{D,R,T}
    end

    return cc
end

export trimmed_polynomial_complex
"""
trimmed_polynomial_complex

Pᵣ⁻ Λᵏ
"""
function trimmed_polynomial_complex(::Val{D}, ::Type{T}, p::Int) where {D,T}
    D::Int
    @assert 0 <= D
    @assert p >= -1

    cc = Dict{Int,Basis{D,R,T} where R}()

    for R in 0:D
        if R == 0
            b = full_basis(Basis{D,R,T}, p)
        else
            b = full_basis(Basis{D,R,T}, max(-1, p - 1))
            if R + 1 <= D
                b = b ⊕ koszul(homogeneous_basis(Basis{D,R + 1,T}, p - 1))
            end
            # if R - 1 >= 0
            #     b = b ⊕ deriv(homogenous_basis(Basis{D,R - 1,T}, p + 1))
            # end
        end
        cc[R] = b::Basis{D,R,T}
    end

    return cc
end

export extended_trimmed_polynomial_complex
"""
extended_trimmed_polynomial_complex

This is experimental; note that this is not actually a complex.
"""
function extended_trimmed_polynomial_complex(::Val{D}, ::Type{T},
                                             p::Int) where {D,T}
    D::Int
    @assert 0 <= D
    @assert p >= -1

    cc = Dict{Int,Basis{D,R,T} where R}()

    for R in 0:D
        if R == 0
            b = full_basis(Basis{D,R,T}, p)
        else
            b = full_basis(Basis{D,R,T}, max(-1, p - 1))
            if R + 1 <= D
                b = b ⊕ koszul(homogeneous_basis(Basis{D,R + 1,T}, p - 1))
            end
            if R - 1 >= 0
                b = b ⊕ deriv(homogeneous_basis(Basis{D,R - 1,T}, p + 1))
            end
        end
        cc[R] = b::Basis{D,R,T}
    end

    return cc
end

################################################################################

export whitney
"""
whitney

Calculate Whitney forms
"""
function whitney(::Val{D1}, inds::SVector{N,Int}) where {D1,N}
    D1::Int
    N::Int
    @assert 1 <= D1
    @assert 1 <= N <= D1
    R = N - 1
    @assert all(1 <= inds[n] <= D1 for n in 1:N)
    @assert all(inds[n] < inds[n + 1] for n in 1:(N - 1))
    ϕ = zero(Form{D1,R,Poly{D1,Int}})
    q = factorial(N - 1)
    for n in 1:N
        inds′ = deleteat(inds, n)
        f = unit(Form{D1,R,Int}, inds′)
        p = q * bitsign(n - 1) * unit(Poly{D1,Int}, inds[n])
        ϕ += p * f
    end
    return ϕ::Form{D1,R,Poly{D1,Int}}
end
function whitney(::Val{D1}, inds::NTuple{N,Int}) where {D1,N}
    return whitney(Val(D1), SVector{N,Int}(inds))
end
function whitney(::Type{Basis{D1,R,Int}}) where {D1,R}
    D1::Int
    R::Int
    @assert 1 <= D1
    @assert 0 <= R <= D1 - 1
    nelts = binomial(D1, R + 1)
    forms = [whitney(Val(D1), Forms.lin2lst(Val(D1), Val(R + 1), n))
             for n in 1:nelts]
    return Basis{D1,R,Int}(forms)
end

export whitney_support
"""
whitney_support

Determine on what part of a simplex (vertex, edge, face, ...) a
particular Whitney forms should live.
"""
function whitney_support(::Val{D1}, inds::SVector{N,Int}) where {D1,N}
    return inds
end

################################################################################

export barycentric2cartesian
function barycentric2cartesian(λterm::Term{D1,T}) where {D1,T}
    D1::Int
    @assert 1 <= D1
    D = D1 - 1
    # This assumes a standard simplex:
    #     λᵢ = xᵢ
    #     λₙ = 1 - Σᵢ λᵢ
    λn = one(Poly{D,T})
    if D > 0
        λn -= sum(unit(Poly{D,T}, d) for d in 1:D)
    end
    xpoly = Poly{D,T}([Term{D,T}(deleteat(λterm.powers, D1), λterm.coeff)])
    xpoly *= λn^λterm.powers[D1]
    return xpoly::Poly{D,T}
end
function barycentric2cartesian(λpoly::Poly{D1,T}) where {D1,T}
    D1::Int
    @assert 1 <= D1
    D = D1 - 1
    isempty(λpoly.terms) && return zero(Poly{D,T})
    xpoly = sum(barycentric2cartesian(λterm) for λterm in λpoly.terms)
    return xpoly::Poly{D,T}
end
function barycentric2cartesian(λform::Form{D1,R,Poly{D1,T}}) where {D1,R,T}
    D1::Int
    R::Int
    @assert 1 <= D1
    D = D1 - 1
    @assert 0 <= R <= D
    # This assumes a standard simplex:
    #     λᵢ = xᵢ
    #     λₙ = 1 - Σᵢ λᵢ
    #     dλᵢ = dxᵢ
    #     dλₙ = -Σᵢ dλᵢ
    if D == 0
        λpoly = λform[]
        xpoly = barycentric2cartesian(λpoly)
        xform = Form{D,R,Poly{D,T}}((xpoly,))
    else
        dλn = -sum(unit(Form{D,1,Poly{D,T}}, d) for d in 1:D)
        xform = zero(Form{D,R,Poly{D,T}})
        for (λlin, λpoly) in enumerate(λform)
            λbits = Forms.lin2bit(Val(D1), Val(R), λlin)
            xbits = deleteat(λbits, D1)
            if λbits[D1]
                xlin = Forms.bit2lin(Val(D), Val(R - 1), xbits)
                xunit = unit(Form{D,R - 1,Poly{D,T}}, xlin) ∧ dλn
            else
                xlin = Forms.bit2lin(Val(D), Val(R), xbits)
                xunit = unit(Form{D,R,Poly{D,T}}, xlin)
            end
            xpoly = barycentric2cartesian(λpoly)
            xform += xpoly * xunit
        end
    end
    return xform::Form{D,R,Poly{D,T}}
end
function barycentric2cartesian(λbasis::Basis{D1,R,T}) where {D1,R,T}
    D1::Int
    R::Int
    @assert 1 <= D1
    D = D1 - 1
    @assert 0 <= R <= D
    return Basis{D,R,T}(barycentric2cartesian.(λbasis.forms))
end

end


module FiniteDifferences

using LinearAlgebra
using SparseArrays

import Base: eltype, show

struct Basis{T}
    j::UnitRange{Integer}
    ρ::T
    Z::T
    δβ₁::T # Correction used for bare Coulomb potentials, Eq. (22) Schafer2009
end

function Basis(n::I, ρ::T, j₀::I, Z::T=one(T)) where {I<:Integer, T}
    j₀ < zero(I) && throw(ArgumentError("Illegal starting point of grid"))
    j = (1:n) .+ j₀
    Basis{T}(j, ρ, Z, j₀ == 0 ? (Z*ρ/8 * (one(T) + Z*ρ)) : zero(T))
end

Basis(n::I, ρ::T, Z::T=one(T), r₀::T=zero(T)) where {I<:Integer, T} =
    Basis(n, ρ, floor(Int, r₀/ρ), Z)

# Basis(rₘₐₓ::T, n::I, Z::T=one(T)) where {I<:Integer,T} =
#     Basis{T}(n, rₘₐₓ/(n-1/2), Z)

eltype(::Basis{T}) where T = T

locs(basis::Basis{T}) where T = (basis.j .- 1/2)*basis.ρ

function (basis::Basis)(D::Diagonal)
    n = length(basis.j)
    @assert size(D) == (n,n)
    D
end
(basis::Basis)(f::Function) = Diagonal(f.(locs(basis)))
(basis::Basis)(::UniformScaling) = I

# Not really useful, only implemented to be compatible with FEDVR and
# BSplines.
function (basis::Basis)(r::AbstractVector)
    B = spzeros(eltype(r), length(r), length(basis.j))
    for j = basis.j[1:end-1]
        a = (j-1/2) * basis.ρ
        b = (j+1/2) * basis.ρ
        B[:,j] = (a .<= r .< b)
    end
    B
end

# Variationally derived coefficients for three-point stencil
α(j::Integer) = j^2/(j^2 - 1/4)
β(j::Integer) = (j^2 - j + 1/2)/(j^2 - j + 1/4)

function β(basis::Basis)
    b = β.(basis.j)
    b[1] += basis.δβ₁
    b
end

function derop(::Type{U}, basis::Basis{T}, o::Integer) where {U,T}
    o ∉ 0:2 && error("Unsupported derivative order $(o) ∉ 0:2")
    if o == 0
        basis(I)
    elseif o == 1
        a = α.(basis.j[1:end-1])
        Tridiagonal{U}(-a, zeros(T, length(basis.j)), a)/2basis.ρ
    else
        SymTridiagonal{U}(-2β(basis), α.(basis.j[1:end-1]))/basis.ρ^2
    end
end
derop(basis::Basis{T}, o::Integer) where T = derop(T, basis, o)

show(io::IO, basis::Basis) =
    write(io, "Finite differences basis with $(length(basis.j)) points spaced by ρ = $(basis.ρ)")

end # module


export Interval

using ..Points

struct Interval <: AbstractShape{1,2}
    start::Float64
    stop::Float64
    origin::Point{1}
    a::Float64
end
(i::Interval)(p::Point) = i.start ≤ p.x ≤ i.stop
(i::Interval)(x::Real,y...) = i(Point(x,y...))

Interval(start,stop;ref::Symbol=:center) = Interval(start,stop,generate_origin(start,stop,ref),abs(stop-start))

function generate_origin(start::Real,stop::Real,ref::Symbol)
    left,right = minmax(start,stop)
    if Base.sym_in(ref,(:left,:l,:Left,:L))
        origin = Point(left)
    elseif Base.sym_in(ref,(:right,:r,:Right,:R))
        origin = Point(right)
    else
        origin = Point((left+right)/2)
    end
    return origin
end

# struct BraggStack{N,T}
#     origin::Float64
#     depths::NTuple{N,Float64}
#     indices::NTuple{N,T}
#     periods::Int
#     integrated_depths::NTuple{N,Float64}
#
#     function BraggStack(origin,depths,indices,periods)
#         new{length(depths),eltype(indices)}(origin,depths,indices,periods,tuple(cumsum(depths...)))
#     end
# end
# function BraggStack(p::Point)
#     0 ≤ (p.x-origin)
# end


# function for computing and applying the mass matrix and its inverse

@doc """
  This function calculates the inverse mass matrix and returns it.
  Because we use SBP operators, the mass matrix is diagonal, so it is stored
  in a vector.  mesh.dofs is used to put the components of the inverse
  mass matrix in the same place as the corresponding values in eqn.res_vec

  Arguments:
    mesh: AbstractMesh
    sbp: SBP operator
    eqn: an implementation of AbstractSolutionData. Does not have to be fully initialized.

  Outputs:
    Minv: vector containing inverse mass matrix

"""->
# used by AbstractSolutionData Constructor
# mid level functions
function calcMassMatrixInverse(mesh::AbstractMesh{Tmsh},
                               sbp::AbstractOperator,
                               eqn::AbstractSolutionData{Tsol, Tres}) where {Tmsh,  Tsol, Tres}
# calculate the inverse mass matrix so it can be applied to the entire solution vector
# mass matrix is diagonal, stores in vector eqn.Minv

  Minv = zeros(Tmsh, mesh.numDof)

  for i=1:mesh.numEl
    for j=1:sbp.numnodes
      for k=1:mesh.numDofPerNode
        dofnum_k = mesh.dofs[k,j,i]
        # multiplication is faster than division, so do the divisions here
        # and then multiply solution vector times Minv
        Minv[dofnum_k] += (sbp.w[j]/mesh.jac[j,i])
      end
    end
  end

  for i=1:mesh.numDof
    Minv[i] = 1/Minv[i]
  end

  return Minv

end     # end of calcMassMatrixInverse function

@doc """
  This function calculate the mass matrix and returns it.
  Beause w are using SBP operators, the mass matrix is diagonal, so it is
  stored in a vector.

  Arguments:
    mesh: AbstractMesh
    sbp: SBP operator
    eqn: an implementation of AbstractSolutionData. Does not have to be fully initialized.

  Outputs:
    M: vector containing mass matrix

"""->
function calcMassMatrix(mesh::AbstractMesh{Tmsh},
                        sbp::AbstractOperator,
                        eqn::AbstractSolutionData{Tsol, Tres}) where {Tmsh,  Tsol, Tres}
# calculate the (diagonal) mass matrix as a vector
# return the vector M

  M = zeros(Tmsh, mesh.numDof)

  for i=1:mesh.numEl
    for j=1:sbp.numnodes
      for k=1:mesh.numDofPerNode
        dofnum_k = mesh.dofs[k,j,i]
        # multiplication is faster than division, so do the divions here
        # and then multiply solution vector times M
        M[dofnum_k] += (sbp.w[j]/mesh.jac[j,i])
      end
    end
  end

  return M

end     # end of calcMassMatrix function

@doc """

  This function calculates the mass matrix and returns it, in a 3D array format suitable for
  application to eqn.res.

  Arguments:
    mesh: AbstractMesh
    sbp: SBP operator
    eqn: an implementation of AbstractSolutionData. Does not have to be fully initialized.

  Outputs:
    M: vector containing mass matrix

"""->
# calcMassMatrixInverse3D:
#   calculates the inverse mass matrix, returning it as a 3D array suitable for application to eqn.res
function calcMassMatrixInverse3D(mesh::AbstractMesh{Tmsh},
                               sbp::AbstractOperator,
                               eqn::AbstractSolutionData{Tsol, Tres}) where {Tmsh,  Tsol, Tres}

  Minv3D = zeros(Tmsh, mesh.numDofPerNode, sbp.numnodes, mesh.numEl)

  for i=1:mesh.numEl
    for j=1:sbp.numnodes
      for k=1:mesh.numDofPerNode
        dofnum_k = mesh.dofs[k,j,i]
        # multiplication is faster than division, so do the divisions here
        # and then multiply solution vector times Minv
        Minv3D[k, j, i] += (sbp.w[j]/mesh.jac[j,i])
      end
    end
  end

  for i=1:mesh.numEl
    for j=1:sbp.numnodes
      for k=1:mesh.numDofPerNode
        Minv3D[k, j, i] = 1/Minv3D[k, j, i]
      end
    end
  end

  return Minv3D

end

@doc """
  This function multiplies eqn.res_vec (the residual in vector form  by eqn.Minv,
  the diagonal mass matrix.  This is a very fast function because all values
  are precomputed and stored linearly in memory.

  This is a mid level function, and does the correct thing regardless of the
  dimension of the equation.

  Aliasing restrictions: none
"""->
# mid level function (although it doesn't really need to Tdim)
function applyMassMatrixInverse(eqn::AbstractSolutionData{Tsol, Tres},
                                res_vec::AbstractVector{Tsol}) where {Tsol, Tres}
  # apply the inverse mass matrix stored eqn to res_vec

  ndof = length(res_vec)
  for i=1:ndof
    res_vec[i] *= eqn.Minv[i]
  end

  return nothing
end


using Ipopt
using MathProgBase
using Random

struct Quadratic <: MathProgBase.AbstractNLPEvaluator
    V::Matrix{Float64}
    m::Vector{Float64}
    A::Matrix{Float64}
end

function MathProgBase.initialize( ::Quadratic, requested_features::Vector{Symbol} )
    unimplemented = setdiff( requested_features, [:Grad,:Jac] )
    if !isempty( unimplemented )
        error( "The following features aren't implemented: " * join( string.(unimplemented), ", " ) )
    end
end

MathProgBase.features_available( ::Quadratic ) = [:Grad]

MathProgBase.eval_f( q::Quadratic, x ) = (x' * 0.5 * q.V + q.m') * x

function MathProgBase.eval_g( q::Quadratic, g, x )
    g[:] = q.A * x
end

function MathProgBase.eval_grad_f( q::Quadratic, grad_f, x )
    grad_f[:] = q.V * x + q.m
end

MathProgBase.jac_structure( q::Quadratic ) =
    ((m,n) -> ([(i % m) + 1 for i in 1:m*n], vcat([fill(i,m) for i in 1:n]...)))(size(q.A)...)

function MathProgBase.eval_jac_g( q::Quadratic, J, x )
    J[:] = q.A
end

solver = IpoptSolver()

model = MathProgBase.NonlinearModel(solver)

Random.seed!(1)
S = rand( 3, 3 )
V = S'*S
m = rand(3)
A = rand(1,3)
b = rand(1)
q = Quadratic(V,m,A)
x0 = rand(3)
MathProgBase.loadproblem!(model, 3, 1, fill(-Inf,3), fill(Inf,3), fill(-Inf,1), b, :Min, q)
MathProgBase.setwarmstart!( model, rand(3) )
MathProgBase.optimize!(model)
@assert( MathProgBase.status(model) == :Optimal )
x = MathProgBase.getsolution(model)

@assert( maximum(abs.(x + q.V\q.m)) < 1e-8 )

if hasmethod( MathProgBase.freemodel!, Tuple{typeof(model)} )
    MathProgBase.freemodel!( model )
end

b = [-5.0]
model = MathProgBase.NonlinearModel(solver)
MathProgBase.loadproblem!(model, 3, 1, fill(-Inf,3), fill(Inf,3), fill(-Inf,1), b, :Min, q)
x0 = rand(3)
MathProgBase.setwarmstart!( model, x0 )
MathProgBase.optimize!(model)
@assert( MathProgBase.status(model) == :Optimal )
x = MathProgBase.getsolution(model)

@assert( q.A * x <= b .+ 1e-6 )

lambda = -((q.A * inv(q.V) * q.m + b)/(q.A * inv(q.V) * q.A'))[1]
@assert( maximum(abs.(x - inv(V)*(- q.m' - lambda * q.A)')) < 1e-6 )

if hasmethod( MathProgBase.freemodel!, Tuple{typeof(model)} )
    MathProgBsae.freemodel!( model )
end

# the following optimization takes many iterations
# I'm not sure why
#A = rand(2,3)
#b = rand(2)
#q = Quadratic( V, m, A )
#model = MathProgBase.NonlinearModel(solver)
#MathProgBase.loadproblem!(model, 3, 2, fill(-Inf,3), fill(Inf,3), fill(-Inf,2), b, :Min, q)
#x0 = rand(3)
#MathProgBase.setwarmstart!( model, x0
#MathProgBase.optimize!(model)

#if hasmethod( MathProgBase.freemodel!, Tuple{typeof(model)} )
#    MathProgBsae.freemodel!( model )
#end

# looseing the constraints does work
Ap = A[1:1,:]
bp = b[1:1]
q = Quadratic( V, m, Ap )
model = MathProgBase.NonlinearModel(solver)
MathProgBase.loadproblem!(model, 3, 1, fill(-Inf,3), fill(Inf,3), fill(-Inf,1), bp, :Min, q)
MathProgBase.setwarmstart!( model, rand(3) )
MathProgBase.optimize!(model)
x = MathProgBase.getsolution(model)
@assert( all(Ap * x .<= bp .+ 1e-6) )


using Gadfly

# Set the random seed to get consistent data
srand(1)

# Number of examples to use
n = 100

# Specify the true value of the variable
true_coeffs = [2; -2; 0.5];

# Generate data
x_data = rand(n, 1) * 5;
x_data_expanded = hcat([x_data .^ i for i in 1 : 3]...)
y_data = x_data_expanded * true_coeffs + 0.5 * rand(n, 1)

p = plot(
  x=x_data, y=y_data, Geom.point,
  Theme(panel_fill=color("white"))
)
# draw(PNG("data.png", 16cm, 12cm), p)


#= MULTIVECTOR TYPES

Type hierarchy:

           AbstractMultivector
            /               \
HomogeneousMultivector     MixedMultivector
   /        \
Blade   Multivector                 │
                                    │
           ╰─ CompositeMultivector ─╯

- `Blade`: a scalar multiple of a wedge product of orthogonal basis vectors.
   Note that the mathematical definition of a ``k``-blade is the wedge product
   of ``k`` different _vectors_, not necessarily basis vectors. Thus, not all
   ``k``-blades are representable as a `Blade` (but always as a `Multivector`).
- `Multivector`: a homogeneous multivector; a sum of same-grade blades.
- `MixedMultivector`: an inhomogeneous multivector. All elements in a geometric
   algebra can be represented as this type.

=#

"""
	AbstractMultivector{sig}

Supertype of all elements in the geometric algebra over the vector space with
metric signature `sig`, retrieved with the `signature` method.
"""
abstract type AbstractMultivector{sig} <: Number end

signature(::Type{<:AbstractMultivector{sig}}) where sig = sig
signature(::T) where {T<:AbstractMultivector} = signature(T)

dimension(::Union{T,Type{T}}) where {T<:AbstractMultivector} = dimension(signature(T))

Base.broadcastable(a::AbstractMultivector) = Ref(a)

"""
	HomogeneousMultivector{sig,k} <: AbstractMultivector{sig}

Supertype of grade `k` elements in the geometric algebra with metric signature `sig`.
"""
abstract type HomogeneousMultivector{sig,k} <: AbstractMultivector{sig} end

grade(::Type{<:HomogeneousMultivector{sig,k} where sig}) where k = k
grade(::T) where {T<:HomogeneousMultivector} = grade(T)

"""
	Blade{sig,k,bits,T} <: HomogeneousMultivector{sig,k}

A blade of grade `k` with basis blade `bits` and scalar coefficient of type `T`.

Parameters
===
- `sig`: metric signature defining the parent geometric algebra
- `k`: grade of the blade, equal to `grade(bits) === count_ones(bits)`
- `bits`: unsigned integer representing the indices of the basis vectors whose wedge product is the unit blade
- `T`: type of the scalar coefficient
"""
struct Blade{sig,k,bits,T} <: HomogeneousMultivector{sig,k}
	coeff::T
end
Blade{sig}(coeff::T, bits) where {sig,T} = Blade{sig,grade(bits),bits,T}(coeff)

# for internal use only (assumes `k == grade(bits)` without checking)
Blade{sig,k}(coeff::T, bits) where {sig,k,T} = Blade{sig,k,bits,T}(coeff)
Blade{sig,k,bits}(coeff::T) where {sig,k,bits,T} = Blade{sig,k,bits,T}(coeff)

"""
	bitsof(a::Blade{sig,k,bits,T}) = bits

The bits parameter of a blade, representing its basis `k`-blade.
"""
bitsof(::Type{<:Blade{sig,k,bits} where {sig,k}}) where bits = bits
bitsof(::T) where {T<:Blade} = bitsof(T)

"""
	Multivector{sig,k,S} <: HomogeneousMultivector{sig,k}

A homogeneous multivector of grade `k` with storage type `S`.

Parameters
===
- `sig`: metric signature defining the parent geometric algebra
- `k`: grade of the multivector
- `S`: type in which the multivector components are stored; usually a vector-like or dictionary-like type
"""
struct Multivector{sig,k,S} <: HomogeneousMultivector{sig,k}
	components::S
end
Multivector{sig,k}(comps::S) where {sig,k,S} = Multivector{sig,k,S}(comps)

"""
	MixedMultivector{sig,S} <: AbstractMultivector{sig}

An generally inhomogeneous multivector.

All elements of a geometric algebra are representable as a `MixedMultivector`.

Parameters
===
- `sig`: metric signature defining the parent geometric algebra
- `S`: type in which the multivector components are stored; usually a vector-like or dictionary-like type
"""
struct MixedMultivector{sig,S} <: AbstractMultivector{sig}
	components::S
end
MixedMultivector{sig}(comps::S) where {sig,S} = MixedMultivector{sig,S}(comps)

# const UnionMultivectorTypes = Union{Blade,Multivector,AbstractMultivector}
const CompositeMultivector{S} = Union{Multivector{sig,k,S},MixedMultivector{sig,S}} where {sig,k}
const Scalar = Union{filter(T -> !(T <: AbstractMultivector), subtypes(Number))...}

"""
	eltype(a)

The numerical type of the components of a multivector or multivector type.
"""
Base.eltype(::Type{<:Blade{sig,k,bits,T} where {sig,k,bits}}) where T = T
Base.eltype(::Type{<:CompositeMultivector{S}}) where S = valtype(S)

ncomponents(::Type{<:Multivector{sig,k}}) where {sig,k} = binomial(dimension(sig), k)
ncomponents(::Type{<:MixedMultivector{sig}}) where sig = 2^dimension(sig)
ncomponents(::T) where {T<:AbstractMultivector} = ncomponents(T)

#= ZERO & ONE CONSTRUCTORS =#

zeroslike(::Type{Vector{T}}, I...) where T = zeros(T, I...)
zeroslike(::Type{SparseVector{Tv,Ti}}, I...) where {Tv,Ti} = spzeros(Tv, Ti, I...)
zeroslike(::Type{S}, I...) where {S<:StaticVector} = zeros(S)

Base.zero(::Type{<:Blade{sig,k,bits,T}}) where {sig,k,bits,T} = Blade{sig,k,bits,T}(zero(T))
Base.zero(::Type{<:Multivector{sig,k,S}}) where {sig,k,S} = Multivector{sig,k}(zeroslike(S, binomial(dimension(sig), k)))
Base.zero(::Type{<:MixedMultivector{sig,S}}) where {sig,S} = MixedMultivector{sig}(zeroslike(S, 2^dimension(sig)))

# needed to override default implementation for `<:Number`
Base.zero(::T) where {T<:AbstractMultivector} = zero(T)

Base.iszero(a::Blade) = iszero(a.coeff)
Base.iszero(a::CompositeMultivector{<:AbstractVector}) = iszero(a.components)

Base.one(a::Type{<:Blade}) = Blade{signature(a)}(one(eltype(a)), bits_scalar())

Base.oneunit(a::Type{<:Blade}) = Blade{signature(a)}(one(eltype(a)), bitsof(a))
Base.oneunit(::T) where {T<:AbstractMultivector} = oneunit(T)

#= MULTIVECTOR TYPE INFERENCE UTILITIES

The `<:AbstractMultivector` type system is complex -- any multivector type consists of four orthogonal aspects:

 - `multivectortype`: one of `Blade, Multivector, MixedMultivector`
 - `signature`: an all-bits type parameter defining the geometric algebra
 - `storagetype`: the container type `S` which stores components of a `<:CompositeMultivector{S}` (not applicable for `Blade`)
 - `eltype`: the numerical type of the components

The direction of promotion for these aspects are, respectively:

 - `Blade` -> `Multivector` -> `MixedMultivector`
 -  no promotion occurs -- an error is thrown if types of different signature are compared
 - `StaticVector` -> `Vector` -> `SparseVector` - the storage type shouldn't be affected by `Blade` types
 -  usual promotion behaviour of `<:Number` types

The master function `best_type` performs `<:AbstractMultivector` type promotion, and exposes ways of
controlling various type parameters (e.g., setting the grade or requiring a minimum `multivectortype` or `eltype`).
=#

multivectortype(::Type{<:Blade}) = Blade
multivectortype(::Type{<:Multivector}) = Multivector
multivectortype(::Type{<:MixedMultivector}) = MixedMultivector
multivectortype(::T) where {T<:AbstractMultivector} = multivectortype(T)

shared_sig(::Type{<:AbstractMultivector{sig}}...) where sig = sig
shared_sig(::Type{<:AbstractMultivector}...) = error("multivectors must share the same metric signature")
shared_sig(As::AbstractMultivector...) = shared_sig(typeof.(As)...)

# harness built in type promotion system to promote storage types with more semantic code
struct StorageType{S} end
unwrap(::Type{StorageType{S}}) where S = S

storagetype(::Type{<:Blade}) = Union{} # indented to be type promoted with other storage types
storagetype(::Type{<:CompositeMultivector{S}}) where S = StorageType{S}
storagetype(::T) where {T<:AbstractMultivector} = storagetype(T)

Base.promote_rule(::Type{StorageType{Vector{T}}}, ::Type{StorageType{Vector{S}}}) where {T,S} = StorageType{Vector{promote_type(T, S)}}
Base.promote_rule(::Type{StorageType{SparseVector{Tv,Ti}}}, ::Type{StorageType{Vector{T}}}) where {Tv,Ti,T} = StorageType{SparseVector{promote_type(Tv, T),Ti}}
Base.promote_rule(::Type{StorageType{SparseVector{Tv,Ti}}}, ::Type{StorageType{SparseVector{Sv,Si}}}) where {Tv,Ti,Sv,Si} = StorageType{SparseVector{promote_type(Tv, Sv),promote_type(Ti, Si)}}
# fallback
Base.promote_rule(::Type{StorageType{T}}, ::Type{StorageType{S}}) where {T,S} = StorageType{promote_type(T, S)}

set_eltype_parameter(::Type{<:Vector}, ::Type{T}) where {T} = Vector{T}
set_eltype_parameter(::Type{<:SparseVector{Tv,Ti} where Tv}, ::Type{T}) where {T,Ti} = SparseVector{T,Ti}
set_eltype_parameter(::Type{<:SVector}, ::Type{T}) where {T} = SVector{N,T} where N
set_eltype_parameter(::Type{<:MVector}, ::Type{T}) where {T} = MVector{N,T} where N

set_size_parameter(::Type{T}, ::Val{N}) where {T,N} = T
set_size_parameter(::Type{<:SVector{N′,T} where N′}, ::Val{N}) where {N,T} = SVector{N,T}
set_size_parameter(::Type{<:MVector{N′,T} where N′}, ::Val{N}) where {N,T} = MVector{N,T}

# TODO: eventually `default_storagetype` should chose types appropriately by taking into account
# the algebra's dimension for optimal memory use/speed
default_storagetype(sig, Tv, Ti) = Vector{Tv}

# NOTE: best_type must work with non-concrete types in order for promotion to work with more than two objects.
# E.g., promote_type(x, x*y, x) == promote_type(promote_type(x, x*y), x)
#                                               \__ non-concrete __/
# Parameters which are allowed to be unspecified are {k,bits}. If any of {sig,S} are unspecified, do not use best_type to promote.

unwrap(::Type{Type{T}}) where T = T
unwrap(::Type{T}) where T = T
# unwrap(::T) where T = T

# must work where each `a...` is a multivector instance *or* a multivector type
@generated function best_type_parameters(a...; set_eltype::Type{SetT}=Nothing, promote_eltype_with::Type{PromT}=Union{}) where {SetT,PromT}
	a = unwrap.(a) # normalize Type{Type{A}} -> Type{A}
	sig = shared_sig(a...)
	T = SetT == Nothing ? promote_type(eltype.(a)..., PromT) : SetT
	S = promote_type(storagetype.(a)...)
	if S === Union{}
		S = default_storagetype(sig, T, UInt)
	end
	S = unwrap(S)
	if S === StorageType error("storagetype promotion failed") end
	S = set_eltype_parameter(S, T)
	sig, T, S
end

# best type if fastest when returning concrete types (as opposed to `UnionAll`s)
function best_type(::Type{Blade}, a...; bits::Val{b}=Val(missing), kwargs...) where b
	sig, T = best_type_parameters(a...; kwargs...)
	ismissing(b) ? Blade{sig,k,bits,T} where {k,bits} : Blade{sig,grade(b),b,T}
end
function best_type(::Type{Multivector}, a...; grade::Val{k}=Val(missing), kwargs...) where k
	sig, T, S = best_type_parameters(a...; kwargs...)
	if ismissing(k)
		Multivector{sig,k,S} where k
	else
		N = binomial(dimension(sig), k)
		Multivector{sig,k,set_size_parameter(S, Val(N))}
	end
end
function best_type(::Type{MixedMultivector}, a...; kwargs...)
	sig, T, S = best_type_parameters(a...; kwargs...)
	S = set_size_parameter(S, Val(2^dimension(sig)))
	MixedMultivector{sig,S}
end

# single argument form is used to modify parameters (e.g., to change the eltype)
best_type(a::Type{<:Blade}; kwargs...) = best_type(Blade, a; bits=Val(bitsof(a)), kwargs...)
best_type(a::Type{<:Multivector{sig,k} where sig}; kwargs...) where k = best_type(Multivector, a; grade=Val(k), kwargs...)
best_type(a::Type{<:Multivector}; kwargs...) = best_type(Multivector, a; kwargs...)
best_type(a::Type{<:MixedMultivector}; kwargs...) = best_type(MixedMultivector, a; kwargs...)
best_type(a::AbstractMultivector; kwargs...) = best_type(typeof(a); kwargs...)

#= CONVERSION

Conversion to more general types is possible:
 Blade ⊂ Multivector ⊂ MixedMultivector

The grade parameter of the target type is *not* respected. For subtypes of `HomogeneousMultivector{sig,k}`,
conversion to a type of a differing grade will still produce an object of grade `k`.
=#

# conversion of element/storage type
Base.convert(::Type{<:Blade{sig,k,bits,T} where {k,bits}}, a::Blade) where {sig,T} = Blade{sig,grade(a),bitsof(a),T}(a.coeff)
Base.convert(T::Type{<:Multivector}, a::Multivector) = best_type(T; grade=Val(grade(a)))(a.components)
Base.convert(::Type{<:MixedMultivector{sig,C}}, a::MixedMultivector) where {sig,C} = MixedMultivector{sig,C}(a.components)

# get (2^dim)-element vector of components for every basis blade in the algebra
full_components_vector(a::MixedMultivector{sig,<:AbstractVector}) where sig = a.components
function full_components_vector(a::HomogeneousMultivector)
	fcv = zeros(eltype(a), 2^dimension(a)) # TODO: use sparse vector? fcv is large and will remain mostly empty
	for b ∈ blades(a)
		fcv[begin + bitsof(b)] = b.coeff
	end
	fcv
end

# conversion from lower multivector type
function Base.convert(T::Type{<:Multivector}, a::Blade)
	T = best_type(T; grade=Val(grade(a)))
	i = bits_to_key(T, bitsof(a))
	comps = [i == j ? convert(eltype(T), a.coeff) : zero(eltype(T)) for j ∈ 1:ncomponents(T)]
	T(comps)
end
Base.convert(T::Type{<:MixedMultivector}, a::AbstractMultivector) = T(full_components_vector(a))

# conversion from scalar
Base.convert(::Type{<:Blade{sig,k,bits,T} where {k,bits}}, a::Scalar) where {sig,T} = Blade{sig,0,bits_scalar(),T}(a)
Base.convert(T::Type{<:Multivector}, a::Scalar) = best_type(T; grade=Val(0))([a])
Base.convert(T::Type{<:MixedMultivector}, a::Scalar) = zero(T) + a

#= PROMOTION

Promotion should result in objects of identical types *except* for the parameters {k,bits}, which should not be changed.
Promotion should only occur between objects of the same signature -- otherwise, fall back on default promotion behaviour.

TODO: is it actually appropriate to define these promote rules? maybe it's more Julian to not do implicit conversions like this...
=#

# same multivector type
Base.promote_rule(T::Type{<:Blade{sig}}, S::Type{<:Blade{sig}}) where sig = best_type(Blade, T, S)
Base.promote_rule(T::Type{<:Multivector{sig}}, S::Type{<:Multivector{sig}}) where sig = best_type(Multivector, T, S)
Base.promote_rule(T::Type{<:MixedMultivector{sig}}, S::Type{<:MixedMultivector{sig}}) where sig = best_type(MixedMultivector, T, S)

# promotion to higher multivector type
Base.promote_rule(T::Type{<:Multivector{sig}}, S::Type{<:Blade{sig}}) where sig = best_type(Multivector, T, S)
Base.promote_rule(T::Type{<:MixedMultivector{sig}}, S::Type{<:AbstractMultivector{sig}}) where sig = best_type(MixedMultivector, T, S)

# promotion from scalars
# note that the grade/bits parameters should *not* be preserved
Base.promote_rule(T::Type{<:AbstractMultivector{sig}}, S::Type{<:Scalar}) where sig = best_type(multivectortype(T), T; promote_eltype_with=S)

#= CONVENIENCE CONSTRUCTORS AND CONVERTERS =#

blade_like(a::AbstractMultivector, coeff=1, bits::Unsigned=bits_scalar()) = Blade{signature(a),grade(bits),bits,eltype(a)}(coeff)

Multivector(a::Blade) = convert(best_type(Multivector, a; grade=Val(grade(a))), a)
MixedMultivector(a::HomogeneousMultivector) = convert(best_type(MixedMultivector, a), a)

"""
	mapcomponents(f, a::AbstractMultivector; kwargs...)

Apply a mapping `f :: Blade -> Scalar` component-wise to `a`.
The function `f` is given a `Blade` representing each component,
and should return a scalar (not a blade).

The same keyword arguments as [`best_type`](@ref) are accepted,
including `set_eltype` and `promote_eltype_with` which can be
used to specify the eltype of the resultant multivector.
"""
mapcomponents(f, a::Blade; kwargs...) = best_type(a; kwargs...)(f(a)) # best_type preserves {k,bits}
function mapcomponents(f, a::CompositeMultivector; kwargs...)
	a′ = zero(best_type(a; kwargs...))
	for b ∈ blades(a)
		a′.components[bits_to_key(a, bitsof(b))] = f(b)
	end
	a′
end

Base.float(T::Type{<:AbstractMultivector}) = best_type(T, set_eltype=float(eltype(T)))
Base.big(T::Type{<:AbstractMultivector}) = best_type(T, set_eltype=big(eltype(T)))
Base.complex(T::Type{<:AbstractMultivector}) = best_type(T, set_eltype=complex(eltype(T)))
Base.real(T::Type{<:AbstractMultivector}) = best_type(T, set_eltype=real(eltype(T)))

Base.float(a::AbstractMultivector) = convert(float(typeof(a)), a)
Base.big(a::AbstractMultivector) = convert(big(typeof(a)), a)
Base.complex(a::AbstractMultivector) = convert(complex(typeof(a)), a)

Base.real(a::AbstractMultivector) = mapcomponents(b -> real(b.coeff), a; set_eltype=real(eltype(a)))
Base.imag(a::AbstractMultivector) = mapcomponents(b -> imag(b.coeff), a; set_eltype=real(eltype(a)))

#= EQUALITY

Only equality between multivectors of the *almost the same type* is defined,
leveraging the promotion interface to enable comparison between differing types.

Multivector promotion (intentionally) does *not* unify the grade and bits parameters,
so the following methods must cover types which are identical except for those parameters.

Note that two blades of differing basis or two multivectors of differing grade are
treated as equal if they are both zero.
=#

==(a::Blade{sig}, b::Blade{sig}) where sig = bitsof(a) == bitsof(b) ? a.coeff == b.coeff : iszero(a) && iszero(b)
==(a::(Multivector{sig,k,S} where k), b::(Multivector{sig,k,S} where k)) where {sig,S} = grade(a) == grade(b) ? a.components == b.components : iszero(a) && iszero(b)
==(a::MixedMultivector{sig,S}, b::MixedMultivector{sig,S}) where {sig,S} = a.components == b.components

==(a::AbstractMultivector, b::Scalar) = iszero(b) && iszero(a) || isscalar(a) && scalar(a) == b
==(a::Scalar, b::AbstractMultivector) = iszero(a) && iszero(b) || isscalar(b) && a == scalar(b)

==(a::AbstractMultivector{sig}...) where sig = ==(promote(a...)...)
==(a::AbstractMultivector...) = false # multivectors with non-identical signatures are teated as non-equal

function Base.isapprox(a::Blade{sig}, b::Blade{sig}; kwargs...) where sig
	if bitsof(a) == bitsof(b)
		isapprox(a.coeff, b.coeff; kwargs...)
	else
		isapprox(a.coeff, zero(a.coeff); kwargs...) && isapprox(b.coeff, zero(a.coeff); kwargs...)
	end
end
Base.isapprox(a::T, b::T; kwargs...) where {T<:MixedMultivector} = isapprox(a.components, b.components; kwargs...)
function Base.isapprox(a::Multivector{sig}, b::Multivector{sig}; kwargs...) where sig
	if grade(a) == grade(b)
		isapprox(a.components, b.components; kwargs...)
	else
		# multivectors of different grade are approximately equal is they are both approximately zero
		isapprox(a, zero(a); kwargs...) && isapprox(b, zero(b); kwargs...)
	end
end
Base.isapprox(a::AbstractMultivector, b::AbstractMultivector; kwargs...) = isapprox(promote(a, b)...; kwargs...)

Base.isapprox(a::AbstractMultivector, b::Scalar; kwargs...) = isapprox(promote(a, b)...; kwargs...)
Base.isapprox(a::Scalar, b::AbstractMultivector; kwargs...) = isapprox(promote(a, b)...; kwargs...)

#= COMPONENT ACCESS =#

blades(a::Blade) = (a,)
blades(a::Multivector{sig,k,<:AbstractVector}) where {sig,k} = (Blade{sig,k}(coeff, bits) for (coeff, bits) ∈ zip(a.components, bits_of_grade(k)))
blades(a::MixedMultivector{sig,<:AbstractVector}) where sig = (Blade{sig}(coeff, unsigned(i - 1)) for (i, coeff) ∈ enumerate(a.components))
# blades(a::CompositeMultivector{<:AbstractDict}) where sig = (Blade{sig}(coeff, bits) for (bits, coeff) ∈ a.components)

"""
	getcomponent(a::AbstractMultivector, I...)
	a[I...]

Return the component of the multivector `a` corresponding to the basis blade
defined by `I`.

Examples
===
```jldoctest
julia> v = basis((1, 1, 1));

julia> a = 1 + 2v[1] + 3v[2]v[3];

julia> a[], a[1], a[2], a[2,3]
(1, 2, 0, 3)

julia> GeometricAlgebra.getcomponent(a, 3, 2)
-3
```
"""
getcomponent

bits_to_key(::Type{<:Multivector{sig,k,<:AbstractVector}}, bits::Unsigned) where {sig,k} = bits_to_linear_index(bits)
bits_to_key(::Type{<:MixedMultivector{sig,<:AbstractVector}}, bits::Unsigned) where sig = 1 + bits
# bits_to_key(::Type{<:CompositeMultivector{<:AbstractDict}}, bits::Unsigned) = bits
bits_to_key(::T, bits) where {T<:AbstractMultivector} = bits_to_key(T, bits)

parity_sign(I) = iseven(parity(sortperm(collect(I)))) ? +1 : -1

function getcomponent(a::Blade, I...)
	indices_to_bits(I) == bitsof(a) || return zero(eltype(a))
	s = parity_sign(I)
	s*a.coeff
end

getcomponent(a::Multivector, I...) = zero(eltype(a))
function getcomponent(a::Multivector{sig,k}, I::Vararg{Any,k}) where {sig,k}
	s = parity_sign(I)
	s*a.components[bits_to_key(a, indices_to_bits(I))]
end

function getcomponent(a::MixedMultivector, I...)
	s = parity_sign(I)
	s*a.components[bits_to_key(a, indices_to_bits(I))]
end

getcomponent(a::MixedMultivector) = first(a.components)

for T ∈ (Integer, Symbol)
	@eval Base.getindex(a::AbstractMultivector{sig}, I::$T...) where sig = getcomponent(a, normalize_bv_index.(Ref(sig), I)...)
end
Base.getindex(a::AbstractMultivector) = getcomponent(a) # for ambiguity

# experimental notations for grade selection
# Base.getindex(a::AbstractMultivector, I::Vector{<:Integer}) = grade(a, Iterators.only(I))
# Base.getindex(a::AbstractMultivector; grade) = GeometricAlgebra2.grade(a, grade)


using Test
using Polyhedra
using JuMP
const MOIT = MOI.Test
const MOIB = MOI.Bridges

@testset "Continuous Linear problems with VRepOptimizer" begin
    optimizer = VRepOptimizer{Float64}()
    @testset "SolverName" begin
        @test MOI.get(optimizer, MOI.SolverName()) == "VRep"
    end
    cache = MOIU.UniversalFallback(Polyhedra._MOIModel{Float64}())
    cached = MOIU.CachingOptimizer(cache, optimizer)
    bridged = MOIB.full_bridge_optimizer(cached, Float64)
    config = MOIT.TestConfig(duals=false)
    MOIT.contlineartest(bridged, config,
                        # linear8a and linear12 will be solved by https://github.com/jump-dev/MathOptInterface.jl/pull/702
                        ["linear8a", "linear12", "partial_start"])
end
@testset "simplex chebyshev center with $T" for T in [Float64, Rational{BigInt}]
    h = HalfSpace([-1, 0], 0) ∩ HalfSpace([0, -1], 0) ∩ HyperPlane([1, 1], 1)
    lib = DefaultLibrary{T}(VRepOptimizer{T})
    poly = polyhedron(h, lib)
    center, radius = chebyshevcenter(poly)
    @test center ≈ [1/2, 1/2]
    @test radius ≈ 1/2
    @test dim(poly) == 1
end


mutable struct SpanningTreeLightGraphsPrimSolver <: SpanningTreeSolver
  graph::SimpleGraph
  weights_matrix::Matrix{Float64}

  function SpanningTreeLightGraphsPrimSolver()
    return new(Graph(0), zeros(0, 0))
  end
end

function build!(solver::SpanningTreeLightGraphsPrimSolver, graph::SimpleGraph)
  n = nv(graph)
  solver.graph = graph
  solver.weights_matrix = zeros(n, n)
end

function solve_linear(solver::SpanningTreeLightGraphsPrimSolver, reward::Dict{Tuple{Int, Int}, Float64})
  # Make up a rewards matrix by copying the input dictionary into the right data structure for the shortest path computations.
  fill!(solver.weights_matrix, 0.0)
  for (k, v) in reward
    solver.weights_matrix[k[1], k[2]] = v
  end
  solver.weights_matrix = maximum(solver.weights_matrix) .- solver.weights_matrix

  # Compute the maximum spanning tree.
  mst = prim_mst(solver.graph, solver.weights_matrix)

  # Transform edges into tuples.
  return _mst_solution_normalise(reward, [(src(e), dst(e)) for e in mst])
end

has_lp_formulation(::SpanningTreeLightGraphsPrimSolver) = false


function seysen{Td}(H::Array{Td,2})
# [B,T,B_dual,num_it] = seysen{Td}(H::Array{Td,2})
#
# Do greedy Seysen lattice reduction on the matrix H (real or
# complex-valued), returning T a unimodular matrix that reduces H;
# B, the reduced lattice basis (i.e. B = H*T);
# B_dual,  dual lattice basis (i.e., B_dual = pinv(B)); and
# the number of iterations (the number of basis updates).

# Follows LLL in D. Wueben, et al, MMSE-Based Lattice-Reduction for Near-ML
# Detection of MIMO Systems International IEEE Workshop on Smart Antennas,
# Munich, March 2004.

if Td<:BigInt || Td<:BigFloat
    Ti= Td<:Complex?Complex{BigInt}:BigInt
else
    if Td<:Integer
        Ti= Td<:Complex?Complex{Td}:Td
    else
        Ti= Td<:Complex?Complex{Int}:Int
    end
end
roundf(r) = Td<:Complex?round(real(r)) + im*round(imag(r)):round(r);

#H = H*1.0

# get size of input
(n, m) = size(H); # m-dimensional lattice in an n-dimensional space

# initialization, outputs
B      = copy(H);    # reduced lattice basis
num_it = 0;          # number of iterations
T      = eye(Ti,m);  # unimodular matrix
A      = (H'*H)*1.0;       # Gram matrix of H
Adual  = inv(A);     # Inverse gram matrix of H
B_dual = H*Adual;    # Dual basis

# calculate all possible update values Λ[s,t)
# and their corresponding reduction Δ[s,t) in Seysen's measure
Λ = zeros(Ti,m,m);
Δ = zeros(Td,m,m)*0.0;

for s = 1:m
    for t = 1:m
        if s != t
	    x = 0.5*(Adual[t,s]/Adual[s,s]-A[t,s]/A[t,t]);
            Λ[s,t] = roundf(x);
	    AbsΛ   = abs(Λ[s,t])^2;
	    if AbsΛ != 0
	        zw = real(Λ[s,t])*real(x)+imag(Λ[s,t])*imag(x);
	        Δ[s,t]  = Adual[s,s]*A[t,t]*(2*zw-AbsΛ);
	    end
        end
    end
end # - end calculation of Λ and Δ

# find maximum reduction in Seysen's measure (greedy approach)
(zw,max_ind) = findmax(abs(Δ[:]));
(s, t)        = ind2sub((m,m),max_ind);

# init loop
do_reduction = true;

# if no improvement can be achieved
if Δ[s,t] == 0
    do_reduction = false;
end

# Lattice reduction loop of subsequent basis updates
while do_reduction

    num_it = num_it + 1;

    # perform basis update
    try
        B[:,s] = B[:,s] + Λ[s,t]*B[:,t];
    catch
        println("B[:,s] = $(B[:,s]), typeof(B[:,s]) = $(typeof(B[:,s]))")
        println("Λ[s,t] = $(Λ[s,t]), typeof(Λ[s,t]) = $(typeof(Λ[s,t]))")
    end

    # compute corresponding unimodular trasformation matrix
    T[:,s] = T[:,s] + Λ[s,t]*T[:,t];  # basis transformation matrix

    # update corresponding dual basis
    B_dual[:,t] = B_dual[:,t] - Λ[s,t]'*B_dual[:,s];

    # Update Gram and inverse Gram matrix
    for ind = 1:m

        # update Gram matrix
        if ind != s
            A[s,ind] = A[s,ind]+Λ[s,t]'*A[t,ind];
            A[ind,s] = A[s,ind]';
        else
            A[s,ind] = norm(B[:,s]).^2;
        end

        # update inverse Gram matrix
        if ind != t
            Adual[t,ind] = Adual[t,ind]-Λ[s,t]*Adual[s,ind];
            Adual[ind,t] = Adual[t,ind]';
        else
            Adual[t,ind] = norm(B_dual[:,t])^2;
        end

    end # - end update Gram und inverse Gram matrix

    # update all possible update values Λ[s,t]
    # and their corresponding reduction Δ[s,t] in Seysen's measure
    for ind1 = 1:m
        for ind2 = 1:m
            if ((ind1==s) | (ind1==t) | (ind2==s) | (ind2==t)) & (ind1!=ind2)
                x = 0.5*(Adual[ind2,ind1]/Adual[ind1,ind1]-A[ind2,ind1]/
                         A[ind2,ind2]);
                ## There is an intermitent bug that I haven't yet figured
                ## out that this try/catch code is aimed at. Yes, it's ugly...
                try
                    Λ[ind1,ind2] = roundf(x);
                catch
                    println("x = $(x), typeof(x) = $(typeof(x))")
                    println("A[ind2,ind2] = $(A[ind2,ind2]),  "*
                            "typeof(A[ind2,ind2]) = $(typeof(A[ind2,ind2]))")
                    println("roundf(x)) = $(roundf(x))  "*
                            "typeof(roundf(x))) = $(typeof(roundf(x)))")
                    println("Λ[ind1,ind2]) = $(Λ[ind1,ind2])  "*
                            "typeof(Λ[ind1,ind2])) = $(typeof(Λ[ind1,ind2]))")
                    println(" ")
                end
                AbsΛ = abs(Λ[ind1,ind2])^2;
                if AbsΛ != 0
                    zw = real(Λ[ind1,ind2])*real(x)+imag(Λ[ind1,ind2])*imag(x);
                    Δ[ind1,ind2] = Adual[ind1,ind1]*A[ind2,ind2]*(2*zw-AbsΛ);
                else
                    Δ[ind1,ind2] = 0;
                end
            end
        end
    end # - end update Λ and Δ

    # find maximum reduction in Seysen's measure (greedy approach)
    (zw, max_ind) = findmax(abs(Δ[:]));
    (s, t)        = ind2sub((m,m),max_ind);

    # if no reduction is possible, exit loop
    if Δ[s,t] == 0
        do_reduction = false;
    end

end # - end lattice reduction loop

return (B,T,B_dual,num_it)
end


using Pkg
Pkg.activate("../Scratchy")
using Scratchy, Scratchy.Layers, Scratchy.Losses
using Scratchy.Activations, Scratchy.Models

X = [0 0; 1 1; 0 1; 1 0]
y = [0, 0, 1, 1]

layer1 = Dense("layer1", (2, 3), Sigmoid)
layer2 = Dense("layer2", (3, 1), Sigmoid)
net = Network("FFNN", [layer1, layer2], BinaryCrossEntropy, 0.2)
Scratchy.Models.display(net)

fit!(net, X, y, 1000)

logits = Scratchy.Models.forward!(net, X)
preds = Scratchy.Utils.decision(logits)
acc = Scratchy.Utils.accuracy(y, preds)

println("Logits: $(logits)")
println("Predictions: $(preds)")
println("Gold Labels: $(y)")
println("Accuracy: $(acc)")


function update_mu!(state::State, data::Data, prior::Prior)
  m_mu, s_mu = params(prior.mu)

  vkernels = zero.(state.mu)
  mkernels = zero.(state.mu)

  for i in samplenames
    yfinite = ref_yfinite(data, i)
    lambda = ref_lambda(state, i)
    zeta = ref_zeta(state, i)
    v = ref_v(state, i)
    for n in eachindex(lambda)
      k = lambda[n]
      vkernels[k] += v[n]
      mkernels[k] += (yfinite[n] - state.psi[k] * zeta[n]) * v[n]
    end
  end

  vnew = 1 ./ (s_mu^-2 .+ vkernels ./ state.omega)
  mnew = vnew .* (m_mu/(s_mu^2) .+ mkernels ./ state.omega)

  state.mu .= randn(prior.K) .* sqrt.(vnew) + mnew
end


# const max_moment_levels = 2 #Just for mean and covariance matrix
const Children = Union{Vector{Vector{Int}},Nothing}

mutable struct BoxTruncatedMvNormalRecursiveMomentsState <: TruncatedMvDistributionState
    d::MvNormal
    r::BoxTruncationRegion
    n::Int                  #dimension
    max_moment_levels::Int

    # # #Children of type 'a' or 'b' are lower dimensional distributions used to for recursive computation
    children_a::Vector{BoxTruncatedMvNormalRecursiveMomentsState}
    children_b::Vector{BoxTruncatedMvNormalRecursiveMomentsState}

    # # #for each moment vector e.g. [0,1,0,1] or [0,0,2,0] has the tuple which is the computed (non-normalized) moment integral
    # # #of that vector and a list of children vectors
    rawMomentDict::Dict{Vector{Int},Float64} #note that the values are non-normalized moment integrals
    treeDict::Dict{Vector{Int},Children}
    rawMomentsComputed::Bool

    tp::Float64
    μ::Vector{Float64}
    Σ::PDMat
    tp_err::Float64
    μ_err::Float64
    Σ_err::Float64

    function BoxTruncatedMvNormalRecursiveMomentsState(d::MvNormal, r::BoxTruncationRegion, max_moment_levels::Int)
        μₑ, Σₑ = d.μ, d.Σ
        a, b = r.a, r.b
        n = length(d)
        length(a) != n && error("The length of a does not match the length")
        length(b) != n && error("The length of b does not match the length")
        a > b && error("The a vector must be less than the b vector")

        if n ≥ 2
            μᵃ = [μₑ[setdiff(1:n,j)]  +  Σₑ[setdiff(1:n,j),j] * (a[j]-μₑ[j])/Σₑ[j,j]
                    for j in 1:n]
            μᵇ = [μₑ[setdiff(1:n,j)]  +  Σₑ[setdiff(1:n,j),j] * (b[j]-μₑ[j])/Σₑ[j,j]
                    for j in 1:n]
            Σ̃ = [Σₑ[setdiff(1:n,j),setdiff(1:n,j)] - (1/Σₑ[j,j])*Σₑ[setdiff(1:n,j),j]*Σₑ[j,setdiff(1:n,j)]' for j in 1:n]
            children_a = [BoxTruncatedMvNormalRecursiveMomentsState(
                            MvNormal(μᵃ[j],Σ̃[j]),
                            BoxTruncationRegion(a[setdiff(1:n,j)],b[setdiff(1:n,j)]),
                            max_moment_levels) for j in 1:n]
            children_b = [BoxTruncatedMvNormalRecursiveMomentsState(
                            MvNormal(μᵇ[j],Σ̃[j]),
                            BoxTruncationRegion(a[setdiff(1:n,j)],b[setdiff(1:n,j)]),
                            max_moment_levels) for j in 1:n]
        else #n==1
            children_a = Array{BoxTruncatedMvNormalRecursiveMomentsState,1}[] #no children
            children_b = Array{BoxTruncatedMvNormalRecursiveMomentsState,1}[] #no children
        end
        rawMomentDict, treeDict = init_dicts(n,max_moment_levels)
        new(d,r,n,max_moment_levels,children_a,children_b,rawMomentDict,treeDict,false,
            NaN,
            Vector{Float64}(undef,0),
            PDMat(Array{Float64,2}(I,n,n)),
            Inf, Inf, Inf)
    end
end

function init_dicts(n::Int,max_moment_levels::Int)
    function addToBaseKey(  baseKey::Vector{Int},
                            n::Int,
                            md::Dict{Vector{Int},Float64},
                            td::Dict{Vector{Int},Children})
        keys = Vector{Vector{Int}}(undef,n)
        for i in 1:n
            key = copy(baseKey)
            key[i] += 1
            md[key] = NaN
            td[key] = nothing
            keys[i] = key
        end
        md[baseKey] = NaN
        td[baseKey] = keys
        keys
    end

    md = Dict{Vector{Int},Float64}() #rawMomentDict
    td = Dict{Vector{Int},Children}() #treeDict
    rootKey = zeros(Int,n)
    key_vals = [rootKey]
    for _ = 1:max_moment_levels
        levelKeys = Vector{Int}[]
        for key in key_vals
            newKeys = addToBaseKey(key,n,md,td)
            append!(levelKeys,newKeys)
        end
        key_vals = levelKeys
    end
    md,td
end

function compute_moments(d::BoxTruncatedMvNormalRecursiveMomentsState)
    function compute_children_moments(d::BoxTruncatedMvNormalRecursiveMomentsState,baseKey::Vector{Int})
        d.treeDict[baseKey] == nothing && return #recursion stopping criteria
        c = c_vector(d,baseKey)
        for k in d.treeDict[baseKey]
            i = findfirst((x)->x==1,k-baseKey)
            d.rawMomentDict[k] = d.d.μ[i]*d.rawMomentDict[baseKey] + (d.d.Σ*c)[i]
            compute_children_moments(d,k)
        end
    end

    function c_vector(d::BoxTruncatedMvNormalRecursiveMomentsState,k::Vector{Int})
        c = Vector{Float64}(undef,d.n)
        for j in 1:d.n
            kMinus = copy(k)
            kMinus[j] = kMinus[j]-1
            F0 = (sum(kMinus .>= 0) == d.n) ? d.rawMomentDict[kMinus] : 0.0
            F1 = length(d.children_a) >= 1 ? raw_moment(d.children_a[j],k[setdiff(1:d.n,j)]) : 0.0
            F2 = length(d.children_b) >= 1 ? raw_moment(d.children_b[j],k[setdiff(1:d.n,j)]) : 0.0
            ϕ = pdf.(Normal(d.d.μ[j],sqrt(d.d.Σ[j,j])),[d.r.a[j],d.r.b[j]] )
            c[j] =  k[j]*F0  +  d.r.a[j]^k[j]*ϕ[1]*F1  -  d.r.b[j]^k[j]*ϕ[2]*F2
        end
        c
    end

    if d.n > 1
        baseKey = zeros(Int,d.n) #[0,0,....,0]
        d.rawMomentDict[baseKey] = LL(d)
        compute_children_moments(d,baseKey) #start recursion
    else  #n==1
        @assert d.n == 1
        distTruncated = TruncatedNormal(d.d.μ[1],sqrt(d.d.Σ[1]),d.r.a[1],d.r.b[1])
        d.rawMomentDict[[0]] = distTruncated.tp
        m = moments(distTruncated, d.max_moment_levels)
        for i in 1:d.max_moment_levels
            d.rawMomentDict[[i]] = m[i]*distTruncated.tp
        end
    end
    d.rawMomentsComputed = true
end

function raw_moment(d::BoxTruncatedMvNormalRecursiveMomentsState,k::Vector{Int})
    !d.rawMomentsComputed && compute_moments(d)
    return d.rawMomentDict[k]
end

# moment(d::BoxTruncatedMvNormalRecursiveMomentsState,k::Vector{Int}) = raw_moment(d,k) / alpha(d)

# alpha(d::BoxTruncatedMvNormalRecursiveMomentsState) = raw_moment(d,zeros(Int,d.n))

# function mean(d::BoxTruncatedMvNormalRecursiveMomentsState)
#     μ = Vector{Float64}(undef,d.n)
#     for i in 1:d.n
#         ee = zeros(Int,d.n)
#         ee[i] = 1
#         μ[i] = moment(d,ee)
#     end
#     μ
# end

# function cov(d::BoxTruncatedMvNormalRecursiveMomentsState)
#     Σ = zeros(Float64,d.n,d.n)
#     for i in 1:d.n, j in 1:d.n
#         ee = zeros(Int,d.n)
#         if i == j
#             ee[i] = 2
#         else
#             ee[i], ee[j] = 1, 1
#         end
#         Σ[i,j] = moment(d,ee)
#     end
#     μ = mean(d)
#     Σ-μ*μ'
# end

# function rand(d::BoxTruncatedMvNormalRecursiveMomentsState)
#     rand(MvNormal(d.μₑ,d.Σₑ)) #TODO QQQQ
# end

# function pdf_nontruncated(d::BoxTruncatedMvNormalRecursiveMomentsState,x)
#     d_nontruncated = MvNormal(d.μₑ,d.Σₑ)
#     pdf(d_nontruncated,x)
# end

function LL(d::BoxTruncatedMvNormalRecursiveMomentsState)
    @info "doing base numerical integral on dimension $(d.n)."
    hcubature((x)->pdf(d.d,x),d.r.a,d.r.b,maxevals = 10^6)[1]
end


using CompScienceMeshes
using Test

m = meshsegment(1.0, 1.0, 3)

p1 = point(0.5, 0, 0)
p2 = point(0.5, 1e-4, 0)
p3 = point(0.0, 0, 0)
p4 = point(-1e-4, 0, 0)

f = inclosure_gpredicate(m)

##
@test f(p1) == true
@test f(p2) == false
@test f(p3) == true
@test f(p4) == false


function is_stationary(v, th, w)
    length(v) < w && return false
    @views pv = v[end - w + 1:end]
    m = Statistics.mean(pv)
    m = iszero(m) ? one(m) : m
    s = Statistics.std(pv)
    return s <= abs(m) * th
end


abstract type InfiniteOrderKernel <: ContinuousUnivariateDistribution end

function Base.show(io::IO, d::InfiniteOrderKernel)
    print(io, nameof(typeof(d)))
    print(io, " | bandwidth = ")
    print(io, d.h)
end

"""
    SincKernel(h) <: InfiniteOrderKernel

Implements the `SincKernel` with bandwidth `h` to be used for kernel density estimation
through the `KernelDensity.jl` package. The sinc kernel is defined as follows:
```math
K_{\\text{sinc}}(x) = \\frac{\\sin(x)}{\\pi x}
```
It is not typically used for kernel density estimation, because this kernel is not
a density itself. However, it is particularly well suited to deconvolution problems
and estimation of very smooth densities because its Fourier transform is the following:
```math
K^*_{\\text{sinc}}(t) = \\mathbf 1( t \\in [-1,1])
```
"""
struct SincKernel{H} <: InfiniteOrderKernel
    h::H
end
SincKernel() = SincKernel(DataBasedDefault())

function Empirikos._set_defaults(
    kernel::InfiniteOrderKernel,
    Zs::AbstractVector{<:Empirikos.AbstractNormalSample};
    kwargs...,
)
    Empirikos.skedasticity(Zs) == Empirikos.Homoskedastic() ||
        throw("Only implemented for Homoskedastic Gaussian data.")
    σ = std(Zs[1])
    n = length(Zs)
    h = σ / sqrt(log(n))
    @set kernel.h = h
end

Distributions.cf(a::SincKernel, t) = one(Float64) * (-1 / a.h <= t <= 1 / a.h)

function Distributions.pdf(a::SincKernel, t::Real)
    if t == zero(Float64)
        return (one(Float64) / pi / a.h)
    else
        return (sin(t / a.h) / pi / t)
    end
end

"""
    DeLaValleePoussinKernel(h) <: InfiniteOrderKernel

Implements the `DeLaValleePoussinKernel` with bandwidth `h` to be used for kernel density estimation
through the `KernelDensity.jl` package. The De La Vallée-Poussin kernel is defined as follows:
```math
K_V(x) = \\frac{\\cos(x)-\\cos(2x)}{\\pi x^2}
```
Its use case is similar to the [`SincKernel`](@ref), however it has the advantage of being integrable
(in the Lebesgue sense) and having bounded total variation. Its Fourier transform is the following:
```math
K^*_V(t) = \\begin{cases}
 1, & \\text{ if } |t|\\leq 1 \\\\
 0, &\\text{ if } |t| \\geq 2 \\\\
 2-|t|,& \\text{ if } |t| \\in [1,2]
 \\end{cases}
```
"""
struct DeLaValleePoussinKernel{H} <: InfiniteOrderKernel
    h::H
end

DeLaValleePoussinKernel() = DeLaValleePoussinKernel(DataBasedDefault())

function Distributions.cf(a::DeLaValleePoussinKernel, t)
    if abs(t * a.h) <= 1
        return (one(Float64))
    elseif abs(t * a.h) <= 2
        return (2 * one(Float64) - abs(t * a.h))
    else
        return (zero(Float64))
    end
end

function Distributions.pdf(a::DeLaValleePoussinKernel, t::Real)
    if t == zero(Float64)
        return (3 * one(Float64) / 2 / pi / a.h)
    else
        return (a.h * (cos(t / a.h) - cos(2 * t / a.h)) / pi / t^2)
    end
end

"""
    FlatTopKernel(h) < InfiniteOrderKernel

Implements the `FlatTopKernel` with bandwidth `h` to be used for kernel density estimation
through the `KernelDensity.jl` package. The flat-top kernel is defined as follows:
```math
K(x) = \\frac{\\sin^2(1.1x/2)-\\sin^2(x/2)}{\\pi x^2/ 20}.
```
Its use case is similar to the [`SincKernel`](@ref), however it has the advantage of being integrable
(in the Lebesgue sense) and having bounded total variation. Its Fourier transform is the following:
```math
K^*(t) = \\begin{cases}
 1, & \\text{ if } t|\\leq 1 \\\\
 0, &\\text{ if } |t| \\geq 1.1 \\\\
 11-10|t|,& \\text{ if } |t| \\in [1,1.1]
 \\end{cases}
```

```jldoctest
julia> Empirikos.FlatTopKernel(0.1)
FlatTopKernel | bandwidth = 0.1
```
"""
struct FlatTopKernel{H} <: InfiniteOrderKernel
    h::H
end

FlatTopKernel() = FlatTopKernel(DataBasedDefault())

function Distributions.cf(a::FlatTopKernel, t)
    if abs(t * a.h) <= 1
        return (one(Float64))
    elseif abs(t * a.h) <= 1.1
        return (one(Float64) - 10 * (abs(t * a.h) - one(Float64)))
    else
        return (zero(Float64))
    end
end

# TODO: This is *not* numerically stable.
# But presumably do not need it to be (since the KDE code just calls cf)
function Distributions.pdf(a::FlatTopKernel, t::Real)
    h = a.h
    th = t / h
    if t == zero(Float64)
        return ((0.55^2 - 0.5^2) * 20 / pi / h)
    else
        return ((abs2(sin(0.55 * th)) - abs2(sin(0.5 * th))) / pi / th^2 * 20 / h)
    end
end

"""
    InfinityNormDensityBand(;a_min,
                             a_max,
                             kernel  =  Empirikos.FlatTopKernel(),
                             bootstrap = :Multinomial,
                             nboot = 1000,
                             α = 0.05,
                             rng = Random.MersenneTwister(1)
                        )  <: FLocalization

This struct contains hyperparameters that will be used for constructing a neighborhood
of the marginal density. The steps of the method (and corresponding hyperparameter meanings)
are as follows
* First a kernel density estimate ``\\bar{f}`` with `kernel` is fit to the data.
* Second, a `bootstrap` (options: `:Multinomial` or `Poisson`) with `nboot` bootstrap replicates will be used to estimate ``c_n``, such that:
```math
\\liminf_{n \\to \\infty}\\mathbb{P}\\left[\\sup_{x \\in [a_{\\text{min}} , a_{\\text{max}}]} | \\bar{f}(x) - f(x)| \\leq c_ n\\right] \\geq 1-\\alpha
```
Note that the bound is valid from `a_min` to `a_max`. `α` is the nominal level and finally
`rng` sets the seed for the bootstrap samples.
"""
Base.@kwdef struct InfinityNormDensityBand <: FLocalization
    a_min = DataBasedDefault()
    a_max = DataBasedDefault()
    npoints::Integer = 1024
    kernel = FlatTopKernel()
    bootstrap = :Multinomial
    nboot::Integer = 1000
    α::Float64 = 0.05
    rng = Random.MersenneTwister(1)
end

Empirikos.vexity(::InfinityNormDensityBand) = Empirikos.LinearVexity()
function Empirikos._set_defaults(
    method::InfinityNormDensityBand,
    Zs::AbstractVector{<:Empirikos.AbstractNormalSample{<:Number}};
    hints...,
)
    Empirikos.skedasticity(Zs) == Empirikos.Homoskedastic() ||
        throw("Only implemented for Homoskedastic Gaussian data.")

    q = get(hints, :quantile, 0.1)
    a_min, a_max = quantile(response.(Zs), (q, 1 - q))
    if isa(method.a_min, DataBasedDefault)
        method = @set method.a_min = a_min
    end
    if isa(method.a_max, DataBasedDefault)
        method = @set method.a_max = a_max
    end
    method
end

"""
    FittedInfinityNormDensityBand

The result of running
```julia
StatsBase.fit(opt::InfinityNormDensityBand, Zs)```
Here `opt` is an instance
of [`InfinityNormDensityBand`](@ref) and `Zs` is a vector of [`AbstractNormalSample`](@ref)s
distributed according to a density ``f``..

## Fields:
* `a_min`,`a_max`, `kernel`: These are the same as the fields in `opt::InfinityNormDensityBand`.
* `C∞`: The half-width of the L∞ band.
* `fitted_kde`: The fitted `KernelDensity` object.
"""
Base.@kwdef struct FittedInfinityNormDensityBand{T<:Real,S,K} <: FittedFLocalization
    C∞::T
    a_min::T
    a_max::T
    fitted_kde::Any
    interp_kde::Any
    midpoints::S
    estimated_density::K
    boot_samples::Any
    method = nothing
end

Empirikos.vexity(method::FittedInfinityNormDensityBand) = Empirikos.LinearVexity()

function Empirikos.nominal_alpha(inftyband::FittedInfinityNormDensityBand)
    Empirikos.nominal_alpha(inftyband.method)
end

function StatsBase.fit(
    opt::InfinityNormDensityBand,
    Zs::AbstractVector{<:Empirikos.AbstractNormalSample{<:Number}};
    kwargs...,
)
    Empirikos.skedasticity(Zs) == Empirikos.Homoskedastic() ||
        throw("Only implemented for Homoskedastic Gaussian data.")
    opt = Empirikos.set_defaults(opt, Zs; kwargs...)

    @unpack a_min, a_max, npoints, kernel, nboot, α, bootstrap, rng = opt

    # deepcopying below to make sure RNG status for sampling here remains the same.
    rng = deepcopy(rng)

    res = certainty_banded_KDE(
        response.(Zs),
        a_min,
        a_max;
        npoints = npoints,
        rng = rng,
        kernel = kernel,
        bootstrap = bootstrap,
        nboot = nboot,
        α = α,
    )
    res = @set res.method = opt
    Z = Zs[1]
    normal_midpoints = [@set Z.Z = mdpt for mdpt in res.midpoints]
    res = @set res.midpoints = normal_midpoints
    res
end

function certainty_banded_KDE(
    Xs,
    a_min,
    a_max;
    kernel,
    rng = Random._GLOBAL_RNG,
    npoints = 4096,
    nboot = 1_000,
    bootstrap = :Multinomial,
    α = 0.5,
)

    h = kernel.h
    m = length(Xs)

    lo, hi = extrema(Xs)
    # avoid FFT wrap-around numerical difficulties
    lo_kde, hi_kde = min(a_min, lo - 6 * h), max(a_max, hi + 6 * h)
    midpts = range(lo_kde, hi_kde; length = npoints)

    fitted_kde = kde(Xs, KernelDensity.UniformWeights(m), midpts, kernel)
    interp_kde = InterpKDE(fitted_kde)

    # quantities only at points of interest
    midpts_idx = findall((midpts .>= a_min) .& (midpts .<= a_max))
    midpoints = fitted_kde.x[midpts_idx]
    estimated_density = fitted_kde.density[midpts_idx]

    C∞_boot = Vector{Float64}(undef, nboot)

    if bootstrap === :Multinomial
        multi = Multinomial(m, fill(1 / m, m))
    end
    for k = 1:nboot
        if bootstrap === :Poisson # Poisson bootstrap to estimate certainty band
            Z_pois = rand(rng, Poisson(1), m)
            ws = Weights(Z_pois / sum(Z_pois))
        elseif bootstrap === :Multinomial # Efron bootstrap
            ws = Weights(vec(rand(rng, multi, 1)))
        else
            throw(error("Only :Multinomial and :Poisson supported."))
        end
        f_kde_pois = kde(Xs, ws, midpts, kernel)
        C∞_boot[k] = maximum(abs.(estimated_density .- f_kde_pois.density[midpts_idx]))
    end

    C∞ = quantile(C∞_boot, 1 - α)

    FittedInfinityNormDensityBand(
        C∞ = C∞,
        a_min = a_min,
        a_max = a_max,
        fitted_kde = fitted_kde,
        interp_kde = interp_kde,
        midpoints = midpoints,
        estimated_density = estimated_density,
        boot_samples = C∞_boot,
    )
end

@recipe function f(ctband::FittedInfinityNormDensityBand; subsample = 300)
    y_all = ctband.estimated_density
    x_all = response.(ctband.midpoints)

    n = length(y_all)
    _step = div(n - 2, subsample)
    idxs = [1; 2:_step:(n-1); n]
    x = x_all[idxs]
    y = y_all[idxs]

    xlims --> (a_min, a_max)
    ylims --> (0, maximum(y) * 1.1)
    yguide --> "Density"

    α = nominal_alpha(ctband)
    ci_level = 100 * (1 - α)

    cis_ribbon = ctband.C∞
    fillalpha --> 0.36
    seriescolor --> "#018AC4"
    ribbon --> cis_ribbon
    framestyle --> :box
    legend --> :topleft
    label --> "$ci_level% CI"

    x, y
end

function Empirikos.flocalization_constraint!(
    model,
    ctband::FittedInfinityNormDensityBand,
    prior::Empirikos.PriorVariable,
)

    C∞ = ctband.C∞
    _midpoints = ctband.midpoints
    _density_values = ctband.estimated_density

    # TODO: perhaps move this evaluation below
    _min, _max = extrema(Empirikos.MarginalDensity(_midpoints[1]))
    for (index, Z) in enumerate(_midpoints)
        _density_hat = _density_values[index]
        marginal_pdf = pdf(prior, Z::EBayesSample)

        if _density_hat + C∞ < _max
            @constraint(model, marginal_pdf <= _density_hat + C∞)
        end
        if _density_hat - C∞ > _min
            @constraint(model, marginal_pdf >= _density_hat - C∞)
        end
    end
    model
end