text:
export cgpneat

using Random

function species_selection(fits::Array{Float64})
    # return the index of the winner of a n-way tournament
    if length(fits) == 1
        return 1
    else
        n = min(3, length(fits))
        fshuffle = randperm(length(fits))[1:n]
        winner = argmax(fits[fshuffle])
        return fshuffle[winner]
    end
end

function speciation(population::Array, reprs::Array, distance::Function)
    # return a vector of ints corresponding to the species of each individual in population
    species = Array{Int64}(undef, length(population))
    for p in eachindex(population)
        distances = Array{Float64}(undef, length(reprs))
        for r in eachindex(reprs)
            distances[r] = distance(population[p], reprs[r])
        end
        # distances = [distance(population[p], reprs[r]) for r in eachindex(reprs)]
        if minimum(distances) < Config.speciation_thresh
            species[p] = indmin(distances)
        else
            species[p] = length(reprs)+1
            append!(reprs, [clone(population[p])])
        end
    end
    species_set = sort(unique(species))
    for s in eachindex(species_set)
        species[species.==species_set[s]] = s
    end
    species
end

function species_sizes(fits::Array{Float64}, species::Array{Int64})
    nspecies = length(unique(species))
    spec_fits = map(x->mean(fits[species.==x])-minimum(fits), 1:nspecies)
    @debug("spec fits: $spec_fits")
    spec_sizes = map(x->spec_fits[x]/sum(spec_fits), 1:nspecies)
    spec_sizes = spec_sizes./sum(spec_sizes).*Config.ga_population
    spec_sizes[isnan.(spec_sizes)] = 0
    spec_sizes = Int64.(round.(spec_sizes))

    while sum(spec_sizes) > Config.ga_population
        spec_sizes[argmax(spec_sizes)] -= 1
    end
    while sum(spec_sizes) < Config.ga_population
        spec_sizes[argmax(spec_sizes)] += 1
    end
    @debug("spec sizes: $spec_sizes")
    spec_sizes
end

function cgpneat(ctype::DataType, nin::Int64, nout::Int64, fitness::Function;
                 seed::Int64=0, record_best::Bool=false, record_fitness::Function=fitness)
    population = Array{ctype}(iundef, Config.ga_population)
    fits = -Inf*ones(Float64, Config.ga_population)
    species = Array{Int64}(undef, Config.ga_population)
    for p in eachindex(population)
        if p <= Config.init_species
            population[p] = ctype(nin, nout)
            species[p] = p
        else
            repr = rand(1:Config.init_species)
            population[p] = mutate(population[repr])
            species[p] = repr
        end
    end
    best = population[1]
    max_fit = -Inf
    eval_count = 0

    for generation in 1:Config.ga_num_generations
        # evaluation
        @debug("evaluation $generation")
        new_best = false
        for p in eachindex(population)
            if fits[p] == -Inf
                fit = fitness(population[p])
                fits[p] = fit
                eval_count += 1
                if fit > max_fit
                    max_fit = fit
                    best = population[p]
                    new_best = true
                end
            end
        end

        if new_best
            refit = max_fit
            if record_best
                refit = record_fitness(best)
            end
            @info(@sprintf("R: %d %d %0.5f %d %d %s %s %s",
                                  seed, eval_count, max_fit,
                                  sum([n.active for n in best.nodes]),
                                  length(best.nodes),
                                  "NEAT", string(ctype),
                                  Config.to_string()))
            if Config.save_best
                @info(@sprintf("C: %s", string(best.genes)))
            end
        end

        # representatives
        @debug("representatives $generation")
        nspecies = length(unique(species))
        reprs = Array{ctype}(undef, nspecies)
        for s in 1:nspecies
            reprs[s] = clone(population[rand(find(species.==s))])
        end

        # species sizes
        @debug("species sizes $generation $nspecies")
        spec_sizes = species_sizes(fits, species)
        new_pop = Array{ctype}(undef, Config.ga_population)
        new_fits = -Inf*ones(Float64, Config.ga_population)
        popi = 1

        # create new population
        @debug("new population $generation $spec_sizes")
        for s in 1:nspecies
            @debug("species $s")
            sfits = fits[species.==s]
            spec = population[species.==s]
            ncross = round(spec_sizes[s] * Config.ga_crossover_rate)
            nmut = round(spec_sizes[s] * Config.ga_mutation_rate)
            ncopy = spec_sizes[s] - (ncross+nmut)
            while ncopy < 0
                if nmut > ncross
                    nmut -= 1
                else
                    ncross -= 1
                end
                ncopy = spec_sizes[s] - (ncross+nmut)
            end

            if popi > length(new_pop)
                break
            end

            # crossover
            @debug("Crossover $s popi: $popi, ncross: $ncross")
            for i in 1:ncross
                p1 = spec[species_selection(sfits)]
                p2 = spec[species_selection(sfits)]
                new_pop[popi] = crossover(p1, p2)
                popi += 1
            end

            if popi > length(new_pop)
                break
            end

            # mutation
            @debug("Mutation $s popi: $popi, nmut: $nmut")
            for i in 1:nmut
                parent = spec[species_selection(sfits)]
                child = mutate(parent)
                if active_distance(parent, child) == 0
                    new_fits[popi] = fits[popi]
                end
                new_pop[popi] = child
                popi += 1
            end

            if popi > length(new_pop)
                break
            end

            # copy
            @debug("Copy $s popi: $popi, ncopy: $ncopy")
            for i in 1:ncopy
                new_pop[popi] = clone(spec[species_selection(sfits)])
                new_fits[popi] = fits[popi]
                popi += 1
            end

            if popi > length(new_pop)
                break
            end
        end

        @debug("variable set $generation")
        species = speciation(new_pop, reprs, distance)
        population = new_pop
        fits = new_fits

        # size limit
        for i in eachindex(population)
            if length(population[i].nodes) > Config.node_size_cap
                population[i] = ctype(nin, nout)
                fits[i] = -Inf
            end
        end
        @debug("done $generation")
    end

    max_fit, best.genes
end


text:
## Storage of constraints
#
# All `F`-in-`S` constraints are stored in a vector of `ConstraintEntry{F, S}`.
# The index in this vector of a constraint of index
# `ci::MOI.ConstraintIndex{F, S}` is given by `model.constrmap[ci.value]`. The
# advantage of this representation is that it does not require any dictionary
# hence it never needs to compute a hash.
#
# It may seem redundant to store the constraint index `ci` as well as the
# function and sets in the tuple but it is used to efficiently implement the
# getter for `MOI.ListOfConstraintIndices{F, S}`. It is also used to implement
# `MOI.delete`. Indeed, when a constraint is deleted, it is removed from the
# vector hence the index in the vector of all the functions that were stored
# after must be decreased by one. As the constraint index is stored in the
# vector, it readily gives the entries of `model.constrmap` that need to be
# updated.
const ConstraintEntry{F, S} = Tuple{CI{F, S}, F, S}

const EMPTYSTRING = ""

# Implementation of MOI for vector of constraint
function _add_constraint(constrs::Vector{ConstraintEntry{F, S}}, ci::CI, f::F,
                         s::S) where {F, S}
    push!(constrs, (ci, f, s))
    length(constrs)
end

function _delete(constrs::Vector, ci::CI, i::Int)
    deleteat!(constrs, i)
    @view constrs[i:end] # will need to shift it in constrmap
end

_getindex(ci::CI, f::MOI.AbstractFunction, s::MOI.AbstractSet) = ci
function _getindex(constrs::Vector, ci::CI, i::Int)
    _getindex(constrs[i]...)
end

_getfun(ci::CI, f::MOI.AbstractFunction, s::MOI.AbstractSet) = f
function _getfunction(constrs::Vector, ci::CI, i::Int)
    @assert ci.value == constrs[i][1].value
    _getfun(constrs[i]...)
end

_gets(ci::CI, f::MOI.AbstractFunction, s::MOI.AbstractSet) = s
function _getset(constrs::Vector, ci::CI, i::Int)
    @assert ci.value == constrs[i][1].value
    _gets(constrs[i]...)
end

_modifyconstr(ci::CI{F, S}, f::F, s::S, change::F) where {F, S} = (ci, change, s)
_modifyconstr(ci::CI{F, S}, f::F, s::S, change::S) where {F, S} = (ci, f, change)
_modifyconstr(ci::CI{F, S}, f::F, s::S, change::MOI.AbstractFunctionModification) where {F, S} = (ci, modify_function(f, change), s)
function _modify(constrs::Vector{ConstraintEntry{F, S}}, ci::CI{F}, i::Int,
                 change) where {F, S}
    constrs[i] = _modifyconstr(constrs[i]..., change)
end

function _getnoc(constrs::Vector{ConstraintEntry{F, S}},
                 ::MOI.NumberOfConstraints{F, S}) where {F, S}
    return length(constrs)
end
# Might be called when calling NumberOfConstraint with different coefficient type than the one supported
_getnoc(::Vector, ::MOI.NumberOfConstraints) = 0

function _getloc(constrs::Vector{ConstraintEntry{F, S}})::Vector{Tuple{DataType, DataType}} where {F, S}
    isempty(constrs) ? [] : [(F, S)]
end

function _getlocr(constrs::Vector{ConstraintEntry{F, S}},
                  ::MOI.ListOfConstraintIndices{F, S}) where {F, S}
    return map(constr -> constr[1], constrs)
end
function _getlocr(constrs::Vector{<:ConstraintEntry},
                  ::MOI.ListOfConstraintIndices{F, S}) where {F, S}
    return CI{F, S}[]
end

# Implementation of MOI for AbstractModel
abstract type AbstractModel{T} <: MOI.ModelLike end

getconstrloc(model::AbstractModel, ci::CI) = model.constrmap[ci.value]

# Variables
function MOI.get(model::AbstractModel, ::MOI.NumberOfVariables)::Int64
    if model.variable_indices === nothing
        model.num_variables_created
    else
        length(model.variable_indices)
    end
end
function MOI.add_variable(model::AbstractModel{T}) where T
    vi = VI(model.num_variables_created += 1)
    push!(model.single_variable_mask, 0x0)
    push!(model.lower_bound, zero(T))
    push!(model.upper_bound, zero(T))
    if model.variable_indices !== nothing
        push!(model.variable_indices, vi)
    end
    return vi
end
function MOI.add_variables(model::AbstractModel, n::Integer)
    return [MOI.add_variable(model) for i in 1:n]
end

"""
    remove_variable(f::MOI.AbstractFunction, s::MOI.AbstractSet, vi::MOI.VariableIndex)

Return a tuple `(g, t)` representing the constraint `f`-in-`s` with the
variable `vi` removed. That is, the terms containing the variable `vi` in the
function `f` are removed and the dimension of the set `s` is updated if
needed (e.g. when `f` is a `VectorOfVariables` with `vi` being one of the
variables).
"""
remove_variable(f, s, vi::VI) = remove_variable(f, vi), s
function remove_variable(f::MOI.VectorOfVariables, s, vi::VI)
    g = remove_variable(f, vi)
    if length(g.variables) != length(f.variables)
        t = MOI.update_dimension(s, length(g.variables))
    else
        t = s
    end
    return g, t
end
function _remove_variable(constrs::Vector, vi::VI)
    for i in eachindex(constrs)
        ci, f, s = constrs[i]
        constrs[i] = (ci, remove_variable(f, s, vi)...)
    end
    return CI{MOI.SingleVariable}[]
end
function _vector_of_variables_with(::Vector, ::Union{VI, MOI.Vector{VI}})
    return CI{MOI.VectorOfVariables}[]
end
function throw_delete_variable_in_vov(vi::VI)
    message = string("Cannot delete variable as it is constrained with other",
                     " variables in a `MOI.VectorOfVariables`.")
    throw(MOI.DeleteNotAllowed(vi, message))
end
function _vector_of_variables_with(
    constrs::Vector{<:ConstraintEntry{MOI.VectorOfVariables}}, vi::VI)
    rm = CI{MOI.VectorOfVariables}[]
    for (ci, f, s) in constrs
        if vi in f.variables
            if length(f.variables) > 1
                # If `supports_dimension_update(s)` then the variable will be
                # removed in `_remove_variable`.
                if !MOI.supports_dimension_update(typeof(s))
                    throw_delete_variable_in_vov(vi)
                end
            else
                push!(rm, ci)
            end
        end
    end
    return rm
end
function _vector_of_variables_with(
    constrs::Vector{<:ConstraintEntry{MOI.VectorOfVariables}},
    vis::Vector{VI}
)
    rm = CI{MOI.VectorOfVariables}[]
    for (ci, f, s) in constrs
        if vis == f.variables
            push!(rm, ci)
        end
    end
    return rm
end
function MOI.delete(model::AbstractModel{T}, vi::VI) where T
    MOI.throw_if_not_valid(model, vi)
    model.objective = remove_variable(model.objective, vi)
    # If a variable is removed, the `VectorOfVariables` constraints using this
    # variable only need to be removed too. `vov_to_remove` is the list of
    # indices of the `VectorOfVariables` constraints of `vi`.
    vov_to_remove = broadcastvcat(constrs -> _vector_of_variables_with(constrs, vi), model)
    for ci in vov_to_remove
        MOI.delete(model, ci)
    end
    # `VectorOfVariables` constraints with sets not supporting dimension update
    # were either deleted or an error was thrown. The rest is modified now.
    broadcastcall(constrs -> _remove_variable(constrs, vi), model)
    model.single_variable_mask[vi.value] = 0x0
    if model.variable_indices === nothing
        model.variable_indices = Set(MOI.get(model,
                                             MOI.ListOfVariableIndices()))
    end
    delete!(model.variable_indices, vi)
    model.name_to_var = nothing
    delete!(model.var_to_name, vi)
    model.name_to_con = nothing
    delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}}(vi.value))
    delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{T}}(vi.value))
    delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{T}}(vi.value))
    delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{T}}(vi.value))
    delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Integer}(vi.value))
    delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.ZeroOne}(vi.value))
    delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semicontinuous{T}}(vi.value))
    delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semiinteger{T}}(vi.value))
end
function MOI.delete(model::AbstractModel, vis::Vector{VI})
    # Delete `VectorOfVariables(vis)` constraints as otherwise, it will error
    # when removing variables one by one.
    vov_to_remove = broadcastvcat(constrs -> _vector_of_variables_with(constrs, vis), model)
    for ci in vov_to_remove
        MOI.delete(model, ci)
    end
    for vi in vis
        MOI.delete(model, vi)
    end
end

function MOI.is_valid(model::AbstractModel,
                      ci::CI{MOI.SingleVariable, S}) where {S}
    return 1 ≤ ci.value ≤ length(model.single_variable_mask) &&
           !iszero(model.single_variable_mask[ci.value] & single_variable_flag(S))
end
function MOI.is_valid(model::AbstractModel, ci::CI{F, S}) where {F, S}
    if ci.value > length(model.constrmap)
        false
    else
        loc = getconstrloc(model, ci)
        if iszero(loc) # This means that it has been deleted
            false
        elseif loc > MOI.get(model, MOI.NumberOfConstraints{F, S}())
            false
        else
            ci == _getindex(model, ci, getconstrloc(model, ci))
        end
    end
end
function MOI.is_valid(model::AbstractModel, vi::VI)
    if model.variable_indices === nothing
        return 1 ≤ vi.value ≤ model.num_variables_created
    else
        return in(vi, model.variable_indices)
    end
end

function MOI.get(model::AbstractModel, ::MOI.ListOfVariableIndices)
    if model.variable_indices === nothing
        return VI.(1:model.num_variables_created)
    else
        vis = collect(model.variable_indices)
        sort!(vis, by=vi->vi.value) # It needs to be sorted by order of creation
        return vis
    end
end

# Names
MOI.supports(::AbstractModel, ::MOI.Name) = true
function MOI.set(model::AbstractModel, ::MOI.Name, name::String)
    model.name = name
end
MOI.get(model::AbstractModel, ::MOI.Name) = model.name

MOI.supports(::AbstractModel, ::MOI.VariableName, vi::Type{VI}) = true
function MOI.set(model::AbstractModel, ::MOI.VariableName, vi::VI, name::String)
    model.var_to_name[vi] = name
    model.name_to_var = nothing # Invalidate the name map.
end
MOI.get(model::AbstractModel, ::MOI.VariableName, vi::VI) = get(model.var_to_name, vi, EMPTYSTRING)

"""
    build_name_to_var_map(con_to_name::Dict{MOI.VariableIndex, String})

Create and return a reverse map from name to variable index, given a map from
variable index to name. The special value `MOI.VariableIndex(0)` is used to
indicate that multiple variables have the same name.
"""
function build_name_to_var_map(var_to_name::Dict{VI, String})
    name_to_var = Dict{String, VI}()
    for (var, var_name) in var_to_name
        if haskey(name_to_var, var_name)
            # 0 is a special value that means this string does not map to
            # a unique variable name.
            name_to_var[var_name] = VI(0)
        else
            name_to_var[var_name] = var
        end
    end
    return name_to_var
end

function throw_multiple_name_error(::Type{MOI.VariableIndex}, name::String)
    error("Multiple variables have the name $name.")
end
function throw_multiple_name_error(::Type{<:MOI.ConstraintIndex}, name::String)
    error("Multiple constraints have the name $name.")
end
function throw_if_multiple_with_name(::Nothing, ::String) end
function throw_if_multiple_with_name(index::MOI.Index, name::String)
    if iszero(index.value)
        throw_multiple_name_error(typeof(index), name)
    end
end

function MOI.get(model::AbstractModel, ::Type{VI}, name::String)
    if model.name_to_var === nothing
        # Rebuild the map.
        model.name_to_var = build_name_to_var_map(model.var_to_name)
    end
    result = get(model.name_to_var, name, nothing)
    throw_if_multiple_with_name(result, name)
    return result
end

function MOI.get(model::AbstractModel, ::MOI.ListOfVariableAttributesSet)::Vector{MOI.AbstractVariableAttribute}
    isempty(model.var_to_name) ? [] : [MOI.VariableName()]
end

MOI.supports(model::AbstractModel, ::MOI.ConstraintName, ::Type{<:CI}) = true
function MOI.set(model::AbstractModel, ::MOI.ConstraintName, ci::CI, name::String)
    model.con_to_name[ci] = name
    model.name_to_con = nothing # Invalidate the name map.
end
MOI.get(model::AbstractModel, ::MOI.ConstraintName, ci::CI) = get(model.con_to_name, ci, EMPTYSTRING)

"""
    build_name_to_con_map(con_to_name::Dict{MOI.ConstraintIndex, String})

Create and return a reverse map from name to constraint index, given a map from
constraint index to name. The special value
`MOI.ConstraintIndex{Nothing, Nothing}(0)` is used to indicate that multiple
constraints have the same name.
"""
function build_name_to_con_map(con_to_name::Dict{CI, String})
    name_to_con = Dict{String, CI}()
    for (con, con_name) in con_to_name
        if haskey(name_to_con, con_name)
            name_to_con[con_name] = CI{Nothing, Nothing}(0)
        else
            name_to_con[con_name] = con
        end
    end
    return name_to_con
end

function MOI.get(model::AbstractModel, ConType::Type{<:CI}, name::String)
    if model.name_to_con === nothing
        # Rebuild the map.
        model.name_to_con = build_name_to_con_map(model.con_to_name)
    end
    ci = get(model.name_to_con, name, nothing)
    throw_if_multiple_with_name(ci, name)
    if ci isa ConType
        return ci
    else
        return nothing
    end
end

function MOI.get(model::AbstractModel, ::MOI.ListOfConstraintAttributesSet)::Vector{MOI.AbstractConstraintAttribute}
    isempty(model.con_to_name) ? [] : [MOI.ConstraintName()]
end

# Objective
MOI.get(model::AbstractModel, ::MOI.ObjectiveSense) = model.sense
MOI.supports(model::AbstractModel, ::MOI.ObjectiveSense) = true
function MOI.set(model::AbstractModel{T}, ::MOI.ObjectiveSense,
                 sense::MOI.OptimizationSense) where T
    if sense == MOI.FEASIBILITY_SENSE
        model.objectiveset = false
        model.objective = zero(MOI.ScalarAffineFunction{T})
    end
    model.senseset = true
    model.sense = sense
end
function MOI.get(model::AbstractModel, ::MOI.ObjectiveFunctionType)
    return MOI.typeof(model.objective)
end
function MOI.get(model::AbstractModel, ::MOI.ObjectiveFunction{T})::T where T
    return model.objective
end
function MOI.supports(
    model::AbstractModel{T},
    ::MOI.ObjectiveFunction{<:Union{
        MOI.SingleVariable,
        MOI.ScalarAffineFunction{T},
        MOI.ScalarQuadraticFunction{T}}}) where T
    return true
end
function MOI.set(model::AbstractModel, ::MOI.ObjectiveFunction, f::MOI.AbstractFunction)
    model.objectiveset = true
    # f needs to be copied, see #2
    model.objective = copy(f)
end

function MOI.modify(model::AbstractModel, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification)
    model.objective = modify_function(model.objective, change)
end

MOI.get(::AbstractModel, ::MOI.ListOfOptimizerAttributesSet) = MOI.AbstractOptimizerAttribute[]
function MOI.get(model::AbstractModel, ::MOI.ListOfModelAttributesSet)::Vector{MOI.AbstractModelAttribute}
    listattr = MOI.AbstractModelAttribute[]
    if model.senseset
        push!(listattr, MOI.ObjectiveSense())
    end
    if model.objectiveset
        push!(listattr, MOI.ObjectiveFunction{typeof(model.objective)}())
    end
    if !isempty(model.name)
        push!(listattr, MOI.Name())
    end
    listattr
end

# Constraints
single_variable_flag(::Type{<:MOI.EqualTo}) = 0x1
single_variable_flag(::Type{<:MOI.GreaterThan}) = 0x2
single_variable_flag(::Type{<:MOI.LessThan}) = 0x4
single_variable_flag(::Type{<:MOI.Interval}) = 0x8
single_variable_flag(::Type{MOI.Integer}) = 0x10
single_variable_flag(::Type{MOI.ZeroOne}) = 0x20
single_variable_flag(::Type{<:MOI.Semicontinuous}) = 0x40
single_variable_flag(::Type{<:MOI.Semiinteger}) = 0x80
# If a set is added here, a line should be added in
# `MOI.delete(::AbstractModel, ::MOI.VariableIndex)`

function flag_to_set_type(flag::UInt8, T::Type)
    if flag == 0x1
        return MOI.EqualTo{T}
    elseif flag == 0x2
        return MOI.GreaterThan{T}
    elseif flag == 0x4
        return MOI.LessThan{T}
    elseif flag == 0x8
        return MOI.Interval{T}
    elseif flag == 0x10
        return MOI.Integer
    elseif flag == 0x20
        return MOI.ZeroOne
    elseif flag == 0x40
        return MOI.Semicontinuous{T}
    elseif flag == 0x80
        return MOI.Semiinteger{T}
    else
        # $flag would print it in decimal
        error("Invalid flag `$(sprint(show, flag))`.")
    end
end

function throw_if_lower_bound_set(variable, S2, mask, T)
    flag = single_variable_flag(S2)
    if !iszero(flag & LOWER_BOUND_MASK) && !iszero(mask & LOWER_BOUND_MASK)
       S1 = flag_to_set_type(mask & LOWER_BOUND_MASK, T)
       throw(MOI.LowerBoundAlreadySet{S1, S2}(variable))
    end
end

function throw_if_upper_bound_set(variable, S2, mask, T)
    flag = single_variable_flag(S2)
    if !iszero(flag & UPPER_BOUND_MASK) && !iszero(mask & UPPER_BOUND_MASK)
       S1 = flag_to_set_type(mask & UPPER_BOUND_MASK, T)
       throw(MOI.UpperBoundAlreadySet{S1, S2}(variable))
    end
end

# Sets setting lower bound:
extract_lower_bound(set::MOI.EqualTo) = set.value
function extract_lower_bound(set::Union{MOI.GreaterThan, MOI.Interval,
                                        MOI.Semicontinuous, MOI.Semiinteger})
    return set.lower
end
# 0xb = 0x80 | 0x40 | 0x8 | 0x2 | 0x1
const LOWER_BOUND_MASK = 0xcb

# Sets setting upper bound:
extract_upper_bound(set::MOI.EqualTo) = set.value
function extract_upper_bound(set::Union{MOI.LessThan, MOI.Interval,
                                        MOI.Semicontinuous, MOI.Semiinteger})
    return set.upper
end
# 0xd = 0x80 | 0x40 | 0x8 | 0x4 | 0x1
const UPPER_BOUND_MASK = 0xcd

const SUPPORTED_VARIABLE_SCALAR_SETS{T} = Union{
    MOI.EqualTo{T}, MOI.GreaterThan{T}, MOI.LessThan{T}, MOI.Interval{T},
    MOI.Integer, MOI.ZeroOne, MOI.Semicontinuous{T}, MOI.Semiinteger{T}}
function MOI.supports_constraint(
    ::AbstractModel{T}, ::Type{MOI.SingleVariable},
    ::Type{<:SUPPORTED_VARIABLE_SCALAR_SETS{T}}) where T
    return true
end
function MOI.add_constraint(model::AbstractModel{T}, f::MOI.SingleVariable,
                            s::MOI.AbstractScalarSet) where T
    if MOI.supports_constraint(model, MOI.SingleVariable, typeof(s))
        flag = single_variable_flag(typeof(s))
        index = f.variable.value
        mask = model.single_variable_mask[index]
        throw_if_lower_bound_set(f.variable, typeof(s), mask, T)
        throw_if_upper_bound_set(f.variable, typeof(s), mask, T)
        # No error should be thrown now, we can modify `model`.
        if !iszero(flag & LOWER_BOUND_MASK)
            model.lower_bound[index] = extract_lower_bound(s)
        end
        if !iszero(flag & UPPER_BOUND_MASK)
            model.upper_bound[index] = extract_upper_bound(s)
        end
        model.single_variable_mask[index] = mask | flag
        return CI{MOI.SingleVariable, typeof(s)}(index)
    else
        throw(MOI.UnsupportedConstraint{MOI.SingleVariable, typeof(s)}())
    end
end
function MOI.add_constraint(model::AbstractModel, f::F, s::S) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet}
    if MOI.supports_constraint(model, F, S)
        # We give the index value `nextconstraintid + 1` to the new constraint.
        # As the same counter is used for all pairs of F-in-S constraints,
        # the index value is unique across all constraint types as mentioned in
        # `@model`'s doc.
        ci = CI{F, S}(model.nextconstraintid += 1)
        # f needs to be copied, see #2
        push!(model.constrmap, _add_constraint(model, ci, copy(f), copy(s)))
        return ci
    else
        throw(MOI.UnsupportedConstraint{F, S}())
    end
end

function _delete_constraint(model::AbstractModel,
                            ci::CI{MOI.SingleVariable, S}) where S
    model.single_variable_mask[ci.value] &= ~single_variable_flag(S)
end
function _delete_constraint(model::AbstractModel, ci::CI)
    for (ci_next, _, _) in _delete(model, ci, getconstrloc(model, ci))
        model.constrmap[ci_next.value] -= 1
    end
    model.constrmap[ci.value] = 0
end
function MOI.delete(model::AbstractModel, ci::CI)
    MOI.throw_if_not_valid(model, ci)
    _delete_constraint(model, ci)
    model.name_to_con = nothing
    delete!(model.con_to_name, ci)
end

function MOI.modify(model::AbstractModel, ci::CI, change::MOI.AbstractFunctionModification)
    _modify(model, ci, getconstrloc(model, ci), change)
end

function MOI.set(model::AbstractModel, ::MOI.ConstraintFunction, ci::CI{MOI.SingleVariable},
                 change::MOI.AbstractFunction)
    throw(MOI.SettingSingleVariableFunctionNotAllowed())
end
function MOI.set(model::AbstractModel, ::MOI.ConstraintFunction, ci::CI, change::MOI.AbstractFunction)
    _modify(model, ci, getconstrloc(model, ci), change)
end
function MOI.set(model::AbstractModel, ::MOI.ConstraintSet,
                 ci::CI{MOI.SingleVariable}, change::MOI.AbstractSet)
    MOI.throw_if_not_valid(model, ci)
    flag = single_variable_flag(typeof(change))
    if !iszero(flag & LOWER_BOUND_MASK)
        model.lower_bound[ci.value] = extract_lower_bound(change)
    end
    if !iszero(flag & UPPER_BOUND_MASK)
        model.upper_bound[ci.value] = extract_upper_bound(change)
    end
end
function MOI.set(model::AbstractModel, ::MOI.ConstraintSet, ci::CI, change::MOI.AbstractSet)
    _modify(model, ci, getconstrloc(model, ci), change)
end

function MOI.get(model::AbstractModel,
                 ::MOI.NumberOfConstraints{MOI.SingleVariable, S}) where S
    flag = single_variable_flag(S)
    return count(mask -> !iszero(flag & mask), model.single_variable_mask)
end
MOI.get(model::AbstractModel, noc::MOI.NumberOfConstraints) = _getnoc(model, noc)

function _add_contraint_type(list, model::AbstractModel,
                             S::Type{<:MOI.AbstractScalarSet})
    flag = single_variable_flag(S)
    if any(mask -> !iszero(flag & mask), model.single_variable_mask)
        push!(list, (MOI.SingleVariable, S))
    end
    return
end
function MOI.get(model::AbstractModel{T}, loc::MOI.ListOfConstraints) where T
    list = broadcastvcat(_getloc, model)
    for S in (
        MOI.EqualTo{T}, MOI.GreaterThan{T}, MOI.LessThan{T}, MOI.Interval{T},
        MOI.Semicontinuous{T}, MOI.Semiinteger{T}, MOI.Integer, MOI.ZeroOne
    )
        _add_contraint_type(list, model, S)
    end
    return list
end

function MOI.get(model::AbstractModel,
                 ::MOI.ListOfConstraintIndices{MOI.SingleVariable, S}) where S
    list = CI{MOI.SingleVariable, S}[]
    flag = single_variable_flag(S)
    for (index, mask) in enumerate(model.single_variable_mask)
        if !iszero(mask & flag)
            push!(list, CI{MOI.SingleVariable, S}(index))
        end
    end
    return list
end
function MOI.get(model::AbstractModel, loc::MOI.ListOfConstraintIndices)
    broadcastvcat(constrs -> _getlocr(constrs, loc), model)
end

function MOI.get(model::AbstractModel, ::MOI.ConstraintFunction,
                 ci::CI{MOI.SingleVariable})
    MOI.throw_if_not_valid(model, ci)
    return MOI.SingleVariable(MOI.VariableIndex(ci.value))
end
function MOI.get(model::AbstractModel, ::MOI.ConstraintFunction, ci::CI)
    _getfunction(model, ci, getconstrloc(model, ci))
end

function _get_single_variable_set(model::AbstractModel, S::Type{<:MOI.EqualTo},
                                  index)
    return MOI.EqualTo(model.lower_bound[index])
end
function _get_single_variable_set(model::AbstractModel,
                                  S::Type{<:Union{MOI.GreaterThan,
                                                  MOI.EqualTo}},
                                  index)
    # Lower and upper bounds are equal for `EqualTo`, we can take either of them.
    return S(model.lower_bound[index])
end
function _get_single_variable_set(model::AbstractModel, S::Type{<:MOI.LessThan},
                                  index)
    return S(model.upper_bound[index])
end
function _get_single_variable_set(model::AbstractModel,
                                  S::Type{<:Union{MOI.Interval,
                                                  MOI.Semicontinuous,
                                                  MOI.Semiinteger}},
                                  index)
    return S(model.lower_bound[index], model.upper_bound[index])
end
function _get_single_variable_set(model::AbstractModel,
                                  S::Type{<:Union{MOI.Integer, MOI.ZeroOne}},
                                  index)
    return S()
end
function MOI.get(model::AbstractModel, ::MOI.ConstraintSet,
                 ci::CI{MOI.SingleVariable, S}) where S
    MOI.throw_if_not_valid(model, ci)
    return _get_single_variable_set(model, S, ci.value)
end
function MOI.get(model::AbstractModel, ::MOI.ConstraintSet, ci::CI)
    _getset(model, ci, getconstrloc(model, ci))
end

function MOI.is_empty(model::AbstractModel)
    isempty(model.name) && !model.senseset && !model.objectiveset &&
    isempty(model.objective.terms) && iszero(model.objective.constant) &&
    iszero(model.num_variables_created) && iszero(model.nextconstraintid)
end

function MOI.copy_to(dest::AbstractModel, src::MOI.ModelLike; kws...)
    return automatic_copy_to(dest, src; kws...)
end
supports_default_copy_to(model::AbstractModel, copy_names::Bool) = true

# Allocate-Load Interface
# Even if the model does not need it and use default_copy_to, it could be used
# by a layer that needs it
supports_allocate_load(model::AbstractModel, copy_names::Bool) = true

function allocate_variables(model::AbstractModel, nvars)
    return MOI.add_variables(model, nvars)
end
allocate(model::AbstractModel, attr...) = MOI.set(model, attr...)
function allocate_constraint(model::AbstractModel, f::MOI.AbstractFunction,
                             s::MOI.AbstractSet)
    return MOI.add_constraint(model, f, s)
end

function load_variables(::AbstractModel, nvars) end
function load(::AbstractModel, attr...) end
function load_constraint(::AbstractModel, ::CI, ::MOI.AbstractFunction,
                         ::MOI.AbstractSet)
end

# Can be used to access constraints of a model
"""
broadcastcall(f::Function, model::AbstractModel)

Calls `f(contrs)` for every vector `constrs::Vector{ConstraintIndex{F, S}, F, S}` of the model.

# Examples

To add all constraints of the model to a solver `solver`, one can do
```julia
_addcon(solver, ci, f, s) = MOI.add_constraint(solver, f, s)
function _addcon(solver, constrs::Vector)
    for constr in constrs
        _addcon(solver, constr...)
    end
end
MOIU.broadcastcall(constrs -> _addcon(solver, constrs), model)
```
"""
function broadcastcall end

"""
broadcastvcat(f::Function, model::AbstractModel)

Calls `f(contrs)` for every vector `constrs::Vector{ConstraintIndex{F, S}, F, S}` of the model and concatenate the results with `vcat` (this is used internally for `ListOfConstraints`).

# Examples

To get the list of all functions:
```julia
_getfun(ci, f, s) = f
_getfun(cindices::Tuple) = _getfun(cindices...)
_getfuns(constrs::Vector) = _getfun.(constrs)
MOIU.broadcastvcat(_getfuns, model)
```
"""
function broadcastvcat end

# Macro to generate Model
abstract type Constraints{F} end

abstract type SymbolFS end
struct SymbolFun <: SymbolFS
    s::Union{Symbol, Expr}
    typed::Bool
    cname::Expr # `esc(scname)` or `esc(vcname)`
end
struct SymbolSet <: SymbolFS
    s::Union{Symbol, Expr}
    typed::Bool
end

# QuoteNode prevents s from being interpolated and keeps it as a symbol
# Expr(:., MOI, s) would be MOI.s
# Expr(:., MOI, $s) would be Expr(:., MOI, EqualTo)
# Expr(:., MOI, :($s)) would be Expr(:., MOI, :EqualTo)
# Expr(:., MOI, :($(QuoteNode(s)))) is Expr(:., MOI, :(:EqualTo)) <- what we want

# (MOI, :Zeros) -> :(MOI.Zeros)
# (:Zeros) -> :(MOI.Zeros)
_set(s::SymbolSet) = esc(s.s)
_fun(s::SymbolFun) = esc(s.s)
function _typedset(s::SymbolSet)
    if s.typed
        :($(_set(s)){T})
    else
        _set(s)
    end
end
function _typedfun(s::SymbolFun)
    if s.typed
        :($(_fun(s)){T})
    else
        _fun(s)
    end
end

# Base.lowercase is moved to Unicode.lowercase in Julia v0.7
using Unicode

_field(s::SymbolFS) = Symbol(replace(lowercase(string(s.s)), "." => "_"))

_getC(s::SymbolSet) = :(ConstraintEntry{F, $(_typedset(s))})
_getC(s::SymbolFun) = _typedfun(s)

_getCV(s::SymbolSet) = :($(_getC(s))[])
_getCV(s::SymbolFun) = :($(s.cname){T, $(_getC(s))}())

_callfield(f, s::SymbolFS) = :($f(model.$(_field(s))))
_broadcastfield(b, s::SymbolFS) = :($b(f, model.$(_field(s))))

# This macro is for expert/internal use only. Prefer the concrete Model type
# instantiated below.
"""
    macro model(model_name, scalar_sets, typed_scalar_sets, vector_sets, typed_vector_sets, scalar_functions, typed_scalar_functions, vector_functions, typed_vector_functions)

Creates a type `model_name` implementing the MOI model interface and containing
`scalar_sets` scalar sets `typed_scalar_sets` typed scalar sets, `vector_sets`
vector sets, `typed_vector_sets` typed vector sets, `scalar_functions` scalar
functions, `typed_scalar_functions` typed scalar functions, `vector_functions`
vector functions and `typed_vector_functions` typed vector functions.
To give no set/function, write `()`, to give one set `S`, write `(S,)`.

The function [`MathOptInterface.SingleVariable`](@ref) should not be given in
`scalar_functions`. The model supports [`MathOptInterface.SingleVariable`](@ref)-in-`F`
constraints where `F` is [`MathOptInterface.EqualTo`](@ref),
[`MathOptInterface.GreaterThan`](@ref), [`MathOptInterface.LessThan`](@ref),
[`MathOptInterface.Interval`](@ref), [`MathOptInterface.Integer`](@ref),
[`MathOptInterface.ZeroOne`](@ref), [`MathOptInterface.Semicontinuous`](@ref)
or [`MathOptInterface.Semiinteger`](@ref). The sets supported
with the [`MathOptInterface.SingleVariable`](@ref) cannot be controlled from the
macro, use the [`UniversalFallback`](@ref) to support more sets.

This macro creates a model specialized for specific types of constraint,
by defining specialized structures and methods. To create a model that,
in addition to be optimized for specific constraints, also support arbitrary
constraints and attributes, use [`UniversalFallback`](@ref).

This implementation of the MOI model certifies that the constraint indices, in
addition to being different between constraints `F`-in-`S` for the same types
`F` and `S`, are also different between constraints for different types `F` and
`S`. This means that for constraint indices `ci1`, `ci2` of this model,
`ci1 == ci2` if and only if `ci1.value == ci2.value`. This fact can be used to
use the the value of the index directly in a dictionary representing a mapping
between constraint indices and something else.

### Examples

The model describing an linear program would be:
```julia
@model(LPModel,                                                   # Name of model
      (),                                                         # untyped scalar sets
      (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), #   typed scalar sets
      (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives),            # untyped vector sets
      (),                                                         #   typed vector sets
      (),                                                         # untyped scalar functions
      (MOI.ScalarAffineFunction,),                                #   typed scalar functions
      (MOI.VectorOfVariables,),                                   # untyped vector functions
      (MOI.VectorAffineFunction,))                                #   typed vector functions
```

Let `MOI` denote `MathOptInterface`, `MOIU` denote `MOI.Utilities` and
`MOIU.ConstraintEntry{F, S}` be defined as `MOI.Tuple{MOI.ConstraintIndex{F, S}, F, S}`.
The macro would create the types:
```julia
struct LPModelScalarConstraints{T, F <: MOI.AbstractScalarFunction} <: MOIU.Constraints{F}
    equalto::Vector{MOIU.ConstraintEntry{F, MOI.EqualTo{T}}}
    greaterthan::Vector{MOIU.ConstraintEntry{F, MOI.GreaterThan{T}}}
    lessthan::Vector{MOIU.ConstraintEntry{F, MOI.LessThan{T}}}
    interval::Vector{MOIU.ConstraintEntry{F, MOI.Interval{T}}}
end
struct LPModelVectorConstraints{T, F <: MOI.AbstractVectorFunction} <: MOIU.Constraints{F}
    zeros::Vector{MOIU.ConstraintEntry{F, MOI.Zeros}}
    nonnegatives::Vector{MOIU.ConstraintEntry{F, MOI.Nonnegatives}}
    nonpositives::Vector{MOIU.ConstraintEntry{F, MOI.Nonpositives}}
end
mutable struct LPModel{T} <: MOIU.AbstractModel{T}
    name::String
    sense::MOI.OptimizationSense
    objective::Union{MOI.SingleVariable, MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}}
    num_variables_created::Int64
    # If nothing, no variable has been deleted so the indices of the
    # variables are VI.(1:num_variables_created)
    variable_indices::Union{Nothing, Set{MOI.VariableIndex}}
    # Union of flags of `S` such that a `SingleVariable`-in-`S`
    # constraint was added to the model and not deleted yet.
    single_variable_mask::Vector{UInt8}
    # Lower bound set by `SingleVariable`-in-`S` where `S`is
    # `GreaterThan{T}`, `EqualTo{T}` or `Interval{T}`.
    lower_bound::Vector{T}
    # Lower bound set by `SingleVariable`-in-`S` where `S`is
    # `LessThan{T}`, `EqualTo{T}` or `Interval{T}`.
    upper_bound::Vector{T}
    var_to_name::Dict{MOI.VariableIndex, String}
    # If `nothing`, the dictionary hasn't been constructed yet.
    name_to_var::Union{Dict{String, MOI.VariableIndex}, Nothing}
    nextconstraintid::Int64
    con_to_name::Dict{MOI.ConstraintIndex, String}
    name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing}
    constrmap::Vector{Int}
    scalaraffinefunction::LPModelScalarConstraints{T, MOI.ScalarAffineFunction{T}}
    vectorofvariables::LPModelVectorConstraints{T, MOI.VectorOfVariables}
    vectoraffinefunction::LPModelVectorConstraints{T, MOI.VectorAffineFunction{T}}
end
```
The type `LPModel` implements the MathOptInterface API except methods specific
to solver models like `optimize!` or `getattribute` with `VariablePrimal`.
"""
macro model(model_name, ss, sst, vs, vst, sf, sft, vf, vft)
    scalar_sets = [SymbolSet.(ss.args, false); SymbolSet.(sst.args, true)]
    vector_sets = [SymbolSet.(vs.args, false); SymbolSet.(vst.args, true)]

    scname = esc(Symbol(string(model_name) * "ScalarConstraints"))
    vcname = esc(Symbol(string(model_name) * "VectorConstraints"))
    esc_model_name = esc(model_name)

    scalar_funs = [SymbolFun.(sf.args, false, Ref(scname));
                  SymbolFun.(sft.args, true, Ref(scname))]
    vector_funs = [SymbolFun.(vf.args, false, Ref(vcname));
                  SymbolFun.(vft.args, true, Ref(vcname))]
    funs = [scalar_funs; vector_funs]

    scalarconstraints = :(struct $scname{T, F<:$MOI.AbstractScalarFunction} <: Constraints{F}; end)
    vectorconstraints = :(struct $vcname{T, F<:$MOI.AbstractVectorFunction} <: Constraints{F}; end)
    for (c, sets) in ((scalarconstraints, scalar_sets), (vectorconstraints, vector_sets))
        for s in sets
            field = _field(s)
            push!(c.args[3].args, :($field::Vector{$(_getC(s))}))
        end
    end

    modeldef = quote
        mutable struct $esc_model_name{T} <: AbstractModel{T}
            name::String
            senseset::Bool
            sense::$MOI.OptimizationSense
            objectiveset::Bool
            objective::Union{$MOI.SingleVariable, $MOI.ScalarAffineFunction{T}, $MOI.ScalarQuadraticFunction{T}}
            num_variables_created::Int64
            # If nothing, no variable has been deleted so the indices of the
            # variables are VI.(1:num_variables_created)
            variable_indices::Union{Nothing, Set{$VI}}
            # Union of flags of `S` such that a `SingleVariable`-in-`S`
            # constraint was added to the model and not deleted yet.
            single_variable_mask::Vector{UInt8}
            # Lower bound set by `SingleVariable`-in-`S` where `S`is
            # `GreaterThan{T}`, `EqualTo{T}` or `Interval{T}`.
            lower_bound::Vector{T}
            # Lower bound set by `SingleVariable`-in-`S` where `S`is
            # `LessThan{T}`, `EqualTo{T}` or `Interval{T}`.
            upper_bound::Vector{T}
            var_to_name::Dict{$VI, String}
            # If `nothing`, the dictionary hasn't been constructed yet.
            name_to_var::Union{Dict{String, $VI}, Nothing}
            nextconstraintid::Int64
            con_to_name::Dict{$CI, String}
            name_to_con::Union{Dict{String, $CI}, Nothing}
            constrmap::Vector{Int} # Constraint Reference value ci -> index in array in Constraints
            # A useful dictionary for extensions to store things. These are
            # _not_ copied between models!
            ext::Dict{Symbol, Any}
        end
    end
    for f in funs
        cname = f.cname
        field = _field(f)
        push!(modeldef.args[2].args[3].args, :($field::$cname{T, $(_getC(f))}))
    end

    code = quote
        function $MOIU.broadcastcall(f::Function, model::$esc_model_name)
            $(Expr(:block, _broadcastfield.(Ref(:(broadcastcall)), funs)...))
        end
        function $MOIU.broadcastvcat(f::Function, model::$esc_model_name)
            vcat($(_broadcastfield.(Ref(:(broadcastvcat)), funs)...))
        end
        function $MOI.empty!(model::$esc_model_name{T}) where T
            model.name = ""
            model.senseset = false
            model.sense = $MOI.FEASIBILITY_SENSE
            model.objectiveset = false
            model.objective = zero($MOI.ScalarAffineFunction{T})
            model.num_variables_created = 0
            model.variable_indices = nothing
            model.single_variable_mask = UInt8[]
            model.lower_bound = T[]
            model.upper_bound = T[]
            empty!(model.var_to_name)
            model.name_to_var = nothing
            model.nextconstraintid = 0
            empty!(model.con_to_name)
            model.name_to_con = nothing
            empty!(model.constrmap)
            $(Expr(:block, _callfield.(Ref(:($MOI.empty!)), funs)...))
        end
    end
    for (cname, sets) in ((scname, scalar_sets), (vcname, vector_sets))
        code = quote
            $code
            function $MOIU.broadcastcall(f::Function, model::$cname)
                $(Expr(:block, _callfield.(:f, sets)...))
            end
            function $MOIU.broadcastvcat(f::Function, model::$cname)
                vcat($(_callfield.(:f, sets)...))
            end
            function $MOI.empty!(model::$cname)
                $(Expr(:block, _callfield.(Ref(:(Base.empty!)), sets)...))
            end
        end
    end

    for (funct, T) in ((:_add_constraint, CI), (:_modify, CI), (:_delete, CI), (:_getindex, CI), (:_getfunction, CI), (:_getset, CI), (:_getnoc, MOI.NumberOfConstraints))
        for (c, sets) in ((scname, scalar_sets), (vcname, vector_sets))
            for s in sets
                set = _set(s)
                field = _field(s)
                code = quote
                    $code
                    $MOIU.$funct(model::$c, ci::$T{F, <:$set}, args...) where F = $funct(model.$field, ci, args...)
                end
            end
        end

        for f in funs
            fun = _fun(f)
            field = _field(f)
            code = quote
                $code
                $MOIU.$funct(model::$esc_model_name, ci::$T{<:$fun}, args...) = $funct(model.$field, ci, args...)
            end
        end
    end

    code = quote
        $scalarconstraints
        function $scname{T, F}() where {T, F}
            $scname{T, F}($(_getCV.(scalar_sets)...))
        end

        $vectorconstraints
        function $vcname{T, F}() where {T, F}
            $vcname{T, F}($(_getCV.(vector_sets)...))
        end

        $modeldef
        function $esc_model_name{T}() where T
            $esc_model_name{T}("", false, $MOI.FEASIBILITY_SENSE, false,
                              $SAF{T}($MOI.ScalarAffineTerm{T}[], zero(T)), 0,
                              nothing, UInt8[], T[], T[], Dict{$VI, String}(),
                              nothing, 0, Dict{$CI, String}(), nothing, Int[],
                              Dict{Symbol, Any}(), $(_getCV.(funs)...))
        end

        $MOI.supports_constraint(model::$esc_model_name{T}, ::Type{<:Union{$(_typedfun.(scalar_funs)...)}}, ::Type{<:Union{$(_typedset.(scalar_sets)...)}}) where T = true
        $MOI.supports_constraint(model::$esc_model_name{T}, ::Type{<:Union{$(_typedfun.(vector_funs)...)}}, ::Type{<:Union{$(_typedset.(vector_sets)...)}}) where T = true

        $code
    end
    return code
end

const LessThanIndicatorSetOne{T} = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, MOI.LessThan{T}}
const LessThanIndicatorSetZero{T} = MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO, MOI.LessThan{T}}

@model(Model,
       (MOI.ZeroOne, MOI.Integer),
       (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval,
        MOI.Semicontinuous, MOI.Semiinteger),
       (MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives,
        MOI.Complements, MOI.NormInfinityCone, MOI.NormOneCone,
        MOI.SecondOrderCone, MOI.RotatedSecondOrderCone,
        MOI.GeometricMeanCone, MOI.ExponentialCone, MOI.DualExponentialCone,
        MOI.PositiveSemidefiniteConeTriangle, MOI.PositiveSemidefiniteConeSquare,
        MOI.RootDetConeTriangle, MOI.RootDetConeSquare, MOI.LogDetConeTriangle,
        MOI.LogDetConeSquare),
       (MOI.PowerCone, MOI.DualPowerCone, MOI.SOS1, MOI.SOS2,
        LessThanIndicatorSetOne, LessThanIndicatorSetZero),
       (),
       (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),
       (MOI.VectorOfVariables,),
       (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction))

@doc raw"""

An implementation of `ModelLike` that supports all functions and sets defined
in MOI. It is parameterized by the coefficient type.

# Examples
```jl
model = Model{Float64}()
x = add_variable(model)
```
"""
Model

ext: jl


text:
#This file implements a number of popular covariance functions
using SpecialFunctions
function matern( r::Float64, l::Float64, nu::Float64 )
  if r == 0
    return 1.0
  else
    return 2.0^(1.0 - nu) / gamma(nu) * (sqrt( 2.0 * nu ) * r / l )^nu * besselk( nu, sqrt( 2.0 * nu ) * r / l )
  end
end

function gaussian( r::Float64, l::Float64 )
  return exp( - ( r/ l )^2 )
end

function exponential( r::Float64, l::Float64 )
  return exp( -r/l )
end

function invMultiquadratic( r::Float64, l::Float64, c::Float64 = 1. )
  return 1/sqrt( c + ( r / l )^2 )
end

function cauchy( r::Float64, l::Float64 )
  1 / ( 1 + ( r / l )^2 )
end

function ratQuadratic( r::Float64, l::Float64, c::Float64 = 1. )
  return 1 - (r/l)^2/( (r/l)^2 + c )
end

max_stars_repo_name: f-t-s/Spelliptic

max_issues_repo_name: f-t-s/Spelliptic

max_forks_repo_name: f-t-s/Spelliptic


text:
##################################################################################
# This file is part of StateSpaceEcon.jl
# BSD 3-Clause License
# Copyright (c) 2020, Bank of Canada
# All rights reserved.
##################################################################################

empty!(E1.model.sstate.constraints)
@testset "E1.sstate" begin
    let m = E1.model
        m.α = 0.5
        m.β = 1.0 - m.α
        clear_sstate!(m)
        sssolve!(m)
        @test check_sstate(m) == 0
        @test m.sstate.mask == [false, false, true, true]
        #
        @steadystate m y = 1.2
        m.α = 0.5
        m.β = 1.0 - m.α
        clear_sstate!(m)
        sssolve!(m)
        @test check_sstate(m) == 0
        @test m.sstate.mask == [true, false, true, true]
        @test m.sstate.values[1] == 1.2
        #
        m.α = 0.4
        m.β = 1.0 - m.α
        clear_sstate!(m, verbose = false)
        @test m.sstate.mask == trues(4)
        sssolve!(m; verbose = false)
        @test check_sstate(m) == 0
        @test m.sstate.mask == trues(4)
        @test m.sstate.values == [1.2, 0.0, 0.0, 0.0]
        #
        empty!(m.sstate.constraints)
        m.α = 0.3
        m.β = 1.0 - m.α
        clear_sstate!(m, verbose = false)
        sssolve!(m; verbose = false)
        @test check_sstate(m) == 0
        @test m.sstate.values[2] == 0.0
    end
end

empty!(E2.model.sstate.constraints)
@testset "E2.sstate" begin
    let m = E2.model
        clear_sstate!(m)
        sssolve!(m);
        @test check_sstate(m) == 0
        @test m.sstate.mask == trues(12)
        @test m.sstate.values == zeros(12)

        empty!(m.sstate.constraints)
        @steadystate m rate = 0.0
        clear_sstate!(m)
        @test m.sstate.mask[3] == true
        sssolve!(m)
        @test check_sstate(m) == 0
        @test m.sstate.mask == trues(12)
        @test m.sstate.values == zeros(12)

        empty!(m.sstate.constraints)
        @steadystate m rate = 0.1
        clear_sstate!(m)
        sssolve!(m)
        @test check_sstate(m) > 0
    end
end

@testset "E6.sstate" begin
    let m = E6.model
        m.options.maxiter = 50

        clear_sstate!(m)
        sssolve!(m)
        @test check_sstate(m) == 0
        @test sum(m.sstate.mask) == 9+4
        @test m.sstate.values[m.sstate.mask] ≈ [0.005, 0.0, 0.0045, 0.0, 0.0095, 0.0, 0.005, 0.0045, 0.0095, 0, 0, 0, 0]

        empty!(m.sstate.constraints)
        @steadystate m lp = 2
        @steadystate m ly = 3
        @steadystate m lyn = 7
        clear_sstate!(m)
        sssolve!(m);
        @test check_sstate(m) == 0
        @test all(m.sstate.mask)
        @test m.sstate.values ≈ [0.005, 0.0, 0.0045, 0.0, 0.0095, 0.0, 2.0, 0.005, 3.0, 0.0045, 7.0, 0.0095, 0, 0, 0, 0]
    end
end

@testset "E7.sstate" begin
    let m = E7.model
        m.options.maxiter = 100
        m.options.tol = 1e-9

        clear_sstate!(m)
        sssolve!(m);
        @test check_sstate(m) == 0
        # underdetermined system - two free variables
        @test sum(m.sstate.mask) == length(m.sstate.mask) - 2

        empty!(m.sstate.constraints)
        @steadystate m linv = lc - 7;
        @steadystate m lc = 14;
        clear_sstate!(m)
        sssolve!(m);
        @test check_sstate(m) == 0
        @test all(m.sstate.mask)
        @test m.sstate.values ≈ [0.004, 0.0, 0.004, 0.0, 0.004, 0.0, 14.0, 0.004, 7.0, 0.004, 9.267287357063445, 0.004, 14.000911466453774, 0.004, 0, 0, 0, 0, 14.000911466453774, 0.004, 9.267287357063445, 0.004]

        empty!(m.sstate.constraints)
        @steadystate m linv = lc - 7
        @steadystate m lc = 14
        @steadystate m linv = 8
        clear_sstate!(m)
        sssolve!(m);
        # overdetermined inconsistent set of constraints
        @test check_sstate(m) > 0

        empty!(m.sstate.constraints)
        @steadystate m linv = lc - 7
        @steadystate m lc = 14
        @steadystate m ly = log(exp(lc) + exp(linv));
        clear_sstate!(m)
        sssolve!(m);
        # overdetermined consistent set of constraints
        @test check_sstate(m) == 0
        @test all(m.sstate.mask)
        @test m.sstate.values ≈ [0.004, 0.0, 0.004, 0.0, 0.004, 0.0, 14.0, 0.004, 7.0, 0.004, 9.267287357063445, 0.004, 14.000911466453774, 0.004, 0, 0, 0 ,0, 14.000911466453774, 0.004, 9.267287357063445, 0.004]
    end
end

##

using ModelBaseEcon
using TimeSeriesEcon
using StateSpaceEcon

module SSTEST
using ModelBaseEcon
model = Model()
@steadyvariables model a b
@variables model c
@equations model begin
    a[t] = 0.95b[t] + 0.1
    b[t+1] = 0.1b[t] + c[t]
    c[t] = 1.2
end
@initialize model
end

@testset "SSTEST" begin
    let m = SSTEST.model
        @test sum(issteady.(m.allvars)) == 2
        clear_sstate!(m)
        @test issssolved(m)
        @test check_sstate(m) == 0
    end
end


text:
"""
### References

```tex
@article{Wei2022HowTE,
  title={How to empower Gr{\"u}nwald–Letnikov fractional difference equations with available initial condition?},
  author={Yiheng Wei and Jinde Cao and Chuang Li and Yang Quan Chen},
  journal={Nonlinear Analysis: Modelling and Control},
  year={2022}
}
```
"""

function solve(prob::FractionalDifferenceSystem, N, ::GL)
    @unpack fun, α, u0 = prob
    result = zeros(length(u0), N)
    result[:, 1] = u0

    for j=2:N
        du = zeros(Float64, length(u0))
        fun(du, result[:, j-1], nothing, 0)
        result[:, j] = du
        # I think this way would be faster, but if I pass the result[:, j] to fun,
        # that would create a copy, which is not what I want.
        # fun(result[:, j], result[:, j-1], nothing, 0)
        for i=1:j-2
            result[:, j] = result[:, j] - generalized_binomial(i, α).*result[:, j-i]
        end
    end
    return result
end

function generalized_binomial(i, alpha)
    result = ones(i+1)
    for j in 1:i
        result[j+1] = result[j]*(j-1-alpha)/j
    end
    return result[end]
end


text:
using LinearAlgebra;

import LinearAlgebra.ordschur;
import LinearAlgebra.ordschur!;

function eigentri(T)
    n = size(T, 1);

    n == size(T, 2) || error("Non-square matrix passed to eigentri");

    D = diag(T);
    V = -UpperTriangular(one(T));

    @inbounds for j = 2 : n
        # We solve the  shifted linear system, and compute the infinity norm as
        # we do it for the final scaling.
        nrm = abs(V[j,j]);

        V[1:j-1,j] .= T[1:j-1,j];

        @inbounds for k = j-1 : -1 : 1
            V[k,j] = V[k,j] / (T[k,k] - D[j]);
            nrm = max(nrm, abs(V[k,j]));

            # Apply the correction to the other entries of the vector
            @inbounds for i = 1 : k-1
                V[i,j] = V[i,j] - T[i,k] * V[k,j];
            end
        end

        # Make the vector of unit infinity norm
        @inbounds for i = 1 : j
            V[i,j] = V[i,j] / nrm;
        end
    end

    return (D, V)
end

function condeigvec(TA)
    #Estimate the condition number of the eigenvector matrix of a triangular matrix
    # Returns [TAtilde, kVA]

    d_uh = 16; # the default
    u = eps(1/2);
    m = size(TA, 1);

    delta1 = 5e-3;

    EA = diagm(randn(m));
    max_tAij = maximum(map(abs, triu(TA, 1)));
    max_tDA = maximum(map(abs, diag(TA)));

    EA = EA / norm(EA) * u * max_tAij;

    # Use precision u^2 to form T + E since E might be
    # too small to be added by T in precision u
    d_default = 48; # precision with unit roundoff u^2

    TAtilde = with_digits(d_uh) do
        mp(TA, d_default) + mp(EA, d_default)
    end

    # Calculate the largest group size k
    TAtilde_f = convert(Matrix{ComplexF64}, TAtilde)
    ordA = blocking(TAtilde_f, delta1);
    kA = largest_block(ordA);

    if kA > 1  # evaluate the required precision uh
        diagTAtilde = diag(TAtilde);
        sA = length(diagTAtilde);
        sepA = minimum([ convert(Float64, abs(x - y)) for x in diagTAtilde, y in diagTAtilde ] + diagm(Inf * ones(sA)));
        kVA = m * max_tAij * (1 + max_tAij / sepA)^(kA-2) / sepA;
    else
        kVA = 1.0;
    end

    return TAtilde, kVA;
end

function ordschur!(F::Schur{<: Real}, clusters::Array{LinearAlgebra.BlasInt, 1})
    perm = sortperm(clusters, rev = true)
    n = size(F.T, 1)
    i = 0

    cids = unique(clusters);
    sort!(cids, rev = true)

    for j = 1 : length(cids)
        cid = cids[j]
        select = convert(Array{LinearAlgebra.BlasInt, 1}, clusters[i+1:n] .== cid)::Array{LinearAlgebra.BlasInt, 1}
        ncluster = sum(select)

        LAPACK.trsen!('V', 'N', select,
        view(F.T, i+1:n, i+1:n), view(F.Z, 1:n, i+1:n))

        clusters[i+ncluster+1:n] .= filter((j) -> j != cid, clusters[i+1:n])
        clusters[i+1:i+ncluster] .= cid;

        i = i + ncluster
    end

    F
end

function ordschur!(F::Schur{<: Complex}, clusters::Array{LinearAlgebra.BlasInt, 1})

    perm = sortperm(clusters, rev = true);
    n = length(perm);

    for i = 1 : n
        # We swap move perm[i] to i
        if i != perm[i]
            ifst::LinearAlgebra.BlasInt = perm[i];
            ilst::LinearAlgebra.BlasInt = i;

            # FIXME: This does not work in real arithmetic, because
            # blocks are moved in 2x2 patterns.
            LAPACK.trexc!('V', ifst, ilst, F.T, F.Z);

            l = perm[i];
            perm[(perm .>= i) .* (perm .< l)] .+= 1
            perm[i] = i;
        end
    end

    F.values[:] = F.values[perm];

    F
end

function ordschur(F::Schur, clusters::Array{LinearAlgebra.BlasInt, 1})
    ordschur!(deepcopy(F), deepcopy(clusters))
end

max_stars_repo_name: numpi/BivMatFun

max_issues_repo_name: numpi/BivMatFun

max_forks_repo_name: numpi/BivMatFun


text:
using Polygons
using Base.Test

tests = ["definitions", "addition" ]
for t in tests
  test_file = "$(t).jl"
  print_with_color(:green, "* $test_file\n")
  include(test_file)
end


text:
@testset "Eigenvector" begin
    g1 = smallgraph(:house)
    g2 = CycleDiGraph(4)

    for g in testgraphs(g1)
        y = @inferred(eigenvector_centrality(g))
        @test round.(y, digits=3) == round.([
            0.3577513877490464, 0.3577513877490464, 0.5298987782873977,
            0.5298987782873977, 0.4271328349194304
        ], digits=3)
    end
    for g in testdigraphs(g2)
        y = @inferred(eigenvector_centrality(g))
        @test round.(y, digits=3) == round.([0.5, 0.5, 0.5, 0.5], digits=3)
    end
end


text:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
# ==============================================================================
#
#    Tests of headers and sub-headers.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

@testset "Sub-headers" begin
    expected = """
┌───┬───────┬─────┬───┐
│ 1 │     2 │   3 │ 4 │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""
    result = pretty_table(String, data; header = [1, 2, 3, 4])
    @test result == expected

    result = pretty_table(String, data; header = [1, 2, 3, 4])
    @test result == expected

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │     2 │   3 │ 4 │
│ 5 │     6 │   7 │ 8 │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""
    result = pretty_table(String, data; header = ([1, 2, 3, 4], [5, 6, 7, 8]))
    @test result == expected
end

@testset "Hide header and sub-header" begin

    # Header
    # --------------------------------------------------------------------------

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""

    result = pretty_table(String, data; noheader = true)
    @test result == expected

    result = pretty_table(String, data; header = [1, 2], noheader = true)
    @test result == expected

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │ false │ 1.0 │ 1 │
│ 2 │ true  │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │ true  │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │ true  │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""
    result = pretty_table(
        String,
        data;
        header = [1, 2],
        alignment = :l,
        noheader = true
    )
    @test result == expected

    # Sub-header
    # --------------------------------------------------------------------------

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │     2 │   3 │ 4 │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""

    header = (
        [1, 2, 3, 4],
        ["this is", "a very very", "big", "sub-header"]
    )
    result = pretty_table(
        String,
        data;
        header = header,
        nosubheader = true
    )
    @test result == expected
end


text:
module SimpleCache

export cached
using Serialization

"""
    cached(fn::Function, cachename; reset=false)

Cache the result of running `fn` and store the result in a file `cachename`. Future
invocations will be loaded from the cache rather than re-running `fn`. To force `fn` to
be run again (and overwrite the cache), either pass `reset=true` or delete the cache
file.

`cached` uses Julia's built-in serialization, so the cache is not guaranteed to be
loadable from a different Julia version or on a different architecture. It is not
intended for long-term storage, but for saving time when re-running the same code during
development.
"""
function cached(fn, cachename; reset=false)
    if isfile(cachename) && !reset
        deserialize(cachename)
    else
        result = fn()
        serialize(cachename, result)
        result
    end
end

end # module


text:
import FHIRClient
import Generate
import Documenter

Documenter.makedocs(;
    modules=[FHIRClient, Generate],
    authors="Dilum Aluthge, Rhode Island Quality Institute, and contributors",
    repo="https://github.com/JuliaHealth/FHIRClient.jl/blob/{commit}{path}#L{line}",
    sitename="FHIRClient.jl",
    format=Documenter.Documenter.HTML(;
        prettyurls=get(ENV, "CI", "false") == "true",
        canonical="https://JuliaHealth.github.io/FHIRClient.jl",
        assets=String[],
    ),
    pages=[
        "Home" => "index.md",
        "Installing FHIRClient" => "installation.md",
        "Examples" => "examples.md",
        "API" => "api.md",
        "Auto-generating the type definitions" => "generate.md",
    ],
    strict=true,
)

Documenter.deploydocs(;
    repo="github.com/JuliaHealth/FHIRClient.jl",
)


text:
function savevalues!(integrator::DDEIntegrator, force_save=false)
    # update time of ODE integrator (can be slightly modified (< 10ϵ) because of time stops)
    # integrator.EEst has unitless type of integrator.t
    if typeof(integrator.EEst) <: AbstractFloat
        if integrator.integrator.t != integrator.t
            if abs(integrator.t - integrator.integrator.t) >= 10eps(integrator.t)
                error("unexpected time discrepancy detected")
            end

            integrator.integrator.t = integrator.t
            integrator.integrator.dt = integrator.integrator.t - integrator.integrator.tprev
        end
    end

    # If forced, then the user or an event changed integrator.u directly.
    if force_save
        if typeof(integrator.cache) <: OrdinaryDiffEq.OrdinaryDiffEqMutableCache
            integrator.integrator.u .= integrator.u
        else
            integrator.integrator.u = integrator.u
        end
    end

    # update solution
    saved_tuple = savevalues!(integrator.integrator, force_save, false) # reduce_size = false

    # update prev2_idx to indices of tprev and u(tprev) in solution
    # allows reset of ODE integrator (and hence history function) to the last
    # successful time step after failed steps
    integrator.prev2_idx = integrator.prev_idx

    # cache dt of interval [tprev, t] of ODE integrator since it can only be retrieved by
    # a possibly incorrect subtraction
    # NOTE: does not interfere with usual use of dtcache for non-adaptive methods since ODE
    # integrator is only used for inter- and extrapolation of future values and saving of
    # the solution but does not affect the size of time steps
    integrator.integrator.dtcache = integrator.integrator.dt

    # reduce ODE solution
    if integrator.saveat !== nothing
        # obtain constant lags
        constant_lags = integrator.sol.prob.constant_lags

        # delete part of ODE solution that is not required for DDE solution
        reduce_solution!(integrator,
                         # function values at later time points might be necessary for
                         # calculation of next step, thus keep those interpolation data
                         integrator.t - integrator.tdir * maximum(abs, constant_lags))
    end

    # prevent reset of ODE integrator to cached values in the calculation of the next step
    # NOTE: does not interfere with usual use of accept_step since ODE integrator is only
    # used for inter- and extrapolation of future values and saving of the solution but does
    # not affect whether time steps are accepted
    integrator.integrator.accept_step = true

    return saved_tuple
end

"""
    postamble!(integrator::DDEIntegrator)

Clean up solution of `integrator`.
"""
function postamble!(integrator::DDEIntegrator)
    # clean up solution of ODE integrator
    postamble!(integrator.integrator)

    # reduce solution if possible
    integrator.saveat !== nothing && reduce_solution!(integrator, integrator.sol.t[end])
end

"""
    perform_step!(integrator::DDEIntegrator)

Calculate next step of `integrator`.
"""
@muladd function perform_step!(integrator::DDEIntegrator)
    # reset ODE integrator to cached values if last step failed
    if !integrator.integrator.accept_step
        if isinplace(integrator.sol.prob)
            recursivecopy!(integrator.integrator.u, integrator.sol.u[end])
        else
            integrator.integrator.u = integrator.sol.u[end]
        end
        integrator.integrator.t = integrator.sol.t[end]
        integrator.integrator.tprev = integrator.sol.t[integrator.prev2_idx]
        integrator.integrator.dt = integrator.integrator.dtcache

        # u(tprev) is not modified hence we do not have to copy it
        integrator.integrator.uprev = integrator.sol.u[integrator.prev2_idx]

        # do not have to reset interpolation data in initial time step since always a
        # constant extrapolation is used (and interpolation data of solution at initial
        # time point is not complete!)
        if length(integrator.sol.t) > 1
            recursivecopy!(integrator.integrator.k, integrator.sol.k[end])
        end
    end

    # reset boolean which indicates whether history function was evaluated at a time point
    # past the final point of the current solution
    # NOTE: does not interfere with usual use of isout since ODE integrator is only used for
    # inter- and extrapolation of future values and saving of the solution but does not
    # affect whether time steps are accepted
    integrator.integrator.isout = false

    # perform always at least one calculation
    perform_step!(integrator, integrator.cache)

    # if the history function was evaluated at time points past the final time point of the
    # solution, i.e. returned extrapolated values, continue with a fixed-point iteration
    if integrator.integrator.isout
        # update ODE integrator to next time interval together with correct interpolation
        advance_ode_integrator!(integrator)

        numiters = 1

        while true
            # calculate next step
            perform_step!(integrator, integrator.cache, true) # repeat_step=true

            # calculate residuals of fixed-point iteration
            if isinplace(integrator.sol.prob)
                OrdinaryDiffEq.calculate_residuals!(integrator.resid,
                                                    integrator.integrator.u,
                                                    integrator.u,
                                                    integrator.fixedpoint_abstol,
                                                    integrator.fixedpoint_reltol,
                                                    integrator.opts.internalnorm)
            else
                integrator.resid = OrdinaryDiffEq.calculate_residuals(
                    integrator.integrator.u, integrator.u, integrator.fixedpoint_abstol,
                    integrator.fixedpoint_reltol, integrator.opts.internalnorm)
            end

            # update error estimate of integrator with a combined error
            # estimate of both integrator and fixed-point iteration
            # this prevents acceptance of steps with poor performance in fixed-point
            # iteration
            integrator.EEst = max(integrator.EEst,
                                  integrator.fixedpoint_norm(integrator.resid))

            # complete interpolation data of DDE integrator for time interval [t, t+dt]
            # and copy it to ODE integrator
            # has to be done before updates to ODE integrator, otherwise history function
            # is incorrect
            if typeof(integrator.cache) <: OrdinaryDiffEq.CompositeCache
                addsteps!(integrator.k, integrator.t, integrator.uprev,
                                             integrator.u, integrator.dt, integrator.f,
                                             integrator.p,
                                             integrator.cache.caches[integrator.cache.current],
                                             false, true, true)
            else
                addsteps!(integrator.k, integrator.t, integrator.uprev,
                                             integrator.u, integrator.dt, integrator.f,
                                             integrator.p,
                                             integrator.cache, false, true,
                                             true)
            end
            recursivecopy!(integrator.integrator.k, integrator.k)

            # update value u(t+dt)
            if isinplace(integrator.sol.prob)
                recursivecopy!(integrator.integrator.u, integrator.u)
            else
                integrator.integrator.u = integrator.u
            end

            # stop fixed-point iteration when error estimate is small or maximal number of
            # steps is exceeded
            if integrator.EEst <= 1 || numiters > integrator.max_fixedpoint_iters
                break
            end

            numiters += 1
        end
    else
        # update ODE integrator to next time interval together with correct interpolation
        advance_ode_integrator!(integrator)
    end

    # is reset if time step is saved, prevents unnecessary copies and assignments
    integrator.integrator.accept_step = false
end

"""
    initialize!(integrator::DDEIntegrator)

Set initial values of `integrator`.
"""
function initialize!(integrator::DDEIntegrator)
    # initialize DDE integrator
    initialize!(integrator, integrator.cache)

    # copy interpolation data to ODE integrator
    integrator.integrator.kshortsize = integrator.kshortsize
    integrator.integrator.k = recursivecopy(integrator.k)

    # add interpolation steps to ODE integrator to ensure that interpolation data
    # is always maximal when calculating the next step
    # exact values do not matter since in the initial time step always a constant
    # extrapolation is used
    addsteps!(integrator.integrator, integrator.f)
end

"""
    u_modified!(integrator::DDEIntegrator, bool::Bool)

Signal `integrator` whether state vector `u` was modified by a callback.

A modified `u` will lead to recalculations in order to prevent discontinuities.
"""
@inline function u_modified!(integrator::DDEIntegrator, bool::Bool)
    integrator.u_modified = bool
end

"""
    get_proposed_dt(integrator::DDEIntegrator)

Get the time step that `integrator` will take after the current step.
"""
@inline get_proposed_dt(integrator::DDEIntegrator) = integrator.dtpropose

"""
    set_proposed_dt!(integrator::DDEIntegrator, dt)

Set the time step that `integrator` will take after the current step to `dt`.
"""
@inline set_proposed_dt!(integrator::DDEIntegrator, dt) = (integrator.dtpropose = dt)

@inline DiffEqBase.get_tmp_cache(integrator::DDEIntegrator) = DiffEqBase.get_tmp_cache(integrator,integrator.alg,integrator.cache)
user_cache(integrator::DDEIntegrator) = user_cache(integrator.cache)
u_cache(integrator::DDEIntegrator) = u_cache(integrator.cache)
du_cache(integrator::DDEIntegrator)= du_cache(integrator.cache)
full_cache(integrator::DDEIntegrator) = chain(user_cache(integrator),u_cache(integrator),du_cache(integrator))

resize!(integrator::DDEIntegrator, i::Int) = resize!(integrator, integrator.cache, i)
function resize!(integrator::DDEIntegrator, cache, i)
    for c in full_cache(integrator)
        resize!(c, i)
    end
end

function resize!(integrator::DDEIntegrator, cache::Union{Rosenbrock23Cache,
                                                         Rosenbrock32Cache}, i)
    for c in full_cache(integrator)
        resize!(c, i)
    end
    for c in vecu_cache(integrator.cache)
        resize!(c, i)
    end
    Jvec = vec(cache.J)
    cache.J = reshape(resize!(Jvec, i*i), i, i)
    Wvec = vec(cache.W)
    cache.W = reshape(resize!(Wvec, i*i), i, i)
end

function resize!(integrator::DDEIntegrator, cache::Union{ImplicitEulerCache,TrapezoidCache},
                 i)
    for c in full_cache(integrator)
        resize!(c, i)
    end
    for c in vecu_cache(integrator.cache)
        resize!(c, i)
    end
    for c in dual_cache(integrator.cache)
        resize!(c.du, i)
        resize!(c.dual_du, i)
    end
    if alg_autodiff(integrator.alg)
        cache.adf = autodiff_setup(cache.rhs, cache.uhold, integrator.alg)
    end
end

function deleteat!(integrator::DDEIntegrator, i::Int)
    for c in full_cache(integrator)
        deleteat!(c, i)
    end
end

"""
    terminate!(integrator::DDEIntegrator)

Stop further calculations of `integrator`.
"""
function terminate!(integrator::DDEIntegrator)
    integrator.opts.tstops.valtree = typeof(integrator.opts.tstops.valtree)()
end

# DDE integrator can be reinitialized
has_reinit(integrator::DDEIntegrator) = true

"""
    reinit!(integrator::DDEIntegrator[, u0 = integrator.sol.prob.u0;
            t0 = integrator.sol.prob.tspan[1],
            tf = integrator.sol.prob.tspan[2],
            erase_sol = true,
            kwargs...])

Reinitialize `integrator` with (optionally) different initial state `u0`, different
integration interval from `t0` to `tf`, and erased solution if `erase_sol = true`.
"""
function reinit!(integrator::DDEIntegrator, u0 = integrator.sol.prob.u0;
                 t0 = integrator.sol.prob.tspan[1],
                 tf = integrator.sol.prob.tspan[2],
                 erase_sol = true,
                 tstops = integrator.opts.tstops_cache,
                 saveat = integrator.opts.saveat_cache,
                 d_discontinuities = integrator.opts.d_discontinuities_cache,
                 initial_order = agrees(integrator.sol.prob.h, u0, integrator.sol.prob.p, t0) ? 1 : 0,
                 reset_dt = iszero(integrator.dtcache) && integrator.opts.adaptive,
                 reinit_callbacks = true, initialize_save = true,
                 reinit_cache = true)
    # reinit ODE integrator
    reinit!(integrator.integrator, u0; t0 = t0, tf = tf, erase_sol = erase_sol,
            reset_dt = false, reinit_callbacks = false, reinit_cache = false)
    integrator.integrator.dt = zero(integrator.dt)
    integrator.integrator.dtcache = zero(integrator.dt)

    # reinit initial values of DDE integrator
    if isinplace(integrator.sol.prob)
        recursivecopy!(integrator.u, u0)
        recursivecopy!(integrator.uprev, integrator.u)
    else
        integrator.u = u0
        integrator.uprev = integrator.u
    end

    integrator.u_modified = false

    if OrdinaryDiffEq.alg_extrapolates(integrator.alg)
        if isinplace(integrator.sol.prob)
            recursivecopy!(integrator.uprev2, integrator.uprev)
        else
            integrator.uprev2 = integrator.uprev
        end
    end

    integrator.t = t0
    integrator.tprev = t0

    # reinit time stops, time points at which solution is saved, and discontinuities
    integrator.opts.tstops, integrator.opts.saveat, integrator.opts.d_discontinuities =
        tstop_saveat_disc_handling(tstops, saveat, d_discontinuities, integrator.tdir,
                                   (t0,tf), initial_order, alg_maximum_order(integrator.alg),
                                   integrator.sol.prob.constant_lags, typeof(integrator.t))

    # copy time points at which solution is saved if solution should be
    # reduced during integration
    if integrator.saveat !== nothing
        integrator.saveat = deepcopy(integrator.opts.saveat)
    end

    if erase_sol
        # erase array of tracked discontinuities
        if initial_order ≤ alg_maximum_order(integrator.alg)
            resize!(integrator.tracked_discontinuities, 1)
            integrator.tracked_discontinuities[1] = Discontinuity(t0, initial_order)
        else
            resize!(integrator.tracked_discontinuities, 0)
        end

        # reset solution counters
        integrator.saveiter = 1
        integrator.prev_idx = 1
        integrator.prev2_idx = 1
    end

    # reset integration counters
    integrator.iter = 0
    integrator.success_iter = 0

    # full re-initialize the PI in timestepping
    integrator.qold = integrator.opts.qoldinit
    integrator.q11 = one(integrator.t)
    integrator.erracc = one(integrator.erracc)
    integrator.dtacc = one(integrator.dtacc)

    if reset_dt
        auto_dt_reset!(integrator)
    end

    if reinit_callbacks
        initialize_callbacks!(integrator, initialize_save)
    end

    if reinit_cache
        initialize!(integrator)
    end
end

"""
    auto_dt_reset!(dde_int::DDEIntegrator)

Automatically determine initial time step of `dde_int`.
"""
function auto_dt_reset!(dde_int::DDEIntegrator)
    # determine maximal time step
    constant_lags = dde_int.sol.prob.constant_lags
    dtmax = (constant_lags === nothing || isempty(constant_lags)) ? dde_int.opts.dtmax :
        dde_int.tdir * minimum(abs, constant_lags)

    # determine initial time step
    ode_prob = ODEProblem(dde_int.f, dde_int.sol.prob.u0, dde_int.sol.prob.tspan,
                          dde_int.sol.prob.p)
    dde_int.dt = OrdinaryDiffEq.ode_determine_initdt(dde_int.u, dde_int.t, dde_int.tdir,
                                                     dtmax, dde_int.opts.abstol,
                                                     dde_int.opts.reltol,
                                                     dde_int.opts.internalnorm,
                                                     ode_prob,
                                                     dde_int)
end

function DiffEqBase.add_tstop!(integrator::DDEIntegrator,t)
  integrator.tdir * (t - integrator.t) < 0 && error("Tried to add a tstop that is behind the current time. This is strictly forbidden")
  push!(integrator.integrator.opts.tstops,t)
  push!(integrator.opts.tstops,t)
end

function DiffEqBase.add_saveat!(integrator::DDEIntegrator,t)
  integrator.tdir * (t - integrator.t) < 0 && error("Tried to add a saveat that is behind the current time. This is strictly forbidden")
  push!(integrator.integrator.opts.saveat,t)
  push!(integrator.opts.saveat,t)
end

@inline function DiffEqBase.get_du(integrator::DDEIntegrator)
  integrator.fsallast
end

@inline function DiffEqBase.get_du!(out,integrator::DDEIntegrator)
  out .= integrator.fsallast
end

DiffEqBase.addsteps!(integrator::DDEIntegrator,args...) = OrdinaryDiffEq._ode_addsteps!(integrator,args...)
DiffEqBase.change_t_via_interpolation!(integrator::DDEIntegrator,
                                        t,modify_save_endpoint::Type{Val{T}}=Val{false}) where T =
                                          OrdinaryDiffEq._change_t_via_interpolation!(integrator,t,modify_save_endpoint)


text:
"""
    Stack{T}

a Stack for Last In First Out(LIFO) access

# Examples
```jldoctest
julia> s = Stack{Int}() # create a stack
Stack{Int64}(Deque [Int64[]])

julia> push!(s, 1) # push back a item
Stack{Int64}(Deque [[1]])

julia> x = top(s) # get a first item
1

julia> length(s)
1

julia> x = pop!(s) # get and remove a first item
1

julia> length(s)
0
```
"""
mutable struct Stack{T}
    store::Deque{T}
end

Stack{T}() where {T} = Stack(Deque{T}())
Stack{T}(blksize::Integer) where {T} = Stack(Deque{T}(blksize))

isempty(s::Stack) = isempty(s.store)
length(s::Stack) = length(s.store)
Base.eltype(::Type{Stack{T}}) where T = T

"""
    top(s::Stack)

Get a top item from a stack
"""
top(s::Stack) = back(s.store)

function push!(s::Stack, x)
    push!(s.store, x)
    s
end

pop!(s::Stack) = pop!(s.store)

empty!(s::Stack) = (empty!(s.store); s)

iterate(st::Stack, s...) = iterate(reverse_iter(st.store), s...)

"""
    reverse_iterate(s::Stack)

Get a FILO iterator of a stack
"""
reverse_iter(s::Stack{T}) where {T} = DequeIterator{T}(s.store)

==(x::Stack, y::Stack) = x.store == y.store


text:
using JGUI
using Base.Test

store = Store(Int, Float64, String)
push!(store, (1, 1.0, "one"))
push!(store, (2, 2.0, "two"))
push!(store, (3, 3.0, "three"))

w = window(size=[300, 300])
sv = storeview(w, store)
sv[:names] = ["Column one", "column two", "column three"]
push!(w, sv)
raise(w)

@assert sv[:index] == 0         # no selection
sv[:index] = 3
@assert sv[:index] == 3

sv[:index] = 0                  # clear?
@assert sv[:index] == 0

sv[:selectmode] = :multiple
## XXX

raise(w)


text:
@testset "DenseKernelType" begin
include("denseKernelTest.jl")
end

@testset "SparseKernelType" begin
include("sparseKernelTest.jl")
end

@testset "convFFTKernel" begin
include("convFFTKernelTest.jl")
end

@testset "convGEMMKernel" begin
include("convGEMMKernelTest.jl")
end


text:
using Monteiro2022Results

function run()
    modes = [
        Dict(
            "name" => "both",
            "hc" => [-1.0, 0.0, 1.0],
        ),
        Dict(
            "name" => "dg",
            "hc" => [0.0, 1.0],
        ),
        Dict(
            "name" => "ev",
            "hc" => [-1.0, 0.0],
        )
    ]
    for mode in modes
        julia_main(mode)
    end

end

run()


text:
__precompile__(true)

module RingBuffers

export RingBuffer, notifyhandle, framesreadable, frameswritable
export writeavailable, writeavailable!, readavailable! # these don't exist in Base
export PaUtilRingBuffer

import Base: read, read!, readavailable, write, flush
import Base: wait, notify
import Base: unsafe_convert, pointer
import Base: isopen, close

using Base: AsyncCondition
import Compat
import Compat: Libdl, Cvoid, undef, popfirst!, @compat

depsjl = joinpath(@__DIR__, "..", "deps", "deps.jl")
isfile(depsjl) ? include(depsjl) : error("RingBuffers not properly installed. Please run Pkg.build(\"RingBuffers\")")
__init__() = check_deps()

include("pa_ringbuffer.jl")

"""
    RingBuffer{T}(nchannels, nframes)

A lock-free ringbuffer wrapping PortAudio's implementation. The underlying
representation stores multi-channel data as interleaved. The buffer will hold
`nframes` frames.

This ring buffer can be used to pass data between tasks similar to the built-in
`Channel` type, but it has the additional ability to pass data to and from C
code running on a different thread. The C code can use the API defined in
`pa_ringbuffer.h`. Note that this is only safe with a single-threaded reader and
single-threaded writer.
"""
struct RingBuffer{T}
    pabuf::PaUtilRingBuffer
    nchannels::Int
    readers::Vector{Condition}
    writers::Vector{Condition}
    datanotify::AsyncCondition

    function RingBuffer{T}(nchannels, frames) where {T}
        frames = nextpow2(frames)
        buf = PaUtilRingBuffer(sizeof(T) * nchannels, frames)
        new(buf, nchannels, Condition[], Condition[], AsyncCondition())
    end
end

###############
# Writing
###############

"""
    write(rbuf::RingBuffer{T}, data::AbstractArray{T}[, nframes])

Write `nframes` frames to `rbuf` from `data`. Assumes the data is interleaved.
If the buffer is full the call will block until space is available or the ring
buffer is closed. If `data` is a `Vector`, it's treated as a block of memory
from which to read the interleaved data. If it's a `Matrix`, it is treated as
nchannels × nframes.

Returns the number of frames written, which should be equal to the requested
number unless the buffer was closed prematurely.
"""
function write(rbuf::RingBuffer{T}, data::AbstractArray{T}, nframes) where {T}
    if length(data) < nframes * rbuf.nchannels
        dframes = (div(length(data), rbuf.nchannels))
        throw(ErrorException("data array is too short ($dframes frames) for requested write ($nframes frames)"))
    end

    isopen(rbuf) || return 0

    cond = Condition()
    nwritten = 0
    try
        push!(rbuf.writers, cond)
        if length(rbuf.writers) > 1
            # we're behind someone in the queue
            wait(cond)
            isopen(rbuf) || return 0
        end
        # now we're in the front of the queue
        n = PaUtil_WriteRingBuffer(rbuf.pabuf,
                                   pointer(data),
                                   nframes)
        nwritten += n
        # notify any waiting readers that there's data available
        notify(rbuf.datanotify.cond)
        while nwritten < nframes
            wait(rbuf.datanotify)
            isopen(rbuf) || return nwritten
            n = PaUtil_WriteRingBuffer(rbuf.pabuf,
                                       pointer(data)+(nwritten*rbuf.nchannels*sizeof(T)),
                                       nframes-nwritten)
            nwritten += n
            # notify any waiting readers that there's data available
            notify(rbuf.datanotify.cond)
        end
    finally
        # we're done, remove our condition and notify the next writer if necessary
        popfirst!(rbuf.writers)
        if length(rbuf.writers) > 0
           notify(rbuf.writers[1])
        end
    end

    nwritten
end

function write(rbuf::RingBuffer{T}, data::AbstractMatrix{T}) where {T}
    if size(data, 1) != rbuf.nchannels
        throw(ErrorException(
            "Tried to write a $(size(data, 1))-channel array to a $(rbuf.nchannels)-channel ring buffer"))
    end
    write(rbuf, data, size(data, 2))
end

function write(rbuf::RingBuffer{T}, data::AbstractVector{T}) where {T}
    write(rbuf, data, div(length(data), rbuf.nchannels))
end

"""
    frameswritable(rbuf::RingBuffer)

Returns the number of frames that can be written to the ring buffer without
blocking.
"""
function frameswritable(rbuf::RingBuffer)
    PaUtil_GetRingBufferWriteAvailable(rbuf.pabuf)
end

"""
    writeavailable(rbuf::RingBuffer{T}, data::AbstractArray{T}[, nframes])

Write up to `nframes` frames to `rbuf` from `data` without blocking. If `data`
is a `Vector`, it's treated as a block of memory from which to read the
interleaved data. If it's a `Matrix`, it is treated as nchannels × nframes.

Returns the number of frames written.
"""
function writeavailable(rbuf::RingBuffer{T}, data::AbstractVector{T},
                        nframes=div(length(data), rbuf.nchannels)) where {T}
    write(rbuf, data, min(nframes, frameswritable(rbuf)))
end

function writeavailable(rbuf::RingBuffer{T}, data::AbstractMatrix{T},
                        nframes=size(data, 2)) where {T}
    write(rbuf, data, min(nframes, frameswritable(rbuf)))
end

# basically add ourselves to the queue as if we're a writer, but wait until the
# ringbuf is emptied
function flush(rbuf::RingBuffer)
    isopen(rbuf) || return

    cond = Condition()
    try
        push!(rbuf.writers, cond)
        if length(rbuf.writers) > 1
            # we're behind someone in the queue
            wait(cond)
            isopen(rbuf) || return
        end
        # now we're in the front of the queue
        while frameswritable(rbuf) < rbuf.pabuf.bufferSize
            wait(rbuf.datanotify)
            isopen(rbuf) || return
        end

    finally
        # we're done, remove our condition and notify the next writer if necessary
        popfirst!(rbuf.writers)
        if length(rbuf.writers) > 0
           notify(rbuf.writers[1])
        end
    end
end

###############
# Reading
###############

"""
    read!(rbuf::RingBuffer, data::AbstractArray[, nframes])

Read `nframes` frames from `rbuf` into `data`. Data will be interleaved.
If the buffer is empty the call will block until data is available or the ring
buffer is closed. If `data` is a `Vector`, it's treated as a block of memory
in which to write the interleaved data. If it's a `Matrix`, it is treated as
nchannels × nframes.

Returns the number of frames read, which should be equal to the requested
number unless the buffer was closed prematurely.
"""
function read!(rbuf::RingBuffer{T}, data::AbstractArray{T}, nframes) where {T}
    if length(data) < nframes * rbuf.nchannels
        dframes = (div(length(data), rbuf.nchannels))
        throw(ErrorException("data array is too short ($dframes frames) for requested read ($nframes frames)"))
    end

    cond = Condition()
    nread = 0
    try
        push!(rbuf.readers, cond)
        if length(rbuf.readers) > 1
            # we're behind someone in the queue
            wait(cond)
            isopen(rbuf) || return 0
        end
        # now we're in the front of the queue
        n = PaUtil_ReadRingBuffer(rbuf.pabuf,
                                  pointer(data),
                                  nframes)
        nread += n
        # notify any waiting writers that there's space available
        notify(rbuf.datanotify.cond)
        while nread < nframes
            wait(rbuf.datanotify)
            isopen(rbuf) || return nread
            n = PaUtil_ReadRingBuffer(rbuf.pabuf,
                                      pointer(data)+(nread*rbuf.nchannels*sizeof(T)),
                                      nframes-nread)
            nread += n
            # notify any waiting writers that there's space available
            notify(rbuf.datanotify.cond)
        end
    finally
        # we're done, remove our condition and notify the next reader if necessary
        popfirst!(rbuf.readers)
        if length(rbuf.readers) > 0
           notify(rbuf.readers[1])
        end
    end

    nread
end

function read!(rbuf::RingBuffer{T}, data::AbstractMatrix{T}) where {T}
    if size(data, 1) != rbuf.nchannels
        throw(ErrorException(
            "Tried to write a $(size(data, 1))-channel array to a $(rbuf.nchannels)-channel ring buffer"))
    end
    read!(rbuf, data, size(data, 2))
end

function read!(rbuf::RingBuffer{T}, data::AbstractVector{T}) where {T}
    read!(rbuf, data, div(length(data), rbuf.nchannels))
end

"""
    read(rbuf::RingBuffer, nframes)

Read `nframes` frames from `rbuf` and return an (nchannels × nframes) `Array`
holding the interleaved data. If the buffer is empty the call will block until
data is available or the ring buffer is closed.
"""
function read(rbuf::RingBuffer{T}, nframes) where {T}
    data = Array{T}(undef, rbuf.nchannels, nframes)
    nread = read!(rbuf, data, nframes)

    if nread < nframes
        data[:, 1:nread]
    else
        data
    end
end

"""
    read(rbuf::RingBuffer; blocksize=4096)

Read `blocksize` frames at a time from `rbuf` until the ringbuffer is closed,
and return an (nchannels × nframes) `Array` holding the data. When no data is
available  the call will block until it can read more or the ring buffer is
closed.
"""
function read(rbuf::RingBuffer{T}; blocksize=4096) where {T}
    readbuf = Array{T}(undef, rbuf.nchannels, blocksize)
    # during accumulation we keep the channels separate so we can grow the
    # arrays without needing to copy data around as much
    cumbufs = [Vector{T}() for _ in 1:rbuf.nchannels]
    while true
        n = read!(rbuf, readbuf)
        for ch in 1:length(cumbufs)
            append!(cumbufs[ch], @view readbuf[ch, 1:n])
        end
        n == blocksize || break
    end
    vcat((x' for x in cumbufs)...)
end

"""
    frameswritable(rbuf::RingBuffer)

Returns the number of frames that can be written to the ring buffer without
blocking.
"""
function framesreadable(rbuf::RingBuffer)
    PaUtil_GetRingBufferReadAvailable(rbuf.pabuf)
end

"""
    readavailable!(rbuf::RingBuffer{T}, data::AbstractArray{T}[, nframes])

Read up to `nframes` frames from `rbuf` into `data` without blocking. If `data`
is a `Vector`, it's treated as a block of memory to write the interleaved data
to. If it's a `Matrix`, it is treated as nchannels × nframes.

Returns the number of frames read.
"""
function readavailable!(rbuf::RingBuffer{T}, data::AbstractVector{T},
                        nframes=div(length(data), rbuf.nchannels)) where {T}
    read!(rbuf, data, min(nframes, framesreadable(rbuf)))
end

function readavailable!(rbuf::RingBuffer{T}, data::AbstractMatrix{T},
                        nframes=size(data, 2)) where {T}
    read!(rbuf, data, min(nframes, framesreadable(rbuf)))
end

"""
    readavailable(rbuf::RingBuffer[, nframes])

Read up to `nframes` frames from `rbuf` into `data` without blocking. If `data`
is a `Vector`, it's treated as a block of memory to write the interleaved data
to. If it's a `Matrix`, it is treated as nchannels × nframes.

Returns an (nchannels × nframes) `Array`.
"""
function readavailable(rbuf::RingBuffer, nframes)
    read(rbuf, min(nframes, framesreadable(rbuf)))
end

function readavailable(rbuf::RingBuffer)
    read(rbuf, framesreadable(rbuf))
end

######################
# Resource Management
######################

function close(rbuf::RingBuffer)
    close(rbuf.pabuf)
    # wake up any waiting readers or writers
    notify(rbuf.datanotify.cond)
end

isopen(rbuf::RingBuffer) = isopen(rbuf.pabuf)

# """
#     notify(rbuf::RingBuffer)
#
# Notify the ringbuffer that new data might be available, which will wake up
# any waiting readers or writers. This is safe to call from a separate thread
# context.
# """
# notify(rbuf::RingBuffer) = ccall(:uv_async_send, rbuf.datacond.handle)

"""
    notifyhandle(rbuf::RingBuffer)

Return the AsyncCondition handle that can be used to wake up the buffer from
another thread.
"""
notifyhandle(rbuf::RingBuffer) = rbuf.datanotify.handle

"""
    pointer(rbuf::RingBuffer)

Return the pointer to the underlying PaUtilRingBuffer that can be passed to C
code and manipulated with the pa_ringbuffer.h API.
"""
pointer(rbuf::RingBuffer) = Ptr{PaUtilRingBuffer}(pointer_from_objref(rbuf.pabuf))

# set it up so we can pass this directly to `ccall` expecting a PA ringbuffer
unsafe_convert(::Type{Ptr{PaUtilRingBuffer}}, buf::RingBuffer) = unsafe_convert(Ptr{PaUtilRingBuffer}, buf.pabuf)

end # module

max_stars_repo_name: wookay/TestRingBuffers

max_issues_repo_name: wookay/TestRingBuffers

max_forks_repo_name: wookay/TestRingBuffers