text:
export AbstractConstraint, CFiniteConstraint, Constraint, Clause, ClauseSet, @cstr
export EQ, NEQ, LT, LEQ, GT, GEQ
export variables

@enum ConstraintRel EQ NEQ LT LEQ GT GEQ

_constraintrel_map = Dict(
    EQ  => :(==),
    NEQ => :(!=),
    LT  => :(<),
    LEQ => :(<=),
    LT  => :(>),
    LEQ => :(>=)
)

const XExpr = Union{Expr,Symbol,Number}

abstract type AbstractConstraint end
const Clause = OrderedSet{AbstractConstraint}
const ClauseSet = OrderedSet{Clause}

struct CFiniteConstraint{R} <: AbstractConstraint
    us::Vector{XExpr}
    ms::Vector{XExpr}

    function CFiniteConstraint{R}(us, ms) where {R}
        @assert R == EQ || R == NEQ
        @assert length(us) == length(ms)
        _us = [Meta.parse(string(u)) for u in us]
        _ms = [Meta.parse(string(m)) for m in ms]
        new(_us, _ms)
    end
end

Base.:~(c::CFiniteConstraint{EQ}) = CFiniteConstraint{NEQ}(c.us, c.ms)
Base.:~(c::CFiniteConstraint{NEQ}) = CFiniteConstraint{EQ}(c.us, c.ms)

function expand(c::CFiniteConstraint{R}, limit=length(c.us)) where {R}
    cs = ClauseSet()
    for i in 1:limit
        ms = map(x->:($x^($i-1)), c.ms)
        terms = [:($u*$m) for (u,m) in zip(c.us,ms)]
        if length(terms) == 1
            cs &= Constraint{EQ}(terms[1])
        else
            cs &= Constraint{EQ}(Expr(:call, :+, terms...))
        end
    end
    R == NEQ ? ~cs : cs
end

expand(c::T) where {T<:AbstractConstraint} = ClauseSet(Clause(c))
expand(c::Clause) = isempty(c) ? c : foldl(|, (expand(x) for x in c))
expand(c::ClauseSet) = isempty(c) ? c : foldl(&, (expand(x) for x in c))

struct Constraint{ConstraintRel} <: AbstractConstraint
    poly::Union{Expr,Symbol,Number}

    function Constraint{ConstraintRel}(x) where {ConstraintRel}
        (x isa Expr || x isa Symbol || x isa Number) && return new{ConstraintRel}(x)
        new{ConstraintRel}(Meta.parse(string(x)))
    end
end

Clause(c::AbstractConstraint) = Clause([c])
ClauseSet(c::Clause) = ClauseSet([c])

Base.:~(c::Constraint{EQ}) = Constraint{NEQ}(c.poly)
Base.:~(c::Constraint{NEQ}) = Constraint{EQ}(c.poly)
Base.:~(c::Constraint{LT}) = Constraint{GEQ}(c.poly)
Base.:~(c::Constraint{LEQ}) = Constraint{GT}(c.poly)
Base.:~(c::Constraint{GT}) = Constraint{LEQ}(c.poly)
Base.:~(c::Constraint{GEQ}) = Constraint{LT}(c.poly)

Base.:|(x::AbstractConstraint, y::AbstractConstraint) = Clause([x, y])
Base.:&(x::AbstractConstraint, y::AbstractConstraint) = ClauseSet([Clause([x]), Clause([y])])

Base.:~(c::Clause) = ClauseSet([Clause([~x]) for x in c])
Base.:|(x::Clause, y::Clause) = Clause(union(x, y))
Base.:&(x::Clause, y::Clause) = ClauseSet([x, y])

Base.:~(cs::ClauseSet) = reduce(Base.:|, [~c for c in cs])
Base.:|(x::ClauseSet, y::ClauseSet) = ClauseSet(map(z->(z[1] | z[2]), Iterators.product(x, y)))
Base.:&(x::ClauseSet, y::ClauseSet) = union(x, y)

Base.:|(x, y) = Base.:|(promote(x, y)...)
Base.:&(x, y) = Base.:&(promote(x, y)...)

Base.convert(::Type{Clause}, c::AbstractConstraint) = Clause([c])
Base.convert(::Type{ClauseSet}, c::AbstractConstraint) = ClauseSet([Clause([c])])
Base.convert(::Type{ClauseSet}, c::Clause) = ClauseSet([c])

Base.convert(::Type{Expr}, c::Constraint{EQ}) = :($(c.poly) == 0)
Base.convert(::Type{Expr}, c::Constraint{NEQ}) = :($(c.poly) != 0)
Base.convert(::Type{Expr}, c::Constraint{LT}) = :($(c.poly) < 0)
Base.convert(::Type{Expr}, c::Constraint{LEQ}) = :($(c.poly) <= 0)
Base.convert(::Type{Expr}, c::Constraint{GT}) = :($(c.poly) > 0)
Base.convert(::Type{Expr}, c::Constraint{GEQ}) = :($(c.poly) >= 0)
Base.convert(::Type{Expr}, c::Clause) = length(c) == 1 ? convert(Expr, first(c)) : Expr(:call, :|, [convert(Expr, x) for x in c]...)
Base.convert(::Type{Expr}, c::ClauseSet) = Expr(:call, :&, [convert(Expr, x) for x in c]...)

Base.promote_rule(::Type{Clause}, ::Type{<:AbstractConstraint}) = Clause
Base.promote_rule(::Type{<:AbstractConstraint}, ::Type{Clause}) = Clause
Base.promote_rule(::Type{ClauseSet}, ::Type{<:AbstractConstraint}) = ClauseSet
Base.promote_rule(::Type{<:AbstractConstraint}, ::Type{ClauseSet}) = ClauseSet
Base.promote_rule(::Type{ClauseSet}, ::Type{Clause}) = ClauseSet
Base.promote_rule(::Type{Clause}, ::Type{ClauseSet}) = ClauseSet

variables(c::Constraint) = symbols(c.poly)
variables(c::CFiniteConstraint) = union((symbols(x) for x in c.us)..., (symbols(x) for x in c.ms)...)
variables(c::Clause) = union((variables(x) for x in c)...)
variables(c::ClauseSet) = union((variables(x) for x in c)...)

function Base.show(io::IO, c::Constraint{R}) where {R}
    print(io, c.poly)
    print(io, " ")
    print(io, string(_constraintrel_map[R]))
    print(io, " 0")
end

function Base.show(io::IO, c::Clause)
    compact = get(io, :compact, false)
    if compact
        print(io, string("[", join(c, ", "), "]"))
    else
        print(io, "$(length(c))-element Clause:")
        for x in c
            print(io, "\n ")
            print(io, x)
        end
    end
end

function Base.show(io::IO, ::MIME"text/plain", cs::ClauseSet)
    print(io, "$(length(cs))-element ClauseSet:")
    for c in cs
        print(io, "\n ")
        print(IOContext(io, :compact => true), c)
    end
end

# ------------------------------------------------------------------------------

macro cstr(expr::Expr)
    c = atom_walk(expr) do ex
        @match ex begin
            x_ && y_ => :($x & $y)
            x_ || y_ => :($x | $y)
            x_ == 0  => Constraint{EQ}(x)
            0  == x_ => Constraint{EQ}(x)
            x_ == y_ => Constraint{EQ}(:($x-$y))
            x_ != 0  => Constraint{NEQ}(x)
            0  != x_ => Constraint{NEQ}(x)
            x_ != y_ => Constraint{NEQ}(:($x-$y))
            x_ < 0  => Constraint{LT}(x)
            0  < x_ => Constraint{LT}(x)
            x_ < y_ => Constraint{LT}(:($x-$y))
            x_ <= 0  => Constraint{LEQ}(x)
            0  <= x_ => Constraint{LEQ}(x)
            x_ <= y_ => Constraint{LEQ}(:($x-$y))
            x_ > 0  => Constraint{GT}(x)
            0  > x_ => Constraint{GT}(x)
            x_ > y_ => Constraint{GT}(:($x-$y))
            x_ >= 0  => Constraint{GEQ}(x)
            0  >= x_ => Constraint{GEQ}(x)
            x_ >= y_ => Constraint{GEQ}(:($x-$y))
            x_Symbol => x
            x_Number => x
            _        => @error("Invalid constraint")
        end
    end
    if c isa AbstractConstraint
        return ClauseSet(Clause(c))
    end
    c
end


text:
###
### COIN-OR Clp API Wrapper
###

module Clp

include("ClpCInterface.jl")
include("ClpSolverInterface.jl")
include("MOIWrapper.jl")

using Clp.ClpMathProgSolverInterface
export ClpSolver

end # module

ext: jl


text:
import StateSpaceReconstruction: Embeddings

function marginal_NN(points, dists_to_kth)
    D = pairwise(Chebyshev(), points, dims = 2)

    npts = size(points, 2)
    N = zeros(Int, npts)

    for i = 1:npts
        N[i] = sum(D[:, i] .<= dists_to_kth[i])
    end

    return N
end

"""
    transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        v::TEVars; metric = Chebyshev(), b = 2)

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments

- `points`: The set of points representing the embedding for which to compute
    transfer entropy. Must be provided as an array of size `dim`-by-`n` points.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.

## Keyword arguments

- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b=2` gives the transfer entropy in bits.

## References

1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
    mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        v::TEVars; metric = Chebyshev(), normalise = false, b = 2) where T

    # Make sure that the array contains points as columns.
    if size(points, 1) > size(points, 2)
        error("The dimension of the dataset exceeds the number of points.")
    end
    # The total number of points
    N = size(points, 2)

    # Create some dummy variable names to avoid cluttering the code too much
    X = v.target_future
    Y = v.target_presentpast
    Z = vcat(v.source_presentpast, v.conditioned_presentpast)
    XY = vcat(X, Y)
    YZ = vcat(Y, Z)

    pts_X = points[X, :]
    pts_Y = points[Y, :]
    pts_XY = points[XY, :]
    pts_YZ = points[YZ, :]
    pts_XYZ = points

    # Create trees to search for nearest neighbors
    tree_XYZ = KDTree(pts_XYZ, metric)
    tree_XY = KDTree(pts_XY, metric)

    # Find the k nearest neighbors to all of the points in each of the trees
    idxs_XYZ, dists_XYZ = knn(tree_XYZ, pts_XYZ, k1, true)
    idxs_XY, dists_XY   = knn(tree_XY,  pts_XY, k2, true)

    # In each of the trees, find the index of the k-th nearest neighbor to all
    # of the points.
    kth_NN_idx_XYZ = [idx[k1] for idx in idxs_XYZ]
    kth_NN_idx_XY  = [idx[k2] for idx in idxs_XY]

    # Distances between points in the XYZ and XY spaces and their
    # kth nearest neighbour, along marginals X, YZ (for the XYZ space)
    # and X, Y (for the XY space).
    ϵ_XYZ_X = colwise(metric, pts_X, pts_X[:, kth_NN_idx_XYZ])
    ϵ_XYZ_YZ = colwise(metric, pts_YZ, pts_YZ[:, kth_NN_idx_XYZ])
    ϵ_XY_X = colwise(metric, pts_X, pts_X[:, kth_NN_idx_XY])
    ϵ_XY_Y = colwise(metric, pts_Y, pts_Y[:, kth_NN_idx_XY])

    NXYZ_X  = marginal_NN(pts_X,  ϵ_XYZ_X)
    NXYZ_YZ = marginal_NN(pts_YZ, ϵ_XYZ_YZ)
    NXY_X   = marginal_NN(pts_X,  ϵ_XY_X)
    NXY_Y   = marginal_NN(pts_Y,  ϵ_XY_Y)

    if normalise
        warn("Normalisation not properly tested yet")
        te = sum(digamma.(NXY_X) + digamma.(NXY_Y) -
            digamma.(NXYZ_X) - digamma.(NXYZ_YZ)) / N

        # Distiances between points in the XY space and their k-th nearest beighbour, along
        # both marginals (so, effectively, the joint distribution)
        ϵ_XY_XY = colwise(metric, pts_XY, pts_XY[:, kth_NN_idx_XY])
        NXY_XY   = marginal_NN(pts_XY,  ϵ_XY_XY)

        te = te / (sum(digamma.(NXY_XY) - digamma.(NXY_Y)) / N)
    else
        te = sum(digamma.(NXY_X) + digamma.(NXY_Y) -
            digamma.(NXYZ_X) - digamma.(NXYZ_YZ)) / N
    end

    # Convert from nats to to the desired unit (b^x = e^1 => 1/ln(b))
    te / log(b)
end

"""
    transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        conditioned_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments
- `points`: The set of points representing the embedding for which to compute
    transfer entropy. Must be provided as an array of size `dim`-by-`n` points.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.
- `target_future`: Which rows of `points` correspond to future values of the
    target variable?
- `target_presentpast`: Which rows of `points` correspond to present and past
    values of the target variable?
- `source_presentpast`: Which rows of `points` correspond to present and past
    values of the source variable?
- `conditioned_presentpast`: Which rows of `points` correspond to present and
    past values of conditional variables?

## Keyword arguments
- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b=2` gives the transfer entropy in bits.

# References
1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
    mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        conditioned_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

    # Make sure that the array contains points as columns.
    if size(points, 1) > size(points, 2)
        error("The dimension of the dataset exceeds the number of points.")
    end

    # Create some dummy variable names to avoid cluttering the code too much
    v = TEVars(target_future, target_presentpast,
                source_presentpast, conditioned_presentpast)

    transferentropy_kraskov(points, k1, k2, v, b = b)
end

"""
    transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments

- `points`: The set of points representing the embedding for which to compute
    transfer entropy. Must be provided as an array of size `dim`-by-`n` points.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.
- `target_future`: Which rows of `points` correspond to future values of the
    target variable?
- `target_presentpast`: Which rows of `points` correspond to present and past
    values of the target variable?
- `source_presentpast`: Which rows of `points` correspond to present and past
    values of the source variable?

This version of the function assumes there is no conditioning.

## Keyword arguments

- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b=2` gives the transfer entropy in bits.

# References
1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
    mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

    # Make sure that the array contains points as columns.
    if size(points, 1) > size(points, 2)
        error("The dimension of the dataset exceeds the number of points.")
    end

    # Create some dummy variable names to avoid cluttering the code too much
    v = TEVars(target_future, target_presentpast,
                source_presentpast, Int[])

    transferentropy_kraskov(points, k1, k2, v, b = b)
end

"""
    transferentropy_kraskov(E::StateSpaceReconstruction.AbstractEmbedding,
            k1::Int, k2::Int, v::TEVars; metric = Chebyshev(), b = 2)

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments

- `E`: The embedding for which to compute transfer entropy.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.

## Keyword arguments

- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b=2` gives the transfer entropy in bits.

## References

1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(E::Embeddings.AbstractEmbedding{D, T},
            k1::Int, k2::Int, v::TEVars; metric = Chebyshev(), b = 2) where {D, T}

    # Make sure that the array contains points as columns.
    if size(E.points, 1) > size(E.points, 2)
        error("The dimension exceeds the number of points.")
    end

    transferentropy_kraskov(E.points, k1, k2, v, metric = metric, b = b)
end

"""
    transferentropy_kraskov(E::AbstractEmbedding, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        conditioned_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments

- `points`: The set of points representing the embedding for which to compute
    transfer entropy. Must be provided as an array of size `dim`-by-`n` points.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.
- `target_future`: Which rows of `points` correspond to future values of the
    target variable?
- `target_presentpast`: Which rows of `points` correspond to present and past
    values of the target variable?
- `source_presentpast`: Which rows of `points` correspond to present and past
    values of the source variable?
- `conditioned_presentpast`: Which rows of `points` correspond to present and
    past values of conditional variables?

## Keyword arguments

- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b = 2` gives the transfer entropy in bits.

# References

1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
    mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(E::Embeddings.AbstractEmbedding{D, T}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        conditioned_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where {D, T}

    # Make sure that the array contains points as columns.
    if size(E.points, 1) > size(E.points, 2)
        error("The dimension of the dataset exceeds the number of points.")
    end

    # Create some dummy variable names to avoid cluttering the code too much
    v = TEVars(target_future, target_presentpast,
                source_presentpast, conditioned_presentpast)

    transferentropy_kraskov(E.points, k1, k2, v, b = b)
end

tekraskov = transferentropy_kraskov
tekNN = transferentropy_kraskov

export
transferentropy_kraskov,
tekraskov,
tekNN


text:
@testset "reduce_subtrees!" begin
    @testset for reduce = (+,*)
        @testset for n = 0:10
            Random.seed!(42)
            v = rand(0:10,n)
            r = Vector{Int}(undef, n)
            reduce_subtrees!(v,r,reduce)
            @test isreduction(v,r,reduce)
        end
    end
end

@testset "Reducer!" begin
    @testset for reduce = (+,*)
        @testset for n = 1:10
            Random.seed!(42)
            for _ = 1:10
                v = rand(0:10,n)
                r = Vector{Int}(undef, n)
                reduce_subtrees!(v,r,reduce)

                i = rand(1:n)
                v[i] = rand(1:10)
                walk(Reducer!(v,r,reduce), n, i, Val(:up))
                @test isreduction(v,r,reduce)
            end
        end
    end
end


text:
export PARAMETER_DEFAULT, PARAMETER_UNSTABLE_BUT_FAST, PARAMETER_STABLE_BUT_SLOW

using MathOptInterface
MOI = MathOptInterface

mutable struct SDOptimizer <: SDOI.AbstractSDOptimizer
    problem::SDPAProblem
    options::Dict{Symbol,Any}
    function SDOptimizer(; kwargs...)
        new(SDPAProblem(), Dict{Symbol, Any}(kwargs))
    end
end
Optimizer(; kws...) = SDOI.SDOIOptimizer(SDOptimizer(; kws...))

MOI.get(::SDOptimizer, ::MOI.SolverName) = "SDPA"

function MOI.empty!(optimizer::SDOptimizer)
    optimizer.problem = SDPAProblem()
end

function SDOI.init!(m::SDOptimizer, blkdims::Vector{Int}, nconstrs::Int)
    @assert nconstrs >= 0
    dummy = nconstrs == 0
    if dummy
        nconstrs = 1
        blkdims = [blkdims; -1]
    end
    m.problem = SDPAProblem()
    setParameterType(m.problem, PARAMETER_DEFAULT)
    setparameters!(m.problem, m.options)
    inputConstraintNumber(m.problem, nconstrs)
    inputBlockNumber(m.problem, length(blkdims))
    for (i, blkdim) in enumerate(blkdims)
        inputBlockSize(m.problem, i, blkdim)
        inputBlockType(m.problem, i, blkdim < 0 ? LP : SDP)
    end
    initializeUpperTriangleSpace(m.problem)
    if dummy
        SDOI.setconstraintconstant!(m, 1., 1)
        SDOI.setconstraintcoefficient!(m, 1., 1, length(blkdims), 1, 1)
    end
end

function SDOI.setconstraintconstant!(m::SDOptimizer, val, constr::Integer)
    @assert constr > 0
    #println("b[$constr] = $val")
    inputCVec(m.problem, constr, val)
end
function SDOI.setconstraintcoefficient!(m::SDOptimizer, coef, constr::Integer, blk::Integer, i::Integer, j::Integer)
    @assert constr > 0
    #println("A[$constr][$blk][$i, $j] = $coef")
    inputElement(m.problem, constr, blk, i, j, float(coef), false)
end
function SDOI.setobjectivecoefficient!(m::SDOptimizer, coef, blk::Integer, i::Integer, j::Integer)
    #println("C[$blk][$i, $j] = $coef")
    inputElement(m.problem, 0, blk, i, j, float(coef), false)
end

function MOI.optimize!(m::SDOptimizer)
    SDPA.initializeUpperTriangle(m.problem, false)
    SDPA.initializeSolve(m.problem)
    SDPA.solve(m.problem)
end

function MOI.get(m::SDOptimizer, ::MOI.TerminationStatus)
    status = getPhaseValue(m.problem)
    if status == noINFO
        return MOI.OPTIMIZE_NOT_CALLED
    elseif status == pFEAS
        return MOI.SLOW_PROGRESS
    elseif status == dFEAS
        return MOI.SLOW_PROGRESS
    elseif status == pdFEAS
        return MOI.OPTIMAL
    elseif status == pdINF
        return MOI.INFEASIBLE_OR_UNBOUNDED
    elseif status == pFEAS_dINF
        return MOI.DUAL_INFEASIBLE
    elseif status == pINF_dFEAS
        return MOI.INFEASIBLE
    elseif status == pdOPT
        return MOI.OPTIMAL
    elseif status == pUNBD
        return MOI.DUAL_INFEASIBLE
    elseif status == dUNBD
        return MOI.INFEASIBLE
    end
end

function MOI.get(m::SDOptimizer, ::MOI.PrimalStatus)
    status = getPhaseValue(m.problem)
    if status == noINFO
        return MOI.UNKNOWN_RESULT_STATUS
    elseif status == pFEAS
        return MOI.FEASIBLE_POINT
    elseif status == dFEAS
        return MOI.UNKNOWN_RESULT_STATUS
    elseif status == pdFEAS
        return MOI.FEASIBLE_POINT
    elseif status == pdINF
        return MOI.UNKNOWN_RESULT_STATUS
    elseif status == pFEAS_dINF
        return MOI.INFEASIBILITY_CERTIFICATE
    elseif status == pINF_dFEAS
        return MOI.INFEASIBLE_POINT
    elseif status == pdOPT
        return MOI.FEASIBLE_POINT
    elseif status == pUNBD
        return MOI.INFEASIBILITY_CERTIFICATE
    elseif status == dUNBD
        return MOI.INFEASIBLE_POINT
    end
end

function MOI.get(m::SDOptimizer, ::MOI.DualStatus)
    status = getPhaseValue(m.problem)
    if status == noINFO
        return MOI.UNKNOWN_RESULT_STATUS
    elseif status == pFEAS
        return MOI.UNKNOWN_RESULT_STATUS
    elseif status == dFEAS
        return MOI.FEASIBLE_POINT
    elseif status == pdFEAS
        return MOI.FEASIBLE_POINT
    elseif status == pdINF
        return MOI.UNKNOWN_RESULT_STATUS
    elseif status == pFEAS_dINF
        return MOI.INFEASIBLE_POINT
    elseif status == pINF_dFEAS
        return MOI.INFEASIBILITY_CERTIFICATE
    elseif status == pdOPT
        return MOI.FEASIBLE_POINT
    elseif status == pUNBD
        return MOI.INFEASIBLE_POINT
    elseif status == dUNBD
        return MOI.INFEASIBILITY_CERTIFICATE
    end
end

SDOI.getprimalobjectivevalue(m::SDOptimizer) = getPrimalObj(m.problem)
SDOI.getdualobjectivevalue(m::SDOptimizer) = getDualObj(m.problem)
SDOI.getX(m::SDOptimizer) = PrimalSolution(m.problem)
function SDOI.gety(m::SDOptimizer)
    unsafe_wrap(Array, getResultXVec(m.problem), getConstraintNumber(m.problem))
end
SDOI.getZ(m::SDOptimizer) = VarDualSolution(m.problem)


text:
import Munkres: munkres

function parallel_pairwise_register{N, T}(n::Int, g::Int, kinds::NTuple{N, Vector{Matrix{T}}}, weights::NTuple{N, T})
    queue = []

    for i in 1:n
        for j in 1:i
            X = ntuple(N) do (k) kinds[k][i] end
            Y = ntuple(N) do (k) kinds[k][j] end
            push!(queue, (weights, X, Y))
        end
    end

    cells = pmap(queue) do pair
        weights, X, Y = pair
        G = grid_coords(2π, g)
        d1, R1, P1 = register(weights, X, Y, G, false)
        d2, R2, P2 = register(weights, X, Y, G, true)
        if d1 < d2
            return d1, R1, P1
        else
            return d2, R2, P2
        end
    end

    D = zeros(n, n)
    R = Matrix{Matrix{T}}(n, n)
    P = [Matrix{Matrix{T}}(n, n) for k in 1:N]

    for i in 1:n
        for j in 1:i
            d, r, p = shift!(cells)
            D[i, j] = D[j, i] = d
            R[i, j] = r
            R[j, i] = r'

            for k in 1:N
                P[k][i, j] = p[k]'
                P[k][j, i] = p[k]
            end
        end
    end

    return D, R, P
end

function stiefel_to_permutations{T}(U::Matrix{T})
    n = size(U, 2)
    m = fld(size(U, 1), n)

    P = map(1:m) do i
        P_i1 = U[(i-1)*n + 1:i*n, 1:n] * U[1:n, 1:n]'
        p = munkres(-P_i1')
        eye(T, n)[:, p]
    end

    return P
end


text:
"""
struct ArrayModel{T <: MillFunction} <: AbstractMillModel
m::T
end

use a Chain, Dense, or any other function on an ArrayNode
"""
struct ArrayModel{T} <: AbstractMillModel
    m::T
end

Flux.@functor ArrayModel

(m::ArrayModel)(x::ArrayNode) = mapdata(x -> m.m(x), x)

identity_model() = ArrayModel(identity)
const IdentityModel = ArrayModel{typeof(identity)}

function HiddenLayerModel(m::ArrayModel, x::ArrayNode, k::Int)
	os = Flux.activations(m.m, x.data)
	layers = map(x -> Dense(size(x,1), k), os)
	ArrayModel(layers), ArrayNode(os[end])
end

function mapactivations(hm::ArrayModel, x::ArrayNode, m::ArrayModel)
	os = Flux.activations(m.m, x.data)
	hx = mapfoldl((mx) -> mx[1](mx[2]),+,zip(hm.m, os))
	(ArrayNode(hx), ArrayNode(os[end]))
end

function fold(f, m::ArrayModel, x)
	f(m, x)
end

Flux.activations(::typeof(identity), x::Array{Float32,2}) = (x,)


text:
# Run tests for the waveguide eigenvalue problem

using NonlinearEigenproblemsTest
using NonlinearEigenproblems
using Test
using LinearAlgebra

using GalleryWaveguide

import GalleryWaveguide.SchurMatVec

@bench @testset "WEP" begin

    @bench @testset "SPMF - NEP, and Preconditioner" begin
        nx = 11
        nz = 7
        nep_spmf=nep_gallery(WEP, nx = nx, nz = nz, benchmark_problem = "TAUSCH", neptype = "SPMF")
        nep=nep_gallery(WEP, nx = nx, nz = nz, benchmark_problem = "TAUSCH", neptype = "WEP")
        λ = -1.3-0.31im
        v1 = compute_Mlincomb(nep_spmf, λ, ones(size(nep_spmf,1)))
        v2 = compute_Mlincomb(nep     , λ, ones(size(nep     ,1)))
        @test norm(v1-v2)/norm(v1) < 1e-14

        precond = wep_generate_preconditioner(nep, nz, λ)
        b1 = rand(ComplexF64, nx*nz)
        Schur_fun = SchurMatVec(nep, λ)
        b2 = ldiv!(precond, (Schur_fun*b1))
        @test norm(b1-b2)/norm(b1) < 1e-14
    end

    nep=nep_gallery(WEP, nx = 3*5*7+4, nz = 3*5*7, benchmark_problem = "JARLEBRING", neptype = "WEP")
    n=size(nep,1);
    λ0=-3-3.5im
    v0=ones(n); v0=v0/norm(v0);
    λref =-2.743228671961724 - 3.1439375599649972im  # Reference eigenvalue
    myerrmeasure = EigvalReferenceErrmeasure(nep,λref);

    @bench @testset "Linear solvers" begin

        @info "WEP default linsolver"
        λ,v = resinv(ComplexF64,nep,logger=displaylevel,λ=λ0,v=v0,
                     errmeasure=myerrmeasure,tol=1e-12,
                     linsolvercreator=GalleryWaveguide.WEPLinSolverCreator()
                     );
        @test  norm(compute_Mlincomb(nep,λ,v))/norm(v)  < 1e-10

        @info "WEP backslash linsolver"
        λ,v = resinv(ComplexF64,nep,logger=displaylevel,λ=λ0,v=v0,
                     errmeasure=myerrmeasure,tol=1e-12,
                     linsolvercreator=GalleryWaveguide.WEPLinSolverCreator(solver_type=:backslash)
                     );
        @test  norm(compute_Mlincomb(nep,λ,v))/norm(v)  < 1e-10

        @info "WEP gmres linsolver"
        precond = wep_generate_preconditioner(nep, 3*7, λ0)
        λ,v = resinv(ComplexF64,nep,logger=displaylevel,λ=λ0,v=v0,
                     errmeasure=myerrmeasure,tol=1e-12,
                     linsolvercreator=GalleryWaveguide.WEPLinSolverCreator(solver_type=:gmres,kwargs=((:Pl,precond),(:tol,1e-7)))
                     );
        @test  norm(compute_Mlincomb(nep,λ,v))/norm(v)  < 1e-10
    end

    @bench @testset "NEP solvers" begin
        λ,v = quasinewton(ComplexF64,nep,logger=displaylevel,λ=λ0,v=v0,
                          errmeasure=myerrmeasure,tol=1e-12,
                          linsolvercreator=GalleryWaveguide.WEPLinSolverCreator(solver_type=:factorized)
                          );
        @test  norm(compute_Mlincomb(nep,λ,v))/norm(v)  < 1e-10

        nev=3
        λ,v = iar(ComplexF64,nep,σ=λ0, logger=displaylevel,neigs=nev,maxit=100,v=v0,
                  tol=1e-8, linsolvercreator=GalleryWaveguide.WEPLinSolverCreator(solver_type=:factorized)
                  );
        @test minimum(abs.(λref .- λ)) < 1e-10
    end

end


text:
# Atomic Functions (B.12)

#
# Low-level intrinsics
#

# TODO:
# - scoped atomics: _system and _block versions (see CUDA programming guide, sm_60+)
#   https://github.com/Microsoft/clang/blob/86d4513d3e0daa4d5a29b0b1de7c854ca15f9fe5/test/CodeGen/builtins-nvptx.c#L293

## LLVM

# all atomic operations have acquire and/or release semantics,
# depending on whether they load or store values (mimics Base)
const atomic_acquire = LLVM.API.LLVMAtomicOrderingAcquire
const atomic_release = LLVM.API.LLVMAtomicOrderingRelease
const atomic_acquire_release = LLVM.API.LLVMAtomicOrderingAcquireRelease

# common arithmetic operations on integers using LLVM instructions
#
# > 8.6.6. atomicrmw Instruction
# >
# > nand is not supported. The other keywords are supported for i32 and i64 types, with the
# > following restrictions.
# >
# > - The pointer must be either a global pointer, a shared pointer, or a generic pointer
# >   that points to either the global address space or the shared address space.
@generated function llvm_atomic_op(::Val{binop}, ptr::LLVMPtr{T,A}, val::T) where {binop, T, A}
    Context() do ctx
        T_val = convert(LLVMType, T; ctx)
        T_ptr = convert(LLVMType, ptr; ctx)

        T_typed_ptr = LLVM.PointerType(T_val, A)

        llvm_f, _ = create_function(T_val, [T_ptr, T_val])

        Builder(ctx) do builder
            entry = BasicBlock(llvm_f, "entry"; ctx)
            position!(builder, entry)

            typed_ptr = bitcast!(builder, parameters(llvm_f)[1], T_typed_ptr)

            rv = atomic_rmw!(builder, binop,
                            typed_ptr, parameters(llvm_f)[2],
                            atomic_acquire_release, #=single_threaded=# false)

            ret!(builder, rv)
        end

        call_function(llvm_f, T, Tuple{LLVMPtr{T,A}, T}, :ptr, :val)
    end
end

const binops = Dict(
    :xchg  => LLVM.API.LLVMAtomicRMWBinOpXchg,
    :add   => LLVM.API.LLVMAtomicRMWBinOpAdd,
    :sub   => LLVM.API.LLVMAtomicRMWBinOpSub,
    :and   => LLVM.API.LLVMAtomicRMWBinOpAnd,
    :or    => LLVM.API.LLVMAtomicRMWBinOpOr,
    :xor   => LLVM.API.LLVMAtomicRMWBinOpXor,
    :max   => LLVM.API.LLVMAtomicRMWBinOpMax,
    :min   => LLVM.API.LLVMAtomicRMWBinOpMin,
    :umax  => LLVM.API.LLVMAtomicRMWBinOpUMax,
    :umin  => LLVM.API.LLVMAtomicRMWBinOpUMin,
    :fadd  => LLVM.API.LLVMAtomicRMWBinOpFAdd,
    :fsub  => LLVM.API.LLVMAtomicRMWBinOpFSub,
)

for T in (Int32, Int64, UInt32, UInt64)
    ops = [:xchg, :add, :sub, :and, :or, :xor, :max, :min]

    for op in ops
        # LLVM distinguishes signedness in the operation, not the integer type.
        rmw = if T <: Unsigned && (op == :max || op == :min)
            Symbol("u$op")
        else
            Symbol("$op")
        end

        fn = Symbol("atomic_$(op)!")
        @eval @inline $fn(ptr::Union{LLVMPtr{$T,AS.Generic},
                                     LLVMPtr{$T,AS.Global},
                                     LLVMPtr{$T,AS.Shared}}, val::$T) =
            llvm_atomic_op($(Val(binops[rmw])), ptr, val)
    end
end

for T in (Float32, Float64)
    ops = [:add]

    for op in ops
        # LLVM has specific operations for floating point types.
        rmw = Symbol("f$op")

        fn = Symbol("atomic_$(op)!")
        # XXX: cannot select
        @eval @inline $fn(ptr::Union{LLVMPtr{$T,AS.Generic},
                                     LLVMPtr{$T,AS.Global},
                                     LLVMPtr{$T,AS.Shared}}, val::$T) =
           llvm_atomic_op($(Val(binops[rmw])), ptr, val)
    end

    # there's no specific NNVM intrinsic for fsub, resulting in a selection error.
    @eval @inline atomic_sub!(ptr::Union{LLVMPtr{$T,AS.Generic},
                                         LLVMPtr{$T,AS.Global},
                                         LLVMPtr{$T,AS.Shared}}, val::$T) =
        atomic_add!(ptr, -val)
end

@generated function llvm_atomic_cas(ptr::LLVMPtr{T,A}, cmp::T, val::T) where {T, A}
    Context() do ctx
        T_val = convert(LLVMType, T; ctx)
        T_ptr = convert(LLVMType, ptr,;ctx)

        T_typed_ptr = LLVM.PointerType(T_val, A)

        llvm_f, _ = create_function(T_val, [T_ptr, T_val, T_val])

        Builder(ctx) do builder
            entry = BasicBlock(llvm_f, "entry"; ctx)
            position!(builder, entry)

            typed_ptr = bitcast!(builder, parameters(llvm_f)[1], T_typed_ptr)

            res = atomic_cmpxchg!(builder, typed_ptr, parameters(llvm_f)[2],
                                parameters(llvm_f)[3], atomic_acquire_release, atomic_acquire,
                                #=single threaded=# false)

            rv = extract_value!(builder, res, 0)

            ret!(builder, rv)
        end

        call_function(llvm_f, T, Tuple{LLVMPtr{T,A}, T, T}, :ptr, :cmp, :val)
    end
end

for T in (Int32, Int64, UInt32, UInt64)
    @eval @inline atomic_cas!(ptr::LLVMPtr{$T}, cmp::$T, val::$T) =
        llvm_atomic_cas(ptr, cmp, val)
end

# NVPTX doesn't support cmpxchg with i16 yet
for A in (AS.Generic, AS.Global, AS.Shared), T in (Int16, UInt16)
    if A == AS.Global
        scope = ".global"
    elseif A == AS.Shared
        scope = ".shared"
    else
        scope = ""
    end

    intr = "atom$scope.cas.b16 \$0, [\$1], \$2, \$3;"
    @eval @inline atomic_cas!(ptr::LLVMPtr{$T,$A}, cmp::$T, val::$T) =
        @asmcall($intr, "=h,l,h,h", true, $T, Tuple{Core.LLVMPtr{$T,$A},$T,$T}, ptr, cmp, val)
end

## NVVM

# floating-point operations using NVVM intrinsics

for A in (AS.Generic, AS.Global, AS.Shared)
    # declare i32 @llvm.nvvm.atomic.load.inc.32.p0i32(i32* address, i32 val)
    # declare i32 @llvm.nvvm.atomic.load.inc.32.p1i32(i32 addrspace(1)* address, i32 val)
    # declare i32 @llvm.nvvm.atomic.load.inc.32.p3i32(i32 addrspace(3)* address, i32 val)
    #
    # declare i32 @llvm.nvvm.atomic.load.dec.32.p0i32(i32* address, i32 val)
    # declare i32 @llvm.nvvm.atomic.load.dec.32.p1i32(i32 addrspace(1)* address, i32 val)
    # declare i32 @llvm.nvvm.atomic.load.dec.32.p3i32(i32 addrspace(3)* address, i32 val)
    for T in (Int32,), op in (:inc, :dec)
        nb = sizeof(T)*8
        fn = Symbol("atomic_$(op)!")
        intr = "llvm.nvvm.atomic.load.$op.$nb.p$(convert(Int, A))i$nb"
        @eval @inline $fn(ptr::LLVMPtr{$T,$A}, val::$T) =
            @typed_ccall($intr, llvmcall, $T, (LLVMPtr{$T,$A}, $T), ptr, val)
    end
end

## PTX

# half-precision atomics using PTX instruction

for A in (AS.Generic, AS.Global, AS.Shared), T in (Float16,)
    if A == AS.Global
        scope = ".global"
    elseif A == AS.Shared
        scope = ".shared"
    else
        scope = ""
    end

    intr = "atom$scope.add.noftz.f16 \$0, [\$1], \$2;"
    @eval @inline atomic_add!(ptr::LLVMPtr{$T,$A}, val::$T) =
        @asmcall($intr, "=h,l,h", true, $T, Tuple{Core.LLVMPtr{$T,$A},$T}, ptr, val)
end

## Julia

# floating-point CAS via bitcasting

inttype(::Type{T}) where {T<:Integer} = T
inttype(::Type{Float16}) = Int16
inttype(::Type{Float32}) = Int32
inttype(::Type{Float64}) = Int64

for T in [Float16, Float32, Float64]
    @eval @inline function atomic_cas!(ptr::LLVMPtr{$T,A}, cmp::$T, new::$T) where {A}
        IT = inttype($T)
        cmp_i = reinterpret(IT, cmp)
        new_i = reinterpret(IT, new)
        old_i = atomic_cas!(reinterpret(LLVMPtr{IT,A}, ptr), cmp_i, new_i)
        return reinterpret($T, old_i)
    end
end

# generic atomic support using compare-and-swap

@inline function atomic_op!(ptr::LLVMPtr{T}, op::Function, val) where {T}
    old = Base.unsafe_load(ptr)
    while true
        cmp = old
        new = convert(T, op(old, val))
        old = atomic_cas!(ptr, cmp, new)
        (old == cmp) && return new
    end
end

## documentation

"""
    atomic_cas!(ptr::LLVMPtr{T}, cmp::T, val::T)

Reads the value `old` located at address `ptr` and compare with `cmp`. If `old` equals to
`cmp`, stores `val` at the same address. Otherwise, doesn't change the value `old`. These
operations are performed in one atomic transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32 and UInt64.
Additionally, on GPU hardware with compute capability 7.0+, values of type UInt16 are
supported.
"""
atomic_cas!

"""
    atomic_xchg!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr` and stores `val` at the same address. These
operations are performed in one atomic transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32 and UInt64.
"""
atomic_xchg!

"""
    atomic_add!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `old + val`, and stores the result
back to memory at the same address. These operations are performed in one atomic
transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32, UInt64, and Float32.
Additionally, on GPU hardware with compute capability 6.0+, values of type Float64 are
supported.
"""
atomic_add!

"""
    atomic_sub!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `old - val`, and stores the result
back to memory at the same address. These operations are performed in one atomic
transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32 and UInt64.
"""
atomic_sub!

"""
    atomic_and!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `old & val`, and stores the result
back to memory at the same address. These operations are performed in one atomic
transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32 and UInt64.
"""
atomic_and!

"""
    atomic_or!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `old | val`, and stores the result
back to memory at the same address. These operations are performed in one atomic
transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32 and UInt64.
"""
atomic_or!

"""
    atomic_xor!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `old ⊻ val`, and stores the result
back to memory at the same address. These operations are performed in one atomic
transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32 and UInt64.
"""
atomic_xor!

"""
    atomic_min!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `min(old, val)`, and stores the
result back to memory at the same address. These operations are performed in one atomic
transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32 and UInt64.
"""
atomic_min!

"""
    atomic_max!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `max(old, val)`, and stores the
result back to memory at the same address. These operations are performed in one atomic
transaction. The function returns `old`.

This operation is supported for values of type Int32, Int64, UInt32 and UInt64.
"""
atomic_max!

"""
    atomic_inc!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `((old >= val) ? 0 : (old+1))`, and
stores the result back to memory at the same address. These three operations are performed
in one atomic transaction. The function returns `old`.

This operation is only supported for values of type Int32.
"""
atomic_inc!

"""
    atomic_dec!(ptr::LLVMPtr{T}, val::T)

Reads the value `old` located at address `ptr`, computes `(((old == 0) | (old > val)) ? val
: (old-1) )`, and stores the result back to memory at the same address. These three
operations are performed in one atomic transaction. The function returns `old`.

This operation is only supported for values of type Int32.
"""
atomic_dec!

#
# High-level interface
#

# prototype of a high-level interface for performing atomic operations on arrays
#
# this design could be generalized by having atomic {field,array}{set,ref} accessors, as
# well as acquire/release operations to implement the fallback functionality where any
# operation can be applied atomically.

if VERSION <= v"1.7-"
export @atomic
end

const inplace_ops = Dict(
    :(+=)   => :(+),
    :(-=)   => :(-),
    :(*=)   => :(*),
    :(/=)   => :(/),
    :(\=)   => :(\),
    :(%=)   => :(%),
    :(^=)   => :(^),
    :(&=)   => :(&),
    :(|=)   => :(|),
    :(⊻=)   => :(⊻),
    :(>>>=) => :(>>>),
    :(>>=)  => :(>>),
    :(<<=)  => :(<<),
)

struct AtomicError <: Exception
    msg::AbstractString
end

Base.showerror(io::IO, err::AtomicError) =
    print(io, "AtomicError: ", err.msg)

"""
    @atomic a[I] = op(a[I], val)
    @atomic a[I] ...= val

Atomically perform a sequence of operations that loads an array element `a[I]`, performs the
operation `op` on that value and a second value `val`, and writes the result back to the
array. This sequence can be written out as a regular assignment, in which case the same
array element should be used in the left and right hand side of the assignment, or as an
in-place application of a known operator. In both cases, the array reference should be pure
and not induce any side-effects.

!!! warn
    This interface is experimental, and might change without warning.  Use the lower-level
    `atomic_...!` functions for a stable API, albeit one limited to natively-supported ops.
"""
macro atomic(ex)
    # decode assignment and call
    if ex.head == :(=)
        ref = ex.args[1]
        rhs = ex.args[2]
        Meta.isexpr(rhs, :call) || throw(AtomicError("right-hand side of an @atomic assignment should be a call"))
        op = rhs.args[1]
        if rhs.args[2] != ref
            throw(AtomicError("right-hand side of a non-inplace @atomic assignment should reference the left-hand side"))
        end
        val = rhs.args[3]
    elseif haskey(inplace_ops, ex.head)
        op = inplace_ops[ex.head]
        ref = ex.args[1]
        val = ex.args[2]
    else
        throw(AtomicError("unknown @atomic expression"))
    end

    # decode array expression
    Meta.isexpr(ref, :ref) || throw(AtomicError("@atomic should be applied to an array reference expression"))
    array = ref.args[1]
    indices = Expr(:tuple, ref.args[2:end]...)

    esc(quote
        $atomic_arrayset($array, $indices, $op, $val)
    end)
end

# FIXME: make this respect the indexing style
@inline atomic_arrayset(A::AbstractArray{T}, Is::Tuple, op::Function, val) where {T} =
    atomic_arrayset(A, Base._to_linear_index(A, Is...), op, convert(T, val))

# native atomics
for (op,impl,typ) in [(+,   atomic_add!, [UInt32,Int32,UInt64,Int64,Float32]),
                      (-,   atomic_sub!, [UInt32,Int32,UInt64,Int64,Float32]),
                      (&,   atomic_and!, [UInt32,Int32,UInt64,Int64]),
                      (|,   atomic_or!,  [UInt32,Int32,UInt64,Int64]),
                      (⊻,   atomic_xor!, [UInt32,Int32,UInt64,Int64]),
                      (max, atomic_max!, [UInt32,Int32,UInt64,Int64]),
                      (min, atomic_min!, [UInt32,Int32,UInt64,Int64])]
    @eval @inline atomic_arrayset(A::AbstractArray{T}, I::Integer, ::typeof($op),
                                  val::T) where {T<:Union{$(typ...)}} =
        $impl(pointer(A, I), val)
end

# native atomics that are not supported on all devices
@inline function atomic_arrayset(A::AbstractArray{T}, I::Integer, op::typeof(+),
                                 val::T) where {T <: Union{Float64}}
    ptr = pointer(A, I)
    if compute_capability() >= sv"6.0"
        atomic_add!(ptr, val)
    else
        atomic_op!(ptr, op, val)
    end
end

# fallback using compare-and-swap
@inline atomic_arrayset(A::AbstractArray{T}, I::Integer, op::Function, val) where {T} =
    atomic_op!(pointer(A, I), op, val)


text:
function get_current_ticker(client, config; use_api=false, ticker_dir="/home/felix/pinguu/wp_data/ticker/")
    if use_api
        ticker = client.get_ticker(symbol=config["symbol"])["lastPrice"]
    else
        ticker_data = JSON.parsefile(joinpath(ticker_dir, config["symbol"] * "_kline_1m_candle.json"));
        ticker = Pinguu.price2string(ticker_data[end][end], config["sell_precision"])
    end
    return ticker, parse(Float64, ticker)
end
export get_current_ticker


text:
"""
    split_state(o::Tuple, u::AbstractArray)

Split state vector into multiple views, one for each organ.

We do this recursively for type stability.
"""
split_state(o::Tuple, u::AbstractArray) = split_state(o, u, 0)
split_state(o::Tuple{O,Vararg}, u::AbstractArray, offset) where O = begin
    statedim = dims(flux(o[1]), X)
    v = view(parent(u), offset+1:offset+length(statedim))
    lv = DimensionalArray(v, (statedim,))
    (lv, split_state(tail(o), u, offset + length(statedim))...)
end
split_state(o::Tuple{}, u::AbstractArray, offset) = ()

unwrap(::Val{X}) where X = X
unwrap(::Type{Val{X}}) where X = X

zero_flux!(o) = flux(o) .= zero(eltype(flux(o)))

"""
    check_params(o)

Check DEB parameters to avoid breaking mass balance. Can be pased an organ or
tuple of organs.
"""
check_params(organs::Tuple) = map(check_params, organs)
check_params(o::Organ) = begin
    y_V_E(o) <= n_N_V(o) / n_N_E(o) || error("y_V_E too high for these valuse of n_N_V and n_N_E ", (y_V_E(o), n_N_V(o), n_N_E(o) ))
    y_E_C(o) <= 1 || error("y_E_C must be less than or equal to 1: ", y_E_C(o))
    # 1 <= y_E_N(o) || error("y_E_N must be less than or equal to 1: ", y_E_N(o))
    # 2n_N_E(o) <= n_N_C(o) * y_E_C(o) + n_N_N(o) * y_E_N(o) || error("y_E_C or y_E_N too high for N conservation ",
    # (2n_N_E(o), n_N_C(o), y_E_C(o), n_N_N(o), y_E_N(o)))
    2n_N_E(o) <= y_E_C(o) + y_E_N(o) || error("y_E_C or y_E_N too high for N conservation ",
                                              (n_N_E(o), y_E_C(o), y_E_N(o)))
    check_params(production_pars(o), o)

    # These will be required if structures can have different reserve N ratios
    # y_E_ET(o) < n_N_E/n_N_E
    # y_E_ECT(o) <n_N_C/n_N_C
    # y_N_NT(o) < n_N_N/n_N_N
end
check_params(p::Nothing, o::Organ) = nothing
check_params(p::Production, o::Organ) = begin
    p.y_P_V <= p.n_N_P/n_N_V(o) || error("y_P_V too high for N conservation ", (p.y_P_V, p.n_N_P, n_N_V(o)))
    p.j_P_mai <= j_E_mai(o) || error("j_P_mai must be lower than j_E_mai ", (p.j_P_mai, j_E_mai(o)))
end

using Base: tail

offset_apply(f, a::AbstractArray, o::Tuple{O,Vararg}, offset::Int, args...) where O = begin
    offset = f(a, o[1], offset, args...)
    offset_apply(f, a, tail(o), offset::Int, args...)
    nothing
end
offset_apply(f, a::AbstractArray, o::Tuple{}, offset::Int, args...) = nothing


text:
"""
Submodule of `SirenSeq`.
Used for writing MIDI files.
"""
module Midi

export

## from this midi.jl

MidiTrack, writeMidiFile, tim, timm,

## from chanmess.jl

progSel!, bankSel!, noteOn!, noteOff!, pitchWheel!, chanAfter!, polyAfter!, control14!, control7!,

## from sysmess.jl

timeSigSet!, tempoSet!, trackEnd!, trackMarker!

abstract MidiMes
abstract ChanMes <: MidiMes
abstract SysMes <: MidiMes

"a collection of midi messages in a track"
type MidiTrack
	ms::Vector{MidiMes}		# set of midi messages, order in `ms` is irrelevant
	tpq::Int				# ticks per quarter note
	ff::Int					# current track head offset in ticks

	function MidiTrack(tpq::Int)
		@assert tpq > 0
		new(Any[],tpq,0)
	end
end

"""
	tim(mt::MidiTrack, v::Rational{Int}) -> Int

converts note length `v` to midi ticks for `mt`
"""
function tim(mt::MidiTrack, v::Rational{Int})
	x = v*mt.tpq*4 ;  @assert x >= 0
	round(Int,x)
end

"""
	timm(mt::MidiTrack, v::Rational{Int}) -> Int

converts note length `v` to midi ticks for `mt` and adds offset of `mt`
"""
function timm(mt::MidiTrack, v::Rational{Int})
	x = v*mt.tpq*4 + mt.ff ;  @assert x >= 0
	round(Int,x)
end

include("inout.jl")
include("sysmess.jl")
include("chanmess.jl")

## if events occur on the same tick in the track
## these numbers are used to determine thier order

precValMidiMes(m::NoteOff) = 0
precValMidiMes(m::TrackMarker) = 1
precValMidiMes(m::TimeSigSet) = 2
precValMidiMes(m::TempoSet) = 3
precValMidiMes(m::BankSel) = 4
precValMidiMes(m::ProgSel) = 5
precValMidiMes(m::Control14) = 6
precValMidiMes(m::Control7) = 7
precValMidiMes(m::ChanAfter) = 8
precValMidiMes(m::NoteOn) = 9
precValMidiMes(m::PolyAfter) = 10
precValMidiMes(m::PitchWheel) = 11
precValMidiMes(m::TrackEnd) = 12

compMidiEvByType(m1::MidiMes, m2::MidiMes) = precValMidiMes(m1) < precValMidiMes(m2)

function compMidiMes(m1::MidiMes, m2::MidiMes)
	m1.time < m2.time && return true
	m1.time > m2.time && return false
	compMidiEvByType(m1,m2)
end

## used for verbose mode of `writeMidiFile`

function midiMesDesc(m::MidiMes)
	"$(typeof(m))"
end

function midiMesDesc(m::NoteOn)
	"$NoteOn    $(m.pitch)"
end

function midiMesDesc(m::NoteOff)
	"$NoteOff   $(m.pitch)"
end

function midiMesDesc(m::TrackMarker)
	"$TrackMarker   $(m.mes)"
end

function midiMessToBytes(mt::MidiTrack, verbose::Bool)
	ms::Vector{Any} = sort(mt.ms,lt=compMidiMes)
	if verbose
		println()
		for m::MidiMes in ms
			print("$(lpad(m.time,6))    $(lpad(round(10*m.time/(mt.tpq*4))/10,4))    $(midiMesDesc(m))\n")
		end
		println()
	end
	bs = UInt8[]
	tm = 0
	for m::MidiMes in ms
		@assert typeof(m) != TrackEnd
		append!(bs,bytesev(m,tm))
		tm = m.time
	end
	tm = max(tm,mt.ff)
	append!(bs,bytesev(TrackEnd(mt.ff+1),mt.ff))
	bs
end

"""
	writeMidiFile(out::IOStream, mt::MidiTrack, [verbose::Bool])

Writes `mt` as a midi file to `out`. If `verbose` is true, it will write a description of all events to standard output
"""
function writeMidiFile(out::IOStream, mt::MidiTrack, verbose::Bool)
	bs = midiMessToBytes(mt,verbose)
	writeFileHeaderT0(out,mt.tpq)
	writeTrackHeader(out,length(bs))
	write(out,bs)
end

end


text:
# Automatically generated using Clang.jl

const LIBSSH2_H = 1
const LIBSSH2_COPYRIGHT = "2004-2019 The libssh2 project and its contributors."
const LIBSSH2_VERSION = "1.9.0"
const LIBSSH2_VERSION_MAJOR = 1
const LIBSSH2_VERSION_MINOR = 9
const LIBSSH2_VERSION_PATCH = 0
const LIBSSH2_VERSION_NUM = 0x00010900
const LIBSSH2_TIMESTAMP = "Thu Jun 20 06:19:26 UTC 2019"
const LIBSSH2_INVALID_SOCKET = -1
const LIBSSH2_STRUCT_STAT_SIZE_FORMAT = "%zd"
const LIBSSH2_SSH_BANNER = "SSH-2.0-libssh2_"
const LIBSSH2_SSH_DEFAULT_BANNER = LIBSSH2_SSH_BANNER
const LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF = LIBSSH2_SSH_DEFAULT_BANNER
const LIBSSH2_DH_GEX_MINGROUP = 1024
const LIBSSH2_DH_GEX_OPTGROUP = 1536
const LIBSSH2_DH_GEX_MAXGROUP = 2048
const LIBSSH2_TERM_WIDTH = 80
const LIBSSH2_TERM_HEIGHT = 24
const LIBSSH2_TERM_WIDTH_PX = 0
const LIBSSH2_TERM_HEIGHT_PX = 0
const LIBSSH2_SOCKET_POLL_UDELAY = 250000
const LIBSSH2_SOCKET_POLL_MAXLOOPS = 120
const LIBSSH2_PACKET_MAXCOMP = 32000
const LIBSSH2_PACKET_MAXDECOMP = 40000
const LIBSSH2_PACKET_MAXPAYLOAD = 40000

# Skipping MacroDefinition: LIBSSH2_ALLOC_FUNC ( name ) void * name ( size_t count , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_REALLOC_FUNC ( name ) void * name ( void * ptr , size_t count , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_FREE_FUNC ( name ) void name ( void * ptr , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC ( name ) int name ( LIBSSH2_SESSION * session , unsigned char * * sig , size_t * sig_len , const unsigned char * data , size_t data_len , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC ( name_ ) void name_ ( const char * name , int name_len , const char * instruction , int instruction_len , int num_prompts , const LIBSSH2_USERAUTH_KBDINT_PROMPT * prompts , LIBSSH2_USERAUTH_KBDINT_RESPONSE * responses , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_IGNORE_FUNC ( name ) void name ( LIBSSH2_SESSION * session , const char * message , int message_len , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_DEBUG_FUNC ( name ) void name ( LIBSSH2_SESSION * session , int always_display , const char * message , int message_len , const char * language , int language_len , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_DISCONNECT_FUNC ( name ) void name ( LIBSSH2_SESSION * session , int reason , const char * message , int message_len , const char * language , int language_len , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_PASSWD_CHANGEREQ_FUNC ( name ) void name ( LIBSSH2_SESSION * session , char * * newpw , int * newpw_len , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_MACERROR_FUNC ( name ) int name ( LIBSSH2_SESSION * session , const char * packet , int packet_len , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_X11_OPEN_FUNC ( name ) void name ( LIBSSH2_SESSION * session , LIBSSH2_CHANNEL * channel , const char * shost , int sport , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_CHANNEL_CLOSE_FUNC ( name ) void name ( LIBSSH2_SESSION * session , void * * session_abstract , LIBSSH2_CHANNEL * channel , void * * channel_abstract )
# Skipping MacroDefinition: LIBSSH2_RECV_FUNC ( name ) ssize_t name ( libssh2_socket_t socket , void * buffer , size_t length , int flags , void * * abstract )
# Skipping MacroDefinition: LIBSSH2_SEND_FUNC ( name ) ssize_t name ( libssh2_socket_t socket , const void * buffer , size_t length , int flags , void * * abstract )

const LIBSSH2_CALLBACK_IGNORE = 0
const LIBSSH2_CALLBACK_DEBUG = 1
const LIBSSH2_CALLBACK_DISCONNECT = 2
const LIBSSH2_CALLBACK_MACERROR = 3
const LIBSSH2_CALLBACK_X11 = 4
const LIBSSH2_CALLBACK_SEND = 5
const LIBSSH2_CALLBACK_RECV = 6
const LIBSSH2_METHOD_KEX = 0
const LIBSSH2_METHOD_HOSTKEY = 1
const LIBSSH2_METHOD_CRYPT_CS = 2
const LIBSSH2_METHOD_CRYPT_SC = 3
const LIBSSH2_METHOD_MAC_CS = 4
const LIBSSH2_METHOD_MAC_SC = 5
const LIBSSH2_METHOD_COMP_CS = 6
const LIBSSH2_METHOD_COMP_SC = 7
const LIBSSH2_METHOD_LANG_CS = 8
const LIBSSH2_METHOD_LANG_SC = 9
const LIBSSH2_FLAG_SIGPIPE = 1
const LIBSSH2_FLAG_COMPRESS = 2
const LIBSSH2_POLLFD_SOCKET = 1
const LIBSSH2_POLLFD_CHANNEL = 2
const LIBSSH2_POLLFD_LISTENER = 3
const LIBSSH2_POLLFD_POLLIN = 0x0001
const LIBSSH2_POLLFD_POLLPRI = 0x0002
const LIBSSH2_POLLFD_POLLEXT = 0x0002
const LIBSSH2_POLLFD_POLLOUT = 0x0004
const LIBSSH2_POLLFD_POLLERR = 0x0008
const LIBSSH2_POLLFD_POLLHUP = 0x0010
const LIBSSH2_POLLFD_SESSION_CLOSED = 0x0010
const LIBSSH2_POLLFD_POLLNVAL = 0x0020
const LIBSSH2_POLLFD_POLLEX = 0x0040
const LIBSSH2_POLLFD_CHANNEL_CLOSED = 0x0080
const LIBSSH2_POLLFD_LISTENER_CLOSED = 0x0080
const LIBSSH2_SESSION_BLOCK_INBOUND = 0x0001
const LIBSSH2_SESSION_BLOCK_OUTBOUND = 0x0002
const LIBSSH2_HOSTKEY_HASH_MD5 = 1
const LIBSSH2_HOSTKEY_HASH_SHA1 = 2
const LIBSSH2_HOSTKEY_HASH_SHA256 = 3
const LIBSSH2_HOSTKEY_TYPE_UNKNOWN = 0
const LIBSSH2_HOSTKEY_TYPE_RSA = 1
const LIBSSH2_HOSTKEY_TYPE_DSS = 2
const LIBSSH2_HOSTKEY_TYPE_ECDSA_256 = 3
const LIBSSH2_HOSTKEY_TYPE_ECDSA_384 = 4
const LIBSSH2_HOSTKEY_TYPE_ECDSA_521 = 5
const LIBSSH2_HOSTKEY_TYPE_ED25519 = 6
const SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
const SSH_DISCONNECT_PROTOCOL_ERROR = 2
const SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3
const SSH_DISCONNECT_RESERVED = 4
const SSH_DISCONNECT_MAC_ERROR = 5
const SSH_DISCONNECT_COMPRESSION_ERROR = 6
const SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7
const SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
const SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
const SSH_DISCONNECT_CONNECTION_LOST = 10
const SSH_DISCONNECT_BY_APPLICATION = 11
const SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12
const SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13
const SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
const SSH_DISCONNECT_ILLEGAL_USER_NAME = 15
const LIBSSH2_ERROR_NONE = 0
const LIBSSH2_ERROR_SOCKET_NONE = -1
const LIBSSH2_ERROR_BANNER_RECV = -2
const LIBSSH2_ERROR_BANNER_SEND = -3
const LIBSSH2_ERROR_INVALID_MAC = -4
const LIBSSH2_ERROR_KEX_FAILURE = -5
const LIBSSH2_ERROR_ALLOC = -6
const LIBSSH2_ERROR_SOCKET_SEND = -7
const LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE = -8
const LIBSSH2_ERROR_TIMEOUT = -9
const LIBSSH2_ERROR_HOSTKEY_INIT = -10
const LIBSSH2_ERROR_HOSTKEY_SIGN = -11
const LIBSSH2_ERROR_DECRYPT = -12
const LIBSSH2_ERROR_SOCKET_DISCONNECT = -13
const LIBSSH2_ERROR_PROTO = -14
const LIBSSH2_ERROR_PASSWORD_EXPIRED = -15
const LIBSSH2_ERROR_FILE = -16
const LIBSSH2_ERROR_METHOD_NONE = -17
const LIBSSH2_ERROR_AUTHENTICATION_FAILED = -18
const LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED = LIBSSH2_ERROR_AUTHENTICATION_FAILED
const LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED = -19
const LIBSSH2_ERROR_CHANNEL_OUTOFORDER = -20
const LIBSSH2_ERROR_CHANNEL_FAILURE = -21
const LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED = -22
const LIBSSH2_ERROR_CHANNEL_UNKNOWN = -23
const LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED = -24
const LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED = -25
const LIBSSH2_ERROR_CHANNEL_CLOSED = -26
const LIBSSH2_ERROR_CHANNEL_EOF_SENT = -27
const LIBSSH2_ERROR_SCP_PROTOCOL = -28
const LIBSSH2_ERROR_ZLIB = -29
const LIBSSH2_ERROR_SOCKET_TIMEOUT = -30
const LIBSSH2_ERROR_SFTP_PROTOCOL = -31
const LIBSSH2_ERROR_REQUEST_DENIED = -32
const LIBSSH2_ERROR_METHOD_NOT_SUPPORTED = -33
const LIBSSH2_ERROR_INVAL = -34
const LIBSSH2_ERROR_INVALID_POLL_TYPE = -35
const LIBSSH2_ERROR_PUBLICKEY_PROTOCOL = -36
const LIBSSH2_ERROR_EAGAIN = -37
const LIBSSH2_ERROR_BUFFER_TOO_SMALL = -38
const LIBSSH2_ERROR_BAD_USE = -39
const LIBSSH2_ERROR_COMPRESS = -40
const LIBSSH2_ERROR_OUT_OF_BOUNDARY = -41
const LIBSSH2_ERROR_AGENT_PROTOCOL = -42
const LIBSSH2_ERROR_SOCKET_RECV = -43
const LIBSSH2_ERROR_ENCRYPT = -44
const LIBSSH2_ERROR_BAD_SOCKET = -45
const LIBSSH2_ERROR_KNOWN_HOSTS = -46
const LIBSSH2_ERROR_CHANNEL_WINDOW_FULL = -47
const LIBSSH2_ERROR_KEYFILE_AUTH_FAILED = -48
const LIBSSH2_ERROR_BANNER_NONE = LIBSSH2_ERROR_BANNER_RECV
const LIBSSH2_INIT_NO_CRYPTO = 0x0001

# Skipping MacroDefinition: libssh2_session_init ( ) libssh2_session_init_ex ( NULL , NULL , NULL , NULL )
# Skipping MacroDefinition: libssh2_session_disconnect ( session , description ) libssh2_session_disconnect_ex ( ( session ) , SSH_DISCONNECT_BY_APPLICATION , ( description ) , "" )
# Skipping MacroDefinition: libssh2_userauth_password ( session , username , password ) libssh2_userauth_password_ex ( ( session ) , ( username ) , ( unsigned int ) strlen ( username ) , ( password ) , ( unsigned int ) strlen ( password ) , NULL )
# Skipping MacroDefinition: libssh2_userauth_publickey_fromfile ( session , username , publickey , privatekey , passphrase ) libssh2_userauth_publickey_fromfile_ex ( ( session ) , ( username ) , ( unsigned int ) strlen ( username ) , ( publickey ) , ( privatekey ) , ( passphrase ) )
# Skipping MacroDefinition: libssh2_userauth_hostbased_fromfile ( session , username , publickey , privatekey , passphrase , hostname ) libssh2_userauth_hostbased_fromfile_ex ( ( session ) , ( username ) , ( unsigned int ) strlen ( username ) , ( publickey ) , ( privatekey ) , ( passphrase ) , ( hostname ) , ( unsigned int ) strlen ( hostname ) , ( username ) , ( unsigned int ) strlen ( username ) )
# Skipping MacroDefinition: libssh2_userauth_keyboard_interactive ( session , username , response_callback ) libssh2_userauth_keyboard_interactive_ex ( ( session ) , ( username ) , ( unsigned int ) strlen ( username ) , ( response_callback ) )

const LIBSSH2_CHANNEL_WINDOW_DEFAULT = 2 * 1024 * 1024
const LIBSSH2_CHANNEL_PACKET_DEFAULT = 32768
const LIBSSH2_CHANNEL_MINADJUST = 1024
const LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL = 0
const LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE = 1
const LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE = 2
const SSH_EXTENDED_DATA_STDERR = 1
const LIBSSH2CHANNEL_EAGAIN = LIBSSH2_ERROR_EAGAIN

# Skipping MacroDefinition: libssh2_channel_open_session ( session ) libssh2_channel_open_ex ( ( session ) , "session" , sizeof ( "session" ) - 1 , LIBSSH2_CHANNEL_WINDOW_DEFAULT , LIBSSH2_CHANNEL_PACKET_DEFAULT , NULL , 0 )
# Skipping MacroDefinition: libssh2_channel_direct_tcpip ( session , host , port ) libssh2_channel_direct_tcpip_ex ( ( session ) , ( host ) , ( port ) , "127.0.0.1" , 22 )
# Skipping MacroDefinition: libssh2_channel_forward_listen ( session , port ) libssh2_channel_forward_listen_ex ( ( session ) , NULL , ( port ) , NULL , 16 )
# Skipping MacroDefinition: libssh2_channel_setenv ( channel , varname , value ) libssh2_channel_setenv_ex ( ( channel ) , ( varname ) , ( unsigned int ) strlen ( varname ) , ( value ) , ( unsigned int ) strlen ( value ) )
# Skipping MacroDefinition: libssh2_channel_request_pty ( channel , term ) libssh2_channel_request_pty_ex ( ( channel ) , ( term ) , ( unsigned int ) strlen ( term ) , NULL , 0 , LIBSSH2_TERM_WIDTH , LIBSSH2_TERM_HEIGHT , LIBSSH2_TERM_WIDTH_PX , LIBSSH2_TERM_HEIGHT_PX )
# Skipping MacroDefinition: libssh2_channel_request_pty_size ( channel , width , height ) libssh2_channel_request_pty_size_ex ( ( channel ) , ( width ) , ( height ) , 0 , 0 )
# Skipping MacroDefinition: libssh2_channel_x11_req ( channel , screen_number ) libssh2_channel_x11_req_ex ( ( channel ) , 0 , NULL , NULL , ( screen_number ) )
# Skipping MacroDefinition: libssh2_channel_shell ( channel ) libssh2_channel_process_startup ( ( channel ) , "shell" , sizeof ( "shell" ) - 1 , NULL , 0 )
# Skipping MacroDefinition: libssh2_channel_exec ( channel , command ) libssh2_channel_process_startup ( ( channel ) , "exec" , sizeof ( "exec" ) - 1 , ( command ) , ( unsigned int ) strlen ( command ) )
# Skipping MacroDefinition: libssh2_channel_subsystem ( channel , subsystem ) libssh2_channel_process_startup ( ( channel ) , "subsystem" , sizeof ( "subsystem" ) - 1 , ( subsystem ) , ( unsigned int ) strlen ( subsystem ) )
# Skipping MacroDefinition: libssh2_channel_read ( channel , buf , buflen ) libssh2_channel_read_ex ( ( channel ) , 0 , ( buf ) , ( buflen ) )
# Skipping MacroDefinition: libssh2_channel_read_stderr ( channel , buf , buflen ) libssh2_channel_read_ex ( ( channel ) , SSH_EXTENDED_DATA_STDERR , ( buf ) , ( buflen ) )
# Skipping MacroDefinition: libssh2_channel_window_read ( channel ) libssh2_channel_window_read_ex ( ( channel ) , NULL , NULL )
# Skipping MacroDefinition: libssh2_channel_write ( channel , buf , buflen ) libssh2_channel_write_ex ( ( channel ) , 0 , ( buf ) , ( buflen ) )
# Skipping MacroDefinition: libssh2_channel_write_stderr ( channel , buf , buflen ) libssh2_channel_write_ex ( ( channel ) , SSH_EXTENDED_DATA_STDERR , ( buf ) , ( buflen ) )
# Skipping MacroDefinition: libssh2_channel_window_write ( channel ) libssh2_channel_window_write_ex ( ( channel ) , NULL )
# Skipping MacroDefinition: libssh2_channel_ignore_extended_data ( channel , ignore ) libssh2_channel_handle_extended_data ( ( channel ) , ( ignore ) ? LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE : LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL )

const LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA = -1
const LIBSSH2_CHANNEL_FLUSH_ALL = -2

# Skipping MacroDefinition: libssh2_channel_flush ( channel ) libssh2_channel_flush_ex ( ( channel ) , 0 )
# Skipping MacroDefinition: libssh2_channel_flush_stderr ( channel ) libssh2_channel_flush_ex ( ( channel ) , SSH_EXTENDED_DATA_STDERR )
# Skipping MacroDefinition: libssh2_scp_send ( session , path , mode , size ) libssh2_scp_send_ex ( ( session ) , ( path ) , ( mode ) , ( size ) , 0 , 0 )

const HAVE_LIBSSH2_KNOWNHOST_API = 0x00010101
const HAVE_LIBSSH2_VERSION_API = 0x00010100
const LIBSSH2_KNOWNHOST_TYPE_MASK = Float32(0x0fff)
const LIBSSH2_KNOWNHOST_TYPE_PLAIN = 1
const LIBSSH2_KNOWNHOST_TYPE_SHA1 = 2
const LIBSSH2_KNOWNHOST_TYPE_CUSTOM = 3
const LIBSSH2_KNOWNHOST_KEYENC_MASK = 3 << 16
const LIBSSH2_KNOWNHOST_KEYENC_RAW = 1 << 16
const LIBSSH2_KNOWNHOST_KEYENC_BASE64 = 2 << 16
const LIBSSH2_KNOWNHOST_KEY_MASK = 15 << 18
const LIBSSH2_KNOWNHOST_KEY_SHIFT = 18
const LIBSSH2_KNOWNHOST_KEY_RSA1 = 1 << 18
const LIBSSH2_KNOWNHOST_KEY_SSHRSA = 2 << 18
const LIBSSH2_KNOWNHOST_KEY_SSHDSS = 3 << 18
const LIBSSH2_KNOWNHOST_KEY_ECDSA_256 = 4 << 18
const LIBSSH2_KNOWNHOST_KEY_ECDSA_384 = 5 << 18
const LIBSSH2_KNOWNHOST_KEY_ECDSA_521 = 6 << 18
const LIBSSH2_KNOWNHOST_KEY_ED25519 = 7 << 18
const LIBSSH2_KNOWNHOST_KEY_UNKNOWN = 15 << 18
const LIBSSH2_KNOWNHOST_CHECK_MATCH = 0
const LIBSSH2_KNOWNHOST_CHECK_MISMATCH = 1
const LIBSSH2_KNOWNHOST_CHECK_NOTFOUND = 2
const LIBSSH2_KNOWNHOST_CHECK_FAILURE = 3
const LIBSSH2_KNOWNHOST_FILE_OPENSSH = 1
const HAVE_LIBSSH2_AGENT_API = 0x00010202
const LIBSSH2_TRACE_TRANS = 1 << 1
const LIBSSH2_TRACE_KEX = 1 << 2
const LIBSSH2_TRACE_AUTH = 1 << 3
const LIBSSH2_TRACE_CONN = 1 << 4
const LIBSSH2_TRACE_SCP = 1 << 5
const LIBSSH2_TRACE_SFTP = 1 << 6
const LIBSSH2_TRACE_ERROR = 1 << 7
const LIBSSH2_TRACE_PUBLICKEY = 1 << 8
const LIBSSH2_TRACE_SOCKET = 1 << 9
const libssh2_uint64_t = Culonglong
const libssh2_int64_t = Clonglong
#const libssh2_socket_t = Cint
const stat = Cvoid
const libssh2_struct_stat = stat
const libssh2_struct_stat_size = Cint

struct _LIBSSH2_USERAUTH_KBDINT_PROMPT
    text::Cstring
    length::UInt32
    echo::Cuchar
end

const LIBSSH2_USERAUTH_KBDINT_PROMPT = _LIBSSH2_USERAUTH_KBDINT_PROMPT

struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE
    text::Cstring
    length::UInt32
end

const LIBSSH2_USERAUTH_KBDINT_RESPONSE = _LIBSSH2_USERAUTH_KBDINT_RESPONSE
const _LIBSSH2_SESSION = Cvoid
const LIBSSH2_SESSION = _LIBSSH2_SESSION
const _LIBSSH2_CHANNEL = Cvoid
const LIBSSH2_CHANNEL = _LIBSSH2_CHANNEL
const _LIBSSH2_LISTENER = Cvoid
const LIBSSH2_LISTENER = _LIBSSH2_LISTENER
const _LIBSSH2_KNOWNHOSTS = Cvoid
const LIBSSH2_KNOWNHOSTS = _LIBSSH2_KNOWNHOSTS
const _LIBSSH2_AGENT = Cvoid
const LIBSSH2_AGENT = _LIBSSH2_AGENT

struct ANONYMOUS1_fd
    channel::Ptr{LIBSSH2_CHANNEL}
end

struct _LIBSSH2_POLLFD
    type::Cuchar
    fd::ANONYMOUS1_fd
    events::Culong
    revents::Culong
end

const LIBSSH2_POLLFD = _LIBSSH2_POLLFD

struct libssh2_knownhost
    magic::UInt32
    node::Ptr{Cvoid}
    name::Cstring
    key::Cstring
    typemask::Cint
end

struct libssh2_agent_publickey
    magic::UInt32
    node::Ptr{Cvoid}
    blob::Ptr{Cuchar}
    blob_len::Csize_t
    comment::Cstring
end

const libssh2_trace_handler_func = Ptr{Cvoid}
const LIBSSH2_PUBLICKEY_H = 1

# Skipping MacroDefinition: libssh2_publickey_attribute ( name , value , mandatory ) { ( name ) , strlen ( name ) , ( value ) , strlen ( value ) , ( mandatory ) } ,
# Skipping MacroDefinition: libssh2_publickey_attribute_fast ( name , value , mandatory ) { ( name ) , sizeof ( name ) - 1 , ( value ) , sizeof ( value ) - 1 , ( mandatory ) } ,
# Skipping MacroDefinition: libssh2_publickey_add ( pkey , name , blob , blob_len , overwrite , num_attrs , attrs ) libssh2_publickey_add_ex ( ( pkey ) , ( name ) , strlen ( name ) , ( blob ) , ( blob_len ) , ( overwrite ) , ( num_attrs ) , ( attrs ) )
# Skipping MacroDefinition: libssh2_publickey_remove ( pkey , name , blob , blob_len ) libssh2_publickey_remove_ex ( ( pkey ) , ( name ) , strlen ( name ) , ( blob ) , ( blob_len ) )

const _LIBSSH2_PUBLICKEY = Cvoid
const LIBSSH2_PUBLICKEY = _LIBSSH2_PUBLICKEY

struct _libssh2_publickey_attribute
    name::Cstring
    name_len::Culong
    value::Cstring
    value_len::Culong
    mandatory::UInt8
end

const libssh2_publickey_attribute = _libssh2_publickey_attribute

struct _libssh2_publickey_list
    packet::Ptr{Cuchar}
    name::Ptr{Cuchar}
    name_len::Culong
    blob::Ptr{Cuchar}
    blob_len::Culong
    num_attrs::Culong
    attrs::Ptr{libssh2_publickey_attribute}
end

const libssh2_publickey_list = _libssh2_publickey_list
const LIBSSH2_SFTP_H = 1
const LIBSSH2_SFTP_VERSION = 3
const LIBSSH2_SFTP_OPENFILE = 0
const LIBSSH2_SFTP_OPENDIR = 1
const LIBSSH2_SFTP_RENAME_OVERWRITE = 0x00000001
const LIBSSH2_SFTP_RENAME_ATOMIC = 0x00000002
const LIBSSH2_SFTP_RENAME_NATIVE = 0x00000004
const LIBSSH2_SFTP_STAT = 0
const LIBSSH2_SFTP_LSTAT = 1
const LIBSSH2_SFTP_SETSTAT = 2
const LIBSSH2_SFTP_SYMLINK = 0
const LIBSSH2_SFTP_READLINK = 1
const LIBSSH2_SFTP_REALPATH = 2
const LIBSSH2_SFTP_DEFAULT_MODE = -1
const LIBSSH2_SFTP_ATTR_SIZE = 0x00000001
const LIBSSH2_SFTP_ATTR_UIDGID = 0x00000002
const LIBSSH2_SFTP_ATTR_PERMISSIONS = 0x00000004
const LIBSSH2_SFTP_ATTR_ACMODTIME = 0x00000008
const LIBSSH2_SFTP_ATTR_EXTENDED = 0x80000000
const LIBSSH2_SFTP_ST_RDONLY = 0x00000001
const LIBSSH2_SFTP_ST_NOSUID = 0x00000002
const LIBSSH2_SFTP_TYPE_REGULAR = 1
const LIBSSH2_SFTP_TYPE_DIRECTORY = 2
const LIBSSH2_SFTP_TYPE_SYMLINK = 3
const LIBSSH2_SFTP_TYPE_SPECIAL = 4
const LIBSSH2_SFTP_TYPE_UNKNOWN = 5
const LIBSSH2_SFTP_TYPE_SOCKET = 6
const LIBSSH2_SFTP_TYPE_CHAR_DEVICE = 7
const LIBSSH2_SFTP_TYPE_BLOCK_DEVICE = 8
const LIBSSH2_SFTP_TYPE_FIFO = 9
const LIBSSH2_SFTP_S_IFMT = 170000
const LIBSSH2_SFTP_S_IFIFO = 10000
const LIBSSH2_SFTP_S_IFCHR = 20000
const LIBSSH2_SFTP_S_IFDIR = 40000
const LIBSSH2_SFTP_S_IFBLK = 60000
const LIBSSH2_SFTP_S_IFREG = 100000
const LIBSSH2_SFTP_S_IFLNK = 120000
const LIBSSH2_SFTP_S_IFSOCK = 140000
const LIBSSH2_SFTP_S_IRWXU = 700
const LIBSSH2_SFTP_S_IRUSR = 400
const LIBSSH2_SFTP_S_IWUSR = 200
const LIBSSH2_SFTP_S_IXUSR = 100
const LIBSSH2_SFTP_S_IRWXG = 70
const LIBSSH2_SFTP_S_IRGRP = 40
const LIBSSH2_SFTP_S_IWGRP = 20
const LIBSSH2_SFTP_S_IXGRP = 10
const LIBSSH2_SFTP_S_IRWXO = 7
const LIBSSH2_SFTP_S_IROTH = 4
const LIBSSH2_SFTP_S_IWOTH = 2
const LIBSSH2_SFTP_S_IXOTH = 1

# Skipping MacroDefinition: LIBSSH2_SFTP_S_ISLNK ( m ) ( ( ( m ) & LIBSSH2_SFTP_S_IFMT ) == LIBSSH2_SFTP_S_IFLNK )
# Skipping MacroDefinition: LIBSSH2_SFTP_S_ISREG ( m ) ( ( ( m ) & LIBSSH2_SFTP_S_IFMT ) == LIBSSH2_SFTP_S_IFREG )
# Skipping MacroDefinition: LIBSSH2_SFTP_S_ISDIR ( m ) ( ( ( m ) & LIBSSH2_SFTP_S_IFMT ) == LIBSSH2_SFTP_S_IFDIR )
# Skipping MacroDefinition: LIBSSH2_SFTP_S_ISCHR ( m ) ( ( ( m ) & LIBSSH2_SFTP_S_IFMT ) == LIBSSH2_SFTP_S_IFCHR )
# Skipping MacroDefinition: LIBSSH2_SFTP_S_ISBLK ( m ) ( ( ( m ) & LIBSSH2_SFTP_S_IFMT ) == LIBSSH2_SFTP_S_IFBLK )
# Skipping MacroDefinition: LIBSSH2_SFTP_S_ISFIFO ( m ) ( ( ( m ) & LIBSSH2_SFTP_S_IFMT ) == LIBSSH2_SFTP_S_IFIFO )
# Skipping MacroDefinition: LIBSSH2_SFTP_S_ISSOCK ( m ) ( ( ( m ) & LIBSSH2_SFTP_S_IFMT ) == LIBSSH2_SFTP_S_IFSOCK )

const LIBSSH2_FXF_READ = 0x00000001
const LIBSSH2_FXF_WRITE = 0x00000002
const LIBSSH2_FXF_APPEND = 0x00000004
const LIBSSH2_FXF_CREAT = 0x00000008
const LIBSSH2_FXF_TRUNC = 0x00000010
const LIBSSH2_FXF_EXCL = 0x00000020
const LIBSSH2_FX_OK = 0
const LIBSSH2_FX_EOF = 1
const LIBSSH2_FX_NO_SUCH_FILE = 2
const LIBSSH2_FX_PERMISSION_DENIED = 3
const LIBSSH2_FX_FAILURE = 4
const LIBSSH2_FX_BAD_MESSAGE = 5
const LIBSSH2_FX_NO_CONNECTION = 6
const LIBSSH2_FX_CONNECTION_LOST = 7
const LIBSSH2_FX_OP_UNSUPPORTED = 8
const LIBSSH2_FX_INVALID_HANDLE = 9
const LIBSSH2_FX_NO_SUCH_PATH = 10
const LIBSSH2_FX_FILE_ALREADY_EXISTS = 11
const LIBSSH2_FX_WRITE_PROTECT = 12
const LIBSSH2_FX_NO_MEDIA = 13
const LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM = 14
const LIBSSH2_FX_QUOTA_EXCEEDED = 15
const LIBSSH2_FX_UNKNOWN_PRINCIPLE = 16
const LIBSSH2_FX_UNKNOWN_PRINCIPAL = 16
const LIBSSH2_FX_LOCK_CONFlICT = 17
const LIBSSH2_FX_LOCK_CONFLICT = 17
const LIBSSH2_FX_DIR_NOT_EMPTY = 18
const LIBSSH2_FX_NOT_A_DIRECTORY = 19
const LIBSSH2_FX_INVALID_FILENAME = 20
const LIBSSH2_FX_LINK_LOOP = 21
const LIBSSH2SFTP_EAGAIN = LIBSSH2_ERROR_EAGAIN

# Skipping MacroDefinition: libssh2_sftp_open ( sftp , filename , flags , mode ) libssh2_sftp_open_ex ( ( sftp ) , ( filename ) , strlen ( filename ) , ( flags ) , ( mode ) , LIBSSH2_SFTP_OPENFILE )
# Skipping MacroDefinition: libssh2_sftp_opendir ( sftp , path ) libssh2_sftp_open_ex ( ( sftp ) , ( path ) , strlen ( path ) , 0 , 0 , LIBSSH2_SFTP_OPENDIR )
# Skipping MacroDefinition: libssh2_sftp_readdir ( handle , buffer , buffer_maxlen , attrs ) libssh2_sftp_readdir_ex ( ( handle ) , ( buffer ) , ( buffer_maxlen ) , NULL , 0 , ( attrs ) )
# Skipping MacroDefinition: libssh2_sftp_close ( handle ) libssh2_sftp_close_handle ( handle )
# Skipping MacroDefinition: libssh2_sftp_closedir ( handle ) libssh2_sftp_close_handle ( handle )
# Skipping MacroDefinition: libssh2_sftp_rewind ( handle ) libssh2_sftp_seek64 ( ( handle ) , 0 )
# Skipping MacroDefinition: libssh2_sftp_fstat ( handle , attrs ) libssh2_sftp_fstat_ex ( ( handle ) , ( attrs ) , 0 )
# Skipping MacroDefinition: libssh2_sftp_fsetstat ( handle , attrs ) libssh2_sftp_fstat_ex ( ( handle ) , ( attrs ) , 1 )
# Skipping MacroDefinition: libssh2_sftp_rename ( sftp , sourcefile , destfile ) libssh2_sftp_rename_ex ( ( sftp ) , ( sourcefile ) , strlen ( sourcefile ) , ( destfile ) , strlen ( destfile ) , LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE )
# Skipping MacroDefinition: libssh2_sftp_unlink ( sftp , filename ) libssh2_sftp_unlink_ex ( ( sftp ) , ( filename ) , strlen ( filename ) )
# Skipping MacroDefinition: libssh2_sftp_mkdir ( sftp , path , mode ) libssh2_sftp_mkdir_ex ( ( sftp ) , ( path ) , strlen ( path ) , ( mode ) )
# Skipping MacroDefinition: libssh2_sftp_rmdir ( sftp , path ) libssh2_sftp_rmdir_ex ( ( sftp ) , ( path ) , strlen ( path ) )
# Skipping MacroDefinition: libssh2_sftp_stat ( sftp , path , attrs ) libssh2_sftp_stat_ex ( ( sftp ) , ( path ) , strlen ( path ) , LIBSSH2_SFTP_STAT , ( attrs ) )
# Skipping MacroDefinition: libssh2_sftp_lstat ( sftp , path , attrs ) libssh2_sftp_stat_ex ( ( sftp ) , ( path ) , strlen ( path ) , LIBSSH2_SFTP_LSTAT , ( attrs ) )
# Skipping MacroDefinition: libssh2_sftp_setstat ( sftp , path , attrs ) libssh2_sftp_stat_ex ( ( sftp ) , ( path ) , strlen ( path ) , LIBSSH2_SFTP_SETSTAT , ( attrs ) )
# Skipping MacroDefinition: libssh2_sftp_symlink ( sftp , orig , linkpath ) libssh2_sftp_symlink_ex ( ( sftp ) , ( orig ) , strlen ( orig ) , ( linkpath ) , strlen ( linkpath ) , LIBSSH2_SFTP_SYMLINK )
# Skipping MacroDefinition: libssh2_sftp_readlink ( sftp , path , target , maxlen ) libssh2_sftp_symlink_ex ( ( sftp ) , ( path ) , strlen ( path ) , ( target ) , ( maxlen ) , LIBSSH2_SFTP_READLINK )
# Skipping MacroDefinition: libssh2_sftp_realpath ( sftp , path , target , maxlen ) libssh2_sftp_symlink_ex ( ( sftp ) , ( path ) , strlen ( path ) , ( target ) , ( maxlen ) , LIBSSH2_SFTP_REALPATH )

const _LIBSSH2_SFTP = Cvoid
const LIBSSH2_SFTP = _LIBSSH2_SFTP
const _LIBSSH2_SFTP_HANDLE = Cvoid
const LIBSSH2_SFTP_HANDLE = _LIBSSH2_SFTP_HANDLE

struct _LIBSSH2_SFTP_ATTRIBUTES
    flags::Culong
    filesize::libssh2_uint64_t
    uid::Culong
    gid::Culong
    permissions::Culong
    atime::Culong
    mtime::Culong
end

const LIBSSH2_SFTP_ATTRIBUTES = _LIBSSH2_SFTP_ATTRIBUTES

struct _LIBSSH2_SFTP_STATVFS
    f_bsize::libssh2_uint64_t
    f_frsize::libssh2_uint64_t
    f_blocks::libssh2_uint64_t
    f_bfree::libssh2_uint64_t
    f_bavail::libssh2_uint64_t
    f_files::libssh2_uint64_t
    f_ffree::libssh2_uint64_t
    f_favail::libssh2_uint64_t
    f_fsid::libssh2_uint64_t
    f_flag::libssh2_uint64_t
    f_namemax::libssh2_uint64_t
end

const LIBSSH2_SFTP_STATVFS = _LIBSSH2_SFTP_STATVFS


text:
function _precompile_()
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
    @assert precompile(LLegend, (Scene, Node{Vector{Tuple{Optional{String}, Vector{LegendEntry}}}}))
    # @assert precompile(LLegend, (Scene, AbstractArray, Vector{String}))
    @assert precompile(LColorbar, (Scene,))
    @assert precompile(LAxis, (Scene,))
    # @assert precompile(Core.kwfunc(Type), (NamedTuple{(:title,), Tuple{String}}, Type{LAxis}, Scene))
    @assert precompile(LineAxis, (Scene,))
    @assert precompile(LMenu, (Scene,))
    @assert precompile(LButton, (Scene,))
    @assert precompile(LSlider, (Scene,))
    @assert precompile(LTextbox, (Scene,))

    @assert precompile(layoutscene, ())  # doesn't fully precompile
    @assert precompile(get_ticklabels, (AbstractPlotting.Automatic, AbstractPlotting.Automatic, Vector{Float64}))
end


text:
using BinDeps

@BinDeps.setup

@linux_only begin
    hdf5 = library_dependency("libhdf5", aliases = ["libhdf5", "libhdf5_serial", "libhdf5_openmpi", "libhdf5_mpich"])
    provides(AptGet, "hdf5-tools", hdf5)
    provides(Yum, "hdf5", hdf5)
end

@windows_only begin
    using WinRPM
    hdf5 = library_dependency("libhdf5")
    provides(WinRPM.RPM, "hdf5", hdf5, os = :Windows )
end

@osx_only begin
    using Homebrew
    hdf5 = library_dependency("libhdf5")
    provides(Homebrew.HB, "hdf5", hdf5, os = :Darwin )
end

@BinDeps.install [:libhdf5 => :libhdf5]