mutable struct Body
    vertices

    position
    velocity

    angle  # orientation
    angular_velocity

    force
    torque

    restitution
    friction

    mass
    inertia
end

"""
    translate!(body::Body, translation::Vector)

Translate a body by a given vector without imparting velocity.
"""
function translate!(body::Body, translation::Vector)
    body.position += translation
end

"""
    rotate!(body::Body, rotation)

Rotate a body by a given angle without imparting angular velocity.
"""
function rotate!(body::Body, rotation)
    body.angle += rotation
end

"""
    update!(body::Body, dt)

Update the position, velocity, angle, and angular velocity of the body.
"""
function update!(body::Body, dt)

    # update velocity
    body.velocity.x += (body.force.x / body.mass) * dt
    body.velocity.y += (body.force.y / body.mass) * dt

    # TODO: add friction/drag

    # update position
    body.position.x += body.velocity.x
    body.position.y += body.velocity.y

    # update angular velocity
    body.angular_velocity += (body.torque / body.inertia) * dt

    # TODO: add friction/drag

    # update angle
    body.angle += body.angular_velocity

    # update vertices
    translate!(body.vertices, body.velocity)
    rotate!(body.vertices, body.angular_velocity)
end

"""
    apply_force!(body::Body, force, position)

Apply a force to a body at a given position.
"""
function apply_force!(b::Body, force, position)
    body.force += force
    offset = position - body.position
    body.torque += offset × force  # "cross product" = "determinant"
end


using BenchmarkTools
using Clustering, Distances, Random

Random.seed!(345678)

const SUITE = BenchmarkGroup()

SUITE["hclust"] = BenchmarkGroup()

function random_distance_matrix(n::Integer, m::Integer=10, dist::PreMetric=Euclidean())
    pts = rand(m, n)
    return Distances.pairwise(dist, pts, dims=2)
end

function hclust_benchmark(n::Integer, m::Integer=10, dist::PreMetric=Euclidean())
    res = BenchmarkGroup()
    for linkage in ("single", "average", "complete")
        res[linkage] = @benchmarkable hclust(D, linkage=Symbol($linkage)) setup=(D=random_distance_matrix($n, $m, $dist))
    end
    return res
end

for n in (10, 100, 1000, 10000)
    SUITE["hclust"][n] = hclust_benchmark(n)
end

SUITE["cutree"] = BenchmarkGroup()

for (n, k) in ((10, 3), (100, 10), (1000, 100), (10000, 1000))
    SUITE["cutree"][(n,k)] = @benchmarkable cutree(hclu, k=$k) setup=(D=random_distance_matrix($n, 5); hclu=hclust(D, linkage=:single))
end


using LinearAlgebra: BlasComplex, BlasFloat, BlasReal
"""
a "safe" version of `floatmin`, such that `1/sfmin` does not overflow.
"""
function safemin(T)
    sfmin = floatmin(T)
    small = one(T) / floatmax(T)
    if small >= sfmin
        sfmin = small * (one(T) + eps(T))
    end
    sfmin
end

function _scale!(A::AbstractArray{T}) where {T}
    smlnum = sqrt(safemin(real(T))) / eps(real(T))
    bignum = 1 / smlnum
    anrm = norm(A,Inf)
    scaleA = false
    cscale = one(real(T))
    if anrm > 0 && anrm < smlnum
        scaleA = true
        cscale = smlnum
    elseif anrm > bignum
        scaleA = true
        cscale = bignum
    end
    scaleA && safescale!(A, anrm, cscale)
    scaleA, cscale, anrm
end

abs1(z) = abs(z)
abs1(z::T) where {T <: Complex} = abs(real(z))+abs(imag(z))

# translated from xLASCL (LAPACK)
"""
multiply an array `A` by a real number `cto/cfrom` expressed as a ratio,
computing without over/underflow where possible.

`cfrom` must not be zero.
"""
function safescale!(A::AbstractArray{T}, cfrom::Real, cto::Real) where T
    # CHECKME: what are correct type constraints on cfrom,cto?
    isnan(cto) && throw(ArgumentError("cto must be a valid number"))
    (isnan(cfrom) || (cfrom == 0)) && throw(ArgumentError("cfrom must be a valid nonzero number"))
    smlnum = safemin(real(T))
    bignum = 1/smlnum
    cfromc = cfrom
    ctoc = cto
    done = false
    while !done
        cfrom1 = cfromc * smlnum
        if cfrom1 == cfromc
            # cfromc is ±∞; multiplier is signed 0 or NaN
            mul = ctoc / cfromc
            done = true
            cto1 = ctoc
        else
            cto1 = ctoc / bignum
            if cto1 == ctoc
                # ctoc is 0 or ±∞, so is correct multiplier
                mul = cto
                done = true
                cfromc = one(real(T))
            elseif abs(cfrom1) > abs(ctoc) && (ctoc != 0)
                mul = smlnum
                done = false
                cfromc = one(real(T))
            elseif abs(cto1) > abs(cfromc)
                mul = bignum
                done = false
                cfromc = cfrom1
            else
                mul = ctoc / cfromc
                done = true
            end
        end
        A .= A .* mul
    end
end

# Rank one update

## General
### BLAS
rankUpdate!(α::T, x::StridedVector{T}, y::StridedVector{T}, A::StridedMatrix{T}) where {T<:BlasReal} = BLAS.ger!(α, x, y, A)

### Generic
function rankUpdate!(α::Number, x::StridedVector, y::StridedVector, A::StridedMatrix)
    m, n = size(A, 1), size(A, 2)
    m == length(x) || throw(DimensionMismatch("x vector has wrong length"))
    n == length(y) || throw(DimensionMismatch("y vector has wrong length"))
    for j = 1:n
        yjc = y[j]'
        for i = 1:m
            A[i,j] += x[i]*α*yjc
        end
    end
end

# index-unsafe upper triangular solve
# with scaling to prevent overflow
# Actually solves A[1:n,1:n] * x = s * b and returns s, a scaling factor
# such that components of x are manageable.
# NB: works with leading n×n block of A and leading n terms of b, x
# Hence all the loops must be explicit.
# This is a translation of (part of) LAPACK::zlatrs, q.v. for details.
# Provide `b` in argument `x`; note that `cnorm` may be updated.
function _usolve!(A::StridedMatrix{T}, n, x, cnorm) where {T}

    cabs1half(z) = abs(real(z)/2) + abs(imag(z)/2)

    RT = real(T)
    half = one(RT) / 2
    smallnum = safemin(RT) / eps(RT)
    bignum = one(RT)/ smallnum
    xscale = one(RT)

    tmax = abs(cnorm[1])
    @inbounds for j=2:n
        tmax = max(tmax, abs(cnorm[j]))
    end

    if tmax <= bignum * half
        tscale = one(RT)
    else
        tscale = half / (smallnum * tmax)
        cnorm[1:n] .*= tscale
    end
    # bound on solution vector:
    xmax = cabs1half(x[1])
    @inbounds for j=2:n
        xmax = max(xmax,cabs1half(x[j]))
    end
    xbound = xmax
    if tscale != one(RT)
        grow = zero(RT)
    else
        # compute grow
        grow = half / max(xbound, smallnum)
        xbound = grow
        toosmall = false
        for j=n:-1:1
            # give up if too small
            toosmall = (grow <= smallnum)
            toosmall && break
            tjjs = A[j,j]
            tjj = abs1(tjjs)
            if tjj >= smallnum
                xbound = min(xbound, min(one(RT), tjj)*grow)
            else
                # M[j] could overflow
                xbound = zero(RT)
            end
            if tjj + cnorm[j] >= smallnum
                grow *= (tjj / (tjj + cnorm[j]))
            else
                # grow could overflow, clamp
                grow = zero(RT)
            end
        end
        if !toosmall
            grow = xbound
        end
    end

    if grow * tscale > smallnum
        # bound is ok; use standard arithmetic
        @inbounds for j in n:-1:1
            xj = x[j] = A[j,j] \ x[j]
            for i in j-1:-1:1
                x[i] -= A[i,j] * xj
            end
        end
    else
        if xmax > bignum * half
            # scale so all(abs.(x) .<= bignum)
            xscale = (bignum * half) / xmax
            @inbounds for i=1:n; x[i] *= xscale; end
            xmax = bignum
        else
            xmax *= 2
        end
        for j in n:-1:1
            # compute x[j] = b[j] / A[j,j], scaling if necessary
            xj = abs1(x[j])
            tjjs = A[j,j] * tscale
            tjj = abs1(tjjs)
            if tjj > smallnum
                # abs(A[j,j] > smallnum):
                if tjj < one(RT)
                    if xj > tjj*bignum
                        # scale x by 1/b[j]
                        rec = one(RT) / xj
                        @inbounds for i=1:n; x[i] *= rec; end
                        xscale *= rec
                        xmax *= rec
                    end
                end
                x[j] = x[j] / tjjs
                xj = abs1(x[j])
            elseif tjj > 0
                # 0 < abs(A[j,j]) <= smallnum
                if xj > tjj*bignum
                    # scale by (1/abs(x[j]))*abs(A[j,j]))*bignum
                    # to avoid overflow when dividing by A[j,j]
                    rec = (tjj * bignum) / xj
                    if cnorm[j] > one(RT)
                        # scale by 1/cnorm[j] to avoid overflow
                        # when multiplying x[j] by column j
                        rec /= cnorm[j]
                    end
                    @inbounds for i=1:n; x[i] *= rec; end
                    xscale *= rec
                    xmax *= rec
                end
                x[j] = x[j] / tjjs # FIXME: use zladiv scheme
                xj = abs1(x[j])
            else
                # A[j,j] = 0; set x to eⱼ, xscale to 0
                # and compute a null vector.
                @inbounds for i=1:n; x[i] = zero(T); end
                x[j] = one(T)
                xscale = zero(RT)
                xmax = zero(RT)
            end
            # scale x if necc. to avoid overflow when adding multiple of A[:,j]
            if xj > one(RT)
                rec = one(RT) / xj
                if cnorm[j] > (bignum - xmax) * rec
                    # scale by 1/(2abs(x[j]))
                    rec *= half
                    @inbounds for i=1:n; x[i] *= rec; end
                    xscale *= rec
                end
            elseif (xj * cnorm[j] > bignum - xmax)
                # scale by 1/2
                @inbounds for i=1:n; x[i] *= half; end
                xscale *= half
            end
            if j > 1
                # compute update
                xjt = x[j] * tscale
                @inbounds for i=1:j-1
                    x[i] -= xjt * A[i,j]
                end
                xmax = abs1(x[1])
                @inbounds for i=2:j-1
                    xmax = max(xmax, abs1(x[i]))
                end
            end
        end
        xscale /= tscale
    end
    if tscale != one(RT)
        @inbounds for i=1:n; cnorm[i] *= one(RT)/tscale; end
    end
    return xscale
end

# conjugate transpose version
function _cusolve!(A::StridedMatrix{T}, n, x, cnorm) where {T}

    cabs1half(z) = abs(real(z)/2) + abs(imag(z)/2)

    RT = real(T)
    half = one(RT) / 2
#    smallnum = safemin(RT) / (2*eps(RT)) # WARNING: assumes base 2
    smallnum = safemin(RT) / eps(RT)
    bignum = one(RT)/ smallnum
    xscale = one(RT)

    tmax = abs(cnorm[1])
    @inbounds for j=2:n
        tmax = max(tmax, abs(cnorm[j]))
    end

    if tmax <= bignum * half
        tscale = one(RT)
    else
        tscale = half / (smallnum * tmax)
        cnorm[1:n] .*= tscale
    end
    # bound on solution vector:
    xmax = cabs1half(x[1])
    @inbounds for j=2:n
        xmax = max(xmax,cabs1half(x[j]))
    end
    xbound = xmax
    if tscale != one(RT)
        grow = zero(RT)
    else
        # compute grow
        grow = half / max(xbound, smallnum)
        xbound = grow
        toosmall = false
        for j=1:n
            # give up if too small
            toosmall = (grow <= smallnum)
            toosmall && break
            # G[j] = max(G[j-1], M[j-1]*(1+cnorm[j]))
            xj = one(RT) + cnorm[j]
            grow = min(grow, xbound / xj)
            tjjs = A[j,j]
            tjj = abs1(tjjs)
            if tjj >= smallnum
                if xj > tjj
                    xbound *= (tjj / xj)
                end
            else
                # M[j] could overflow
                xbound = zero(RT)
            end
        end
        if !toosmall
            grow = min(grow, xbound)
        end
    end

    if grow * tscale > smallnum
        # bound is ok; use standard arithmetic
        @inbounds for j in 1:n
            z = x[j]
            for i in 1:j-1
                z -= conj(A[i,j]) * x[i]
            end
            x[j] = conj(A[j,j]) \ z
        end
    else
        if xmax > bignum * half
            # scale so all(abs.(x) .<= bignum)
            xscale = (bignum * half) / xmax
            @inbounds for i=1:n; x[i] *= xscale; end
            xmax = bignum
        else
            xmax *= 2
        end
        for j=1:n
            # compute x[j] = b[j] - Σ_(k≠j) A[k,j] x[k]
            xj = abs1(x[j])
            uscale = complex(tscale)
            rec = one(RT) / max(xmax, one(RT))
            if cnorm[j] > (bignum - xj) * rec
                # if x[j] could overflow scale x by 1/(2 xmax)
                rec *= half
                tjjs = conj(A[j,j]) * tscale
                tjj = abs1(tjjs)
                if tjj > one(RT)
                    # divide by A[j,j] when scaling if A[j,j] > 1
                    rec = min(one(RT), rec*tjj)
                    uscale /= tjjs
                end
                if rec < one(RT)
                    @inbounds for i=1:n; x[i] *= rec; end
                    xscale *= rec
                    xmax *= rec
                end
            end
            csumj = zero(T)
            @inbounds for i=1:j-1
                csumj += (conj(A[i,j]) * uscale) * x[i]
            end
            if uscale == complex(tscale)
                # if diagonal wasn't used to scale
                # compute x[j] = (x[j] - csumj) / A[j,j]
                x[j] -= csumj
                xj = abs1(x[j])
                tjjs = conj(A[j,j]) * tscale

                # compute x[j] /= A[j,j], scaling if necessary
                tjj = abs1(tjjs)
                if tjj > smallnum
                    if tjj < one(RT)
                        if xj > tjj * bignum
                            rec = one(RT) / xj
                            @inbounds for i=1:n; x[i] *= rec; end
                            xscale *= rec
                            xmax *= rec
                        end
                    end
                    x[j] = x[j] / tjjs
                elseif tjj > zero(RT)
                    # tiny diag
                    if xj > tjj * bignum
                        rec = (tjj * bignum) / xj
                        @inbounds for i=1:n; x[i] *= rec; end
                        xscale *= rec
                        xmax *= rec
                    end
                    x[j] = x[j] / tjjs
                else
                    # zero diag: compute a null vector of Aᴴ
                    x[1:n] .= zero(T)
                    x[j] = one(T)
                    xscale = zero(RT)
                    xmax = zero(RT)
                end
            else
                # if dot product was already divided by A[j,j]
                # compute x[j] = x[j] / A[j,j] - csumj
                x[j] = x[j] / tjjs - csumj
            end
            xmax = max(xmax, abs1(x[j]))
        end
        xscale /= tscale
    end
    if tscale != one(RT)
        @inbounds for i=1:n; cnorm[i] *= one(RT)/tscale; end
    end
    xscale
end

# update scale and sumsq s.t. (scale^2 * sumsq) is increased by ∑ |x[j]|^2
# scale is a running quasi-inf-norm.
# based on LAPACK::zlassq
function _ssq(x::AbstractVector{T}, scale, sumsq) where {T}
    n = length(x)
    rone = one(real(T))
    for ix=1:n
        t1 = abs(real(x[ix]))
        if t1 > 0 || isnan(t1)
            if scale < t1
                sumsq = rone + sumsq * (scale / t1)^2
                scale = t1
            else
                sumsq += (t1 / scale)^2
            end
        end
        if T <: Complex
            t1 = abs(imag(x[ix]))
            if t1 > 0 || isnan(t1)
                if scale < t1
                    sumsq = rone + sumsq * (scale / t1)^2
                    scale = t1
                else
                    sumsq += (t1 / scale)^2
                end
            end
        end
    end
    return scale, sumsq
end

# stdlib norm2 uses a poorly implemented generic scheme for short vectors.
function _norm2(x::AbstractVector{T}) where {T<:Real}
    require_one_based_indexing(x)
    n = length(x)
    n < 1 && return zero(T)
    n == 1 && return abs(x[1])
    scale = zero(T)
    ssq = zero(T)
    for xi in x
        if !iszero(xi)
            a = abs(xi)
            if scale < a
                ssq = one(T) + ssq * (scale / a)^2
                scale = a
            else
                ssq += (a / scale)^2
            end
        end
    end
    return scale * sqrt(ssq)
end

function _norm2(x::AbstractVector{T}) where {T<:Complex}
    require_one_based_indexing(x)
    n = length(x)
    RT = real(T)
    n < 1 && return zero(RT)
    n == 1 && return abs(x[1])
    scale = zero(RT)
    ssq = zero(RT)
    for xx in x
        xr,xi = reim(xx)
        if !iszero(xr)
            a = abs(xr)
            if scale < a
                ssq = one(RT) + ssq * (scale / a)^2
                scale = a
            else
                ssq += (a / scale)^2
            end
        end
        if !iszero(xi)
            a = abs(xi)
            if scale < a
                ssq = one(RT) + ssq * (scale / a)^2
                scale = a
            else
                ssq += (a / scale)^2
            end
        end
    end
    return scale * sqrt(ssq)
end

# As of v1.5, Julia hypot() w/ >2 args is unprotected (the documentation lies),
# so we need this.
# translation of dlapy3, assuming NaN propagation
function _hypot3(x::T, y::T, z::T) where {T}
    xa = abs(x)
    ya = abs(y)
    za = abs(z)
    w = max(xa, ya, za)
    rw = one(real(T)) / w
    r::real(T) = w * sqrt((rw * xa)^2 + (rw * ya)^2 + (rw * za)^2)
    return r
end

function _enormalize!(v::AbstractMatrix{T}) where T <: STypes
    n = size(v,1)
    for j=1:n
        s = one(real(T)) / norm(v[:,j],2)
        t = abs2(v[1,j])
        i0 = 1
        for i=2:n
            u = abs2(v[i,j])
            if  u > t
                i0 = i
                t = u
            end
        end
        t = s * conj(v[i0,j]) / sqrt(t)
        for i=1:n
            v[i,j] *=  t
        end
        v[i0,j] = real(v[i0,j])
    end
end


function update_weights(data, e, closest_same, closest_other, weights, weights_mult, max_f_vals, min_f_vals)

	# Penalty term
	penalty = sum(abs.(e .- closest_same)./(max_f_vals .- min_f_vals .+ eps(Float64)), dims=1)

	# Reward term
	reward = sum(weights_mult .* abs.(e .- closest_other)./(max_f_vals .- min_f_vals .+ eps(Float64)), dims=1)

	# Weights update
	weights = weights .- penalty./(size(data, 1)*size(closest_same, 1) + eps(Float64)) .+
		reward./(size(data, 1)*size(closest_other, 1) + eps(Float64))

	# Return updated weights.
	return vec(weights)
end


module models
export Conv, Dense, Chain, train!, set_optim!, zero_grad_model

using Knet, Statistics, Random

function zero_grad_model(model)
    for p in params(model)
        zero_grad(p.opt)
    end

function zero_grad(optim::Adam)
    # gradient resetting
    layer1.opt.fstm = Nothing
    layer1.opt.scndm = Nothing

function set_optim!(model, optim, learning_rate)
    for p in params(model)
        p.opt = optim(lr=learning_rate)
    end

function train!(model, L)
    for p in params(model)
        g = grad(L, p)
        update!(value(p), value(g), p.opt)
    end
    return value(L)
end

# Defining the convolutional layer:
struct Conv
    w
    b
    f
    p
end
(c::Conv)(x) = c.f.(pool(conv4(c.w, dropout(x, c.p)) .+ c.b))
Conv(w1::Int, w2::Int, cx::Int, cy::Int, f = relu; pdrop = 0) = Conv(param(w1, w2, cx, cy), param0(1, 1, cy, 1), f, pdrop)

# Defining dense layer
struct Dense
    w
    b
    f
    p
end
(d::Dense)(x) = d.f.(d.w * mat(dropout(x, d.p)) .+ d.b) # mat reshapes 4-D tensor to 2-D matrix so we can use matmul
Dense(i::Int, o::Int, f = relu; pdrop = 0) = Dense(param(o, i), param0(o), f, pdrop)

struct Chain
    layers
    Chain(layers...) = new(layers)
end
(c::Chain)(x) = (for l in c.layers
    x = l(x)
end;
x)
(c::Chain)(x, y) = nll(c(x), y)

end


module Classification

using LinearAlgebra
export SVC, Logistic

include("utils.jl")
include("Logistic.jl")
include("SVC.jl")
end


using Random, Distributions, Turing, Plots

#=
generate data
=#

Δt = 0.1
duration = 4
timesteps = range(0, stop=duration, step=Δt)
T = length(timesteps)
x = zeros(T) # state
y = zeros(T) # observations

σy = 1
σx = 1
x[1] = rand(Normal(0, 1))
y[1] = rand(Normal(x[1], σy))

for t ∈ 2:T
    x[t] = rand(Normal(x[t-1], σx))
    y[t] = rand(Normal(x[t], σy))
end

p_data = plot(timesteps, x, legend = false)
scatter!(p_data, timesteps, y)

#=
inference model
=#

@model smc_model(y) = begin
    N = length(y)
    x = tzeros(Real, N)

    x[1] ~ Normal(0, σx)
    y[1] ~ Normal(x[1], σy)

    for i ∈ 2:N
        x[i] ~ Normal(x[i-1], σx)
        y[i] ~ Normal(x[i], σy)
    end
end

posterior = sample(smc_model(y), SMC(1000))

plot!(p_data, timesteps, posterior[:x], alpha = 0.01)


using ADCME
include("nnutil.jl")
nntype="ae_scaled"
X, Y = prepare_strain_stress_data1D(domain)
x = (constant(X[:,1]), constant(X[:,2]), constant(X[:,3]))
y = squeeze(nn(x...))
# error()
loss = sum((y-Y)^2)
variable_scope("nn") do
    global opt = AdamOptimizer().minimize(loss)
end

sess = Session(); init(sess)

# for i = 1:10000
#     l, _ = run(sess, [loss, opt])
#     @show i,l
# end
ADCME.load(sess, "Data/learned_nn.mat")
BFGS!(sess, loss, 5000)

out = run(sess, y)
close("all")
plot(X[:,2], out,"+", label="NN")
plot(X[:,2], Y, ".", label="Exact")
legend()

ADCME.save(sess, "Data/learned_nn.mat")

# for α = [0.01,0.05,0.1]
# x = constant(X.*(1 .+ α*rand(size(X)...)))
# y = squeeze(ae(x, [20,20,20,20,1], "nn"))
# out = run(sess, y)
# plot(X[:,2], out,".", label="NN")
# end
# # plot(X[:,2], Y, ".", label="Exact")
# legend()

@load "Data/domain.jld2" domain
X, Y = prepare_strain_stress_data1D(domain)
x = constant(X)
x = (constant(X[:,1]), constant(X[:,2]), constant(X[:,3]))
y = squeeze(nn(x...))
sess = Session(); init(sess)
close("all")
ADCME.load(sess, "Data/learned_nn.mat")
out = run(sess, y)
plot(X[:,1], out,"+", label="NN")
plot(X[:,1], Y, ".", label="Exact")
legend()


function setup_subassembled_RTMM(U,V,dΩ)
  a(a,b) = ∫(a⋅b)dΩ
  v = get_fe_basis(V)
  u = get_trial_fe_basis(U)
  dc=a(u,v)
  collect(dc[dΩ.quad.trian])
end

function setup_subassembled_invL2MM(P,Q,dΩ)
  a(a,b) = ∫(a⋅b)dΩ
  q = get_fe_basis(Q)
  p = get_trial_fe_basis(P)
  dc=a(p,q)
  lazy_map(inv,dc[dΩ.quad.trian])
end

function setup_subassembled_D(U,Q,dω)
  a(u,q) = ∫(q*DIV(u))*dω
  q = get_fe_basis(Q)
  u = get_trial_fe_basis(U)
  dc=a(u,q)
  dc[dω.quad.trian]
end

function setup_subassembled_invL2MMD(U,P,Q,dΩ,dω)
  invL2MM=setup_subassembled_invL2MM(P,Q,dΩ)
  D=setup_subassembled_D(U,Q,dω)
  collect(lazy_map(*,invL2MM,D))
end

function build_subassembled_A_dc(P,V,u,dΩ)
  a(p,v) = ∫(v⋅(u*p))dΩ
  p=get_trial_fe_basis(P)
  v=get_fe_basis(V)
  a(p,v)
end

function setup_subassembled_A(P,V,u,dΩ)
  dc=build_subassembled_A_dc(P,V,u,dΩ)
  collect(dc[dΩ.quad.trian])
end

function setup_subassembled_A!(A,P,V,u,dΩ)
  dc=build_subassembled_A_dc(P,V,u,dΩ)
  subassembled_A=dc[dΩ.quad.trian]
  c=array_cache(subassembled_A)
  for cell=1:length(A)
     A[cell] .= getindex!(c,subassembled_A,cell)
  end
end

function build_implicit_compound_operator_dc(P,V,dt,dΩ,A,RTMM,invL2MMD)
  dtA=lazy_map(Gridap.Fields.BroadcastingFieldOpMap(*), Fill(dt,length(RTMM)), A)
  dtAL2MMinvD=lazy_map(*,dtA,invL2MMD)
  RTMM_plus_dtAL2MMinvD=lazy_map(Gridap.Fields.BroadcastingFieldOpMap(+),RTMM,dtAL2MMinvD)
  dc = Gridap.CellData.DomainContribution()
  Gridap.CellData.add_contribution!(dc, dΩ.quad.trian, RTMM_plus_dtAL2MMinvD)
end

function assemble_implicit_compound_operator(P,U,V,dt,dΩ,A,RTMM,invL2MMD)
  dc=build_implicit_compound_operator_dc(P,V,dt,dΩ,A,RTMM,invL2MMD)
  a=SparseMatrixAssembler(U,V)
  assemble_matrix(a,Gridap.FESpaces.collect_cell_matrix(U,V,dc))
end

function assemble_implicit_compound_operator!(B,P,U,V,dt,dΩ,A,RTMM,invL2MMD)
  dc=build_implicit_compound_operator_dc(P,V,dt,dΩ,A,RTMM,invL2MMD)
  a=SparseMatrixAssembler(U,V)
  Gridap.FESpaces.assemble_matrix!(B,a,Gridap.FESpaces.collect_cell_matrix(U,V,dc))
end

function assemble_Asub_mul_u!(p, U, P, dΩ, A, u)
  ucell=Gridap.FESpaces.scatter_free_and_dirichlet_values(U,u,Float64[])
  A_mul_u=lazy_map(*, A, ucell)
  dc = Gridap.CellData.DomainContribution()
  Gridap.CellData.add_contribution!(dc, dΩ.quad.trian, A_mul_u)
  a = SparseMatrixAssembler(P,P)
  Gridap.FESpaces.assemble_vector!(p,a,Gridap.FESpaces.collect_cell_vector(P,dc))
end

function shallow_water_imex_time_step!(
     h₂, u₂, uₚ, ϕ, F, q₁, q₂,                               # in/out args
     H1h, H1hchol, h_wrk, u_wrk, A, B, Bchol,                # more in/out args
     model, dΩ, dω, U, V, P, Q, R, S, f, g, h₁, u₁, u₀,         # in args
     RTMMchol, L2MMchol, RTMM, L2MMinvD, dt, τ, leap_frog,
     assem=SparseMatrixAssembler(SparseMatrixCSC{Float64,Int},Vector{Float64},R,S))   # more in args

  # energetically balanced implicit-explicit second order shallow water solver
  # reference: eqns (31-33) of
  # https://github.com/BOM-Monash-Collaborations/articles/blob/main/energetically_balanced_time_integration/EnergeticallyBalancedTimeIntegration_SW.tex

  n = get_normal_vector(Triangulation(model))
  # explicit step for provisional velocity, uₚ
  dt1 = dt
  if leap_frog
    dt1 = 2.0*dt
  end

  Fv,h₁v,h₂v = get_free_dof_values(F,h₁,h₂)

  # 1.1: the mass flux
  compute_mass_flux!(F,dΩ,V,RTMMchol,u₁*h₁)
  # 1.2: the bernoulli function
  compute_bernoulli_potential!(ϕ,dΩ,Q,L2MMchol,u₁⋅u₁,h₁,g)
  # 1.3: the potential vorticity
  compute_potential_vorticity!(q₁,H1h,H1hchol,dΩ,R,S,h₁,u₁,f,n,assem)
  # 1.4: solve for the provisional velocity
  compute_velocity!(uₚ,dΩ,dω,V,RTMMchol,u₀,q₁-τ*u₁⋅∇(q₁),F,ϕ,n,dt1,dt1)

  # solve for the second order, semi-implicit mass flux
  # 2.1: mass flux component using the previous depth (explicit)
  compute_mass_flux!(F,dΩ,V,RTMMchol,h₁*(2.0*u₁ + uₚ)/6.0)
  # 2.2: mass flux component using the current depth (implicit)
  setup_subassembled_A!(A,P,V,(u₁ + 2.0*uₚ)/6.0,dΩ)
  assemble_implicit_compound_operator!(B,P,U,V,dt,dΩ,A,RTMM,L2MMinvD) # B = RTMM + dt*A*L2MMinv*D
  assemble_Asub_mul_u!(h_wrk,U,P,dΩ,L2MMinvD,Fv)                  # h_wrk = L2MMinvD * Fv
  h_wrk .= h₁v .- dt .* h_wrk
  assemble_Asub_mul_u!(u_wrk, P, U, dΩ, A, h_wrk)                        # u_wrk = A * h_wrk
  numerical_setup!(Bchol, B)
  solve!(u_wrk, Bchol, u_wrk)
  # 2.3: combine the two mass flux components
  Fv .= Fv .+ u_wrk
  # 2.4: compute the divergence of the total mass flux
  assemble_Asub_mul_u!(h_wrk,U,P,dΩ,L2MMinvD,Fv)                  # h_wrk = L2MMinvD * Fv
  # 2.5: depth field at new time level
  h₂v .= h₁v .- dt .* h_wrk

  # 3.1: the bernoulli function
  compute_bernoulli_potential!(ϕ,dΩ,Q,L2MMchol,(u₁⋅u₁ + u₁⋅uₚ + uₚ⋅uₚ)/3.0,0.5*(h₁ + h₂),g)
  # 3.2: the potential vorticity
  compute_potential_vorticity!(q₂,H1h,H1hchol,dΩ,R,S,h₂,uₚ,f,n,assem)
  # 3.3: solve for the final velocity
  compute_velocity!(u₂,dΩ,dω,V,RTMMchol,u₁,q₁-τ*u₁⋅∇(q₁)+q₂-τ*uₚ⋅∇(q₂),F,ϕ,n,0.5*dt,dt)
end

function shallow_water_imex_time_stepper(
  model, order, degree,
  h₀, u₀, f₀, g,
  dt, τ, N;
  mass_matrix_solver::Gridap.Algebra.LinearSolver=Gridap.Algebra.BackslashSolver(),
  jacobian_matrix_solver::Gridap.Algebra.LinearSolver=Gridap.Algebra.BackslashSolver(),
  write_diagnostics=true,
  write_diagnostics_freq=1,
  dump_diagnostics_on_screen=true,
  write_solution=false,
  write_solution_freq=N/10,
  output_dir="nswe_eq_ncells_$(num_cells(model))_order_$(order)_imex")

  # Forward integration of the shallow water equations
  Ω = Triangulation(model)
  dΩ = Measure(Ω, degree)
  dω = Measure(Ω, degree, ReferenceDomain())

  # Setup the trial and test spaces
  R, S, U, V, P, Q = setup_mixed_spaces(model, order)

  # assemble the mass matrices (RTMM not actually needed in assembled form)
  H1MM, _, L2MM, H1MMchol, RTMMchol, L2MMchol =
      setup_and_factorize_mass_matrices(dΩ, R, S, U, V, P, Q;
                                        mass_matrix_solver=mass_matrix_solver)

  # Project the initial conditions onto the trial spaces
  hn, un, f, hnv, unv, fv =  project_shallow_water_initial_conditions(dΩ, Q, V, S,
                               L2MMchol, RTMMchol, H1MMchol, h₀, u₀, f₀)

  # work arrays
  u_tmp = copy(unv)
  h_tmp = copy(hnv)
  w_tmp = copy(fv)

  # build the potential vorticity lhs operator once just to initialise
  bmm(a,b) = ∫(a*hn*b)dΩ
  H1h      = assemble_matrix(bmm, R, S)
  H1hchol  = numerical_setup(symbolic_setup(mass_matrix_solver,H1h),H1h)

  A        = setup_subassembled_A(P,V,un,dΩ)
  RTMM     = setup_subassembled_RTMM(U,V,dΩ)
  invL2MMD = setup_subassembled_invL2MMD(U,P,Q,dΩ,dω)

  B        = assemble_implicit_compound_operator(P,U,V,dt,dΩ,A,RTMM,invL2MMD)
  Bchol    = numerical_setup(symbolic_setup(jacobian_matrix_solver,B),B)

  function run_simulation(pvd=nothing)
    diagnostics_file = joinpath(output_dir,"nswe_diagnostics.csv")

    hm1    = clone_fe_function(Q,hn)
    ϕ      = clone_fe_function(Q,hn)

    um1    = clone_fe_function(V,un)
    um2    = clone_fe_function(V,un)
    up     = clone_fe_function(V,un)
    F      = clone_fe_function(V,un)

    wn     = clone_fe_function(S,f)
    q1     = clone_fe_function(S,f)
    q2     = clone_fe_function(S,f)

    # first step, no leap frog integration
    shallow_water_imex_time_step!(hn, un, up, ϕ, F, q1, q2,
				                          H1h, H1hchol, h_tmp, u_tmp, A, B, Bchol,
                                  model, dΩ, dω, U, V, P, Q, R, S, f, g, hm1, um1, um2,
                                  RTMMchol, L2MMchol, RTMM, invL2MMD, dt, τ, false)

    if (write_diagnostics)
      initialize_csv(diagnostics_file,"time", "mass", "vorticity", "kinetic", "potential", "power")
    end

    if (write_diagnostics && write_diagnostics_freq==1)
      compute_diagnostic_vorticity!(wn, dΩ, S, H1MMchol, un, get_normal_vector(Ω))
      dump_diagnostics_shallow_water!(h_tmp, w_tmp,
                                      model, dΩ, dω, S, L2MM, H1MM,
                                      hn, un, wn, ϕ, F, g, 1, dt,
                                      diagnostics_file,
                                      dump_diagnostics_on_screen)
    end

    # subsequent steps, do leap frog integration
    # (now that we have the state at two previous time levels)
    for istep in 2:N
      h_aux = hm1
      hm1   = hn
      hn    = h_aux
      u_aux = um2
      um2   = um1
      um1   = un
      un    = u_aux

      shallow_water_imex_time_step!(hn, un, up, ϕ, F, q1, q2,
				                            H1h, H1hchol, h_tmp, u_tmp, A, B, Bchol,
                                    model, dΩ, dω, U, V, P, Q, R, S, f, g, hm1, um1, um2,
                                    RTMMchol, L2MMchol, RTMM, invL2MMD, dt, τ, true)

      if (write_diagnostics && write_diagnostics_freq>0 && mod(istep, write_diagnostics_freq) == 0)
        compute_diagnostic_vorticity!(wn, dΩ, S, H1MMchol, un, get_normal_vector(Ω))
        dump_diagnostics_shallow_water!(h_tmp, w_tmp,
                                        model, dΩ, dω, S, L2MM, H1MM,
                                        hn, un, wn, ϕ, F, g, istep, dt,
                                        diagnostics_file,
                                        dump_diagnostics_on_screen)
      end
      if (write_solution && write_solution_freq>0 && mod(istep, write_solution_freq) == 0)
        compute_diagnostic_vorticity!(wn, dΩ, S, H1MMchol, un, get_normal_vector(Ω))
        pvd[dt*Float64(istep)] = new_vtk_step(Ω,joinpath(output_dir,"n=$(istep)"),["hn"=>hn,"un"=>un,"wn"=>wn])
      end
    end
    hn, un
  end
  if (write_diagnostics || write_solution)
    rm(output_dir,force=true,recursive=true)
    mkdir(output_dir)
  end
  if (write_solution)
    pvdfile=joinpath(output_dir,"nswe_eq_ncells_$(num_cells(model))_order_$(order)_imex")
    paraview_collection(run_simulation,pvdfile)
  else
    run_simulation()
  end
end


using ChaosTools
using Test
using StatsBase
using Statistics

println("\nTesting Takens' best estimate")
@testset "Takens best" begin
  @testset "Henon map" begin
    ds = Systems.henon()
    ts = trajectory(ds, 5000)
    x = ts[:, 1]
    X = embed(x, 2, 1)
    D_C, D_C_95u, D_C_95l = takens_best_estimate(X, std(x)/4)
    @test 1.15 < D_C < 1.25
    @test D_C_95u < 1.05*D_C
    @test D_C_95l > 0.95*D_C
  end
  @testset "Lorenz System" begin
    ds = Systems.lorenz()
    tr = trajectory(ds, 2000; dt = 0.1)
    x = tr[:, 1]
    τ = estimate_delay(x, "mi_min", 1:20)
    X = embed(x, 4, τ)
    D_C, D_C_95u, D_C_95l = takens_best_estimate(X, std(x)/4)
    @test 1.85 < D_C < 2.1
    @test D_C_95u < 1.05*D_C
    @test D_C_95l > 0.95*D_C
  end
end


"""
    AnovaFactor <: AnovaEffect

A set of values for an Anova effect which has a mean square.

`name` - the name of this factor

`ss` - sum of squares

`df` - degrees of freedom

`ms` - mean square
"""
struct AnovaFactor <: AnovaEffect
    name::String
    ss::Float64
    df::Float64
    ms::Float64
end

AnovaFactor(name, ss, df) = AnovaFactor(name, ss, df, ss / df)

import Base.isapprox
isapprox(x::AnovaFactor, y::AnovaFactor) =
    x.name == y.name &&
    x.ss ≈ y.ss &&
    x.df == y.df &&
    (isnan(x.ms) && isnan(y.ms) || x.ms ≈ y.ms)


"""
Struct for Barrier Down and In Option

		barOption=BarrierOptionDownIn(T::num1,K::num2,barrier::num3,isCall::Bool=true) where {num1 <: Number,num2 <: Number,num3 <: Number}

Where:\n
		T	=	Time to maturity of the Option.
		K	=	Strike Price of the Option.
		barrier	=	Down Barrier of the Option.
		isCall  = true for CALL, false for PUT.
"""
struct BarrierOptionDownIn{num1 <: Number, num2 <: Number, num3 <: Number, numtype <: Number} <: BarrierPayoff{numtype}
    T::num1
    K::num2
    barrier::num3
    isCall::Bool
    function BarrierOptionDownIn(T::num1, K::num2, barrier::num3, isCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number}
        if T <= 0.0
            error("Time to Maturity must be positive")
        elseif K <= 0.0
            error("Strike Price must be positive")
        elseif barrier <= 0.0
            error("Barrier must be positive")
        else
            zero_typed = zero(num1) + zero(num2) + zero(num3)
            return new{num1, num2, num3, typeof(zero_typed)}(T, K, barrier, isCall)
        end
    end
end

export BarrierOptionDownIn;

function payout(S::abstractArray, barrierPayoff::BarrierOptionDownIn) where {abstractArray <: AbstractArray{num_}} where {num_ <: Number}
    iscall = barrierPayoff.isCall ? 1 : -1
    zero_typed = zero(eltype(S)) * barrierPayoff.K * barrierPayoff.barrier
    return max(iscall * (S[end] - barrierPayoff.K), zero_typed) * (findfirst(x -> x < barrierPayoff.barrier, S) != nothing)
end


#=
day6:
- Julia version: 1.7.0
- Author: Paul.Mealor
- Date: 2021-12-06
=#

using AoC, Test
using DataStructures: counter

initialAges(lines) = split(lines[1], ",") |> age -> parse.(Int, age)
@test initialAges(["4,6,3,4"]) == [4,6,3,4]

ageMap(ages) = Dict(counter(ages)...) |> r -> Dict((k,BigInt(v)) for (k,v) in r)
@test ageMap([4,6,3,4,6]) == Dict(3=>1, 4=>2, 6=>2)

function round(ages)
    result = Dict()
    for age in keys(ages)
        if age == 0
            result[6] = get(result,6,0) + ages[age]
            result[8] = get(result,8,0) + ages[age]
        else
            result[age - 1] = get(result,age - 1,0) + ages[age]
        end
    end
    return result
end
@test round(Dict(1=>1, 2=>1, 3=>2, 4=>1)) == Dict(0=>1, 1=>1, 2=>2, 3=>1)
@test round(Dict(0=>1, 1=>1, 2=>2, 3=>1)) == Dict(0=>1, 1=>2, 2=>1, 6=>1, 8=>1)

function rounds(ages, count)
    for r in 1:count
        ages = round(ages)
    end
    return ages
end
@test rounds(Dict(1=>1, 2=>1, 3=>2, 4=>1),1) == Dict(0=>1, 1=>1, 2=>2, 3=>1)
@test rounds(Dict(1=>1, 2=>1, 3=>2, 4=>1),2) == Dict(0=>1, 1=>2, 2=>1, 6=>1, 8=>1)
@test rounds(Dict(1=>1, 2=>1, 3=>2, 4=>1),3) == Dict(0=>2, 1=>1, 5=>1, 6=>1, 7=>1, 8=>1)
@test rounds(Dict(1=>1, 2=>1, 3=>2, 4=>1),18) == Dict(0=>3, 1=>5, 2=>3, 3=>2, 4=>2, 5=>1, 6=>5, 7=>1, 8=>4)

countFishes(ages) = sum(values(ages))
@test countFishes(Dict(1=>1, 2=>1, 3=>2, 4=>1)) == 5

part1(lines, count) = lines |> initialAges |> ageMap |> ages -> rounds(ages, count) |> countFishes
@test part1(exampleLines(6,1), 18) == 26
@test part1(exampleLines(6,1), 80) == 5934
@test part1(exampleLines(6,1), 256) == 26984457539

lines(6) |> ll -> @time part1(ll, 80) |> show
lines(6) |> ll -> @time part1(ll, 256) |> show
lines(6) |> ll -> @time part1(ll, 3650) |> show
lines(6) |> ll -> @time part1(ll, 3651) |> show
lines(6) |> ll -> @time part1(ll, 3652) |> show
lines(6) |> ll -> @time part1(ll, 3653) |> show


immutable SVD
  U::Matrix{Float64}
  V::Matrix{Float64}
  s::Vector{Float64}
  transp::Bool
end

function svd_singular_val_creation!(::Type{Val{1}}, e::Vector{Float64}, s::Vector{Float64}, ::Matrix{Float64}, V::Matrix{Float64},
                                    p::Int, ::Int, iter::Int, k::Int, m::Int, n::Int)
  # Deflate negligible s(p)
  f = e[p-1]
  e[p-1] = 0.0
  @inbounds for j = p-1:-1:k
    t = hypot(s[j],f)
    cs = s[j]/t
    sn = f/t
    s[j] = t
    if j != k
      f = -sn*e[j-1]
      e[j-1] = cs*e[j-1]
    end
    @inbounds for i = 1:n
      t = cs*V[i,j] + sn*V[i,p]
      V[i,p] = -sn*V[i,j] + cs*V[i,p]
      V[i,j] = t
    end
  end

  return iter, p, k
end

function svd_singular_val_creation!(::Type{Val{2}}, e::Vector{Float64}, s::Vector{Float64}, U::Matrix{Float64}, ::Matrix{Float64},
                                    p::Int, ::Int, iter::Int, k::Int, m::Int, n::Int)
  # Split at negligible s(k)
  f = e[k-1]
  e[k-1] = 0.0
  @inbounds for j = k:p
    t = hypot(s[j],f)
    cs = s[j]/t
    sn = f/t
    s[j] = t
    f = -sn*e[j]
    e[j] = cs*e[j]
    @inbounds for i = 1:m
      t = cs*U[i,j] + sn*U[i,k-1]
      U[i,k-1] = -sn*U[i,j] + cs*U[i,k-1]
      U[i,j] = t
    end
  end
  return iter, p, k
end

function svd_singular_val_creation!(::Type{Val{3}}, e::Vector{Float64}, s::Vector{Float64}, U::Matrix{Float64}, V::Matrix{Float64},
                                    p::Int, ::Int, iter::Int, k::Int, m::Int, n::Int)
  # Perform one qr step

  # Calculate the shift
  scale_ = max(
            max(
              max(
                max(abs(s[p]), abs(s[p-1])),
                  abs(e[p-1])),
                abs(s[k])),
              abs(e[k]))

  sp = s[p]/scale_
  spm1 = s[p-1]/scale_
  epm1 = e[p-1]/scale_
  sk = s[k]/scale_
  ek = e[k]/scale_
  b = ((spm1 + sp)*(spm1 - sp) + epm1*epm1)/2.0
  c = (sp*epm1)*(sp*epm1)
  shift = 0.0
  if b != 0.0 || c != 0.0
    shift = sqrt(b*b + c)
    if b < 0.0
      shift = -shift
    end
    shift = c/(b + shift)
  end
  f = (sk + sp)*(sk - sp) + shift
  g = sk*ek

  # Chase zeros
  @inbounds for j = k:p-1
    t = hypot(f,g)
    cs = f/t
    sn = g/t
    if j != k
      e[j-1] = t
    end
    f = cs*s[j] + sn*e[j]
    e[j] = cs*e[j] - sn*s[j]
    g = sn*s[j+1]
    s[j+1] = cs*s[j+1]
    @inbounds for i = 1:n
      t = cs*V[i,j] + sn*V[i,j+1]
      V[i,j+1] = -sn*V[i,j] + cs*V[i,j+1]
      V[i,j] = t
    end
    t = hypot(f,g)
    cs = f/t
    sn = g/t
    s[j] = t
    f = cs*e[j] + sn*s[j+1]
    s[j+1] = -sn*e[j] + cs*s[j+1]
    g = sn*e[j+1]
    e[j+1] = cs*e[j+1]
    if j < m
      @inbounds for i = 1:m
        t = cs*U[i,j] + sn*U[i,j+1];
        U[i,j+1] = -sn*U[i,j] + cs*U[i,j+1]
        U[i,j] = t
      end
    end
  end
  e[p-1] = f
  iter = iter + 1

  return iter, p, k
end

function svd_singular_val_creation!(::Type{Val{4}}, e::Vector{Float64}, s::Vector{Float64}, U::Matrix{Float64},
                                    V::Matrix{Float64}, p::Int, pp::Int, iter::Int, k::Int, m::Int, n::Int)
  # Make the singular values positive
  if s[k] <= 0.0
    s[k] = s[k] < 0.0 ? -s[k] : 0.0
    for i = 1:pp+1
      V[i,k] = -V[i,k]
    end
  end

  # Order the singular values
  @inbounds while k <= pp
    if s[k] >= s[k+1]
      break
    end
    s[k], s[k+1] = s[k+1], s[k]
    if k < n
      @inbounds for i = 1:n
        V[i, k], V[i, k+1] = V[i, k+1], V[i, k]
      end
    end
    if k < m
      @inbounds for i = 1:m
        U[i, k], U[i, k+1] = U[i, k+1], U[i, k]
      end
    end
    k += 1
  end

  iter = 0
  p -= 1

  return iter, p, k
end

function SVD(M::Matrix{Float64})
  m, n = size(M)

  if m >= n
    A = copy(M)
    transp = false
  else
    A = M'
    transp = true
  end

  s = Vector{Float64}(n)
  U = zeros(m, n)
  V = Matrix{Float64}(n, n)
  e = Vector{Float64}(n)
  work = Vector{Float64}(m)
  i = j = k = 0

  # reduce A to its bidiagonal form, storing the diagonal elements
  # in s, and super-diagonal elements in e
  nct = min(m-1, n)
  nrt = max(0, n-2)

  @inbounds @simd for k = 1:max(nct, nrt)
    if k <= nct
      # Compute the transformation for the k-th column and
      # place the k-th diagonal in s[k].
      # Compute 2-norm of k-th column without under/overflow.
      s[k] = 0.0
      @inbounds for i = k:m
        s[k] = hypot(s[k],A[i,k]);
      end
      if s[k] != 0.0
        if A[k,k] < 0.0
          s[k] = -s[k];
        end
        for i = k:m
          A[i,k] /= s[k]
        end
        A[k,k] += 1.0;
      end
      s[k] = -s[k]
    end
    @inbounds for j = k+1:n
      if k <= nct && s[k] != 0.0
        # apply transformation
        t = 0.0
        @inbounds for i = k:m
          t += A[i,k]*A[i,j]
        end
        t = -t/A[k,k]
        @inbounds for i = k:m
          A[i,j] += t*A[i,k]
        end
      end
      # Place the k-th row of A into e for the
      # subsequent calculation of the row transformation.
      e[j] = A[k, j]
    end
    if k < nct
      # Place the transformation in U for subsequent back
      # multiplication.
      @inbounds for i = k:m
        U[i,k] = A[i,k]
      end
    end
    if k <= nrt
      # Compute the k-th row transformation and place the
      # k-th super-diagonal in e[k].
      # Compute 2-norm without under/overflow.
      e[k] = 0.0
      @inbounds for i = k+1:n
        e[k] = hypot(e[k],e[i])
      end
      if e[k] != 0.0
        if e[k+1] < 0.0
          e[k] = -e[k]
        end
        @inbounds for i = k+1:n
          e[i] /= e[k]
        end
        e[k+1] += 1.0
      end
      e[k] = -e[k]
      if k+1 <= m && e[k] != 0.0

        # Apply the transformation.
        for i = k+1:m
          work[i] = 0.0
        end
        @inbounds for j = k+1:n
          @inbounds for i = k+1:m
            work[i] += e[j]*A[i,j]
          end
        end
        @inbounds for j = k+1:n
          t = -e[j]/e[k+1]
          @inbounds for i = k+1:m
            A[i,j] += t*work[i]
          end
        end
      end

      # Place the transformation in V for subsequent
      # back multiplication.
      @inbounds for i = k+1:n
        V[i,k] = e[i]
      end
    end
  end

  # Set up the final bidiagonal matrix or order n.

  if nct < n
    s[nct+1] = A[nct+1, nct+1]
  end
  if nrt+1 < n
    e[nrt+1] = A[nrt+1, n]
  end
  e[n] = 0.0

  # Generate U
  @inbounds @simd for j = nct+1:n
    for i = 1:m
      U[i,j] = 0.0
    end
    U[j,j] = 1.0
  end

  @inbounds @simd for k = nct:-1:1
    if s[k] != 0.0
      @inbounds for j = k+1:n
        t = 0
        @inbounds for i = k:m
          t += U[i,k]*U[i,j]
        end
        t = -t/U[k,k]
        @inbounds for i = k:m
          U[i,j] += t*U[i,k]
        end
      end
      @inbounds for i = k:m
        U[i,k] = -U[i,k]
      end
      U[k,k] = 1.0 + U[k,k]
      @inbounds for i = 1:k-1
        U[i,k] = 0.0
      end
    else
      @inbounds for i = 1:m
        U[i,k] = 0.0
      end
      U[k,k] = 1.0
    end
  end

  # Generate V
  @inbounds for k = n:-1:1
    if k <= nrt && e[k] != 0.0
      @inbounds for j = k+1:n
        t = 0.0
        @inbounds for i = k+1:n
          t += V[i,k]*V[i,j]
        end
        t = -t/V[k+1,k]
        @inbounds for i = k+1:n
          V[i,j] += t*V[i,k]
        end
      end
    end
    @inbounds for i = 1:n
      V[i,k] = 0.0
    end
    V[k,k] = 1.0
  end

  # Main iteration loop for singular values
  p = n
  pp = p-1
  iter = 0

  @inbounds while p > 0
    # Here is where a test for too many iterations would go.

    # This section of the program inspects for
    # negligible elements in the s and e arrays.  On
    # completion the variables kase and k are set as follows.

    # kase = 1     if s(p) and e[k-1] are negligible and k<p
    # kase = 2     if s(k) is negligible and k<p
    # kase = 3     if e[k-1] is negligible, k<p, and
    #              s(k), ..., s(p) are not negligible (qr step).
    # kase = 4     if e(p-1) is negligible (convergence).

    for k = p -1:-1:0
      if k == 0
        break
      end

      if abs(e[k]) <= EPS_VAL*(abs(s[k]) + abs(s[k+1]))
        e[k] = 0.0
        break
      end
    end

    if k == p-1
        kase = 4
    else
      ks = 0
      @inbounds for ks = p:-1:k
        if ks == k
          break
        end

        t = (ks != p+1 ? abs(e[ks]) : 0.0) + (ks != k+1 ? abs(e[ks-1]) : 0.0)
        if abs(s[ks]) <= EPS_VAL * t
          s[ks] = 0.0;
          break
        end
      end
      if ks == k
        kase = 3
      elseif ks == p
        kase = 1
      else
        kase = 2
        k = ks
      end
    end
    k += 1

    # perform the task indicated by kase
    iter, p, k = svd_singular_val_creation!(Val{kase}, e, s, U, V, p, pp, iter, k, m, n)

  end
  return SVD(U, V, s, transp)
end


# some auxilary functions
dist(p::Point, q::Point) = norm(p-q)
angle(p::Point, q::Point) = atan2(p.y - q.y, q.x - p.x)

# Center
# ------

# Calibrates the lens center for a camera+lens setup. Each combination of a lens and camera
# is assumed to be unique and every lens center should be calibrated.

# Luckily this is a straightforward procedure, taken from the
#[CAN-EYE user guide](http://www6.paca.inra.fr/can-eye/Documentation-Publications/Documentation).
# Dril 3-4 small holes in your lens cap (or prick an aluminium foil well attached to a lens
#ring) and take a picture for each rotation. Because coordinates of the dots in the pictures
# should lie on circle, the lens center can be calculated.

# The dataframe requires 3 columns with resp. the x-coordinates, corresponding y-coordinates
# and a tag (eg a number) for each dot/circle.
function calibrate_center(df::DataFrame, height::Int, width::Int)

    # First we check the dataframe input and create Nρ and Nϕ, resp. number of circles and
    # points per circle.
    @assert width >= height
    for name = [:x, :y, :circle]
        name in names(df) || error("Column $name not found in dataframe input.")
    end
    eltype(df[:x]) <: Real || throw(DomainError("Column :x should be numeric."))
    eltype(df[:y]) <: Real || throw(DomainError("Column :y should be numeric."))

    pool!(df, :circle)
    Nρ = length(levels(df[:circle]))
    @assert 3 <= Nρ <= 5

    all(by(df, :circle, length)[2]) > 0 || error("Different number of pictures per circle.")
    Nϕ = Int(nrow(df) / Nρ)
    @assert Nϕ * Nρ == nrow(df)

    @assert all(df[:x] .< width)
    @assert all(df[:y] .< height)

    # Create Matrix of Points with a column per circle.
    points = Point[]
    by(df, :circle) do subdf
        for row in eachrow(subdf)
            push!(points, Point(row[:x], row[:y]))
        end
    end
    points = reshape(points, (Nϕ, Nρ))

    # Create fit function as sum of squared errors.
    function sse(inp::Vector)
        # all parameters in single inp vector
        cx, cy = inp[1:2]
        c = Point(cx, cy)
        ρ = inp[3:(Nρ + 2)] #circle radius
        δ = inp[(3 + Nρ):(2Nρ + 2)] #angle offset between circles in single picture
        ϕ = inp[(3 + 2Nρ):end] #angle offset between pictures

        se = Float64[]
        for i = 1:Nρ
            # Normalize this squared error with squared typical radius because we sum upe
            # the errors for both radius and angles.
            push!(se, sum([(ρ[i] - dist(c,points[n, i]))^2 for n = 1:Nϕ])/(height^2/4))
        end
        for i = 1:Nϕ
            push!(se, sum([(ϕ[i] + δ[n] - angle(c,points[i, n]))^2 for n = 1:Nρ]))
        end
        sum(se)
    end

    # Estimate the starting point of the opimization from the inner circle.
    function initsse(c::Point, points::Matrix{Point})
        init = zeros(2 + 2Nρ + Nϕ)
        init[1:2] = [c.x, c.y]
        for i = 1:Nρ
            init[2+i] = dist(c, points[1,i])
            #offset
            init[2+Nρ+i] = angle(c, points[1,i]) - angle(c,points[1,1])
        end
        for i = 1:Nϕ
            init[2+2Nρ+i] = angle(c, points[i,1])
        end
        init
    end
    pic_center = Point(floor(Int, width/2),floor(Int, height/2))

    # We optimize using the Nelder-Mead downhill simplex algorithm,
    # the default for functions without a derivate explicetly defined.
    res = optimize(sse, initsse(pic_center, points), iterations=10_000)
    return lensx, lensy = round(Int, minimizer(res[1:2]))
end

# Projection Function
# -------------------

const N_LATERAL = 5 # number of points on side taken for Δ estimation

# TODO use lensx, lensy from calibrated lens center?
function calibrate_projfun(df::DataFrame, height::Int, width::Int)

    # First we check the dataframe inputs
    @assert width >= height
    for name = [:cm, :px, :H, :pos]
        @assert name in names(df)
    end
    eltype(df[:cm]) <: Real || throw(DomainError("Column :cm should be numeric."))
    eltype(df[:px]) <: Real || throw(DomainError("Column :px should be numeric."))

    @assert all(df[:px] .< width)

    #pool!(df, [:H, :pos])
    @assert length(levels(df[:H])) == 2
    @assert length(levels(df[:pos])) == 3
    for name = ["right", "left", "perpendicular"]
        @assert name in levels(df[:pos])
    end

    # We use 2 pictures at different distances H1, H2 from the perpendicular ruler to
    # estimate the distance to the perpendicular ruler more precisely than with a physical
    # ruler during setup.

    # We first estimate more correctly the distance difference Δ between im1 and im2 by
    # noticing that the shift on the lateral rulers between the two pictures is equal to Δ.
    @show H1, H2 = extrema(df[:H])

    c1 = df[(df[:pos] .== "perpendicular") & (df[:cm] .== 0) & (df[:H] .== H1), :px][1]
    c2 = df[(df[:pos] .== "perpendicular") & (df[:cm] .== 0) & (df[:H] .== H2), :px][1]

    # Utility function to interpolate a pixel from image 1 to the ruler of image 2
    function interpol(x, df2, center2)
        # suffix 2 is for second image with H2
        s2 = sort(df2, cols=:px)
        x2 = s2[:px] - center2
        cm2 = s2[:cm]

        if (x < minimum(x2)) | (x > maximum(x2))
            return NaN
        end

        ind = searchsortedfirst(x2, x)
        out = cm2[ind] - (x2[ind] - x) / (x2[ind] - x2[ind-1]) * (cm2[ind] - cm2[ind-1])
    end

    # fitting function to optimize Δ
    function fitΔ(Δ, df1, df2, center1, center2; errfun::Function=x->x^2)
        diffs = Float64[]
        for row in eachrow(df1)
            x = row[:px] - center1
            cm2 = interpol(x, df2, center2)
            !isnan(cm2) && push!(diffs, cm2 - row[:cm] - Δ)
        end
        return norm(diffs)
    end

    # starting point for optimization
    Δinit = H2 - H1

    left1 = head(sort!(df[(df[:pos] .== "left") & (df[:H] .== H1), :], cols=:cm, rev=true),
                N_LATERAL)
    left2 = head(sort!(df[(df[:pos] .== "left") & (df[:H] .== H2), :], cols=:cm, rev=true),
                N_LATERAL)
    resleft = optimize(Δ -> fitΔ(Δ, left1, left2, c1, c2), Δinit/2, Δinit * 3/2)

    right1 = head(sort!(df[(df[:pos] .== "right") & (df[:H] .== H1), :], cols=:cm, rev=true),
                N_LATERAL)
    right2 = head(sort!(df[(df[:pos] .== "right") & (df[:H] .== H2), :], cols=:cm, rev=true),
                N_LATERAL)
    resright = optimize(Δ -> fitΔ(Δ, right1, right2, c1, c2), Δinit/2, Δinit * 3/2)

    Δ = (minimizer(resleft) + minimizer(resright)) / 2
    @show Δ

    # With a more precise estimation of Δ, we can estimate H1 more precisely.
    # We optimize H1 for the value that provides the best fit between the two projection
    # functions of the two images. We fit a 2nd degree polynomial without intercept to the
    # projection functions ρ(θ) = aθ + bθ^2.
    # For higher order polynomial, simply adjust the `abfit` function accordingly.

    # perpendicular ruler half width
    w = maximum(df[df[:pos] .== "perpendicular", :cm ])

    function projfunpoints(df, center, H)
        θset = Float64[]
        ρset = Int[]
        for row in eachrow(df)
            cm = row[:cm]
            ρ = abs(row[:px] - center)
            θ = row[:pos] == "perpendicular" ? atan(abs(cm) / H) : atan(w / (H - cm))
            push!(θset, θ)
            push!(ρset, ρ)
        end
        θset, ρset
    end

    function abfit(θ, ρ)
    # fit ρ(θ) = aθ + bθ²
        A = [θᵢ^p for θᵢ in θ, p = 1:2]
        a, b = A \ ρ
    end

    # plot to spot outliers
    #function plotfit(H,c)
    #    lens = df[df[:H] .== H, :]
    #    θ, ρ = projfunpoints(lens, c, H)
    #    Winston.plot(θ, ρ, "o")
    #    a, b = abfit(θ, ρ)
    #    Winston.oplot(θ -> a*θ + b*θ.^2, 0, pi/2, "r")
    #    Winston.title("Observations and fit for $H")
    #end
    #display(plotfit(H1, c1)); display(plotfit(H2, c2))

    function projfundiff(H, Δ, lens1, lens2, center1, center2)
        a1, b1 = abfit(projfunpoints(lens1, center1, H)...)
        a2, b2 = abfit(projfunpoints(lens2, center2, H+Δ)...)
        (a1 - a2).^2 + (b1-b2).^2
    end

    lens1 = df[df[:H] .== H1, :]
    lens2 = df[df[:H] .== H2, :]
    Hres = optimize(H->projfundiff(H, Δ, lens1, lens2, c1, c2), H1 / 2, H1 * 2)
    H = minimizer(Hres)
    @show H

    a1, b1 = abfit(projfunpoints(lens1, c1, H)...)
    a2, b2 = abfit(projfunpoints(lens2, c2, H+Δ)...)

    a, b = (a1 + a2) / 2, (b1 + b2) / 2
    return @show a, b
end


#From http://home.online.no/~pjacklam/notes/invnorm/

function FMΦInv(p::Float64)::Float64

    a1::Float64 = -39.6968302866538
    a2::Float64 = 220.946098424521
    a3::Float64 = -275.928510446969
    a4::Float64 = 138.357751867269
    a5::Float64 = -30.6647980661472
    a6::Float64 = 2.50662827745924

    b1::Float64 = -54.4760987982241
    b2::Float64 = 161.585836858041
    b3::Float64 = -155.698979859887
    b4::Float64 = 66.8013118877197
    b5::Float64 = -13.2806815528857

    c1::Float64 = -7.78489400243029E-03
    c2::Float64 = -0.322396458041136
    c3::Float64 = -2.40075827716184
    c4::Float64 = -2.54973253934373
    c5::Float64 = 4.37466414146497
    c6::Float64 = 2.93816398269878

    d1::Float64 = 7.78469570904146E-03
    d2::Float64 = 0.32246712907004
    d3::Float64 = 2.445134137143
    d4::Float64 = 3.75440866190742

    #Rational approximation for lower region.

    if p < 0.02425
        q::Float64 = √(-2.0 * log(p))
        ans::Float64 = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1.0)
    elseif p < 0.97575
        q = (p - 0.5) * (p - 0.5)
        ans = (((((a1 * q + a2) * q + a3) * q + a4) * q + a5) * q + a6) * (p - 0.5) / (((((b1 * q + b2) * q + b3) * q + b4) * q + b5) * q + 1)
    else
        q = √(-2 * log(1 - p))
        ans = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1)
    end

    return ans
end

FMΦ(z::Real)::Real = 0.5+0.5*erf(z*sqrt2Inv)
FMϕ(z::Real)::Real = sqrt2PiInv*exp(-z^2.0/2.0)

#vcats like with rbind in r
function vbind(df1::AbstractDataFrame, df2::AbstractDataFrame)::AbstractDataFrame
  #get info on what we are joining
  fields1::Vector{<:DField} = names(df1)
  fields2::Vector{<:DField} = names(df2)
  allFields::Vector{<:DField} = union(fields1, fields2)

  rows1::Int = size(df1,1)
  rows2::Int = size(df2,1)

  for f::DField ∈ setdiff(allFields, fields1)
    origType::Type = eltype(df2[f])

    if !(Missing <: origType) #in this case we need to promote the eltype
      origType = Union{origType, Missing}
      df2[f] = Vector{origType}(df2[f])
    end

    df1[ f] = Vector{origType}(missing, rows1)
  end

  for f::DField ∈ setdiff(allFields, fields2)
    origType = eltype(df1[f])

    if !(Missing <: origType) #in this case we need to promote the eltype
      origType = Union{origType, Missing}
      df1[f] = Vector{origType}(df1[f])
    end

    #println("origType: $origType")
    df2[ f] = Vector{origType}(missing, rows2)
  end

  return [df1; df2]
end

#recursive version that works similar to vcat with the above stipulations
function vbind(dfs::T...)::T where T<:AbstractDataFrame
  if length(dfs) == 1
    return dfs[1]
  else
    pivot::Int = length(dfs) ÷ 2
    return vbind(vbind(dfs[1:pivot]...), vbind(dfs[(pivot+1):end]...))
  end
end


module TestUtils

using Bootstrap
using Test
using Random

@testset "Statistic functions" begin

    x = collect(100:-1:0) ## avoid sorting

    ## reference values, computed with boot:::norm_inter (R)
    qr = (
        (1.0,      Inf),
        (0.0,     -Inf),
        (0.5,     50.0),
        (0.4713,  47.07265),
        (0.1,      9.205641),
        (0.052,    4.319606)
    )

    @testset "Normal-interpolated quantiles" begin

        @testset "Reference values: alpha scalar" begin
            for (alpha, ref) in qr
                @test isapprox( Bootstrap.iquantile(x, alpha), ref, atol = 1e-5 )
            end
        end

        @testset "Reference values: alpha vector" begin
            a = Float64[x[1] for x in qr]
            r = Float64[x[2] for x in qr]
            y = Bootstrap.iquantile(x, a)
            @test isapprox(y, r, atol = 1e-4 )
        end

        @testset "Monotone quantiles" begin
            x = collect(0:100)
            ## check that quantiles are monotone
            y0 = -Inf
            for alpha in 0:0.001:1
                y1 = Bootstrap.iquantile(x, alpha)
                @test y1 >= y0
                y0 = y1
            end
        end

    end

end

@testset "Utility functions" begin

    @testset "Exact sampling" begin

        mss = collect(Bootstrap.exact(4))
        @test length(mss) == 35
        @test reduce(&, [issorted(c) for c in mss])

        for n in 1:6
            @test length(Bootstrap.exact(n)) == Bootstrap.nrun_exact(n)
        end

    end

    @testset "draw! for views" begin

        x = rand(100)
        xv = view(x, 1:2:100)
        xvc = collect(xv)

        Random.seed!(1)
        d1 = draw!(xv, copy(xv))
        @test d1 isa Vector{Float64}

        Random.seed!(1)
        d2 = draw!(xvc, copy(xvc))
        @test d2 == d1

    end

end

end


# This script runs the KS PDE over ensembles to estimate the time-averaged QoI
include("kuramoto-sivashinsky.jl")
using .kuramoto_sivashinsky

# define integrand functions needed for time averages
function ident(val)
    return val
end
function square(val)
    return val*val
end

# set the parameters
tp = Float64             # floating point type to use
num_nodes = 1023         # number of (interior) nodes to use
dt = 0.005
Spin = convert(tp, 1000) # spin-up period
Time = convert(tp, 1000) # statistics gathering period for simulation
#num_spin = 200000        # number of steps to use during spin up
#num_steps = 200000       # number of time steps to use during simulation
num_spin = convert(Int, Spin/dt)
num_steps = convert(Int, Time/dt)
Lx = convert(tp, 128)    # domain size
order = 8                # order of accuracy
num_samples = 10         # number of ensemble samples to use
num_part = 10            # number of partitions that time is broken into
dx = Lx/(num_nodes+1)
x = Array{tp}(dx:dx:Lx-dx)

# loop over the ensemble ensemble samples
u_avg_vals = zeros(num_samples)
u2_avg_vals = zeros(num_samples)
for n = 1:num_samples
    # set initial condition and set-up the KSData structure
    u = 2*rand(num_nodes) .- 1
    ks = kuramoto_sivashinsky.buildKSData(order, num_nodes, tp)

    # spin simulation
    println("Starting spin-up portion of simulation")
    sol = kuramoto_sivashinsky.solveUsingMidpoint(ks, Spin, num_spin, u)

    for p = 1:num_part
        # to avoid running out of memory, we partition the total time
        u = sol[2:end-1,end]
        #clear!(:sol)
        sol = nothing
        println("Starting statistics gathering simulation #",p)
        cputime = @elapsed begin
            sol = kuramoto_sivashinsky.solveUsingMidpoint(ks, Time,
                                                          num_steps, u)
        end
        u_avg_vals[n] += kuramoto_sivashinsky.
            calcSolutionAverage(order, sol, ident)
        u2_avg_vals[n] += kuramoto_sivashinsky.
            calcSolutionAverage(order, sol, square)
    end
    u_avg_vals[n] /= num_part
    u2_avg_vals[n] /= num_part

    println("u averaged = ", u_avg_vals[n])
    println("u^2 averaged = ", u2_avg_vals[n])
end

# display ensemble values
println("ensemble value of average u   = ", sum(u_avg_vals)/num_samples)
println("ensemble value of average u^2 = ", sum(u2_avg_vals)/num_samples)

# write the values to file
file = open("benchmark_ks_data.dat", "w")
for i = 1:num_samples
    print(file, u_avg_vals[i], " ")
end
println(file)
for i = 1:num_samples
    print(file, u2_avg_vals[i], " ")
end
println(file)
close(file)


using DataStructures, StringDistances

x = map(randstring, rand(5:25,100_000))
y = map(randstring, rand(5:25,100_000))
function f(out, t, x, y)
    d = Array(out, length(x))
    @inbounds for i in 1:length(x)
        d[i] = evaluate(t, x[i], y[i])
    end
end

# similar
@time f(Int, Levenshtein(), x, y)
@time f(Float64, Jaro(), x, y)

# 2x slower compared to StringDist
@time f(Int, QGram(2), x, y)
@time f(Float64, Cosine(2), x, y)
@time f(Float64, Jaccard(2), x, y)

#
@time f(Float64, RatcliffObershelp(), x, y)

#= Rcode
library(stringdist)
x <- sapply(sample(5:25,1e5,replace=TRUE), function(n) paste(sample(letters,n,replace=TRUE),collapse=""))
y <- sapply(sample(5:25,1e5,replace=TRUE), function(n) paste(sample(letters,n,replace=TRUE),collapse=""))
system.time(stringdist(x,y,method='lv', nthread = 1))
system.time(stringdist(x,y,method='jaccard', nthread = 1))
system.time(stringdist(x,y,method='cosine', nthread = 1))
system.time(stringdist(x,y,method='qgram', nthread = 1))

=#


import Oceananigans.Architectures: architecture

const R_Earth = 6371.0e3    # Mean radius of the Earth [m] https://en.wikipedia.org/wiki/Earth

struct RegularLatitudeLongitudeGrid{FT, TX, TY, TZ, A} <: AbstractHorizontallyCurvilinearGrid{FT, TX, TY, TZ}
        Nx :: Int
        Ny :: Int
        Nz :: Int
        Hx :: Int
        Hy :: Int
        Hz :: Int
        Lx :: FT
        Ly :: FT
        Lz :: FT
        Δλ :: FT
        Δφ :: FT
        Δz :: FT
      λᶠᵃᵃ :: A
      λᶜᵃᵃ :: A
      φᵃᶠᵃ :: A
      φᵃᶜᵃ :: A
      zᵃᵃᶠ :: A
      zᵃᵃᶜ :: A
    radius :: FT
end

function RegularLatitudeLongitudeGrid(FT=Float64; size, latitude, longitude, z, radius=R_Earth, halo=(1, 1, 1))
    @assert length(latitude) == 2
    @assert length(longitude) == 2
    @assert length(z) == 2

    λ₁, λ₂ = longitude
    @assert λ₁ < λ₂ && λ₂ - λ₁ ≤ 360

    φ₁, φ₂ = latitude
    @assert -90 <= φ₁ < φ₂ <= 90

    (φ₁ == -90 || φ₂ == 90) &&
        @warn "Are you sure you want to use a latitude-longitude grid with a grid point at the pole?"

    z₁, z₂ = z
    @assert z₁ < z₂

    Lλ = λ₂ - λ₁
    Lφ = φ₂ - φ₁
    Lz = z₂ - z₁

    TX = Lλ == 360 ? Periodic : Bounded
    TY = Bounded
    TZ = Bounded
    topo = (TX, TY, TZ)

    Nλ, Nφ, Nz = N = validate_size(TX, TY, TZ, size)
    Hλ, Hφ, Hz = H = validate_halo(TX, TY, TZ, halo)

            Λ₁ = (λ₁, φ₁, z₁)
            L  = (Lλ, Lφ, Lz)
    Δλ, Δφ, Δz = Δ = @. L / N

    # Calculate end points for cell faces and centers
    λF₋, φF₋, zF₋ = ΛF₋ = @. Λ₁ - H * Δ
    λF₊, φF₊, zF₊ = ΛF₊ = @. ΛF₋ + total_extent(topo, H, Δ, L)

    λC₋, φC₋, zC₋ = ΛC₋ = @. ΛF₋ + Δ / 2
    λC₊, φC₊, zC₊ = ΛC₊ = @. ΛC₋ + L + Δ * (2H - 1)

    TFλ, TFφ, TFz = total_length.(Face, topo, N, H)
    TCλ, TCφ, TCz = total_length.(Center, topo, N, H)

    λᶠᵃᵃ = range(λF₋, λF₊, length = TFλ)
    φᵃᶠᵃ = range(φF₋, φF₊, length = TFφ)
    zᵃᵃᶠ = range(zF₋, zF₊, length = TFz)

    λᶜᵃᵃ = range(λC₋, λC₊, length = TCλ)
    φᵃᶜᵃ = range(φC₋, φC₊, length = TCφ)
    zᵃᵃᶜ = range(zC₋, zC₊, length = TCz)

    λᶠᵃᵃ = OffsetArray(λᶠᵃᵃ, -Hλ)
    φᵃᶠᵃ = OffsetArray(φᵃᶠᵃ, -Hφ)
    zᵃᵃᶠ = OffsetArray(zᵃᵃᶠ, -Hz)

    λᶜᵃᵃ = OffsetArray(λᶜᵃᵃ, -Hλ)
    φᵃᶜᵃ = OffsetArray(φᵃᶜᵃ, -Hφ)
    zᵃᵃᶜ = OffsetArray(zᵃᵃᶜ, -Hz)

    return RegularLatitudeLongitudeGrid{FT, TX, TY, TZ, typeof(λᶠᵃᵃ)}(Nλ, Nφ, Nz, Hλ, Hφ, Hz, Lλ, Lφ, Lz, Δλ, Δφ, Δz, λᶠᵃᵃ, λᶜᵃᵃ, φᵃᶠᵃ, φᵃᶜᵃ, zᵃᵃᶠ, zᵃᵃᶜ, radius)
end

function domain_string(grid::RegularLatitudeLongitudeGrid)
    λ₁, λ₂ = domain(topology(grid, 1), grid.Nx, grid.λᶠᵃᵃ)
    φ₁, φ₂ = domain(topology(grid, 2), grid.Ny, grid.φᵃᶠᵃ)
    z₁, z₂ = domain(topology(grid, 3), grid.Nz, grid.zᵃᵃᶠ)
    return "longitude λ ∈ [$λ₁, $λ₂], latitude ∈ [$φ₁, $φ₂], z ∈ [$z₁, $z₂]"
end

function show(io::IO, g::RegularLatitudeLongitudeGrid{FT, TX, TY, TZ}) where {FT, TX, TY, TZ}
    print(io, "RegularLatitudeLongitudeGrid{$FT, $TX, $TY, $TZ}\n",
              "                   domain: $(domain_string(g))\n",
              "                 topology: ", (TX, TY, TZ), '\n',
              "  resolution (Nx, Ny, Nz): ", (g.Nx, g.Ny, g.Nz), '\n',
              "   halo size (Hx, Hy, Hz): ", (g.Hx, g.Hy, g.Hz), '\n',
              "grid spacing (Δλ, Δφ, Δz): ", (g.Δλ, g.Δφ, g.Δz))
end

#####
##### << Spherical nodes >>
#####

# Node by node
@inline xnode(::Center, i, grid::RegularLatitudeLongitudeGrid) = @inbounds grid.λᶜᵃᵃ[i]
@inline xnode(::Face,   i, grid::RegularLatitudeLongitudeGrid) = @inbounds grid.λᶠᵃᵃ[i]

@inline ynode(::Center, j, grid::RegularLatitudeLongitudeGrid) = @inbounds grid.φᵃᶜᵃ[j]
@inline ynode(::Face,   j, grid::RegularLatitudeLongitudeGrid) = @inbounds grid.φᵃᶠᵃ[j]

@inline znode(::Center, k, grid::RegularLatitudeLongitudeGrid) = @inbounds grid.zᵃᵃᶠ[k]
@inline znode(::Face,   k, grid::RegularLatitudeLongitudeGrid) = @inbounds grid.zᵃᵃᶜ[k]

all_x_nodes(::Type{Center}, grid::RegularLatitudeLongitudeGrid) = grid.λᶜᵃᵃ
all_x_nodes(::Type{Face},   grid::RegularLatitudeLongitudeGrid) = grid.λᶠᵃᵃ
all_y_nodes(::Type{Center}, grid::RegularLatitudeLongitudeGrid) = grid.φᵃᶜᵃ
all_y_nodes(::Type{Face},   grid::RegularLatitudeLongitudeGrid) = grid.φᵃᶠᵃ
all_z_nodes(::Type{Center}, grid::RegularLatitudeLongitudeGrid) = grid.zᵃᵃᶜ
all_z_nodes(::Type{Face},   grid::RegularLatitudeLongitudeGrid) = grid.zᵃᵃᶠ

architecture(::RegularLatitudeLongitudeGrid) = nothing


struct BoxFilter <: Filter
    xwidth::Float64
    ywidth::Float64
end

evaluate(f::BoxFilter, x, y) = 1.0

struct TriangleFilter <: Filter
    xwidth::Float64
    ywidth::Float64
end

evaluate(f::TriangleFilter, x, y) = max(0.0, (f.xwidth - abs(x)) * (f.ywidth - abs(y)))


function grid{T}(width::T, d::Int)
    x = Array{T}(d, d, d)
    y = Array{T}(d, d, d)
    z = Array{T}(d, d, d)

    g = collect(linspace(-width/2, width/2, d))

    @inbounds for ix in 1:d, iy in 1:d, iz in 1:d
        x[ix, iy, iz] = g[ix]
        y[ix, iy, iz] = g[iy]
        z[ix, iy, iz] = g[iz]
    end

    return x, y, z
end

function grid_coords{T}(width::T, d::Int)
    x, y, z = grid(width, d)
    hcat(vec(x), vec(y), vec(z))'
end


module InferenceWrapper

using JavaCall

const AlternativeHypothesis = @jimport org.hipparchus.stat.inference.AlternativeHypothesis
const BinomialTest = @jimport org.hipparchus.stat.inference.BinomialTest
const ChiSquareTest = @jimport org.hipparchus.stat.inference.ChiSquareTest
const Class = @jimport java.lang.Class
const Collection = @jimport java.util.Collection
const Enum = @jimport java.lang.Enum
const GTest = @jimport org.hipparchus.stat.inference.GTest
const InferenceTestUtils = @jimport org.hipparchus.stat.inference.InferenceTestUtils
const JString = @jimport java.lang.String
const KolmogorovSmirnovTest = @jimport org.hipparchus.stat.inference.KolmogorovSmirnovTest
const MannWhitneyUTest = @jimport org.hipparchus.stat.inference.MannWhitneyUTest
const NaNStrategy = @jimport org.hipparchus.stat.ranking.NaNStrategy
const Object = @jimport java.lang.Object
const OneWayAnova = @jimport org.hipparchus.stat.inference.OneWayAnova
const Optional = @jimport java.util.Optional
const RealDistribution = @jimport org.hipparchus.distribution.RealDistribution
const StatisticalSummary = @jimport org.hipparchus.stat.descriptive.StatisticalSummary
const TTest = @jimport org.hipparchus.stat.inference.TTest
const TiesStrategy = @jimport org.hipparchus.stat.ranking.TiesStrategy
const WilcoxonSignedRankTest = @jimport org.hipparchus.stat.inference.WilcoxonSignedRankTest

export AlternativeHypothesis
export BinomialTest
export ChiSquareTest
export GTest
export InferenceTestUtils
export KolmogorovSmirnovTest
export MannWhitneyUTest
export OneWayAnova
export TTest
export WilcoxonSignedRankTest
export anova_f_value
export anova_p_value
export anova_test
export approximate_p
export binomial_test
export bootstrap
export cdf
export cdf_exact
export chi_square
export chi_square_data_sets_comparison
export chi_square_test
export chi_square_test_data_sets_comparison
export compare_to
export describe_constable
export equals
export exact_p
export g
export g_data_sets_comparison
export g_test
export g_test_data_sets_comparison
export g_test_intrinsic
export get_class
export get_declaring_class
export hash_code
export homoscedastic_t
export homoscedastic_t_test
export kolmogorov_smirnov_statistic
export kolmogorov_smirnov_test
export ks_sum
export mann_whitney_u
export mann_whitney_u_test
export name
export notify
export notify_all
export one_way_anova_f_value
export one_way_anova_p_value
export one_way_anova_test
export ordinal
export paired_t
export paired_t_test
export pelz_good
export root_log_likelihood_ratio
export t
export t_test
export to_string
export value_of
export values
export wait
export wilcoxon_signed_rank
export wilcoxon_signed_rank_test

include("alternative_hypothesis.jl")
include("binomial_test.jl")
include("chi_square_test.jl")
include("g_test.jl")
include("inference_test_utils.jl")
include("kolmogorov_smirnov_test.jl")
include("mann_whitney_u_test.jl")
include("one_way_anova.jl")
include("t_test.jl")
include("wilcoxon_signed_rank_test.jl")

end


using LinearAlgebra

"""
    fit(m::HybridNB, f_c::Vector{Vector{Float64}}, f_d::Vector{Vector{Int64}}, labels::Vector{Int64})

Train NB model with discrete and continuous features by estimating P(x⃗|c)
"""
function fit(model::HybridNB,
	     continuous_features::Dict{N, Vector{T}},
	     discrete_features::Dict{N, Vector{U}},
	     labels::Vector{C}) where{C, T <: AbstractFloat, U <: Integer, N}

    A = 1.0/float(length(labels))
    for class in model.classes
        inds = findall(labels .== class)
        model.priors[class] = A*float(length(inds))
        for (name, feature) in continuous_features
            f_data = feature[inds]
            model.c_kdes[class][name] = InterpKDE(kde(f_data[isfinite.(f_data)]), eps(Float64),  BSpline(Linear()), OnGrid())
        end
        for (name, feature) in discrete_features
            f_data = feature[inds]
            model.c_discrete[class][name] = ePDF(f_data[isfinite.(f_data)])
        end
    end
    return model
end

"""
    train(HybridNB, continuous, discrete, labels) -> model2
"""
function train(::Type{HybridNB},
	       continuous_features::Dict{N, Vector{T}},
	       discrete_features::Dict{N, Vector{U}},
	       labels::Vector{C}) where{C, T<: AbstractFloat, U <: Integer, N}
    return fit(HybridNB(labels, N), continuous_features, discrete_features, labels)
end

"""
    fit(m::HybridNB, f_c::Matrix{Float64}, labels::Vector{Int64})

Train NB model with continuous features only
"""
function fit(model::HybridNB,
	     continuous_features::Matrix{T},
	     labels::Vector{C}) where{C, T<: AbstractFloat}
    discrete_features = Dict{Symbol, Vector{Int64}}()
    return fit(model, restructure_matrix(continuous_features), discrete_features, labels)
end

"""computes log[P(x⃗ⁿ|c)] ≈ ∑ᵢ log[p(xⁿᵢ|c)] """
function sum_log_x_given_c!(class_prob::Vector{Float64},
			    feature_prob::Vector{Float64},
			    m::HybridNB,
			    continuous_features::Dict{N, Vector{T}},
			    discrete_features::Dict{N, Vector{U}}, c) where{T <: AbstractFloat, U <: Integer, N}
    for i = 1:num_samples(m, continuous_features, discrete_features)
        for (j, name) in enumerate(keys(continuous_features))
            x_i = continuous_features[name][i]
            feature_prob[j] = isnan(x_i) ? NaN : pdf(m.c_kdes[c][name], x_i)
        end

        for (j, name) in enumerate(keys(discrete_features))
            x_i = discrete_features[name][i]
            feature_prob[num_kdes(m)+j] = isnan(x_i) ? NaN : probability(m.c_discrete[c][name], x_i)
        end
        sel = isfinite.(feature_prob)
        class_prob[i] = sum(log.(feature_prob[sel]))
    end
end

""" compute the number of samples """
function num_samples(m::HybridNB,
		     continuous_features::Dict{N, Vector{T}},
		     discrete_features::Dict{N, Vector{U}}) where{T <: AbstractFloat, U <: Integer, N}
    if length(keys(continuous_features)) > 0
        return length(continuous_features[collect(keys(continuous_features))[1]])
    end
    if length(keys(discrete_features)) > 0
        return length(discrete_features[collect(keys(discrete_features))[1]])
    end
    return 0
end

"""
    predict_logprobs(m::HybridNB, features_c::Vector{Vector{Float64}, features_d::Vector{Vector{Int})

Return the log-probabilities for each column of X, where each row is the class
"""
function predict_logprobs(m::HybridNB,
			  continuous_features::Dict{N, Vector{T}},
			  discrete_features::Dict{N, Vector{U}}) where{T <: AbstractFloat, U <: Integer, N}
    n_samples = num_samples(m, continuous_features, discrete_features)
    log_probs_per_class = zeros(length(m.classes) ,n_samples)
    feature_prob = Vector{Float64}(undef, num_kdes(m) + num_discrete(m))
    for (i, c) in enumerate(m.classes)
        class_prob = Vector{Float64}(undef, n_samples)
        sum_log_x_given_c!(class_prob, feature_prob, m, continuous_features, discrete_features, c)
        log_probs_per_class[i, :] = class_prob .+ log(m.priors[c])
    end
    return log_probs_per_class
end

"""
    predict_proba{V<:Number}(m::HybridNB, f_c::Vector{Vector{Float64}}, f_d::Vector{Vector{Int64}})

Predict log-probabilities for the input features.
Returns tuples of predicted class and its log-probability estimate.
"""
function predict_proba(m::HybridNB,
		       continuous_features::Dict{N, Vector{T}}, discrete_features::Dict{N, Vector{U}}) where{T <: AbstractFloat, U <: Integer, N}
    logprobs = predict_logprobs(m, continuous_features, discrete_features)
    n_samples = num_samples(m, continuous_features, discrete_features)
    predictions = Array{Tuple{eltype(m.classes), Float64}}(undef, n_samples)
    for i = 1:n_samples
        maxprob_idx = argmax(logprobs[:, i])
        c = m.classes[maxprob_idx]
        logprob = logprobs[maxprob_idx, i]
        predictions[i] = (c, logprob)
    end
    return predictions
end

""" Predict kde naive bayes for continuos featuers only""" # TODO: remove this
function predict(m::HybridNB, X::Matrix{T}) where {T <: Number}
    eltype(X) <: AbstractFloat || throw("Continuous features must be floats!")
    return predict(m, restructure_matrix(X), Dict{Symbol, Vector{Int}}())
end

"""
    predict(m::HybridNB, f_c::Vector{Vector{Float64}}, f_d::Vector{Vector{Int64}}) -> labels

Predict hybrid naive bayes for continuos featuers only
"""
function predict(m::HybridNB,
		 continuous_features::Dict{N, Vector{T}},
		 discrete_features::Dict{N, Vector{U}}) where  {T <: AbstractFloat, U <: Integer, N}
    return [k for (k,v) in predict_proba(m, continuous_features, discrete_features)]
end

# TODO Temporary fix to add extrapolation when outside (Remove once PR in KernelDensity.jl is merged)
import KernelDensity: InterpKDE
import Interpolations: ExtrapDimSpec
function InterpKDE(kde::UnivariateKDE, extrap::Union{ExtrapDimSpec, Number}, opts...)
    itp_u = interpolate(kde.density, opts...)
    itp_u = extrapolate(itp_u, extrap)
    itp = Interpolations.scale(itp_u, kde.x)
    InterpKDE{typeof(kde),typeof(itp)}(kde, itp)
end
InterpKDE(kde::UnivariateKDE) = InterpKDE(kde, NaN, BSpline(Quadratic(Line())), OnGrid())

function write_model(model::HybridNB, filename::S) where {S <: AbstractString}
    h5open(filename, "w") do f
        name_type = eltype(keys(model.c_kdes[model.classes[1]]))
        f["NameType"] = "$name_type"
        @info("Writing a model with names of type $name_type")
        f["Labels"] = model.classes
        for c in model.classes
            grp = g_create(f, "$c")
            grp["Prior"] = model.priors[c]
            sub = g_create(grp, "Discrete")
            for (name, discrete) in model.c_discrete[c]
                f_grp = g_create(sub, "$name")
                f_grp["range"] = collect(keys(discrete.pairs))
                f_grp["probability"] = collect(values(discrete.pairs))
            end
            sub = g_create(grp, "Continuous")
            for (name, continuous) in model.c_kdes[c]
                f_grp = g_create(sub, "$name")
                f_grp["x"] = collect(continuous.kde.x)
                f_grp["density"] = collect(continuous.kde.density)
            end
        end
    end
    @info("Writing HybridNB model to file $filename")
end

function to_range(y::Vector{T}) where {T <: Number}
    min, max = extrema(y)
    dy = (max-min)/(length(y)-1)
    return min:dy:max
end

function load_model(filename::S) where {S <: AbstractString}
    model = h5open(filename, "r") do f
        N = read(f["NameType"]) == "Symbol" ? Symbol : AbstractString
        fnc = N == AbstractString ? string : Symbol
        classes = read(f["Labels"])
        C = eltype(classes)
        priors = Dict{C, Float64}()
        kdes = Dict{C, Dict{N, InterpKDE}}()
        discrete = Dict{C, Dict{N, ePDF}}()
        for c in classes
            priors[c] = read(f["$c"]["Prior"])
            kdes[c] = Dict{N, InterpKDE}()
            for (name, dist) in read(f["$c"]["Continuous"])
                kdes[c][fnc(name)] = InterpKDE(UnivariateKDE(to_range(dist["x"]), dist["density"]), eps(Float64), BSpline(Linear()), OnGrid())
            end
            discrete[c] = Dict{N, ePDF}()
            for (name, dist) in read(f["$c"]["Discrete"])
                rng = dist["range"]
                prob = dist["probability"]
                d = Dict{eltype(rng), eltype(prob)}()
                [d[k]=v for (k,v) in zip(rng, prob)]
                discrete[c][fnc(name)] = ePDF(d)
            end
        end
        return HybridNB{C, N}(kdes, discrete, classes, priors)
    end
    return model
end


import Base: +, -, *, /, ^, !=, <, >, ==
import Base: isequal, isless, isapprox, cmp, size, getindex, broadcastable

"""
The Point type holds two coordinates. It's immutable, you can't
change the values of the x and y values directly.
"""
struct Point
   x::Float64
   y::Float64
end

"""
O is a shortcut for the current origin, `0/0`
"""
const O = Point(0, 0)

# basics
+(z::Number, p1::Point)              = Point(p1.x + z,    p1.y + z)
+(p1::Point, z::Number)              = Point(p1.x + z,    p1.y + z)
+(p1::Point, p2::Point)              = Point(p1.x + p2.x, p1.y + p2.y)
-(p1::Point, p2::Point)              = Point(p1.x - p2.x, p1.y - p2.y)
-(p::Point)                          = Point(-p.x,        -p.y)
-(z::Number, p1::Point)              = Point(p1.x - z,    p1.y - z)
-(p1::Point, z::Number)              = Point(p1.x - z,    p1.y - z)
*(k::Number, p2::Point)              = Point(k * p2.x,    k * p2.y)
*(p2::Point, k::Number)              = Point(k * p2.x,    k * p2.y)
/(p2::Point, k::Number)              = Point(p2.x/k,      p2.y/k)
*(p1::Point, p2::Point)              = Point(p1.x * p2.x, p1.y * p2.y)
^(p::Point, e::Integer)              = Point(p.x^e,       p.y^e)
^(p::Point, e::Float64)              = Point(p.x^e,       p.y^e)

# some refinements
# modifying points with tuples
+(p1::Point, shift::NTuple{2, Real}) = Point(p1.x + shift[1], p1.y + shift[2])
-(p1::Point, shift::NTuple{2, Real}) = Point(p1.x - shift[1], p1.y - shift[2])
*(p1::Point, shift::NTuple{2, Real}) = Point(p1.x * shift[1], p1.y * shift[2])
/(p1::Point, shift::NTuple{2, Real}) = Point(p1.x / shift[1], p1.y / shift[2])

# for broadcasting
Base.size(::Point) = 2
Base.getindex(p::Point, i) = [p.x, p.y][i]

Base.broadcastable(x::Point) = Ref(x)

# for iteration
Base.eltype(::Point) = Float64
Base.iterate(p::Point, state = 1) = state > length(p) ? nothing : (p[state], state + 1)
Base.length(::Point) = 2

"""
    dotproduct(a::Point, b::Point)

Return the scalar dot product of the two points.
"""
function dotproduct(a::Point, b::Point)
    result = 0.0
    for i in [:x, :y]
        result += getfield(a, i) * getfield(b, i)
    end
    return result
end

# comparisons

isequal(p1::Point, p2::Point)         = isapprox(p1.x, p2.x, atol=0.00000001) && (isapprox(p1.y, p2.y, atol=0.00000001))
isapprox(p1::Point, p2::Point)        = isapprox(p1.x, p2.x, atol=0.00000001) && (isapprox(p1.y, p2.y, atol=0.00000001))
isless(p1::Point, p2::Point)          = (p1.x < p2.x || (isapprox(p1.x, p2.x) && p1.y < p2.y))
!=(p1::Point, p2::Point)              = !isequal(p1, p2)
<(p1::Point, p2::Point)               = isless(p1, p2)
>(p1::Point, p2::Point)               = p2 < p1
==(p1::Point, p2::Point)              = isequal(p1, p2)

cmp(p1::Point, p2::Point)             = (p1 < p2) ? -1 : (p2 < p1) ? 1 : 0

# a unique that works better on points?
# I think this uses ==
# TODO perhaps unique(x -> round(x, sigdigits=13), myarray) ?
# "any implementation of unique with a tolerance will have some odd behaviors" Steven GJohnson
function Base.unique(pts::Array{Point, 1})
    apts = Point[]
    for pt in pts
        if pt ∉ apts
            push!(apts, pt)
        end
    end
    return apts
end

"""
    distance(p1::Point, p2::Point)

Find the distance between two points (two argument form).
"""
function distance(p1::Point, p2::Point)
    dx = p2.x - p1.x
    dy = p2.y - p1.y
    return sqrt(dx*dx + dy*dy)
end

"""
    pointlinedistance(p::Point, a::Point, b::Point)

Find the distance between a point `p` and a line between two points `a` and `b`.
"""
function pointlinedistance(p::Point, a::Point, b::Point)
  dx = b.x - a.x
  dy = b.y - a.y
  return abs(p.x * dy - p.y * dx + b.x * a.y - b.y * a.x) / hypot(dx, dy);
end

"""
    getnearestpointonline(pt1::Point, pt2::Point, startpt::Point)

Given a line from `pt1` to `pt2`, and `startpt` is the start of a perpendicular heading
to meet the line, at what point does it hit the line?
"""
function getnearestpointonline(pt1::Point, pt2::Point, startpt::Point)
    # first convert line to normalized unit vector
    dx = pt2.x - pt1.x
    dy = pt2.y - pt1.y
    mag = hypot(dx, dy)
    dx /= mag
    dy /= mag

    # translate the point and get the dot product
    lambda = (dx * (startpt.x - pt1.x)) + (dy * (startpt.y - pt1.y))
    return Point((dx * lambda) + pt1.x, (dy * lambda) + pt1.y)
end

"""
    midpoint(p1, p2)

Find the midpoint between two points.
"""
midpoint(p1::Point, p2::Point) = Point((p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0)

"""
    midpoint(a)

Find midpoint between the first two elements of an array of points.
"""
midpoint(pt::Array) = midpoint(pt[1], pt[2])

"""
    between(p1::Point, p2::Point, x)
    between((p1::Point, p2::Point), x)

Find the point between point `p1` and point `p2` for `x`, where `x` is typically between 0
and 1. `between(p1, p2, 0.5)` is equivalent to `midpoint(p1, p2)`.
"""
function between(p1::Point, p2::Point, x)
    return p1 + (x * (p2 - p1))
end

function between(couple::NTuple{2, Point}, x)
    p1, p2 = couple
    return p1 + (x * (p2 - p1))
end

"""
    perpendicular(p1, p2, k)

Return a point `p3` that is `k` units away from `p1`, such that a line `p1 p3`
is perpendicular to `p1 p2`.

Convention? to the right?
"""
function perpendicular(p1::Point, p2::Point, k)
    px = p2.x - p1.x
    py = p2.y - p1.y
    l = hypot(px, py)
    if l > 0.0
        ux = -py/l
        uy = px/l
        return Point(p1.x + (k * ux), p1.y + (k * uy))
    else
        error("these two points are the same")
    end
end

"""
    perpendicular(p::Point)

Returns point `Point(p.y, -p.x)`.
"""
function perpendicular(p::Point)
    return Point(p.y, -p.x)
end

"""
    crossproduct(p1::Point, p2::Point)

This is the *perp dot product*, really, not the crossproduct proper (which is 3D):
"""
function crossproduct(p1::Point, p2::Point)
    return dotproduct(p1, perpendicular(p2))
end

function randomordinate(low, high)
    low + rand() * abs(high - low)
end

"""
    randompoint(lowpt, highpt)

Return a random point somewhere inside the rectangle defined by the two points.
"""
function randompoint(lowpt, highpt)
  Point(randomordinate(lowpt.x, highpt.x), randomordinate(lowpt.y, highpt.y))
end

"""
    randompoint(lowx, lowy, highx, highy)

Return a random point somewhere inside a rectangle defined by the four values.
"""
function randompoint(lowx, lowy, highx, highy)
    Point(randomordinate(lowx, highx), randomordinate(lowy, highy))
end

"""
    randompointarray(lowpt, highpt, n)

Return an array of `n` random points somewhere inside the rectangle defined by two points.
"""
function randompointarray(lowpt, highpt, n)
  array = Point[]
  for i in 1:n
    push!(array, randompoint(lowpt, highpt))
  end
  array
end

"""
    randompointarray(lowx, lowy, highx, highy, n)

Return an array of `n` random points somewhere inside the rectangle defined by the four
coordinates.
"""
function randompointarray(lowx, lowy, highx, highy, n)
    array = Point[]
    for i in 1:n
        push!(array, randompoint(lowx, lowy, highx, highy))
    end
    array
end
"""
    ispointonline(pt::Point, pt1::Point, pt2::Point;
        extended = false,
        atol = 10E-5)

Return `true` if the point `pt` lies on a straight line between `pt1` and `pt2`.

If `extended` is false (the default) the point must lie on the line segment between
`pt1` and `pt2`. If `extended` is true, the point lies on the line if extended in
either direction.
"""
function ispointonline(pt::Point, pt1::Point, pt2::Point;
        atol=10E-5,
        extended=false)
    dxc = pt.x - pt1.x
    dyc = pt.y - pt1.y
    dxl = pt2.x - pt1.x
    dyl = pt2.y - pt1.y
    cpr = (dxc * dyl) - (dyc * dxl)

    # point not on line
    if !isapprox(cpr, 0.0, atol=atol)
        return false
    end

    # point somewhere on extended line
    if extended == true
        return true
    end

    # point on the line
    if (abs(dxl) >= abs(dyl))
        return dxl > 0 ?
            pt1.x <= pt.x && pt.x <= pt2.x :
            pt2.x <= pt.x && pt.x <= pt1.x
    else
        return dyl > 0 ?
            pt1.y <= pt.y && pt.y <= pt2.y :
            pt2.y <= pt.y && pt.y <= pt1.y
    end
end

# """
#     intersection(p1::Point, p2::Point, p3::Point, p4::Point;
#         commonendpoints = false,
#         crossingonly = false,
#         collinearintersect = false)
#
# Find intersection of two lines `p1`-`p2` and `p3`-`p4`
#
# Deprecated. Use `intersectionlines().`
# """
# function intersection(A::Point, B::Point, C::Point, D::Point;
#         commonendpoints = false,
#         crossingonly = false,
#         collinearintersect = false
#         )
#     # false if either line is undefined
#     if (A == B) || (C == D)
#         return (false, Point(0, 0))
#     end
#
#     # false if the lines share a common end point
#     if commonendpoints
#         if (A == C) || (B == C) || (A == D) || (B == D)
#             return (false, Point(0, 0))
#         end
#     end
#
#     # Cramer's ?
#     A1 = (A.y - B.y)
#     B1 = (B.x - A.x)
#     C1 = (A.x * B.y - B.x * A.y)
#
#     L1 = (A1, B1, -C1)
#
#     A2 = (C.y - D.y)
#     B2 = (D.x - C.x)
#     C2 = (C.x * D.y - D.x * C.y)
#
#     L2 = (A2, B2, -C2)
#
#     d  = L1[1] * L2[2] - L1[2] * L2[1]
#     dx = L1[3] * L2[2] - L1[2] * L2[3]
#     dy = L1[1] * L2[3] - L1[3] * L2[1]
#
#     # if you ask me collinear points don't really intersect
#     if (C1 == C2) && (collinearintersect == false)
#         return (false, Point(0, 0))
#     end
#
#     if d != 0
#         pt = Point(dx/d, dy/d)
#         if crossingonly == true
#             if ispointonline(pt, A, B) && ispointonline(pt, C, D)
#                return (true, pt)
#             else
#                return (false, pt)
#             end
#         else
#             if ispointonline(pt, A, B, extended=true) && ispointonline(pt, C, D, extended=true)
#                 return (true, pt)
#             else
#                return (false, pt)
#             end
#         end
#     else
#         return (false, Point(0, 0))
#     end
# end

"""
    slope(pointA::Point, pointB::Point)

Find angle of a line starting at `pointA` and ending at `pointB`.

Return a value between 0 and 2pi. Value will be relative to the current axes.

    slope(O, Point(0, 100)) |> rad2deg # y is positive down the page
    90.0

    slope(Point(0, 100), O) |> rad2deg
    270.0

"""
function slope(pointA, pointB)
    return mod2pi(atan(pointB.y - pointA.y, pointB.x - pointA.x))
end

function intersection_line_circle(p1::Point, p2::Point, cpoint::Point, r)
    a = (p2.x - p1.x)^2 + (p2.y - p1.y)^2
    b = 2.0 * ((p2.x - p1.x) * (p1.x - cpoint.x) +
               (p2.y - p1.y) * (p1.y - cpoint.y))
    c = ((cpoint.x)^2 + (cpoint.y)^2 + (p1.x)^2 + (p1.y)^2 - 2.0 *
        (cpoint.x * p1.x + cpoint.y * p1.y) - r^2)
    i = b^2 - 4.0 * a * c
    if i < 0.0
        number_of_intersections = 0
        intpoint1 = O
        intpoint2 = O
    elseif isapprox(i, 0.0)
        number_of_intersections = 1
        mu = -b / (2.0 * a)
        intpoint1 = Point(p1.x + mu * (p2.x - p1.x), p1.y + mu * (p2.y - p1.y))
        intpoint2 = O
    elseif i > 0.0
        number_of_intersections = 2
        # first intersection
        mu = (-b + sqrt(i)) / (2.0 * a)
        intpoint1 = Point(p1.x + mu * (p2.x - p1.x),
                     p1.y + mu * (p2.y - p1.y))
        # second intersection
        mu = (-b - sqrt(i)) / (2.0 * a)
        intpoint2 = Point(p1.x + mu * (p2.x - p1.x), p1.y + mu * (p2.y - p1.y))
    end
    return number_of_intersections, intpoint1, intpoint2
end

"""
    intersectionlinecircle(p1::Point, p2::Point, cpoint::Point, r)

Find the intersection points of a line (extended through points `p1` and `p2`) and a circle.

Return a tuple of `(n, pt1, pt2)`

where

- `n` is the number of intersections, `0`, `1`, or `2`
- `pt1` is first intersection point, or `Point(0, 0)` if none
- `pt2` is the second intersection point, or `Point(0, 0)` if none

The calculated intersection points won't necessarily lie on the line segment between `p1` and `p2`.
"""
intersectionlinecircle(p1::Point, p2::Point, cpoint::Point, r) =
    intersection_line_circle(p1, p2, cpoint, r)

"""
    @polar (p)

Convert a tuple of two numbers to a Point of x, y Cartesian coordinates.

    @polar (10, pi/4)
    @polar [10, pi/4]

produces

    Luxor.Point(7.0710678118654755, 7.071067811865475)
"""
macro polar(p)
    quote
      Point($(esc(p))[1] * cos($(esc(p))[2]), $(esc(p))[1] * sin($(esc(p))[2]))
    end
end

"""
    polar(r, theta)

Convert point in polar form (radius and angle) to a Point.

    polar(10, pi/4)

produces

    Luxor.Point(7.071067811865475, 7.0710678118654755)
"""
polar(r, theta) = Point(r * cos(theta), r * sin(theta))

"""
    intersectionlines(p0, p1, p2, p3,
        crossingonly=false)

Find point where two lines intersect.

If `crossingonly == true` the point of intersection must lie on both lines.

If `crossingonly == false` the point of intersection can be where the lines meet
if extended almost to 'infinity'.

Accordng to this function, collinear, overlapping, and parallel lines never
intersect. Ie, the line segments might be collinear but have no points in
common, or the lines segments might be collinear and have many points in
common, or the line segments might be collinear and one is entirely contained
within the other.

If the lines are collinear and share a point in common, that
is the intersection point.
"""
function intersectionlines(p0::Point, p1::Point, p2::Point, p3::Point;
        crossingonly = false)
    resultflag = false
    resultip = Point(0.0, 0.0)
    if p0 == p1 # no lines at all
    elseif p2 == p3
    elseif p0 == p2 && p1 == p3 # lines are the same
    elseif p0 == p3 && p1 == p2
    elseif p0 == p2 # common end points
        resultflag = true
        resultip = p0
    elseif p0 == p3
        resultflag = true
        resultip = p0
    elseif p1 == p2
        resultflag = true
        resultip = p1
    elseif p1 == p3
        resultflag = true
        resultip = p1
    else
        # Cramers rule
        a1 = p0.y - p1.y
        b1 = p1.x - p0.x
        c1 = p0.x * p1.y - p1.x * p0.y

        l1 = (a1, b1, -c1)

        a2 = p2.y - p3.y
        b2 = p3.x - p2.x
        c2 = p2.x * p3.y - p3.x * p2.y

        l2 = (a2, b2, -c2)

        d  = l1[1] * l2[2] - l1[2] * l2[1]
        dx = l1[3] * l2[2] - l1[2] * l2[3]
        dy = l1[1] * l2[3] - l1[3] * l2[1]

        if !iszero(d)
            resultip = pt = Point(dx/d, dy/d)
            if crossingonly == true
                if ispointonline(resultip, p0, p1) && ispointonline(resultip, p2, p3)
                    resultflag = true
                else
                    resultflag = false
                end
            else
                if ispointonline(resultip, p0, p1, extended=true) &&
                   ispointonline(resultip, p2, p3, extended=true)
                    resultflag = true
                else
                    resultflag = false
                end
            end
        else
            resultflag = false
            resultip =  Point(0, 0)
        end
    end
    return (resultflag, resultip)
end

"""
    pointinverse(A::Point, centerpoint::Point, rad)

Find `A′`, the inverse of a point A with respect to a circle `centerpoint`/`rad`, such that:

```
distance(centerpoint, A) * distance(centerpoint, A′) == rad^2
```

Return (true, A′) or (false, A).
"""
function pointinverse(A::Point, centerpoint, rad)
    A == centerpoint && throw(error("pointinverse(): point $A and centerpoint $centerpoint are the same"))
    result = (false, A)
    n, C, pt2 = intersectionlinecircle(centerpoint, A, centerpoint, rad)
    if n > 0
        B = polar(rad, 0.7)                # arbitrary point on circle
        h = getnearestpointonline(B, C, A) # perp
        d = between(A, h, 2)               # reflection
        flag, A′ = intersectionlines(B, d, centerpoint, A, crossingonly = false)
        if flag == true
            result = (true, A′)
        end
    end
    return result
end

"""
    currentpoint()

Return the current point.
"""
function currentpoint()
    x, y = Cairo.get_current_point(get_current_cr())
    return Point(x, y)
end

"""
    hascurrentpoint()

Return true if there is a current point. Obtain the current point
with `currentpoint()`.
"""
function hascurrentpoint()
    return Cairo.has_current_point(get_current_cr())
end