"""Convenience type for passing around a sequence and its per-base
error probabilities.

"""
mutable struct RifrafSequence
    seq::DNASeq
    est_n_errors::Float64
    error_log_p::Vector{LogProb}
    match_scores::Vector{Score}
    mismatch_scores::Vector{Score}
    ins_scores::Vector{Score}
    del_scores::Vector{Score}
    codon_ins_scores::Vector{Score}
    codon_del_scores::Vector{Score}
    bandwidth::Int
    bandwidth_fixed::Bool
end

function RifrafSequence(seq::DNASeq, error_log_p::Vector{LogProb},
                        bandwidth::Int, scores::Scores)
    if bandwidth < 1
        error("bandwidth must be positive")
    end

    if length(seq) != length(error_log_p)
        error("length mismatch")
    end
    if length(seq) == 0
        return RifrafSequence()
    end
    if minimum(error_log_p) == -Inf
        error("a log error probability is negative infinity")
    end
    if maximum(error_log_p) > 0.0
        bad_value = maximum(error_log_p)
        error("a log error probability is > 0: $bad_value")
    end
    # all scores are symmetric on the ends so there are fewer
    # branches in the alignment code
    match_scores = Vector{Score}(length(error_log_p))
    mismatch_scores = Vector{Score}(length(error_log_p))
    ins_scores = Vector{Score}(length(error_log_p))
    del_scores = Vector{Score}(length(error_log_p) + 1)

    match_scores[:] = log10.(1.0 - exp10.(error_log_p))
    mismatch_scores[:] = error_log_p + scores.mismatch
    ins_scores[:] = error_log_p + scores.insertion

    del_scores[1] = error_log_p[1] + scores.deletion
    del_scores[end] = error_log_p[end] + scores.deletion
    for i = 1:(length(error_log_p) - 1)
        del_scores[i + 1] = max(error_log_p[i], error_log_p[i + 1]) + scores.deletion
    end

    codon_ins_scores = Vector{Score}()
    codon_del_scores = Vector{Score}()
    if scores.codon_insertion > -Inf
        codon_ins_scores = Vector{Score}(length(error_log_p) - 2)
        for i = 2:(length(error_log_p) - 1)
            codon_ins_scores[i - 1] = max(error_log_p[i - 1],
                                          error_log_p[i],
                                          error_log_p[i + 1]) + scores.codon_insertion
        end
    end
    if scores.codon_deletion > -Inf
        codon_del_scores = Vector{Score}(length(error_log_p) + 1)
        codon_del_scores[1] = error_log_p[1] + scores.codon_deletion
        codon_del_scores[end] = error_log_p[end] + scores.codon_deletion
        for i = 1:(length(error_log_p) - 1)
            codon_del_scores[i + 1] = max(error_log_p[i], error_log_p[i + 1]) + scores.codon_deletion
        end
    end

    n_errors = sum(exp10.(error_log_p))

    return RifrafSequence(seq, n_errors, error_log_p,
                          match_scores, mismatch_scores,
                          ins_scores, del_scores,
                          codon_ins_scores, codon_del_scores,
                          bandwidth, false)
end

"""Given PHRED scores instead of log error rates."""
function RifrafSequence(seq::DNASeq, phreds::Vector{Int8}, bandwidth::Int, scores::Scores)
    error_log_p = phred_to_log_p(phreds)
    return RifrafSequence(seq, error_log_p, bandwidth, scores)
end

"""Update scores for the given sequence."""
function RifrafSequence(seq::RifrafSequence, scores::Scores)
    result = RifrafSequence(seq.seq, seq.error_log_p, seq.bandwidth, scores)
    result.bandwidth_fixed = seq.bandwidth_fixed
    return result
end

"""Empty sequence"""
function RifrafSequence()
    RifrafSequence(DNASeq(), 0.0, LogProb[], Score[], Score[], Score[],
                   Score[], Score[], Score[], 0, false)
end

do_codon_ins(s::RifrafSequence) = length(s.codon_ins_scores) > 0
do_codon_del(s::RifrafSequence) = length(s.codon_del_scores) > 0
do_codon_moves(s::RifrafSequence) = do_codon_ins(s) || do_codon_del(s)

function Base.length(s::RifrafSequence)
    return length(s.seq)
end


function update_and_get_max_abs_diff!(  fssrb::PotentialSimulationSetupRB{T, N1, N2},
                                        depletion_handling::Val{depletion_handling_enabled}, only2d::Val{only_2d} = Val{false}(), is_weighting_potential::Val{_is_weighting_potential} = Val{false}(),
                                        use_nthreads::Int = Base.Threads.nthreads())::T where {T, N1, N2, depletion_handling_enabled, only_2d, _is_weighting_potential}
    tmp_potential::Array{T, N2} = copy(fssrb.potential)
    update!(fssrb, use_nthreads = use_nthreads, depletion_handling = depletion_handling, only2d = only2d, is_weighting_potential = is_weighting_potential)
    max_diff::T = maximum(abs.(tmp_potential - fssrb.potential))
    return max_diff
end

function update_till_convergence!(  fssrb::PotentialSimulationSetupRB{T, N1, N2},
                                    convergence_limit::Real, bias_voltage::AbstractFloat;
                                    n_iterations_between_checks = 500,
                                    depletion_handling::Val{depletion_handling_enabled} = Val{false}(), only2d::Val{only_2d} = Val{false}(), is_weighting_potential::Val{_is_weighting_potential} = Val{false}(),
                                    use_nthreads::Int = Base.Threads.nthreads(), max_n_iterations::Int = -1)::Real where {T, N1, N2, depletion_handling_enabled, only_2d, _is_weighting_potential}
    n_iterations::Int = 0
    cf::T = Inf
    cl::T = abs(convergence_limit * bias_voltage) # to get relative change in respect to bias voltage
    prog = ProgressThresh(cl, 0.1, "Convergence: ")
    while cf > cl
        for i in 1:n_iterations_between_checks
            update!(fssrb, use_nthreads = use_nthreads, depletion_handling = depletion_handling, only2d = only2d, is_weighting_potential = is_weighting_potential)
        end
        cf = update_and_get_max_abs_diff!(fssrb, depletion_handling, only2d, is_weighting_potential, use_nthreads)
        ProgressMeter.update!(prog, cf)
        n_iterations += n_iterations_between_checks
        if max_n_iterations > 0 && n_iterations > max_n_iterations
            @info "Maximum number of iterations reached. (`n_iterations = $(n_iterations)`)"
            break
        end
    end
    ProgressMeter.finish!(prog)
    return cf
end

function check_for_refinement( potential::Array{T, 3}, grid::Grid{T, 3, :cylindrical}, max_diff::AbstractArray, minimum_distance::AbstractArray)::NTuple{3, Vector{Int}} where {T}
    inds_r::Vector{Int} = Int[]
    inds_φ::Vector{Int} = Int[]
    inds_z::Vector{Int} = Int[]

    ax1::DiscreteAxis{T} = grid[1]
    ax2::DiscreteAxis{T} = grid[2]
    ax3::DiscreteAxis{T} = grid[3]
    int1::Interval = ax1.interval
    int2::Interval = ax2.interval
    int3::Interval = ax3.interval

    refinement_value_r::T = max_diff[1]
    refinement_value_φ::T = max_diff[2]
    refinement_value_z::T = max_diff[3]

    minimum_distance_r::T = minimum_distance[1]
    minimum_distance_φ::T = minimum_distance[2]
    minimum_distance_z::T = minimum_distance[3]

    minimum_distance_φ_deg::T = rad2deg(minimum_distance_φ)

    axr::Vector{T} = collect(grid.axes[1])
    axφ::Vector{T} = collect(grid.axes[2])
    axz::Vector{T} = collect(grid.axes[3])
    cyclic::T = grid.axes[2].interval.right

    for iz in 1:size(potential, 3)
        z = axz[iz]
        for iφ in 1:size(potential, 2)
            φ = axφ[iφ]
            isum = iz + iφ
            for ir in 1:size(potential, 1)
                r = axr[ir]

                # r
                if ir != size(potential, 1)
                    if abs(potential[ir + 1, iφ, iz] - potential[ir, iφ, iz]) >= refinement_value_r
                        if axr[ir + 1] - axr[ir] > minimum_distance_r
                            if !in(ir, inds_r) push!(inds_r, ir) end
                        end
                    end
                end

                # φ
                if iφ == size(potential, 2)
                    if typeof(int2).parameters[2] == :open # ugly solution for now. should be done over multiple dispatch..
                        if abs(potential[ir, 1, iz] - potential[ir, iφ, iz]) >= refinement_value_φ
                            if cyclic - axφ[iφ] > minimum_distance_φ
                                if !in(iφ, inds_φ) push!(inds_φ, iφ) end
                            end
                        end
                    end
                else
                    if abs(potential[ir, iφ + 1, iz] - potential[ir, iφ, iz]) >= refinement_value_φ
                        if axφ[iφ + 1] - axφ[iφ] > minimum_distance_φ
                            if !in(iφ, inds_φ) push!(inds_φ, iφ) end
                        end
                    end
                end

                # z
                if iz != size(potential, 3)
                    if abs(potential[ir, iφ, iz + 1] - potential[ir, iφ, iz]) >= refinement_value_z
                        if axz[iz + 1] - axz[iz] > minimum_distance_z
                            if !in(iz, inds_z) push!(inds_z, iz) end
                        end
                    end
                end

            end
        end
    end

    if typeof(int2).parameters[2] == :open
        if isodd(length(inds_φ))
            for iφ in 1:size(potential, 2)
                if !in(iφ, inds_φ)
                    if iφ != size(potential, 2)
                        if axφ[iφ + 1] - axφ[iφ] > minimum_distance_φ
                            append!(inds_φ, iφ)
                            break
                        end
                    else
                        if cyclic - axφ[iφ] > minimum_distance_φ
                            append!(inds_φ, iφ)
                            break
                        end
                    end
                end
            end
        end
        if isodd(length(inds_φ))
            b::Bool = true
            diffs = diff(axφ)
            while b
                if length(diffs) > 0
                    idx = findmax(diffs)[2]
                    if !in(idx, inds_φ)
                        append!(inds_φ, idx)
                        b = false
                    else
                        deleteat!(diffs, idx)
                    end
                else
                    for i in eachindex(axφ)
                        if !in(i, inds_φ) push!(inds_φ, i) end
                    end
                    b = false
                end
            end
        end
        @assert iseven(length(inds_φ)) "Refinement would result in uneven grid in φ."
    end

    if isodd(length(inds_z))
        for iz in 1:size(potential, 3)
            if !in(iz, inds_z)
                if iz != size(potential, 3)
                    if axz[iz + 1] - axz[iz] > minimum_distance_z
                        append!(inds_z, iz)
                        break
                    end
                end
            end
        end
    end
    if isodd(length(inds_z))
        b = true
        diffs = diff(axz)
        while b
            if length(diffs) > 0
                idx = findmax(diffs)[2]
                if !in(idx, inds_z)
                    append!(inds_z, idx)
                    b = false
                else
                    deleteat!(diffs, idx)
                end
            else
                for i in eachindex(axz[1:end-1])
                    if !in(i, inds_z) push!(inds_z, i) end
                end
                b = false
            end
        end
    end
    if isodd(length(inds_z)) inds_z = inds_z[1:end-1] end
    @assert iseven(length(inds_z)) "Refinement would result in uneven grid in z."

    return sort(inds_r), sort(inds_φ), sort(inds_z)
end

function check_for_refinement( potential::Array{T, 3}, grid::Grid{T, 3, :cartesian}, max_diff::AbstractArray, minimum_distance::AbstractArray)::NTuple{3, Vector{Int}} where {T}
    inds_x::Vector{Int} = Int[]
    inds_y::Vector{Int} = Int[]
    inds_z::Vector{Int} = Int[]

    ax1::DiscreteAxis{T} = grid[1]
    ax2::DiscreteAxis{T} = grid[2]
    ax3::DiscreteAxis{T} = grid[3]
    int1::Interval = ax1.interval
    int2::Interval = ax2.interval
    int3::Interval = ax3.interval

    refinement_value_x::T = max_diff[1]
    refinement_value_y::T = max_diff[2]
    refinement_value_z::T = max_diff[3]

    minimum_distance_x::T = minimum_distance[1]
    minimum_distance_y::T = minimum_distance[2]
    minimum_distance_z::T = minimum_distance[3]

    ax_x::Vector{T} = collect(grid.axes[1])
    ax_y::Vector{T} = collect(grid.axes[2])
    ax_z::Vector{T} = collect(grid.axes[3])

    for iz in 1:size(potential, 3)
        z = ax_z[iz]
        for iy in 1:size(potential, 2)
            y = ax_y[iy]
            isum = iz + iy
            for ix in 1:size(potential, 1)
                x = ax_x[ix]

                # x
                if ix != size(potential, 1)
                    if abs(potential[ix + 1, iy, iz] - potential[ix, iy, iz]) >= refinement_value_x
                        if ax_x[ix + 1] - ax_x[ix] > minimum_distance_x
                            if !in(ix, inds_x) push!(inds_x, ix) end
                        end
                    end
                end

                # y
                if iy != size(potential, 2)
                    if abs(potential[ix, iy + 1, iz] - potential[ix, iy, iz]) >= refinement_value_y
                        if ax_y[iy + 1] - ax_y[iy] > minimum_distance_y
                            if !in(iy, inds_y) push!(inds_y, iy) end
                        end
                    end
                end

                # z
                if iz != size(potential, 3)
                    if abs(potential[ix, iy, iz + 1] - potential[ix, iy, iz]) >= refinement_value_z
                        if ax_z[iz + 1] - ax_z[iz] > minimum_distance_z
                            if !in(iz, inds_z) push!(inds_z, iz) end
                        end
                    end
                end

            end
        end
    end

    if isodd(length(inds_x))
        for ix in 1:size(potential, 1)
            if !in(ix, inds_x)
                if ix != size(potential, 1)
                    if ax_x[ix + 1] - ax_x[ix] > minimum_distance_x
                        append!(inds_x, ix)
                        break
                    end
                end
            end
        end
    end
    if isodd(length(inds_x))
        b = true
        diffs = diff(ax_x)
        while b
            if length(diffs) > 0
                idx = findmax(diffs)[2]
                if !in(idx, inds_x)
                    append!(inds_x, idx)
                    b = false
                else
                    deleteat!(diffs, idx)
                end
            else
                for i in eachindex(ax_x[1:end-1])
                    if !in(i, inds_x) push!(inds_x, i) end
                end
                b = false
            end
        end
    end
    if isodd(length(inds_x)) inds_x = inds_x[1:end-1] end
    @assert iseven(length(inds_x)) "Refinement would result in uneven grid in x. This is not allowed since this is the red black dimension."

    return sort(inds_x), sort(inds_y), sort(inds_z)
end


include(joinpath("src", "module.jl"))

config_list = [
    Dict(
        0 => [(1, 1), (1, 2)],
        1 => [(10, 10), (10, 9)]
    ),
    Dict(
        0 => [(5, 5), (5, 6)],
        1 => [(6, 5), (6, 6)]
    )
]

AxelFraud.run_batch(
    config_list=config_list,
    batch_name="competitive",
    grid_dims=(10, 10),
    levels=2,
    steps=3000,
    replicates=300,
    when=10,
    write=true
)

AxelFraud.run_random(
    batch_name="test",
    grid_dims=(10, 10),
    stubborn_count=10,
    steps=100,
    replicates=2,
    when=10,
    rnd_seed=0,
    write=true
)


include("funcs/messages/messages.jl")

module caffeCalJuli

using Main.Message
using BenchmarkTools

msg = Message.Msgs(Message.start_st, Message.check_drinks_st, Message.mode_stmt, Message.mode1_stmt, Message.mode2_stmt)

println(msg)

sum = 0

function f(n)
  res = 0.0
  for i in 1:n
      res += rand()
  end
  return res
end

function g(n1, n2)
  t1 = Threads.@spawn f(n1)
  t2 = Threads.@spawn f(n2)
  fetch(t1) + fetch(t2)
end

@btime g($10^8, $10^8)
#println(summer(sum))

end


# Input, 1-based, closed interval
# returned bin index is 0-based!!!
function reg2bin(s::Int, e::Int)
	@assert s>0 && e>0 "Region coordinates must be positive integers."
	@assert s<=2^29 && e<=2^29 "Region coordinates must be <= 2^29."

	s = s-1 # 1-based to 0-based
	e = e-1 # 1-based to 0-based
	# Original implementation is for closed intervals (if we skip end--), so e is correct.

	s>>14 == e>>14 && return div((1<<15)-1,7) + (s>>14)
	s>>17 == e>>17 && return div((1<<12)-1,7) + (s>>17)
	s>>20 == e>>20 && return div( (1<<9)-1,7) + (s>>20)
	s>>23 == e>>23 && return div( (1<<6)-1,7) + (s>>23)
	s>>26 == e>>26 && return div( (1<<3)-1,7) + (s>>26)
	0

	# from BAM specification pdf
	# --end;
	# if (beg>>14 == end>>14) return ((1<<15)-1)/7 + (beg>>14);
	# if (beg>>17 == end>>17) return ((1<<12)-1)/7 + (beg>>17);
	# if (beg>>20 == end>>20) return ((1<<9)-1)/7 + (beg>>20);
	# if (beg>>23 == end>>23) return ((1<<6)-1)/7 + (beg>>23);
	# if (beg>>26 == end>>26) return ((1<<3)-1)/7 + (beg>>26);
	# return 0;
end

# Input, 1-based, closed interval
# returned bin indices are 0-based!!!
function reg2bins(s::Int, e::Int)
	@assert s>0 && e>0 "Region coordinates must be positive integers."
	@assert s<=2^29 && e<=2^29 "Region coordinates must be <= 2^29."

	s = s-1 # 1-based to 0-based
	e = e-1 # 1-based to 0-based
	# Original implementation is for closed intervals (if we skip end--), so e is correct.

	bins = Int[]
	push!(bins, 0)
	append!(bins,    1 + (s>>26) :     1 + (e>>26))
	append!(bins,    9 + (s>>23) :     9 + (e>>23))
	append!(bins,   73 + (s>>20) :    73 + (e>>20))
	append!(bins,  585 + (s>>17) :   585 + (e>>17))
	append!(bins, 4681 + (s>>14) :  4681 + (e>>14))

	# from BAM specification pdf
	#const MAX_BIN = div((1<<18)-1,7)
	# int i = 0, k;
	# --end;
	# list[i++] = 0;
	# for (k = 1 + (beg>>26); k <= 1 + (end>>26); ++k) list[i++] = k;
	# for (k = 9 + (beg>>23); k <= 9 + (end>>23); ++k) list[i++] = k;
	# for (k = 73 + (beg>>20); k <= 73 + (end>>20); ++k) list[i++] = k;
	# for (k = 585 + (beg>>17); k <= 585 + (end>>17); ++k) list[i++] = k;
	# for (k = 4681 + (beg>>14); k <= 4681 + (end>>14); ++k) list[i++] = k;
	# return i;
end

# Input, 1-based, closed interval
# returned bin indices are 0-based!!!
function reg2binranges(s::Int, e::Int)
	@assert s>0 && e>0 "Region coordinates must be positive integers."
	@assert s<=2^29 && e<=2^29 "Region coordinates must be <= 2^29."

	s = s-1 # 1-based to 0-based
	e = e-1 # 1-based to 0-based
	# Original implementation is for closed intervals (if we skip end--), so e is correct.

	UnitRange{Int}[             0 :               0,
	                  1 + (s>>26) :     1 + (e>>26),
	                  9 + (s>>23) :     9 + (e>>23),
	                 73 + (s>>20) :    73 + (e>>20),
	                585 + (s>>17) :   585 + (e>>17),
	               4681 + (s>>14) :  4681 + (e>>14)]
end

struct VirtualOffset
	o::UInt64
end
function VirtualOffset(coffset::Integer, uoffset::Integer)
	@assert 0<=coffset<(UInt64(2)^48-1) && 0<=uoffset<2^16
	VirtualOffset(UInt64(coffset)<<16 | uoffset)
end

coffset(v::VirtualOffset) = v.o>>16
uoffset(v::VirtualOffset) = v.o & (1<<16-1)

# allow adding to virual offset as long as we stay within the same block
function +(v::VirtualOffset, i::Integer)
	@assert i>=0
	@assert uoffset(v)+i < (1<<16)
	VirtualOffset( v.o + i )
end

isless(u::VirtualOffset, v::VirtualOffset) = u.o<v.o

function print(io::IO, v::VirtualOffset)
	# print(io, "0x", hex(v.o,16))
	print(io, "c=0x", hex(coffset(v),12), ":u=0x", hex(uoffset(v),4))
end
show(io::IO, v::VirtualOffset) = print(io,v)

struct Chunk
	s::VirtualOffset # start
	e::VirtualOffset # end
end
function Chunk(is::IO)
	s = VirtualOffset(read(is,UInt64))
	e = VirtualOffset(read(is,UInt64))
	Chunk(s,e)
end

# define a total order of chunks
isless(a::Chunk, b::Chunk) = a.s<b.s || (a.s==b.s && a.e<b.e)

function print(io::IO, chunk::Chunk)
	#print(io, "Chunk(0x", hex(chunk.s,16), "-0x", hex(chunk.e,16), ")")
	print(io, "Chunk(", chunk.s, "-", chunk.e, ")")
end
show(io::IO, chunk::Chunk) = print(io,chunk)

mutable struct Bin
	bin::UInt # distinct bin - TODO: remove? store as key in Bin Dict only.
	chunks::Vector{Chunk}
end
function Bin(is::IO)
	bin = UInt( read(is,UInt32) )

	nChunks = Int(read(is,Int32))
	chunks = Vector{Chunk}(undef,nChunks)

	for i=1:nChunks
		chunks[i] = Chunk(is)
	end

	Bin(bin,chunks)
end

function print(io::IO, bin::Bin)
	print(io, "Bin(", Int64(bin.bin), ", ", bin.chunks, ")")
end
show(io::IO, bin::Bin) = print(io,bin)

# BamIndex for a single reference
mutable struct BamIndexRef
	bins::Dict{Int,Bin}
	linearIndex::Vector{VirtualOffset}
end

function BamIndexRef(is::IO)
	nBins = Int(read(is,Int32))
	bins = Dict{Int,Bin}()

	for i=1:nBins
		b = Bin(is)
		bins[b.bin] = b
	end

	nIntervals = Int(read(is,Int32))
	linearIndex = reinterpret(VirtualOffset, read!(is,Vector{UInt64}(undef,nIntervals)))

	BamIndexRef(bins,linearIndex)
end

mutable struct BamIndex
	refs::Vector{BamIndexRef}
	nbrNoCooord::UInt64
end
BamIndex() = BamIndex(Vector{BamIndexRef}(),0) # empty bam index

isempty(index::BamIndex) = length(index.refs)==0 && index.nbrNoCooord==0

function BamIndex(is::IO)
	magic = UInt32('B') | UInt32('A')<<8 | UInt32('I')<<16 | UInt32(1)<<24
	magicRead = read(is,UInt32)

	@assert magicRead == magic "Corrupt BAI file, magic string doesn't match."

	nRef = Int(read(is,Int32))
	refs = Vector{BamIndexRef}(undef,nRef)

	for i=1:nRef
		refs[i] = BamIndexRef(is)
	end

	nbrNoCooord = UInt64(0)
	if !eof(is)
		# handle n_no_coor if it is available
		nbrNoCooord = read(is,UInt64)
	end

	BamIndex(refs,0)
end
function BamIndex(filename::AbstractString)
	is = open(filename)
	bi = BamIndex(is)
	close(is)
	bi
end

function tryloadindex(bamFilename::AbstractString)
	bai = bamFilename * ".bai" # try to load "filename.bam.bai"
	!isfile(bai) && (bai = splitext(bamFilename)[1] * ".bai") # try to load "filename.bai"
	!isfile(bai) && return BamIndex() # neither found - return empty index
	BamIndex(bai) # load index
end

mutable struct ChunkQueueSingle
	index::BamIndexRef
	binRange::UnitRange{Int}
	currBin::Int
	currChunk::Int
end

function next!(q::ChunkQueueSingle)
	q.currChunk += 1

	#while !isempty(q) && q.currChunk>length(q.index.bins[q.currBin].chunks)
	while !isempty(q) &&
	      (!haskey(q.index.bins,q.binRange[q.currBin]) ||
	       q.currChunk>length(q.index.bins[q.binRange[q.currBin]].chunks))
		q.currBin += 1
		q.currChunk = 1
	end
	q
end

function ChunkQueueSingle(index::BamIndexRef, binRange::UnitRange{Int})
	q = ChunkQueueSingle(index,binRange,1,0) # start before first
	next!(q) # move to next
end

isempty(q::ChunkQueueSingle) = q.currBin>length(q.binRange)

function shift!(q::ChunkQueueSingle)
	ret = q.index.bins[q.binRange[q.currBin]].chunks[q.currChunk] # TODO: avoid index[currBin] every iteration, unnecessary lookup.
	next!(q)
	ret
end

mutable struct ChunkQueue
	queues::Vector{ChunkQueueSingle}
	pq::PriorityQueue{Int,Chunk} # bin level -> Chunk (the bin level is the index in array of single queues)

	function ChunkQueue(queues::Vector{ChunkQueueSingle}, pq::PriorityQueue{Int,Chunk})
		@assert length(queues)==6 "Incorrect number of bin levels."
		for i=1:length(queues)
			isempty(queues[i]) || (pq[i] = shift!(queues[i]))
		end
		new(queues,pq)
	end
end

ChunkQueue(queues::Vector{ChunkQueueSingle}) = ChunkQueue(queues,PriorityQueue(Int,Chunk))
ChunkQueue(index::BamIndexRef, binRanges::Vector{UnitRange{Int}}) = ChunkQueue(map(x->ChunkQueueSingle(index,x), binRanges))
ChunkQueue(index::BamIndexRef, region::UnitRange{Int}) = ChunkQueue(index,reg2binranges(region[1], region[end]))
ChunkQueue(index::BamIndex, refID::Int, region::UnitRange{Int}) = ChunkQueue(index.refs[refID+1], reg2binranges(region[1], region[end]))

isempty(q::ChunkQueue) = isempty(q.pq)

function shift!(q::ChunkQueue)
	(binLevel, chunk) = peek(q.pq)
	dequeue!(q.pq)

	# println("binLevel: ", binLevel, ", chunk: ", chunk)

	# add next chunk from this queue (if there is one)
	isempty(q.queues[binLevel]) || (q.pq[binLevel] = shift!(q.queues[binLevel]))

	chunk
end


module XKPasswd

"""
`XKPasswd` generates memorable passwords for you in the style of diceware and
[xkcd #936](https://xkcd.com/936/).

This module does not export symbols but the main functions of interest are:

- `XKPasswd.generate`: gives an array of generated passwords
- `XKPasswd.pwentropy`: gives the bits of entropy in the password, with
  knowledge of the wordlist used and formatting
- `XKPasswd.pw100yattrate`: estimates the guess attempt rate needed to brute
  force the password within 100 years, with knowledge of the wordlist used and
  formatting
- `XKPasswd.spin_the_wheel`: cycles through random passwords until you stop it
  (should be used from REPL or a terminal)

!!! warning

    Diceware fairly explicitly tells you to use dice for randomness and *not* a
    PRNG, so keep that in mind. (Do you know where your entropy comes from?)
"""
XKPasswd

using Base.prettyprint_getunits

const superunits = ["", " k", " M", " G", " T", " P", " E", " Z", " Y"]
const secper100y = 3.154e9

datapath = joinpath(dirname(@__FILE__), "..", "data")
google10kpath = joinpath(datapath, "google-10000-english")

pwentropy(n, total, digit) = n*log2(total) + (digit ? log2(10) : 0)
pw100yattrate(s) = BigFloat(2)^s / secper100y

function pw100yattrate_pretty(s::Real)::AbstractString
    rate = pw100yattrate(s)
    if rate < 1
        @sprintf("%.1E", s)
    else
        (atts, unit) = prettyprint_getunits(rate, length(superunits), 1000)
        string(round(BigInt, atts), superunits[unit])
    end
end

"""
    @enum WordList

An enum type used to indicate which of the builtin wordlists to use.

The elements are:
+ `XKPasswd.simple`, a list of 2248 simple words
+ `XKPasswd.jargon`, a list of 9460 more complicated words
+ `XKPasswd.immemorable`, the list of all 94 non-whitespace printable ASCII
  characters (probably want to use `delimstr=""` and lots of "words")
+ `XKPasswd.google_20k`, a list of 20k words with *UK spelling*
+ `XKPasswd.google_10k`, a list of 10k words with *UK spelling*
+ `XKPasswd.google_10k_clean`, (86 "bad" words removed) with *UK spelling*
+ `XKPasswd.google_10k_usa` the 10k list with *USA spelling*
+ `XKPasswd.google_10k_usa_clean` (98 "bad words removed)
+ `XKPasswd.google_10k_usa_clean_short` 2186 words with length < 5
+ `XKPasswd.google_10k_usa_clean_medium` 5471 words with length in [5,8]
+ `XKPasswd.google_10k_usa_clean_long` 2246 words with length > 8
"""
@enum WordList simple jargon immemorable chinese google_20k google_10k google_10k_clean google_10k_usa google_10k_usa_clean google_10k_usa_clean_short google_10k_usa_clean_medium google_10k_usa_clean_long

wordlistfiles = Dict(simple => joinpath(datapath, "simple.txt"),
                     jargon => joinpath(datapath, "jargon.txt"),
                     immemorable => joinpath(datapath, "immemorable.txt"),
                     chinese => joinpath(datapath, "chinese.txt"),
                     google_20k => joinpath(google10kpath, "20k.txt"),
                     google_10k => joinpath(google10kpath,
                                            "google-10000-english.txt"),
                     google_10k_clean => joinpath(google10kpath,
                         "google-10000-english-no-swears.txt"),
                     google_10k_usa => joinpath(google10kpath,
                         "google-10000-english-usa.txt"),
                     google_10k_usa_clean => joinpath(google10kpath,
                         "google-10000-english-usa-no-swears.txt"),
                     google_10k_usa_clean_short => joinpath(google10kpath,
                         "google-10000-english-usa-no-swears-short.txt"),
                     google_10k_usa_clean_medium => joinpath(google10kpath,
                         "google-10000-english-usa-no-swears-medium.txt"),
                     google_10k_usa_clean_long => joinpath(google10kpath,
                         "google-10000-english-usa-no-swears-long.txt"))

function stats(n, l, d)::AbstractString
    s = pwentropy(n, l, d)
    string("Entropy: ~", round(Int, s), " bits; 100y BF attempt rate: ",
           pw100yattrate_pretty(s), " att/s.")
end

"""
    XKPasswd.generate(n[, wordlist=XKPasswd.simple]; <keyword arguments>)

Generate an array of memorable passwords of a given specification.

# Arguments
* `n::Integer`: the number of random words per password
* `wordlist::WordList=XKPasswd.simple`: selection from the bundled word list options OR
* `wordlist::AbstractString`: the path to a newline delimited word list file
* `npws::integer=1`: the number of passwords to generate
* `captialize::Bool=false`: whether to capitalize the first letter of each word
* `delimstr::AbstractString=" "`: what to insert between each word
* `append_digit::Bool=false`: whether to tack on a single digit at the end
* `quiet::Bool=false`: whether to print entropy information to STDERR

# Examples

```julia
julia> XKPasswd.generate(4) # output here is just a joke
Entropy: ~45 bits; 100y BF attempt rate: 9 k att/s.
1-element Array{String,1}:
 "correct horse battery staple"
```
"""
function generate(n::Integer, wordlist::AbstractString; npws::Integer=1,
                  capitalize::Bool=false, delimstr::AbstractString=" ",
                  append_digit::Bool=false, quiet::Bool=false)
    @assert n > 0 "Must use at least 1 random word"
    @assert npws > 0 "Must generate at least 1 password"
    open(wordlist) do f
#        lines = readlines(f, chomp=true)
        lines = readlines(f);

        if (!quiet)
            println(STDERR, stats(n, length(lines), append_digit))
        end

        [(
#            words = rand(lines, n);
            words = map(chomp, rand(lines, n));
            cased_words = capitalize ?  map(ucfirst, words) : words;
            string(join(cased_words, delimstr),
                   append_digit ? "$(delimstr)$(rand(0:9))" : "")
         ) for k in 1:npws]
    end
end

function generate(n::Integer, wordlist::WordList=simple; npws::Integer=1,
                  capitalize::Bool=false, delimstr::AbstractString=" ",
                  append_digit::Bool=false, quiet::Bool=false)
    generate(n, wordlistfiles[wordlist], npws=npws, capitalize=capitalize,
             delimstr=delimstr, append_digit=append_digit, quiet=quiet)
end

"""
    XKPasswd.spin_the_wheel(n::Integer=4[, wordlist=XKPasswd.simple]; <keyword arguments>)

Starts cycling through randomly generated passwords, selecting the last one once
you hit [Enter] on your keyboard. Arguments are the same as in
[`XKPasswd.generate`](@ref), though without `npws` and `quiet`.

!!! note

    This function puts VT100 commands to `STDOUT` and reads from file descriptor
    `0`, so it might not be applicable in all situations.
"""
function spin_the_wheel(n::Integer, wordlist::AbstractString;
                        capitalize::Bool=false, delimstr::AbstractString=" ",
                        append_digit::Bool=false)
    open(wordlist) do f
        lines = readlines(f);
        println(STDERR, stats(n, length(lines), append_digit))
    end
    println("Spinning... Press [enter] to end\n\n")
    while true
        ev = poll_fd(RawFD(0), 0, readable=true)
        p = rand(XKPasswd.generate(n, wordlist, npws=20, quiet=true,
                                   capitalize=capitalize, delimstr=delimstr,
                                   append_digit=append_digit))
        print("\u1b[1A\u1b[K\u1b[1K")
        println(p)
        if ev.readable
            print("\u1b[1A\u1b[K\u1b[1K")
            break
        end
    end
end

function spin_the_wheel(n::Integer=4, wordlist::WordList=simple;
                        capitalize::Bool=false, delimstr::AbstractString=" ",
                        append_digit::Bool=false)
    spin_the_wheel(n, wordlistfiles[wordlist], capitalize=capitalize,
                   delimstr=delimstr, append_digit=append_digit)
end

end # module


"""
    SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer

The `SingleBridgeOptimizer` bridges any objective functions supported by the
bridge `BT`. This is in contrast with the [`MathOptInterface.Bridges.LazyBridgeOptimizer`](@ref)
which only bridges the objective functions that are unsupported by the internal model,
even if they are supported by one of its bridges.
"""
mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: MOIB.AbstractBridgeOptimizer
    model::OT
    map::Map # `MOI.ObjectiveFunction` -> objective bridge
end
function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike}
    SingleBridgeOptimizer{BT, OT}(model, Map())
end

function bridges(bridge::MOI.Bridges.AbstractBridgeOptimizer)
    return EmptyMap()
end
bridges(bridge::SingleBridgeOptimizer) = bridge.map

MOIB.supports_constraint_bridges(::SingleBridgeOptimizer) = false
function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractSet})
    return false
end
function MOIB.is_bridged(::SingleBridgeOptimizer,
                         ::Type{<:MOI.AbstractFunction},
                         ::Type{<:MOI.AbstractSet})
    return false
end
function MOIB.supports_bridging_objective_function(
    ::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractScalarFunction}) where BT
    return supports_objective_function(BT, F)
end
function MOIB.is_bridged(
    b::SingleBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunction})
    return MOIB.supports_bridging_objective_function(b, F)
end
function MOIB.bridge_type(::SingleBridgeOptimizer{BT},
                          ::Type{<:MOI.AbstractScalarFunction}) where BT
    return BT
end


module Numerics

  using Reexport

  @reexport using DataFrames
  @reexport using DataStructures
  @reexport using LinearAlgebra
  @reexport using SparseArrays
  @reexport using Statistics
  @reexport using StatsBase
  @reexport using Random

end


using Test
using Nystrom
using StaticArrays
using LinearAlgebra

@testset "Kernels" begin
    pde  = Helmholtz(;dim=3,k=1)
    G    = SingleLayerKernel(pde)
    dG    = DoubleLayerKernel(pde)
    @test Nystrom.kernel_type(G) == Nystrom.SingleLayer()
    @test Nystrom.kernel_type(dG) == Nystrom.DoubleLayer()
    @test Nystrom.return_type(G) == ComplexF64
    @test Nystrom.return_type(dG) == ComplexF64
    @test Nystrom.combined_field_coefficients(G) == (0,-1)
    @test Nystrom.combined_field_coefficients(dG) == (1,0)

    pde  = Laplace(;dim=3)
    G    = SingleLayerKernel(pde)
    dG    = DoubleLayerKernel(pde)
    @test Nystrom.kernel_type(G) == Nystrom.SingleLayer()
    @test Nystrom.kernel_type(dG) == Nystrom.DoubleLayer()
    @test Nystrom.return_type(G) == Float64
    @test Nystrom.return_type(dG) == Float64
    @test Nystrom.combined_field_coefficients(G) == (0,-1)
    @test Nystrom.combined_field_coefficients(dG) == (1,0)

    pde  = Elastostatic(;μ=1,λ=2.0,dim=3)
    G    = SingleLayerKernel(pde)
    dG   = DoubleLayerKernel(pde)
    @test Nystrom.kernel_type(G) == Nystrom.SingleLayer()
    @test Nystrom.kernel_type(dG) == Nystrom.DoubleLayer()
    @test Nystrom.return_type(G) == SMatrix{3,3,Float64,9}
    @test Nystrom.return_type(dG) == SMatrix{3,3,Float64,9}
    @test Nystrom.combined_field_coefficients(G) == (0,-1)
    @test Nystrom.combined_field_coefficients(dG) == (1,0)

    pde  = Maxwell(;k=1.0)
    G    = SingleLayerKernel(pde)
    dG   = DoubleLayerKernel(pde)
    @test Nystrom.kernel_type(G) == Nystrom.SingleLayer()
    @test Nystrom.kernel_type(dG) == Nystrom.DoubleLayer()
    @test Nystrom.return_type(G) == SMatrix{3,3,ComplexF64,9}
    @test Nystrom.return_type(dG) == SMatrix{3,3,ComplexF64,9}
    @test Nystrom.combined_field_coefficients(G) == (0,-1)
    @test Nystrom.combined_field_coefficients(dG) == (1,0)
end

@testset "Maxwell identities" begin
    # essentially vector calculus identities verified by Maxwell's diadic greens
    # function
    op = Maxwell(2)
    T  = Nystrom.default_density_eltype(op)
    G   = SingleLayerKernel(op)
    dG  = DoubleLayerKernel(op)
    E  = rand(T)
    x  = rand(SVector{3})
    y  = rand(SVector{3})
    jac = rand(SMatrix{3,2})
    xdof = NystromDOF(x,1.,jac,0,0)
    ydof = NystromDOF(y,1.,jac,0,0)
    # [ n(y) × (∇ʸ × G(x,y) ) ]ᵗ E = [ ∇ʸ × G ]ᵗ (E × n(y)) = -[ ∇ˣ G ] (n(y) × E)
    lhs = dG(xdof,ydof)*E
    rhs = Nystrom._curl_y_green_tensor_maxwell(x,y,op.k) * cross(E,normal(ydof))
    @test lhs ≈ rhs
    # [ ∇ʸ × G ]ᵗ = ∇ˣ × G
    lhs =  Nystrom._curl_y_green_tensor_maxwell(x,y,op.k) |> transpose
    rhs =  Nystrom._curl_x_green_tensor_maxwell(x,y,op.k)
    @test lhs == rhs
end


module ComponentArrays

using ArrayInterface
using LinearAlgebra
using Requires

const FlatIdx = Union{Int, CartesianIndex, AbstractArray}
const FlatOrColonIdx = Union{FlatIdx, Colon}

include("utils.jl")
export fastindices # Deprecated

include("lazyarray.jl")

include("axis.jl")
export AbstractAxis, Axis, PartitionedAxis, ShapedAxis, ViewAxis, FlatAxis

include("componentindex.jl")
export KeepIndex

include("componentarray.jl")
export ComponentArray, ComponentVector, ComponentMatrix, getaxes, getdata, valkeys

include("array_interface.jl")
# Base methods: parent, size, elsize, axes, reinterpret, hcat, vcat, permutedims, IndexStyle, to_indices, to_index, getindex, setindex!, view, pointer, unsafe_convert, strides, stride
# ArrayInterface methods: strides, size, lu_instance, parent_type

include("namedtuple_interface.jl")
# Base methods: hash, ==, keys, haskey, propertynames, getproperty, setproperty!

include("similar_convert_copy.jl")
# Base methods: similar, zero, copy, copyto!, deepcopy, convert (to Array and NamedTuple), promote

include("broadcasting.jl")
# Base methods: BroadcastStyle, convert(to Broadcasted{Nothing}), similar, map, dataids
# Broadcast methods: BroadcastStyle, broadcasted, broadcast_unalias

include("show.jl")

include("plot_utils.jl")
export labels, label2index

required(filename) = include(joinpath("if_required", filename))

function __init__()
    @require ConstructionBase="187b0558-2788-49d3-abe0-74a17ed4e7c9" required("constructionbase.jl")
    @require DiffEqBase="2b5f629d-d688-5b77-993f-72d75c75574e" begin
        @require RecursiveFactorization="f2c3362d-daeb-58d1-803e-2bc74f2840b4" required("diffeqbase.jl")
    end
    @require SciMLBase="0bca4576-84f4-4d90-8ffe-ffa030f20462" required("scimlbase.jl")
    @require RecursiveArrayTools="731186ca-8d62-57ce-b412-fbd966d074cd" required("recursivearraytools.jl")
    @require StaticArrays="90137ffa-7385-5640-81b9-e52037218182" required("staticarrays.jl")
    @require ChainRulesCore="d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" required("chainrulescore.jl")
    @require ReverseDiff="37e2e3b7-166d-5795-8a7a-e32c996b4267" required("reversediff.jl")
end

end


module DebugTest

using Test
using Parametron

import Parametron: mock_model, setdirty!, findallocs

@testset "findallocs" begin
    model = mock_model()
    x = Variable(model)
    p = Parameter{Int}(() -> 3, model)
    expr = @expression p * 4 + x
    setdirty!(model); findallocs(devnull, expr)
end

end


include("../julia/ProgramTunnel_fs_new.jl")

using .ProgramTunnel_fs
using Formatting
using JSON

PTI = ProgramTunnelInfo(reverse_role=true, rotate=30)

sarrs = [ collect(Float64, 1:5),  collect(Float64, 101:105) / 2.0 ]
rarrs = [ collect(Float64, 1:5),  collect(Float64, 1:5)           ]

for t=1:100
    msg = format("[SSM] This is the {:d} time", t)
    println("MSG: ", msg)

    for i = 1:2
        for k = 1:5
            sarrs[i][k] = t*100 + i*10 + k
        end
        rarrs[i] .= 0.0
    end

    sendData(PTI, msg, sarrs)
    msg = recvData!(PTI, rarrs)

    println("Received msg:", msg)
    println("Recved arrs: ")
    print(json(rarrs, 4))
end


include("obc.jl")
include("mpm.jl")


module NeuralNetwork

include("network.jl")
include("SGD.jl")

export Chain, Layer, DSGD!, SGD!, GD!, test

end # module


function test_model_ad(model, logp_manual)
    vi = VarInfo(model)
    model(vi, SampleFromPrior())
    x = DynamicPPL.getall(vi)

    # Log probabilities using the model.
    function logp_model(x)
        new_vi = VarInfo(vi, SampleFromPrior(), x)
        model(new_vi, SampleFromPrior())
        return getlogp(new_vi)
    end

    # Check that both functions return the same values.
    lp = logp_manual(x)
    @test logp_model(x) ≈ lp

    # Gradients based on the manual implementation.
    grad = ForwardDiff.gradient(logp_manual, x)

    y, back = Tracker.forward(logp_manual, x)
    @test Tracker.data(y) ≈ lp
    @test Tracker.data(back(1)[1]) ≈ grad

    y, back = Zygote.pullback(logp_manual, x)
    @test y ≈ lp
    @test back(1)[1] ≈ grad

    # Gradients based on the model.
    @test ForwardDiff.gradient(logp_model, x) ≈ grad

    y, back = Tracker.forward(logp_model, x)
    @test Tracker.data(y) ≈ lp
    @test Tracker.data(back(1)[1]) ≈ grad

    y, back = Zygote.pullback(logp_model, x)
    @test y ≈ lp
    @test back(1)[1] ≈ grad
end


using Mandelbrot
using Test

@testset "Mandelbrot.jl" begin
    # Write your tests here.
end


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Desription
# ==============================================================================
#
#   Tests related to the support of Julia API (iterators, broadcast, etc.).
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# File: ./src/quaternion.jl
# =========================

# Broadcast
# ---------

@testset "Broadcast" begin
    function quat_bcast!(v::AbstractVector, q::Quaternion)
        v .= q
        return nothing
    end

    # Precompile to avoid counting allocations related to compilation.
    precompile(quat_bcast!, (Vector{Float64}, Quaternion{Float64}))
    precompile(quat_bcast!, (Vector{Float32}, Quaternion{Float32}))

    # Float64, Float32
    # --------------------------------------------------------------------------

    for T in (Float32, Float64)
        q = Quaternion{T}(randn(), randn(), randn(), randn())
        q = q / norm(q)
        v = zeros(T, 4)

        alloc = @allocated quat_bcast!(v, q)

        @test v[1]  == q[1]
        @test v[2]  == q[2]
        @test v[3]  == q[3]
        @test v[4]  == q[4]
        @test alloc == 0
    end
end

@testset "Iteration" begin
    q = Quaternion(1.0, 2.0, 3.0, 4.0)

    ret = iterate(q)
    @test ret === (1.0, 1)
    ret = iterate(q, ret[2])
    @test ret === (2.0, 2)
    ret = iterate(q, ret[2])
    @test ret === (3.0, 3)
    ret = iterate(q, ret[2])
    @test ret === (4.0, 4)
    ret = iterate(q, ret[2])
    @test ret === nothing
end

@testset "setindex!" begin
    function quat_setindex!(v::AbstractVector, q::Quaternion)
        v[4:7] = q[:]
        return nothing
    end

    # Precompile to avoid counting allocations related to compilation.
    precompile(quat_setindex!, (Vector{Float64}, Quaternion{Float64}))
    precompile(quat_setindex!, (Vector{Float32}, Quaternion{Float32}))

    # Float64, Float32
    # --------------------------------------------------------------------------

    for T in (Float32, Float64)
        q = Quaternion{T}(randn(), randn(), randn(), randn())
        q = q / norm(q)
        v = zeros(T, 20)

        alloc = @allocated quat_setindex!(v, q)

        @test v[4]  == q[1]
        @test v[5]  == q[2]
        @test v[6]  == q[3]
        @test v[7]  == q[4]
        @test alloc == 0
    end
end

@testset "Other API functions" begin
    q = Quaternion(1.0, 2.0, 3.0, 4.0)

    @test q[end] === 4.0
    @test axes(q) === (Base.OneTo(4),)
    @test first(q) === 1.0
    @test last(q) === 4.0
    @test ndims(q) === 1
    @test ndims(Quaternion) === 1
    @test length(q) === 4
    @test size(q) === (4,)
    @test Broadcast.broadcastable(q) === q
    @test Base.IndexStyle(Quaternion) === IndexLinear()

    q_conv = convert(Quaternion{Float32}, q)
    @test q_conv.q0 == Float32(q.q0)
    @test q_conv.q1 == Float32(q.q1)
    @test q_conv.q2 == Float32(q.q2)
    @test q_conv.q3 == Float32(q.q3)
    @test eltype(q_conv) === Float32
end


module PairsMacros
include("utils.jl")

"""
Create Pairs expressions for use in `DataFrames.jl`

### Details
All symbols are assumed to refer to columns in the DataFrames, with the exception of:
- `missing`
- first `args` of a `:call` or `:.` expression (function calls)
- arguments inside of a splicing/interpolation expression `\$()` (refer to column names programatically).
- arguments inside  `esc()` (refer to outside variables)

See also `@rows`

### Examples
```julia
julia> using PairsMacros
julia> @cols z = sum(x)
[:x] => sum => :z
julia> u = :y
julia> @cols z = sum(\$u)
[:y] => sum => :z
julia> @cols \$u = sum(x)
[:x] => sum => :y
julia> @cols z = sum(\$"my variable name")
"my variable name" => sum => :z
julia> @cols z = map(esc(cos), y)
[:y] => (x -> map(cos, x)) => :z
```
"""
macro cols(arg)
    esc(rewrite(arg, false))
end

"""
Create Pairs expressions for use in `DataFrames.jl`, where the function is enclosed by `ByRow`

### Details
All symbols are assumed to refer to columns in the DataFrames, with the exception of:
- `missing`
- first `args` of a `:call` or `:.` expression (function calls)
- arguments inside of a splicing/interpolation expression `\$()` (refer to column names programatically).
- arguments inside  `esc` (refer to outside variables)

See also `@cols`

### Examples
```julia
using PairsMacros
julia> @rows z = abs(x)
[:x] => ByRow(abs) => :z
julia> @rows z = tryparse(esc(Float64), x)
[:x] => (x -> tryparse(Float64, x) => :z
```
"""
macro rows(arg)
    esc(rewrite(arg, true))
end

# multiple argument version
macro cols(args...)
    Expr(:..., Expr(:tuple, (esc(rewrite(x, false)) for x in args)...))
end
macro rows(args...)
    Expr(:..., Expr(:tuple, (esc(rewrite(x, true)) for x in args)...))
end

function rewrite(e, byrow)
    if isa(e, Expr) && (e.head === :(=))
        # e.g. y = mean(x)
        lhs = e.args[1]
        if lhs isa Symbol
            target = QuoteNode(lhs)
        elseif lhs.head === :$
            target = lhs.args[1]
        end
        source, fn, has_fn = rewrite_rhs(e.args[2], byrow)
        if has_fn
            out = quote $source => $fn => $target end
        else
            out = quote $source => $target end
        end
    else
        # e.g. mean(x)
        source, fn, has_fn = rewrite_rhs(e, byrow)
        if has_fn
            out = quote $source => $fn end
        else
            out = source
        end
    end
    return out
end

function rewrite_rhs(rhs, byrow)
    membernames = Dict{Any, Symbol}()
    body = parse_columns!(membernames, rhs)
    k, v = keys(membernames), values(membernames)
    if length(k) == 1
        source = first(k)
    else
        source = Expr(:vect, k...)
    end
    if is_circ(body, v)
        # e.g. mean(skipmissing(x))
        # in this case, use mean ∘ skipmissing
        # this avoids precompilation + allows fast path
        fn = make_circ(body, v)
    else
        # in this case, use anonymous function
        fn = quote $(Expr(:tuple, v...)) -> $body end
    end
    if byrow
        fn = quote DataFrames.ByRow($fn) end
    end
    return source, fn, body ∉ v
end

parse_columns!(membernames::Dict, x) = x
function parse_columns!(membernames::Dict, x::Symbol)
    if x === :missing
        x
    else
        addkey!(membernames, QuoteNode(x))
    end
end
function parse_columns!(membernames::Dict, e::Expr)
    if e.head === :$
        # e.g. $(x)
        addkey!(membernames, e.args[1])
    elseif (e.head === :call) && (e.args[1] == :esc) && (length(e.args) == 2)
        e.args[2]
    elseif (e.head === :call) && (e.args[1] == :^) && (length(e.args) == 2)
        # e.g. ^(x)
        @warn "^() for escaping is deprecated, use esc() instead"
        e.args[2]
    elseif ((e.head === :.) & (e.args[1] isa Symbol)) | ((e.head === :call) & (e.args[1] isa Symbol))
        # e.g. f(x) or f.(x)
        Expr(e.head, e.args[1],
            (parse_columns!(membernames, x) for x in Iterators.drop(e.args, 1))...)
    else
        Expr(e.head,
            (parse_columns!(membernames, x) for x in e.args)...)
    end
end

function addkey!(membernames::Dict, nam)
    if !haskey(membernames, nam)
        membernames[nam] = gensym()
    end
    membernames[nam]
end

# when Cols() implemented in DataFrames.jl,
# @cols(f, r".*") could be used to return Cols(r".*") .=> f
export @rows, @cols

end


@testset "Schema Object" begin
    ST = ScientificTypes
    sch = ST.Schema((:a, :b), (Count, Count), (Int, Int))
    @test sch isa ST.Schema{(:a, :b), Tuple{Count,Count}, Tuple{Int64,Int64}}
    @test sch.names == (:a, :b)
    @test sch.types == (Int, Int)
    @test sch.scitypes == (Count, Count)

    @test_throws ArgumentError sch.something
    @test propertynames(sch) == (:names, :scitypes, :types)

    # Tables.jl interface
    @test Tables.istable(sch) == true
    @test Tables.columnaccess(sch) == true
    @test Tables.columns(sch) == sch
    @test Tables.columnnames(sch) == (:names, :scitypes, :types)
    @test Tables.schema(sch) == Tables.Schema(Tables.columnnames(sch), Tuple{Symbol, Type, Type})
    @test Tables.getcolumn(sch, 1) == (:a, :b)
    @test Tables.getcolumn(sch, 2) == (Count, Count)
    @test Tables.getcolumn(sch, 3) == (Int, Int)
    @test Tables.getcolumn(sch, :types) == (Int, Int)
    @test Tables.getcolumn(sch, :names) == (:a, :b)

    sch2 = ST.Schema((:a, :b), Tuple{Count, Count}, nothing)
    @test sch2 isa ST.Schema{(:a, :b), Tuple{Count,Count}, nothing}
    @test ST.stored(sch2) == false
    @test Tables.getcolumn(sch2, :types) == [nothing, nothing]
    @test Tables.getcolumn(sch2, 3) == [nothing, nothing]

    sch3 = ST.Schema((:a, :b), (Count, Count), (Int, Int); stored=true)
    @test sch3 isa ST.Schema{nothing, nothing, nothing}
    @test ST.stored(sch3) == true

    # Schema show
    df = DataFrame(x=[1.0,2.0,3.0], y=["a","b","c"])
    s = schema(df)
    io = IOBuffer()
    show(io, MIME("text/plain"), ScientificTypes.schema(df))
    @test String(take!(io)) == "┌───────┬────────────┬─────────┐\n│ names │ scitypes   │ types   │\n├───────┼────────────┼─────────┤\n│ x     │ Continuous │ Float64 │\n│ y     │ Textual    │ String  │\n└───────┴────────────┴─────────┘\n"
end

struct MySchemalessTable{U, V}
    x::Vector{U}
    y::Vector{V}
end

Tables.istable(::MySchemalessTable) = true
Tables.columnaccess(::Type{<:MySchemalessTable}) = true
Tables.columns(t::MySchemalessTable) = t
Tables.columnnames(::MySchemalessTable) = (:x, :y)

function Tables.getcolumn(t::MySchemalessTable, i::Int)
    return Tables.getcolumn(t, Tables.columnnames(t)[i])
end

function Tables.getcolumn(t::MySchemalessTable, nm::Symbol)
    return ifelse(nm === :x, t.x, t.y)
end

Tables.schema(t::MySchemalessTable) = nothing

struct ExtremelyWideTable{U, V}
    a::Vector{U}
    b::Vector{V}
end

Tables.istable(::ExtremelyWideTable) = true
Tables.columnaccess(::Type{<:ExtremelyWideTable}) = true
Tables.columns(t::ExtremelyWideTable) = t
Tables.columnnames(::ExtremelyWideTable) = (:a, :b)

function Tables.getcolumn(t::ExtremelyWideTable, i::Int)
    return Tables.getcolumn(t, Tables.columnnames(t)[i])
end

function Tables.getcolumn(t::ExtremelyWideTable, nm::Symbol)
    return ifelse(nm === :a, t.a, t.b)
end
function Tables.schema(t::ExtremelyWideTable{U, V}) where {U, V}
    return Tables.Schema(Tables.columnnames(t), (U, V); stored=true)
end

@testset "schema" begin
    ST = ScientificTypes
    # NamedTuple schema
    X = (
        x = rand(5),
        y = rand(Int, 5),
        z = categorical(collect("asdfa")),
        w = rand(5)
     )
     s = schema(X)
    @test s.scitypes == (Continuous, Count, Multiclass{4}, Continuous)
    @test s.types == (Float64, Int64, CategoricalValue{Char,UInt32}, Float64)

    # Dataframe schema
    df = DataFrame(X = [1,2,3,missing], Y = [1.0,2.0,3.0,missing])
    sch = schema(df)
    @test sch.scitypes[1] == Union{Missing, Count}
    @test sch.scitypes[2] == Union{Missing, Continuous}
    df2 = df[1:3,:]
    sch = schema(df2)
    @test sch.scitypes[1] == Union{Missing, Count}
    @test sch.scitypes[2] == Union{Missing, Continuous}

    # schema of non-tabular objects
    @test_throws ArgumentError schema([:x, :y])

    # PR #61 "schema check for `Tables.DictColumn`"
    X1 = Dict(:a=>rand(5), :b=>rand(Int, 5))
    s1 = schema(X1)
    @test s1.scitypes == (Continuous, Count)
    @test s1.types == (Float64, Int64)

    # issue 47 (schema for objects, `X` with, `Tables.schema(X) == nothing`)
    X2 = MySchemalessTable(rand(3), rand(Int, 3))
    s2 = schema(X2)
    @test s2 === nothing
    @test ST.__cols_schema(
        Tables.columns(X2), Tables.Schema(Tables.columnnames(X2), nothing)
    ) == ST.Schema(Tables.columnnames(X2), Tuple{Continuous, Count}, nothing)

    # For ExtremelyWideTable's
    X3 = ExtremelyWideTable(rand(3), rand(Int, 3))
    s3 = schema(X3)
    @test s3.names == Tables.columnnames(X3)
    @test s3.scitypes == (Continuous, Count)
    @test s3.types == (Float64, Int)
    @test ST._cols_schema(
        Tables.columns(X3), Tables.schema(X3)
    ) == ST.Schema(Tables.columnnames(X3), (Continuous, Count), (Float64, Int))
    @test ST._rows_schema(
        Tables.rows(X3), Tables.schema(X3)
    ) == ST.Schema(Tables.columnnames(X3), (Continuous, Count), (Float64, Int))

    # test schema for column oreinted tables with number of columns
    # exceeding COLS_SPECIALIZATION_THRESHOLD.
    nt = NamedTuple{
            Tuple(Symbol("x$i") for i in Base.OneTo(ST.COLS_SPECIALIZATION_THRESHOLD + 1))
        }(
            Tuple(rand(2) for i in Base.OneTo(ST.COLS_SPECIALIZATION_THRESHOLD + 1))
    )
    @test ST.__cols_schema(
        Tables.columns(nt), Tables.Schema(Tables.columnnames(nt), nothing)
    ) == ST.Schema(
        Tables.columnnames(nt),
        NTuple{ST.COLS_SPECIALIZATION_THRESHOLD + 1, Continuous},
        nothing
    )

    @test ST.__cols_schema(
        Tables.columns(nt), Tables.schema(nt)
    ) == ST.Schema(
        Tables.columnnames(nt),
        NTuple{ST.COLS_SPECIALIZATION_THRESHOLD + 1, Continuous},
        NTuple{ST.COLS_SPECIALIZATION_THRESHOLD + 1, Float64}
    )

 end


struct FlippedMesh{U,D1,T} <: AbstractMesh{U,D1,T}
    mesh::AbstractMesh{U,D1,T}
end

cells(m::FlippedMesh) = (fliporientation(c) for c in cells(m.mesh))
vertices(m::FlippedMesh) = m.mesh.vertices

Base.:-(m::AbstractMesh) = FlippedMesh(m)
Base.:-(m::FlippedMesh) = m.mesh


Base.parent(reg::AdjointRegister) = reg.parent

function Base.summary(io::IO, reg::AdjointRegister{B,RT}) where {B,RT}
    print(io, "adjoint(", summary(reg.parent), ")")
end

"""
    adjoint(register) -> register

Lazy adjoint for quantum registers.
"""
Base.adjoint(reg::AbstractRegister) = AdjointRegister(reg)
Base.adjoint(reg::AdjointRegister) = parent(reg)

viewbatch(reg::AdjointRegister, i::Int) = adjoint(viewbatch(parent(reg), i))

for FUNC in [:nqubits, :nremain, :nactive]
    @eval $FUNC(r::AdjointRegister) = $FUNC(r.parent)
end


# SingleDecision #
# ========================== #
mutable struct SingleDecisionConstraintBridge{T, S <: MOI.AbstractScalarSet} <: MOIB.Constraint.AbstractBridge
    constraint::CI{MOI.SingleVariable, S}
    decision::SingleDecision
end

function MOIB.Constraint.bridge_constraint(::Type{SingleDecisionConstraintBridge{T,S}},
                                           model,
                                           f::SingleDecision,
                                           set::S) where {T, S <: MOI.AbstractScalarSet}
    # Perform the bridge mapping manually
    g = MOIB.bridged_variable_function(model, f.decision)
    mapped_variable = MOI.SingleVariable(only(g.terms).variable_index)
    # Add the bridged constraint
    constraint = MOI.add_constraint(model,
                                    mapped_variable,
                                    set)
    # Save the constraint index and the decision to allow modifications
    return SingleDecisionConstraintBridge{T,S}(constraint, f)
end

function MOI.supports_constraint(::Type{<:SingleDecisionConstraintBridge{T}},
                                 ::Type{SingleDecision},
                                 ::Type{<:MOI.AbstractScalarSet}) where T
    return true
end
function MOIB.added_constrained_variable_types(::Type{<:SingleDecisionConstraintBridge})
    return Tuple{DataType}[]
end
function MOIB.added_constraint_types(::Type{<:SingleDecisionConstraintBridge{T,S}}) where {T,S}
    return [(MOI.SingleVariable, S), (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T})]
end
function MOIB.Constraint.concrete_bridge_type(::Type{<:SingleDecisionConstraintBridge{T}},
                              ::Type{SingleDecision},
                              S::Type{<:MOI.AbstractScalarSet}) where T
    return SingleDecisionConstraintBridge{T,S}
end

MOI.get(b::SingleDecisionConstraintBridge{T,S}, ::MOI.NumberOfConstraints{MOI.SingleVariable, S}) where {T,S} = 1
MOI.get(b::SingleDecisionConstraintBridge{T}, ::MOI.ListOfConstraintIndices{MOI.SingleVariable, S}) where {T,S} = [b.constraint]

function MOI.get(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute,
                 bridge::SingleDecisionConstraintBridge)
    return MOI.get(model, attr, bridge.constraint)
end

function MOI.get(model::MOI.ModelLike, ::DecisionIndex,
                 bridge::SingleDecisionConstraintBridge{T}) where T
    return bridge.constraint
end

function MOI.delete(model::MOI.ModelLike, bridge::SingleDecisionConstraintBridge)
    if bridge.constraint.value != 0
        MOI.delete(model, bridge.constraint)
    end
    return nothing
end

function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet,
                 bridge::SingleDecisionConstraintBridge{T,S}, change::S) where {T, S <: MOI.AbstractScalarSet}
    MOI.set(model, MOI.ConstraintSet(), bridge.constraint, change)
    return nothing
end


module contactForceLaw_ballWithBox_45Deg

using Modia3D
using Modia3D.StaticArrays
import Modia3D.ModiaMath

vmat1 = Modia3D.Material(color="LightBlue" , transparency=0.5)    # material of SolidFileMesh
vmat2 = deepcopy(vmat1)                                           # material of convex decomposition of SolidFileMesh
vmat2.transparency = 0.7

cmat = "Steel"

@assembly ThreeDFiles begin
  world = Modia3D.Object3D(visualizeFrame=true)

  sphereMoving     = Modia3D.Object3D(world, Modia3D.Solid(Modia3D.SolidSphere(0.5) , "Steel", vmat1; contactMaterial = cmat); r=[0.0, 0.0, 0.0],  fixed=false, v_start=[2.0,0.0,-3.0]) # R=ModiaMath.rot2(-pi/2),

  box     = Modia3D.Object3D(world, Modia3D.Solid(Modia3D.SolidBox(20.0,4.0,1.0) , "Steel", vmat1; contactMaterial = cmat); r=[0.0, 0.0, -3.0], fixed=true ) #, R=ModiaMath.rot2(-pi/3))
end

gravField = Modia3D.UniformGravityField(g=9.81, n=[0,0,-1])
threeD = ThreeDFiles(sceneOptions=Modia3D.SceneOptions(gravityField=gravField,visualizeFrames=true, defaultFrameLength=0.7,nz_max = 100, enableContactDetection=true, visualizeContactPoints=true, visualizeSupportPoints=true))

model = Modia3D.SimulationModel( threeD )
ModiaMath.print_ModelVariables(model)
result = ModiaMath.simulate!(model; stopTime=4.0, tolerance=1e-8,interval=0.001, log=false)
ModiaMath.plot(result, ["sphereMoving.r","sphereMoving.v"])

println("... success of contactForceLaw_ballWithBox_45Deg.jl!")

end


function MarkAllCells(r)
    side = Ost
    moves_Sud = movementsandcountmoves(r, Sud)
    moves_West = movementsandcountmoves(r, West)
    markrow(r, side)
    putmarker!(r)
    while isborder(r, Nord) == false
        move!(r, Nord)
        side = inverse(side)
        markrow(r, side)
        putmarker!(r)
    end
    movements(r, Sud)
    movements(r, West)
    movementsbyfixmoves(r, Ost, moves_West)
    movementsbyfixmoves(r, Nord, moves_Sud)
end

function movements(r, side)
    while isborder(r, side) == false
        move!(r, side)
    end
end

function inverse(side)
    return HorizonSide((Int(side) + 2) % 4)
end

function markrow(r, side)
    while isborder(r, side) == false
        putmarker!(r)
        move!(r, side)
    end
end

function movementsandcountmoves(r, side)
    moves = 0
    while isborder(r, side) == false
        move!(r, side)
        moves += 1
    end
    return moves
end

function movementsbyfixmoves(r, side, moves)
    while moves != 0
        move!(r, side)
        moves -= 1
    end
end


using Plots, Test

@testset "Subplot sclicing" begin
    pl = @test_nowarn plot(
        rand(4, 8),
        layout = 4,
        yscale = [:identity :identity :log10 :log10],
    )
    @test pl[1][:yaxis][:scale] == :identity
    @test pl[2][:yaxis][:scale] == :identity
    @test pl[3][:yaxis][:scale] == :log10
    @test pl[4][:yaxis][:scale] == :log10
end


"""
    CircShiftedArray(parent::AbstractArray, shifts)

Custom `AbstractArray` object to store an `AbstractArray` `parent` circularly shifted by `shifts` steps (where `shifts` is
a `Tuple` with one `shift` value per dimension of `parent`). Note that `shift` is modified with a modulo operation and does
not store the passed value but instead a positive number which leads to an equivalent shift.
Use `copy` to collect the values of a `CircShiftedArray` into a normal `Array`.

# Examples

```jldoctest circshiftedarray
julia> v = [1, 3, 5, 4];

julia> s = CircShiftedArray(v, (1,))
4-element CircShiftedVector{Int64, Vector{Int64}}:
 4
 1
 3
 5

julia> copy(s)
4-element Vector{Int64}:
 4
 1
 3
 5
```
"""
struct CircShiftedArray{T, N, S<:AbstractArray} <: AbstractArray{T, N}
    parent::S
    # the field `shifts` stores the circular shifts modulo the size of the parent array
    shifts::NTuple{N, Int}
    function CircShiftedArray(p::AbstractArray{T, N}, n = ()) where {T, N}
        shifts = map(mod, padded_tuple(p, n), size(p))
        return new{T, N, typeof(p)}(p, shifts)
    end
end

"""
    CircShiftedVector{T, S<:AbstractArray}

Shorthand for `CircShiftedArray{T, 1, S}`.
"""
const CircShiftedVector{T, S<:AbstractArray} = CircShiftedArray{T, 1, S}

CircShiftedVector(v::AbstractVector, n = ()) = CircShiftedArray(v, n)

size(s::CircShiftedArray) = size(parent(s))
axes(s::CircShiftedArray) = axes(parent(s))

@inline function bringwithin(ind_with_offset::Int, ranges::AbstractUnitRange)
    return ifelse(ind_with_offset < first(ranges), ind_with_offset + length(ranges), ind_with_offset)
end

@inline function getindex(s::CircShiftedArray{T, N}, x::Vararg{Int, N}) where {T, N}
    @boundscheck checkbounds(s, x...)
    v, ind = parent(s), offset(shifts(s), x)
    i = map(bringwithin, ind, axes(s))
    return @inbounds v[i...]
end

@inline function setindex!(s::CircShiftedArray{T, N}, el, x::Vararg{Int, N}) where {T, N}
    @boundscheck checkbounds(s, x...)
    v, ind = parent(s), offset(shifts(s), x)
    i = map(bringwithin, ind, axes(s))
    @inbounds v[i...] = el
    return s
end

parent(s::CircShiftedArray) = s.parent

"""
    shifts(s::CircShiftedArray)

Return amount by which `s` is shifted compared to `parent(s)`.
"""
shifts(s::CircShiftedArray) = s.shifts


# This file is a part of BAT.jl, licensed under the MIT License (MIT).

using BAT
using Test

using Random, Statistics, LinearAlgebra
using Distributions, PDMats
using StableRNGs

@testset "standard_normal" begin
    stblrng() = StableRNG(789990641)

    @testset "StandardUvNormal" begin
        @test @inferred(BAT.StandardUvNormal()) isa BAT.StandardUvNormal{Float64}
        @test @inferred(BAT.StandardUvNormal{Float32}()) isa BAT.StandardUvNormal{Float32}
        @test @inferred(BAT.StandardUvNormal{Float64}()) isa BAT.StandardUvNormal{Float64}

        @test @inferred(Normal(BAT.StandardUvNormal{Float32}())) isa Normal{Float32}
        @test @inferred(Normal(BAT.StandardUvNormal{Float64}())) isa Normal{Float64}
        @test @inferred(Normal(BAT.StandardUvNormal())) == Normal()
        @test @inferred(convert(Normal, BAT.StandardUvNormal())) == Normal()

        d = BAT.StandardUvNormal()
        dref = Normal()

        @test @inferred(minimum(d)) == minimum(dref)
        @test @inferred(maximum(d)) == maximum(dref)

        @test @inferred(params(d)) == params(dref)
        @test @inferred(partype(d)) == partype(dref)

        @test @inferred(location(d)) == location(dref)
        @test @inferred(scale(d)) == scale(dref)

        @test @inferred(eltype(typeof(d))) == eltype(typeof(dref))
        @test @inferred(eltype(d)) == eltype(dref)

        @test @inferred(length(d)) == length(dref)
        @test @inferred(size(d)) == size(dref)

        @test @inferred(mean(d)) == mean(dref)
        @test @inferred(median(d)) == median(dref)
        @test @inferred(mode(d)) == mode(dref)
        @test @inferred(modes(d)) ≈ modes(dref)

        @test @inferred(var(d)) == var(dref)
        @test @inferred(std(d)) == std(dref)
        @test @inferred(skewness(d)) == skewness(dref)
        @test @inferred(kurtosis(d)) == kurtosis(dref)

        @test @inferred(entropy(d)) == entropy(dref)

        for x in [-Inf, -1.3, 0.0, 1.3, +Inf]
            @test @inferred(gradlogpdf(d, x)) == gradlogpdf(dref, x)

            @test @inferred(logpdf(d, x)) == logpdf(dref, x)
            @test @inferred(pdf(d, x)) == pdf(dref, x)
            @test @inferred(logcdf(d, x)) == logcdf(dref, x)
            @test @inferred(cdf(d, x)) == cdf(dref, x)
            @test @inferred(logccdf(d, x)) == logccdf(dref, x)
            @test @inferred(ccdf(d, x)) == ccdf(dref, x)
        end

        for p in [0.0, 0.25, 0.75, 1.0]
            @test @inferred(quantile(d, p)) == quantile(dref, p)
            @test @inferred(cquantile(d, p)) == cquantile(dref, p)
        end

        for t in [-3, 0, 3]
            @test @inferred(mgf(d, t)) == mgf(dref, t)
            @test @inferred(cf(d, t)) == cf(dref, t)
        end

        @test @inferred(rand(stblrng(), d)) == rand(stblrng(), d)
        @test @inferred(rand(stblrng(), d, 5)) == rand(stblrng(), d, 5)

        @test @inferred(truncated(BAT.StandardUvNormal{Float32}(), -2.2f0, 3.1f0)) isa Truncated{Normal{Float32}}
        @test truncated(BAT.StandardUvNormal{Float32}(), -2.2f0, 3.1f0) == truncated(Normal(0.0f0, 1.0f0), -2.2f0, 3.1f0)

        @test @inferred(product_distribution(fill(BAT.StandardUvNormal{Float32}(), 3))) isa BAT.StandardMvNormal{Float32}
        @test product_distribution(fill(BAT.StandardUvNormal{Float32}(), 3)) == BAT.StandardMvNormal{Float32}(3)
    end

    @testset "StandardMvNormal" begin
        @test @inferred(BAT.StandardMvNormal(3)) isa BAT.StandardMvNormal{Float64}
        @test @inferred(BAT.StandardMvNormal{Float32}(3)) isa BAT.StandardMvNormal{Float32}
        @test @inferred(BAT.StandardMvNormal{Float64}(3)) isa BAT.StandardMvNormal{Float64}

        @test @inferred(Distributions.Product(BAT.StandardMvNormal{Float32}(3))) isa Distributions.Product{Continuous,BAT.StandardUvNormal{Float32}}
        @test @inferred(Distributions.Product(BAT.StandardMvNormal{Float64}(3))) isa Distributions.Product{Continuous,BAT.StandardUvNormal{Float64}}
        @test @inferred(Distributions.Product(BAT.StandardMvNormal{Float64}(3))) == Distributions.Product(Fill(BAT.StandardUvNormal(), 3))
        @test @inferred(convert(Product, BAT.StandardMvNormal(3))) == Distributions.Product(Fill(BAT.StandardUvNormal(), 3))

        @test @inferred(MvNormal(BAT.StandardMvNormal{Float32}(3))) isa MvNormal{Float32}
        @test @inferred(MvNormal(BAT.StandardMvNormal{Float64}(3))) isa MvNormal{Float64}
        @test @inferred(MvNormal(BAT.StandardMvNormal{Float64}(3))) == MvNormal(ScalMat(3, 1.0))
        @test @inferred(convert(MvNormal, BAT.StandardMvNormal(3))) == MvNormal(ScalMat(3, 1.0))

        d = BAT.StandardMvNormal(3)
        dref = MvNormal(ScalMat(3, 1.0))

        @test @inferred(eltype(typeof(d))) == eltype(typeof(dref))
        @test @inferred(eltype(d)) == eltype(dref)

        @test @inferred(length(d)) == length(dref)
        @test @inferred(size(d)) == size(dref)

        @test @inferred(view(BAT.StandardMvNormal{Float32}(7), 3)) isa BAT.StandardUvNormal{Float32}
        @test_throws BoundsError view(BAT.StandardMvNormal{Float32}(7), 9)
        @test @inferred(view(BAT.StandardMvNormal{Float32}(7), 2:4)) isa BAT.StandardMvNormal{Float32}
        @test view(BAT.StandardMvNormal{Float32}(7), 2:4) == BAT.StandardMvNormal{Float32}(3)
        @test_throws BoundsError view(BAT.StandardMvNormal{Float32}(7), 2:8)

        @test @inferred(params(d)) == params(dref)
        @test @inferred(partype(d)) == partype(dref)

        @test @inferred(mean(d)) == mean(dref)
        @test @inferred(var(d)) == var(dref)
        @test @inferred(cov(d)) == cov(dref)

        @test @inferred(mode(d)) == mode(dref)
        @test @inferred(modes(d)) == modes(dref)

        @test @inferred(invcov(d)) == invcov(dref)
        @test @inferred(logdetcov(d)) == logdetcov(dref)

        @test @inferred(entropy(d)) == entropy(dref)

        for x in fill.([-Inf, -1.3, 0.0, 1.3, +Inf], 3)
            @test @inferred(insupport(d, x)) == insupport(dref, x)
            @test @inferred(logpdf(d, x)) == logpdf(dref, x)
            @test @inferred(pdf(d, x)) == pdf(dref, x)
            @test @inferred(sqmahal(d, x)) == sqmahal(dref, x)
            @test @inferred(gradlogpdf(d, x)) == gradlogpdf(dref, x)
        end

        @test @inferred(rand(stblrng(), d)) == rand(stblrng(), d)
        @test @inferred(rand!(stblrng(), d, zeros(3))) == rand!(stblrng(), d, zeros(3))
        @test @inferred(rand!(stblrng(), d, zeros(3, 10))) == rand!(stblrng(), d, zeros(3, 10))
    end
end


#! /usr/bin/julia

# Rosetta Code, Find limit of recursion

function divedivedive()
    global depth
    depth += 1
    divedivedive()
end

function divedivedive(d::Int)
    try
        divedivedive(d+1)
    catch
        return d
    end
end

depth = divedivedive(0)
println("A clean dive reaches a depth of ", depth, ".")

depth = 0
try
    divedivedive()
end
println("A dirty dive reaches a depth of ", depth, ".")


module AdditiveRungeKuttaMethod
export AdditiveRungeKutta
export ARK2GiraldoKellyConstantinescu
export ARK548L2SA2KennedyCarpenter, ARK437L2SA1KennedyCarpenter

# Naive formulation that uses equation 3.8 from Giraldo, Kelly, and Constantinescu (2013) directly.
# Seems to cut the number of solver iterations by half but requires Nstages - 1 additional storage.
struct NaiveVariant end
additional_storage(::NaiveVariant, Q, Nstages) = (Lstages = ntuple(i -> similar(Q), Nstages),)

# Formulation that does things exactly as in Giraldo, Kelly, and Constantinescu (2013).
# Uses only one additional vector of storage regardless of the number of stages.
struct LowStorageVariant end
additional_storage(::LowStorageVariant, Q, Nstages) = (Qtt = similar(Q),)

using GPUifyLoops
include("AdditiveRungeKuttaMethod_kernels.jl")

using StaticArrays

using ..ODESolvers
ODEs = ODESolvers
using ..SpaceMethods
using ..LinearSolvers
using ..MPIStateArrays: device, realview

"""
    op! = EulerOperator(f!, ϵ)

Construct a linear operator which performs an explicit Euler step ``Q + α f(Q)``,
where `f!` and `op!` both operate inplace, with extra arguments passed through, i.e.
```
op!(LQ, Q, args...)
```
is equivalent to
```
f!(dQ, Q, args...)
LQ .= Q .+ ϵ .* dQ
```
"""
mutable struct EulerOperator{F,FT}
  f!::F
  ϵ::FT
end

function (op::EulerOperator)(LQ, Q, args...)
  op.f!(LQ, Q, args..., increment=false)
  @. LQ = Q + op.ϵ * LQ
end

"""
    AdditiveRungeKutta(f, l, linearsolver, RKAe, RKAi, RKB, RKC, Q;
                       split_nonlinear_linear, variant, dt, t0 = 0)

This is a time stepping object for implicit-explicit time stepping of a
decomposed differential equation. When `split_nonlinear_linear == false`
the equation is assumed to be decomposed as

```math
  \\dot{Q} = [l(Q, t)] + [f(Q, t) - l(Q, t)]
```

where `Q` is the state, `f` is the full tendency and
`l` is the chosen linear operator. When `split_nonlinear_linear == true`
the assumed decomposition is

```math
  \\dot{Q} = [l(Q, t)] + [f(Q, t)]
```

where `f` is now only the nonlinear tendency. For both decompositions the
linear operator `l` is integrated implicitly whereas the remaining part
is integrated explicitly. Other arguments are the required time step size `dt`
and the optional initial time `t0`. The resulting linear systems are solved
using the provided `linearsolver` solver. This time stepping object is intended
to be passed to the `solve!` command.

The constructor builds an additive Runge--Kutta scheme
based on the provided `RKAe`, `RKAi`, `RKB` and `RKC` coefficient arrays.
Additionally `variant` specifies which of the analytically equivalent but numerically
different formulations of the scheme is used.

The available concrete implementations are:

  - [`ARK2GiraldoKellyConstantinescu`](@ref)
  - [`ARK548L2SA2KennedyCarpenter`](@ref)
  - [`ARK437L2SA1KennedyCarpenter`](@ref)
"""
mutable struct AdditiveRungeKutta{T, RT, AT, LT, V, VS, Nstages, Nstages_sq} <: ODEs.AbstractODESolver
  "time step"
  dt::RT
  "time"
  t::RT
  "rhs function"
  rhs!
  "rhs linear operator"
  rhs_linear!
  "implicit operator, pre-factorized"
  implicitoperator!
  "linear solver"
  linearsolver::LT
  "Storage for solution during the AdditiveRungeKutta update"
  Qstages::NTuple{Nstages, AT}
  "Storage for RHS during the AdditiveRungeKutta update"
  Rstages::NTuple{Nstages, AT}
  "Storage for the linear solver rhs vector"
  Qhat::AT
  "RK coefficient matrix A for the explicit scheme"
  RKA_explicit::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}
  "RK coefficient matrix A for the implicit scheme"
  RKA_implicit::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}
  "RK coefficient vector B (rhs add in scaling)"
  RKB::SArray{Tuple{Nstages}, RT, 1, Nstages}
  "RK coefficient vector C (time scaling)"
  RKC::SArray{Tuple{Nstages}, RT, 1, Nstages}
  split_nonlinear_linear::Bool
  "Variant of the ARK scheme"
  variant::V
  "Storage dependent on the variant of the ARK scheme"
  variant_storage::VS

  function AdditiveRungeKutta(rhs!,
                              rhs_linear!,
                              linearsolver::AbstractLinearSolver,
                              RKA_explicit, RKA_implicit, RKB, RKC,
                              split_nonlinear_linear, variant,
                              Q::AT; dt=nothing, t0=0) where {AT<:AbstractArray}

    @assert dt != nothing

    T = eltype(Q)
    LT = typeof(linearsolver)
    RT = real(T)

    Nstages = length(RKB)

    Qstages = (Q, ntuple(i -> similar(Q), Nstages - 1)...)
    Rstages = ntuple(i -> similar(Q), Nstages)
    Qhat = similar(Q)

    V = typeof(variant)
    variant_storage = additional_storage(variant, Q, Nstages)
    VS = typeof(variant_storage)

    # The code throughout assumes SDIRK implicit tableau so we assert that
    # here.
    for is = 2:Nstages
      @assert RKA_implicit[is, is] ≈ RKA_implicit[2, 2]
    end

    α = dt * RKA_implicit[2, 2]
    # Here we are passing NaN for the time since prefactorization assumes the
    # operator is time independent.  If that is not the case the NaN will
    # surface.
    implicitoperator! = prefactorize(EulerOperator(rhs_linear!, -α),
                                     linearsolver, Q, nothing, T(NaN))

    new{T, RT, AT, LT, V, VS, Nstages, Nstages ^ 2}(RT(dt), RT(t0),
                                                    rhs!, rhs_linear!, implicitoperator!, linearsolver,
                                                    Qstages, Rstages, Qhat,
                                                    RKA_explicit, RKA_implicit, RKB, RKC,
                                                    split_nonlinear_linear,
                                                    variant, variant_storage)
  end
end

function AdditiveRungeKutta(spacedisc::AbstractSpaceMethod,
                            spacedisc_linear::AbstractSpaceMethod,
                            linearsolver::AbstractLinearSolver,
                            RKA_explicit, RKA_implicit, RKB, RKC,
                            split_nonlinear_linear, variant,
                            Q::AT; dt=nothing, t0=0) where {AT<:AbstractArray}
  rhs! = (x...; increment) -> SpaceMethods.odefun!(spacedisc, x..., increment = increment)
  rhs_linear! = (x...; increment) -> SpaceMethods.odefun!(spacedisc_linear, x..., increment = increment)
  AdditiveRungeKutta(rhs!, rhs_linear!, linearsolver,
                     RKA_explicit, RKA_implicit, RKB, RKC,
                     split_nonlinear_linear, variant,
                     Q; dt=dt, t0=t0)
end

# this will only work for iterative solves
# direct solvers use prefactorization
ODEs.isadjustable(ark::AdditiveRungeKutta) = ark.implicitoperator! isa EulerOperator
function ODEs.updatedt!(ark::AdditiveRungeKutta, dt)
  @assert ODEs.isadjustable(ark)
  ark.dt = dt
  α = dt * ark.RKA_implicit[2, 2]
  ark.implicitoperator! = EulerOperator(ark.rhs_linear!, -α)
end
ODEs.updatetime!(ark::AdditiveRungeKutta, time) = (ark.t = time)

function ODEs.dostep!(Q, ark::AdditiveRungeKutta, p, timeend::Real,
                      adjustfinalstep::Bool)
  time, dt = ark.t, ark.dt
  if adjustfinalstep && time + dt > timeend
    dt = timeend - time
  end
  @assert dt > 0

  ODEs.dostep!(Q, ark, p, time, dt)

  if dt == ark.dt
    ark.t += dt
  else
    ark.t = timeend
  end

end

function ODEs.dostep!(Q, ark::AdditiveRungeKutta, p, time::Real, dt::Real,
                      slow_δ = nothing, slow_rv_dQ = nothing,
                      slow_scaling = nothing)
  ODEs.dostep!(Q, ark, ark.variant, p, time, dt, slow_δ, slow_rv_dQ, slow_scaling)
end

function ODEs.dostep!(Q, ark::AdditiveRungeKutta, variant::NaiveVariant,
                      p, time::Real, dt::Real,
                      slow_δ = nothing, slow_rv_dQ = nothing,
                      slow_scaling = nothing)
  implicitoperator!, linearsolver = ark.implicitoperator!, ark.linearsolver
  RKA_explicit, RKA_implicit = ark.RKA_explicit, ark.RKA_implicit
  RKB, RKC = ark.RKB, ark.RKC
  rhs!, rhs_linear! = ark.rhs!, ark.rhs_linear!
  Qstages, Rstages = ark.Qstages, ark.Rstages
  Qhat = ark.Qhat
  split_nonlinear_linear = ark.split_nonlinear_linear
  Lstages = ark.variant_storage.Lstages

  rv_Q = realview(Q)
  rv_Qstages = realview.(Qstages)
  rv_Lstages = realview.(Lstages)
  rv_Rstages = realview.(Rstages)
  rv_Qhat = realview(Qhat)

  Nstages = length(RKB)

  threads = 256
  blocks = div(length(rv_Q) + threads - 1, threads)

  # calculate the rhs at first stage to initialize the stage loop
  rhs!(Rstages[1], Qstages[1], p, time + RKC[1] * dt, increment = false)

  if dt != ark.dt
    α = dt * RKA_implicit[2, 2]
    implicitoperator! = EulerOperator(rhs_linear!, -α)
  end

  rhs_linear!(Lstages[1], Qstages[1], p, time + RKC[1] * dt, increment = false)

  # note that it is important that this loop does not modify Q!
  for istage = 2:Nstages
    stagetime = time + RKC[istage] * dt

    # this kernel also initializes Qstages[istage] with an initial guess
    # for the linear solver
    @launch(device(Q), threads = threads, blocks = blocks,
            stage_update!(variant, rv_Q, rv_Qstages, rv_Lstages, rv_Rstages, rv_Qhat,
                          RKA_explicit, RKA_implicit, dt, Val(istage),
                          Val(split_nonlinear_linear), slow_δ, slow_rv_dQ))

    #solves Q_tt = Qhat + dt * RKA_implicit[istage, istage] * rhs_linear!(Q_tt)
    α = dt * RKA_implicit[istage, istage]
    linearoperator! = function(LQ, Q)
      rhs_linear!(LQ, Q, p, stagetime; increment = false)
      @. LQ = Q - α * LQ
    end
    linearsolve!(implicitoperator!, linearsolver, Qstages[istage], Qhat, p, stagetime)

    rhs!(Rstages[istage], Qstages[istage], p, stagetime, increment = false)
    rhs_linear!(Lstages[istage], Qstages[istage], p, stagetime, increment = false)
  end

  # compose the final solution
  @launch(device(Q), threads = threads, blocks = blocks,
          solution_update!(variant, rv_Q, rv_Lstages, rv_Rstages, RKB, dt,
                           Val(Nstages), Val(split_nonlinear_linear),
                           slow_δ, slow_rv_dQ, slow_scaling))
end

function ODEs.dostep!(Q, ark::AdditiveRungeKutta, variant::LowStorageVariant,
                      p, time::Real, dt::Real,
                      slow_δ = nothing, slow_rv_dQ = nothing,
                      slow_scaling = nothing)
  implicitoperator!, linearsolver = ark.implicitoperator!, ark.linearsolver
  RKA_explicit, RKA_implicit = ark.RKA_explicit, ark.RKA_implicit
  RKB, RKC = ark.RKB, ark.RKC
  rhs!, rhs_linear! = ark.rhs!, ark.rhs_linear!
  Qstages, Rstages = ark.Qstages, ark.Rstages
  Qhat = ark.Qhat
  split_nonlinear_linear = ark.split_nonlinear_linear
  Qtt = ark.variant_storage.Qtt

  rv_Q = realview(Q)
  rv_Qstages = realview.(Qstages)
  rv_Rstages = realview.(Rstages)
  rv_Qhat = realview(Qhat)
  rv_Qtt = realview(Qtt)

  Nstages = length(RKB)

  threads = 256
  blocks = div(length(rv_Q) + threads - 1, threads)

  # calculate the rhs at first stage to initialize the stage loop
  rhs!(Rstages[1], Qstages[1], p, time + RKC[1] * dt, increment = false)

  if dt != ark.dt
    α = dt * RKA_implicit[2, 2]
    implicitoperator! = EulerOperator(rhs_linear!, -α)
  end

  # note that it is important that this loop does not modify Q!
  for istage = 2:Nstages
    stagetime = time + RKC[istage] * dt

    # this kernel also initializes Qtt for the linear solver
    @launch(device(Q), threads = threads, blocks = blocks,
            stage_update!(variant, rv_Q, rv_Qstages, rv_Rstages, rv_Qhat, rv_Qtt,
                          RKA_explicit, RKA_implicit, dt, Val(istage),
                          Val(split_nonlinear_linear), slow_δ, slow_rv_dQ))

    #solves Q_tt = Qhat + dt * RKA_implicit[istage, istage] * rhs_linear!(Q_tt)
    linearsolve!(implicitoperator!, linearsolver, Qtt, Qhat, p, stagetime)

    #update Qstages
    Qstages[istage] .+= Qtt

    rhs!(Rstages[istage], Qstages[istage], p, stagetime, increment = false)
  end

  if split_nonlinear_linear
    for istage = 1:Nstages
      stagetime = time + RKC[istage] * dt
      rhs_linear!(Rstages[istage], Qstages[istage], p, stagetime, increment = true)
    end
  end

  # compose the final solution
  @launch(device(Q), threads = threads, blocks = blocks,
          solution_update!(variant, rv_Q, rv_Rstages, RKB, dt, Val(Nstages),
                           slow_δ, slow_rv_dQ, slow_scaling))
end

"""
    ARK2GiraldoKellyConstantinescu(f, l, linearsolver, Q; dt, t0,
                                   split_nonlinear_linear, variant, paperversion)

This function returns an [`AdditiveRungeKutta`](@ref) time stepping object,
see the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.
This time stepping object is intended to be passed to the `solve!` command.

`paperversion=true` uses the coefficients from the paper, `paperversion=false`
uses coefficients that make the scheme (much) more stable but less accurate

This uses the second-order-accurate 3-stage additive Runge--Kutta scheme of
Giraldo, Kelly and Constantinescu (2013).

### References
    @article{giraldo2013implicit,
      title={Implicit-explicit formulations of a three-dimensional nonhydrostatic unified model of the atmosphere ({NUMA})},
      author={Giraldo, Francis X and Kelly, James F and Constantinescu, Emil M},
      journal={SIAM Journal on Scientific Computing},
      volume={35},
      number={5},
      pages={B1162--B1194},
      year={2013},
      publisher={SIAM}
    }
"""
function ARK2GiraldoKellyConstantinescu(F, L,
                                        linearsolver::AbstractLinearSolver,
                                        Q::AT; dt=nothing, t0=0,
                                        split_nonlinear_linear=false,
                                        variant=LowStorageVariant(),
                                        paperversion=false) where {AT<:AbstractArray}

  @assert dt != nothing

  T = eltype(Q)
  RT = real(T)

  a32 = RT(paperversion ? (3 + 2sqrt(2)) / 6 : 1 // 2)
  RKA_explicit = [RT(0)           RT(0)   RT(0);
                  RT(2 - sqrt(2)) RT(0)   RT(0);
                  RT(1 - a32)     RT(a32) RT(0)]

  RKA_implicit = [RT(0)               RT(0)               RT(0);
                  RT(1 - 1 / sqrt(2)) RT(1 - 1 / sqrt(2)) RT(0);
                  RT(1 / (2sqrt(2)))  RT(1 / (2sqrt(2)))  RT(1 - 1 / sqrt(2))]

  RKB = [RT(1 / (2sqrt(2))), RT(1 / (2sqrt(2))), RT(1 - 1 / sqrt(2))]
  RKC = [RT(0), RT(2 - sqrt(2)), RT(1)]

  Nstages = length(RKB)

  AdditiveRungeKutta(F, L, linearsolver,
                     RKA_explicit, RKA_implicit, RKB, RKC,
                     split_nonlinear_linear,
                     variant,
                     Q; dt=dt, t0=t0)
end

"""
    ARK548L2SA2KennedyCarpenter(f, l, linearsolver, Q; dt, t0,
                                split_nonlinear_linear, variant)

This function returns an [`AdditiveRungeKutta`](@ref) time stepping object,
see the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.
This time stepping object is intended to be passed to the `solve!` command.

This uses the fifth-order-accurate 8-stage additive Runge--Kutta scheme of
Kennedy and Carpenter (2013).

### References

    @article{kennedy2019higher,
      title={Higher-order additive Runge--Kutta schemes for ordinary differential equations},
      author={Kennedy, Christopher A and Carpenter, Mark H},
      journal={Applied Numerical Mathematics},
      volume={136},
      pages={183--205},
      year={2019},
      publisher={Elsevier}
    }
"""
function ARK548L2SA2KennedyCarpenter(F, L,
                                     linearsolver::AbstractLinearSolver,
                                     Q::AT; dt=nothing, t0=0,
                                     split_nonlinear_linear=false,
                                     variant=LowStorageVariant()) where {AT<:AbstractArray}

  @assert dt != nothing

  T = eltype(Q)
  RT = real(T)

  Nstages = 8
  gamma = RT(2 // 9)

  # declared as Arrays for mutability, later these will be converted to static arrays
  RKA_explicit = zeros(RT, Nstages, Nstages)
  RKA_implicit = zeros(RT, Nstages, Nstages)
  RKB = zeros(RT, Nstages)
  RKC = zeros(RT, Nstages)

  # the main diagonal
  for is = 2:Nstages
    RKA_implicit[is, is] = gamma
  end

  RKA_implicit[3, 2] = RT(2366667076620 //  8822750406821)
  RKA_implicit[4, 2] = RT(-257962897183 // 4451812247028)
  RKA_implicit[4, 3] = RT(128530224461 // 14379561246022)
  RKA_implicit[5, 2] = RT(-486229321650 // 11227943450093)
  RKA_implicit[5, 3] = RT(-225633144460 // 6633558740617)
  RKA_implicit[5, 4] = RT(1741320951451 // 6824444397158)
  RKA_implicit[6, 2] = RT(621307788657 // 4714163060173)
  RKA_implicit[6, 3] = RT(-125196015625 // 3866852212004)
  RKA_implicit[6, 4] = RT(940440206406 // 7593089888465)
  RKA_implicit[6, 5] = RT(961109811699 // 6734810228204)
  RKA_implicit[7, 2] = RT(2036305566805 // 6583108094622)
  RKA_implicit[7, 3] = RT(-3039402635899 // 4450598839912)
  RKA_implicit[7, 4] = RT(-1829510709469 // 31102090912115)
  RKA_implicit[7, 5] = RT(-286320471013 // 6931253422520)
  RKA_implicit[7, 6] = RT(8651533662697 // 9642993110008)

  RKA_explicit[3, 1] = RT(1 // 9)
  RKA_explicit[3, 2] = RT(1183333538310 // 1827251437969)
  RKA_explicit[4, 1] = RT(895379019517 // 9750411845327)
  RKA_explicit[4, 2] = RT(477606656805 // 13473228687314)
  RKA_explicit[4, 3] = RT(-112564739183 // 9373365219272)
  RKA_explicit[5, 1] = RT(-4458043123994 // 13015289567637)
  RKA_explicit[5, 2] = RT(-2500665203865 // 9342069639922)
  RKA_explicit[5, 3] = RT(983347055801 // 8893519644487)
  RKA_explicit[5, 4] = RT(2185051477207 // 2551468980502)
  RKA_explicit[6, 1] = RT(-167316361917 // 17121522574472)
  RKA_explicit[6, 2] = RT(1605541814917 // 7619724128744)
  RKA_explicit[6, 3] = RT(991021770328 // 13052792161721)
  RKA_explicit[6, 4] = RT(2342280609577 // 11279663441611)
  RKA_explicit[6, 5] = RT(3012424348531 // 12792462456678)
  RKA_explicit[7, 1] = RT(6680998715867 // 14310383562358)
  RKA_explicit[7, 2] = RT(5029118570809 // 3897454228471)
  RKA_explicit[7, 3] = RT(2415062538259 // 6382199904604)
  RKA_explicit[7, 4] = RT(-3924368632305 // 6964820224454)
  RKA_explicit[7, 5] = RT(-4331110370267 // 15021686902756)
  RKA_explicit[7, 6] = RT(-3944303808049 // 11994238218192)
  RKA_explicit[8, 1] = RT(2193717860234 // 3570523412979)
  RKA_explicit[8, 2] = RKA_explicit[8, 1]
  RKA_explicit[8, 3] = RT(5952760925747 // 18750164281544)
  RKA_explicit[8, 4] = RT(-4412967128996 // 6196664114337)
  RKA_explicit[8, 5] = RT(4151782504231 // 36106512998704)
  RKA_explicit[8, 6] = RT(572599549169 // 6265429158920)
  RKA_explicit[8, 7] = RT(-457874356192 // 11306498036315)

  RKB[2] = 0
  RKB[3] = RT(3517720773327 // 20256071687669)
  RKB[4] = RT(4569610470461 // 17934693873752)
  RKB[5] = RT(2819471173109 // 11655438449929)
  RKB[6] = RT(3296210113763 // 10722700128969)
  RKB[7] = RT(-1142099968913 // 5710983926999)

  RKC[2] = RT(4 // 9)
  RKC[3] = RT(6456083330201 // 8509243623797)
  RKC[4] = RT(1632083962415 // 14158861528103)
  RKC[5] = RT(6365430648612 // 17842476412687)
  RKC[6] = RT(18 // 25)
  RKC[7] = RT(191 // 200)

  for is = 2:Nstages
    RKA_implicit[is, 1] = RKA_implicit[is, 2]
  end

  for is = 1:Nstages-1
    RKA_implicit[Nstages, is] = RKB[is]
  end

  RKB[1] = RKB[2]
  RKB[8] = gamma

  RKA_explicit[2, 1] = RKC[2]
  RKA_explicit[Nstages, 1] = RKA_explicit[Nstages, 2]

  RKC[1] = 0
  RKC[Nstages] = 1

  # conversion to static arrays
  RKA_explicit = SMatrix{Nstages, Nstages}(RKA_explicit)
  RKA_implicit = SMatrix{Nstages, Nstages}(RKA_implicit)
  RKB = SVector{Nstages}(RKB)
  RKC = SVector{Nstages}(RKC)

  ark = AdditiveRungeKutta(F, L, linearsolver,
                           RKA_explicit, RKA_implicit, RKB, RKC,
                           split_nonlinear_linear,
                           variant,
                           Q; dt=dt, t0=t0)
end

"""
    ARK437L2SA1KennedyCarpenter(f, l, linearsolver, Q; dt, t0,
                                split_nonlinear_linear, variant)

This function returns an [`AdditiveRungeKutta`](@ref) time stepping object,
see the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.
This time stepping object is intended to be passed to the `solve!` command.

This uses the fourth-order-accurate 7-stage additive Runge--Kutta scheme of
Kennedy and Carpenter (2013).

### References
    @article{kennedy2019higher,
      title={Higher-order additive Runge--Kutta schemes for ordinary differential equations},
      author={Kennedy, Christopher A and Carpenter, Mark H},
      journal={Applied Numerical Mathematics},
      volume={136},
      pages={183--205},
      year={2019},
      publisher={Elsevier}
    }
"""
function ARK437L2SA1KennedyCarpenter(F, L,
                                     linearsolver::AbstractLinearSolver,
                                     Q::AT; dt=nothing, t0=0,
                                     split_nonlinear_linear=false,
                                     variant=LowStorageVariant()) where {AT<:AbstractArray}

  @assert dt != nothing

  T = eltype(Q)
  RT = real(T)

  Nstages = 7
  gamma = RT(1235 // 10000)

  # declared as Arrays for mutability, later these will be converted to static arrays
  RKA_explicit = zeros(RT, Nstages, Nstages)
  RKA_implicit = zeros(RT, Nstages, Nstages)
  RKB = zeros(RT, Nstages)
  RKC = zeros(RT, Nstages)

  # the main diagonal
  for is = 2:Nstages
    RKA_implicit[is, is] = gamma
  end

  RKA_implicit[3, 2] = RT(624185399699 // 4186980696204)
  RKA_implicit[4, 2] = RT(1258591069120 // 10082082980243)
  RKA_implicit[4, 3] = RT(-322722984531 // 8455138723562)
  RKA_implicit[5, 2] = RT(-436103496990 // 5971407786587)
  RKA_implicit[5, 3] = RT(-2689175662187 // 11046760208243)
  RKA_implicit[5, 4] = RT(4431412449334 // 12995360898505)
  RKA_implicit[6, 2] = RT(-2207373168298 // 14430576638973)
  RKA_implicit[6, 3] = RT(242511121179 // 3358618340039)
  RKA_implicit[6, 4] = RT(3145666661981 // 7780404714551)
  RKA_implicit[6, 5] = RT(5882073923981 // 14490790706663)
  RKA_implicit[7, 2] = 0
  RKA_implicit[7, 3] = RT(9164257142617 // 17756377923965)
  RKA_implicit[7, 4] = RT(-10812980402763 // 74029279521829)
  RKA_implicit[7, 5] = RT(1335994250573 // 5691609445217)
  RKA_implicit[7, 6] = RT(2273837961795 // 8368240463276)

  RKA_explicit[3, 1] = RT(247 // 4000)
  RKA_explicit[3, 2] = RT(2694949928731 // 7487940209513)
  RKA_explicit[4, 1] = RT(464650059369 // 8764239774964)
  RKA_explicit[4, 2] = RT(878889893998 // 2444806327765)
  RKA_explicit[4, 3] = RT(-952945855348 // 12294611323341)
  RKA_explicit[5, 1] = RT(476636172619 // 8159180917465)
  RKA_explicit[5, 2] = RT(-1271469283451 // 7793814740893)
  RKA_explicit[5, 3] = RT(-859560642026 // 4356155882851)
  RKA_explicit[5, 4] = RT(1723805262919 // 4571918432560)
  RKA_explicit[6, 1] = RT(6338158500785 // 11769362343261)
  RKA_explicit[6, 2] = RT(-4970555480458 // 10924838743837)
  RKA_explicit[6, 3] = RT(3326578051521 // 2647936831840)
  RKA_explicit[6, 4] = RT(-880713585975 // 1841400956686)
  RKA_explicit[6, 5] = RT(-1428733748635 // 8843423958496)
  RKA_explicit[7, 2] = RT(760814592956 // 3276306540349)
  RKA_explicit[7, 3] = RT(-47223648122716 // 6934462133451)
  RKA_explicit[7, 4] = RT(71187472546993 // 9669769126921)
  RKA_explicit[7, 5] = RT(-13330509492149 // 9695768672337)
  RKA_explicit[7, 6] = RT(11565764226357 // 8513123442827)

  RKB[2] = 0
  RKB[3] = RT(9164257142617 // 17756377923965)
  RKB[4] = RT(-10812980402763 // 74029279521829)
  RKB[5] = RT(1335994250573 // 5691609445217)
  RKB[6] = RT(2273837961795 // 8368240463276)
  RKB[7] = RT(247 // 2000)

  RKC[2] = RT(247 // 2000)
  RKC[3] = RT(4276536705230 // 10142255878289)
  RKC[4] = RT(67 // 200)
  RKC[5] = RT(3 // 40)
  RKC[6] = RT(7 // 10)

  for is = 2:Nstages
    RKA_implicit[is, 1] = RKA_implicit[is, 2]
  end

  for is = 1:Nstages-1
    RKA_implicit[Nstages, is] = RKB[is]
  end

  RKB[1] = RKB[2]

  RKA_explicit[2, 1] = RKC[2]
  RKA_explicit[Nstages, 1] = RKA_explicit[Nstages, 2]

  RKC[1] = 0
  RKC[Nstages] = 1

  # conversion to static arrays
  RKA_explicit = SMatrix{Nstages, Nstages}(RKA_explicit)
  RKA_implicit = SMatrix{Nstages, Nstages}(RKA_implicit)
  RKB = SVector{Nstages}(RKB)
  RKC = SVector{Nstages}(RKC)

  ark = AdditiveRungeKutta(F, L, linearsolver,
                           RKA_explicit, RKA_implicit, RKB, RKC,
                           split_nonlinear_linear,
                           variant,
                           Q; dt=dt, t0=t0)
end

end


include("code_needed.jl")
myedge_list,names_data = run_script()
# reformat data to smat
writeToSMAT(myedge_list,"batter_pitcher_edges.smat")
writedlm("batter_pitcher_names.labels",names_data)


module SailRoute

include("uncertainty/discretization_error.jl")
include("route/domain.jl")
include("performance/polar.jl")
include("route/shortest_path.jl")
include("weather/load_weather.jl")

end # module