text:
#
# locks.jl --
#
# Mutexes, condition variables and read/write locks for Julia.
#
#------------------------------------------------------------------------------
#
# This file is part of InterProcessCommunication.jl released under the MIT
# "expat" license.
#
# Copyright (C) 2016-2019, Éric Thiébaut
# (https://github.com/emmt/InterProcessCommunication.jl).
#

# Abstract types to identify pointers to specific kind of C structures.
abstract type MutexData     end
abstract type ConditionData end
abstract type RWLockData    end

# Number of bytes for each C structures.
Base.sizeof(::Type{MutexData})     = _sizeof_pthread_mutex_t
Base.sizeof(::Type{ConditionData}) = _sizeof_pthread_cond_t
Base.sizeof(::Type{RWLockData})    = _sizeof_pthread_rwlock_t

"""
```julia
IPC.Mutex()
```

yields an initialized POSIX mutex.  The mutex is automatically unlocked and
associated ressources are automatically destroyed when the returned object is
garbage collected.

```julia
IPC.Mutex(buf, off=0; shared=false)
```

yields a new mutex object using buffer `buf` at offset `off` (in bytes) for its
storage.  There must be at least `sizeof(IPC.MutexData)` available bytes at
address `pointer(buf) + off`, these bytes must not be used for something else
and must remain accessible during the lifetime of the object.

Keyword `shared` can be set true to create a mutex that is shared between
processes; otherwise, the mutex is private that is it can only be used by
threads of the process which creates the mutex.  A shared mutex must be stored
in a part of the memory, like shared memory, that can be shared with other
processes.

See also: [`IPC.Condition`](@ref), [`IPC.RWLock`](@ref), [`lock`](@ref),
[`unlock`](@ref), [`trylock`](@ref).

"""
mutable struct Mutex{T}
    handle::Ptr{MutexData} # typed pointer to object data
    buffer::T              # object data
    locked::Bool
    function Mutex{T}(buf::T, off::Int; shared::Bool=false) where {T}
        off ≥ 0 || error("offset must be nonnegative")
        sizeof(buf) ≥ sizeof(MutexData) + off ||
            error("insufficient buffer size to store POSIX mutex")
        obj = new{T}(pointer(buf) + off, buf, false)
        attr = Vector{UInt8}(undef, _sizeof_pthread_mutexattr_t)
        code = ccall(:pthread_mutexattr_init, Cint, (Ptr{UInt8},), attr)
        code == 0 || throw_system_error("pthread_mutexattr_init", code)
        code = ccall(:pthread_mutexattr_setpshared, Cint, (Ptr{UInt8}, Cint), attr,
                     (shared ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE))
        code == 0 || throw_system_error("pthread_mutexattr_setpshared", code)
        code = ccall(:pthread_mutex_init, Cint,
                     (Ptr{MutexData}, Ptr{UInt8}), obj, attr)
        code == 0 || throw_system_error("pthread_mutex_init", code)
        code = ccall(:pthread_mutexattr_destroy, Cint, (Ptr{UInt8},), attr)
        code == 0 || (_destroy(obj);
                      throw_system_error("pthread_mutexattr_destroy", code))
        return finalizer(_destroy, obj)
    end
end

Mutex(buf::T, off::Integer = 0; kwds...) where {T} =
    Mutex{T}(buf, Int(off); kwds...)

Mutex(; kwds...) = Mutex(Vector{UInt8}(undef, sizeof(MutexData)); kwds...)

function _destroy(obj::Mutex)
    if (ptr = obj.handle) != C_NULL
        islocked(obj) && unlock(obj)
        obj.handle = C_NULL # to not free twice
        ccall(:pthread_mutex_destroy, Cint, (Ptr{MutexData},), ptr)
    end
    nothing
end

Base.islocked(obj::Mutex) = obj.locked

function Base.lock(obj::Mutex)
    islocked(obj) && error("mutex is already locked by owner")
    code = ccall(:pthread_mutex_lock, Cint, (Ptr{MutexData},), obj)
    code == 0 || throw_system_error("pthread_mutex_lock", code)
    obj.locked = true
    nothing
end

function Base.unlock(obj::Mutex)
    islocked(obj) || error("mutex is not locked by owner")
    code = ccall(:pthread_mutex_unlock, Cint, (Ptr{MutexData},), obj)
    code == 0 || throw_system_error("pthread_mutex_unlock", code)
    obj.locked = false
    nothing
end

function Base.trylock(obj::Mutex)
    if ! islocked(obj)
        code = ccall(:pthread_mutex_trylock, Cint, (Ptr{MutexData},), obj)
        if code == 0
            obj.locked = true
        elseif code == Libc.EBUSY
            return false
        else
            throw_system_error("pthread_mutex_trylock", code)
        end
    end
    return true
end

"""
```julia
IPC.Condition()
```

yields an initialized condition variable.  The ressources associated to the
condition variable are automatically destroyed when the returned object is
garbage collected.

```julia
IPC.Condition(buf, off=0; shared=false)
```

yields a new condition variable object using buffer `buf` at offset `off` (in
bytes) for its storage.  There must be at least `sizeof(IPC.ConditionData)`
available bytes at address `pointer(buf) + off`, these bytes must not be used
for something else and must remain accessible during the lifetime of the
object.

Keyword `shared` can be set true to create a condition variable that is shared
between processes; otherwise, the condition variable is private that is it can
only be used by threads of the process which creates the condition variable.  A
shared condition variable must be stored in a part of the memory, like shared
memory, that can be shared with other processes.

See also: [`IPC.Mutex`](@ref), [`IPC.RWLock`](@ref), [`signal`](@ref),
[`broadcast`](@ref), [`wait`](@ref), [`timedwait`](@ref).

"""
mutable struct Condition{T}
    handle::Ptr{ConditionData} # typed pointer to object data
    buffer::T                  # object data
    function Condition{T}(buf::T, off::Int; shared::Bool=false) where {T}
        off ≥ 0 || error("offset must be nonnegative")
        sizeof(buf) ≥ sizeof(ConditionData) + off ||
            error("insufficient buffer size to store condition variable")
        obj = new{T}(pointer(buf) + off, buf, false)
        attr = Vector{UInt8}(undef, _sizeof_pthread_condattr_t)
        code = ccall(:pthread_condattr_init, Cint, (Ptr{UInt8},), attr)
        code == 0 || throw_system_error("pthread_condattr_init", code)
        code = ccall(:pthread_condattr_setpshared, Cint, (Ptr{UInt8}, Cint), attr,
                     (shared ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE))
        code == 0 || throw_system_error("pthread_condattr_setpshared", code)
        code = ccall(:pthread_cond_init, Cint,
                     (Ptr{ConditionData}, Ptr{UInt8}), obj, attr)
        code == 0 || throw_system_error("pthread_cond_init", code)
        code = ccall(:pthread_condattr_destroy, Cint, (Ptr{UInt8},), attr)
        code == 0 || (_destroy(obj);
                      throw_system_error("pthread_condattr_destroy", code))
        return finalizer(_destroy, obj)
    end
end

Condition(buf::T, off::Integer = 0; kwds...) where {T} =
    Condition{T}(buf, Int(off); kwds...)

Condition(; kwds...) = Condition(Vector{UInt8}(undef, sizeof(ConditionData)); kwds...)

function _destroy(obj::Condition)
    if (ptr = obj.handle) != C_NULL
        obj.handle = C_NULL # to not free twice
        ccall(:pthread_cond_destroy, Cint, (Ptr{ConditionData},), ptr)
    end
    nothing
end

function signal(cond::Condition)
    code = ccall(:pthread_cond_signal, Cint, (Ptr{ConditionData},), cond)
    code == 0 || throw_system_error("pthread_cond_signal", code)
    nothing
end

function Base.broadcast(cond::Condition)
    code = ccall(:pthread_cond_broadcast, Cint, (Ptr{ConditionData},), cond)
    code == 0 || throw_system_error("pthread_cond_broadcast", code)
    nothing
end

function Base.wait(cond::Condition, mutex::Mutex)
    code = ccall(:pthread_cond_wait, Cint,
                 (Ptr{ConditionData}, Ptr{MutexData}), cond, mutex)
    code == 0 || throw_system_error("pthread_cond_wait", code)
    nothing
end

"""

```julia
timedwait(cond, mutex, lim) -> bool
```

waits for condition variable `cond` to be signaled using lock `mutex` for
synchronization but no longer than the time limit specified by `lim`.

Argument `lim` can be and instance of [``TimeSpec`](@ref) or [`TimeVal`](@ref)
to specify an absolute time limit since the Epoch, or a real to specify a limit
as a number of seconds relative to the current time.

The returned value is `true` if the condition is signalled before expiration of
the time limit, and `false` otherwise.

"""
function Base.timedwait(cond::Condition, mutex::Mutex, abstime::TimeSpec)
    code = ccall(:pthread_cond_timedwait, Cint,
                 (Ptr{ConditionData}, Ptr{MutexData}, Ptr{TimeSpec}),
                 cond, mutex, Ref(abstime))
    if code != 0
        code == Libc.ETIMEDOUT || throw_system_error("pthread_cond_timedwait", code)
        return false
    else
        return true
    end
end

Base.timedwait(cond::Condition, mutex::Mutex, abstime::Union{TimeVal,Libc.TimeVal}) =
    timedwait(cond, mutex, TimeSpec(abstime))

Base.timedwait(cond::Condition, mutex::Mutex, secs::Real) =
    timedwait(cond, mutex, now(TimeSpec) + secs)

"""
```julia
IPC.RWLock()
```

yields an initialized read/write lock object.  The lock is automatically
released and associated ressources are automatically destroyed when the
returned object is garbage collected.

```julia
IPC.RWLock(buf, off=0; shared=false)
```

yields a new read/write lock object using buffer `buf` at offset `off` (in
bytes) for its storage.  There must be at least `sizeof(IPC.RWLockData)`
available bytes at address `pointer(buf) + off`, these bytes must not be used
for something else and must remain accessible during the lifetime of the
object.

Keyword `shared` can be set true to create a read/write lock that is shared
between processes; otherwise, the lock is private that is it can only be used
by threads of the process which creates the lock.  A shared lock must be stored
in a part of the memory, like shared memory, that can be shared with other
processes.

See also: [`IPC.Mutex`](@ref), [`IPC.Condition`](@ref), [`lock`](@ref),
[`unlock`](@ref), [`trylock`](@ref).

"""
mutable struct RWLock{T}
    handle::Ptr{RWLockData} # typed pointer to object data
    buffer::T               # object data
    locked::Int             # 0 = unlocked, 1 = locked for reading, 2 = locked for writing
    function RWLock{T}(buf::T, off::Int; shared::Bool=false) where {T}
        off ≥ 0 || error("offset must be nonnegative")
        sizeof(buf) ≥ sizeof(RWLockData) + off ||
            error("insufficient buffer size to store read/write lock")
        obj = new{T}(pointer(buf) + off, buf, 0)
        attr = Vector{UInt8}(undef, _sizeof_pthread_rwlockattr_t)
        code = ccall(:pthread_rwlockattr_init, Cint, (Ptr{UInt8},), attr)
        code == 0 || throw_system_error("pthread_rwlockattr_init", code)
        code = ccall(:pthread_rwlockattr_setpshared, Cint, (Ptr{UInt8}, Cint), attr,
                     (shared ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE))
        code == 0 || throw_system_error("pthread_rwlockattr_setpshared", code)
        code = ccall(:pthread_rwlock_init, Cint,
                     (Ptr{RWLockData}, Ptr{UInt8}), obj, attr)
        code == 0 || throw_system_error("pthread_rwlock_init", code)
        code = ccall(:pthread_rwlockattr_destroy, Cint, (Ptr{UInt8},), attr)
        code == 0 || (_destroy(obj);
                      throw_system_error("pthread_rwlockattr_destroy", code))
        return finalizer(_destroy, obj)
    end
end

RWLock(buf::T, off::Integer = 0;  kwds...) where {T} =
    RWLock{T}(buf, Int(off); kwds...)

RWLock(; kwds...) = RWLock(Vector{UInt8}(undef, sizeof(RWLockData)); kwds...)

function _destroy(obj::RWLock)
    if (ptr = obj.handle) != C_NULL
        islocked(obj) && unlock(obj)
        obj.handle = C_NULL # to not free twice
        ccall(:pthread_rwlock_destroy, Cint, (Ptr{RWLockData},), ptr)
    end
    nothing
end

Base.islocked(obj::RWLock) = (obj.locked != 0)

"""

```julia
lock(obj, mode)
```

locks read/write lock object `obj` for reading if `mode` is `'r'`, for writing
if `mode` is `'w'`.

"""
function Base.lock(obj::RWLock, mode::Char)
    locked = (mode == 'r' ? 1 :
              mode == 'w' ? 2 : throw(ArgumentError("invalid mode")))
    islocked(obj) && error("r/w lock is already locked by owner")
    if mode == 'r'
        code = ccall(:pthread_rwlock_rdlock, Cint, (Ptr{RWLockData},), obj)
        code == 0 || throw_system_error("pthread_rwlock_rdlock", code)
    else
        code = ccall(:pthread_rwlock_wrlock, Cint, (Ptr{RWLockData},), obj)
        code == 0 || throw_system_error("pthread_rwlock_wrlock", code)
    end
    obj.locked = locked
    nothing
end

function Base.unlock(obj::RWLock)
    islocked(obj) || error("r/w lock is not locked by owner")
    code = ccall(:pthread_rwlock_unlock, Cint, (Ptr{RWLockData},), obj)
    code == 0 || throw_system_error("pthread_rwlock_unlock", code)
    obj.locked = 0
    nothing
end

"""

```julia
trylock(obj, mode) -> bool
```

attempts to lock read/write lock object `obj` for reading if `mode` is `'r'`,
for writing if `mode` is `'w'` and returns a boolean indicating whether `obj`
has been successfully locked for the given mode.

See Also: [`timedlock`](@ref).

"""
function Base.trylock(obj::RWLock, mode::Char)
    locked = (mode == 'r' ? 1 :
              mode == 'w' ? 2 : throw(ArgumentError("invalid mode")))
    if obj.locked == 0
        if mode == 'r'
            code = ccall(:pthread_rwlock_tryrdlock, Cint, (Ptr{RWLockData},), obj)
            if code != 0
                code == Libc.EBUSY ||
                    throw_system_error("pthread_rwlock_tryrdlock", code)
                return false
            end
        else
            code = ccall(:pthread_rwlock_trywrlock, Cint, (Ptr{RWLockData},), obj)
            if code != 0
                code == Libc.EBUSY ||
                    throw_system_error("pthread_rwlock_trywrlock", code)
                return false
            end
        end
    elseif obj.locked != locked
        if mode == 'r'
            error("r/w lock is already locked for writing by owner")
        else
            error("r/w lock is already locked for reading by owner")
        end
    end
    obj.locked = locked
    return true
end

"""

```julia
timedlock(rwlock, mode, lim) -> bool
```

locks read/write lock object `rwlock` for reading if `mode` is `'r'`, for
writing if `mode` is `'w'`, but waits no longer than the time limit specified
by `lim`.  The returned boolean value indicates whether the object could be
locked before the time limit.

Argument `lim` can be and instance of [``TimeSpec`](@ref) or [`TimeVal`](@ref)
to specify an absolute time limit since the Epoch, or a real to specify a limit
as a number of seconds relative to the current time.

See Also: [`trylock`](@ref).

"""
function timedlock(obj::RWLock, mode::Char, abstime::TimeSpec)
    locked = (mode == 'r' ? 1 :
              mode == 'w' ? 2 : throw(ArgumentError("invalid mode")))
    if obj.locked == 0
        if mode == 'r'
            code = ccall(:pthread_rwlock_timedrdlock, Cint,
                         (Ptr{RWLockData}, Ref{TimeSpec}), obj, Ref(abstime))
            if code != 0
                code == Libc.ETIMEDOUT ||
                    throw_system_error("pthread_rwlock_timedrdlock", code)
                return false
            end
        else
            code = ccall(:pthread_rwlock_timedwrlock, Cint,
                         (Ptr{RWLockData}, Ref{TimeSpec}), obj, Ref(abstime))
            if code != 0
                code == Libc.ETIMEDOUT ||
                    throw_system_error("pthread_rwlock_timedwrlock", code)
                return false
            end
        end
    elseif obj.locked != locked
        if mode == 'r'
            error("r/w lock is already locked for writing by owner")
        else
            error("r/w lock is already locked for reading by owner")
        end
    end
    obj.locked = locked
    return true
end

timedlock(obj::RWLock, mode::Char, abstime::Union{TimeVal,Libc.TimeVal}) =
    timedlock(obj, mode, TimeSpec(abstime))

timedlock(obj::RWLock, mode::Char, secs::Real) =
    timedlock(obj, mode, now(TimeSpec) + secs)

# The following are needed for ccall's.
Base.unsafe_convert(::Type{Ptr{MutexData}}, obj::Mutex) = obj.handle
Base.unsafe_convert(::Type{Ptr{ConditionData}}, obj::Condition) = obj.handle
Base.unsafe_convert(::Type{Ptr{RWLockData}}, obj::RWLock) = obj.handle


text:
using SchumakerSpline
using Test

xvals = [1,2,3,4,5,6]
shape_map(x,upper,lower) = min(max(x,lower), upper)

# Increasing
yvals = [1.0, 2.0, 2.9, 3.9, 2.3, 1.4]
vals2 = reshape_values(xvals, yvals; increasing = true, concave = true, shape_map = shape_map)
all(abs.([1.0, 2.0, 2.9, 3.8, 3.8, 3.8] .- vals2) .< 10*eps()) # Changes

# Increasing and concave
yvals = [1.0, 2.0, 2.9, 3.9, 4.3, 4.4]
vals2 = reshape_values(xvals, yvals; increasing = true, concave = true, shape_map = shape_map)
all(abs.([1.0, 2.0, 2.9, 3.8, 4.3, 4.4] .- vals2) .< 10*eps()) # Changes
yvals = sqrt.(xvals)
vals2 = reshape_values(xvals, yvals; increasing = true, concave = true, shape_map = shape_map)
all(abs.(vals2 .- yvals) .< 10*eps()) # No changes as input already increasing concave

# Increasing and convex
yvals = [1.0, 2.0, 3.1, 3.9, 4.3, 4.4]
vals2 = reshape_values(xvals, yvals; increasing = true, concave = false, shape_map = shape_map)
all(abs.([1.0, 2.0, 3.1, 4.2, 5.3, 6.4] .- vals2) .< 10*eps()) #changes
yvals = (xvals) .^ 2
vals2 = reshape_values(xvals, yvals; increasing = true, concave = false, shape_map = shape_map)
all(abs.(vals2 .- yvals) .< 10*eps()) # No changes as input already increasing concave

# Decreasing
yvals = -[1.0, 2.0, 2.9, 3.9, 2.3, 1.4]
vals2 = reshape_values(xvals, yvals; increasing = false, concave = false, shape_map = shape_map)
all(abs.([-1.0, -2.0, -2.9, -3.8, -3.8, -3.8] .- vals2) .< 10*eps()) # Changes

# Decreasing and convex
yvals = -[1.0, 2.0, 2.9, 3.9, 4.3, 4.4]
vals2 = reshape_values(xvals, yvals; increasing = false, concave = false, shape_map = shape_map)
all(abs.(-[1.0, 2.0, 2.9, 3.8, 4.3, 4.4] .- vals2) .< 10*eps()) # Changes
yvals = -sqrt.(xvals)
vals2 = reshape_values(xvals, yvals; increasing = false, concave = false, shape_map = shape_map)
all(abs.(vals2 .- yvals) .< 10*eps()) # No changes as input already increasing concave

# Decreasing and concave
yvals = -[1.0, 2.0, 3.1, 3.9, 4.3, 4.4]
vals2 = reshape_values(xvals, yvals; increasing = false, concave = true, shape_map = shape_map)
all(abs.(-[1.0, 2.0, 3.1, 4.2, 5.3, 6.4] .- vals2) .< 10*eps()) #changes
yvals = -(xvals) .^ 2
vals2 = reshape_values(xvals, yvals; increasing = false, concave = true, shape_map = shape_map)
all(abs.(vals2 .- yvals) .< 10*eps()) # No changes as input already increasing concave

ext: jl


text:
using AutoGrad

"""
    svd(A::TrackedArray) -> TrackedTuple

Return tracked tuple of (U, S, V) that `A == USV'`.
"""
@primitive svd!(A), Δ, y svd_back(y..., Δ...)

A = randn(4,4) |> Param
O = randn(4,4)
function test_func(A)
    U, S, V = svd!(A)
    psi = U[:,1]
    psi'*O*psi
end
dU, dS, dV = randn(4,4), randn(4), randn(4,4)
y = @diff test_func(A)
@show value(y)
grad(y, A)

qr(A::TrackedArray) = track(qr, A)
function _forward(::typeof(qr), A)
    Q, R = qr(data(A))
    (Q, R), Δ -> (qr_back(data(A), Q, R, Δ...),)
end

lq(A::TrackedArray) = track(lq, A)
function _forward(::typeof(lq), A)
    L, Q = lq(data(A))
    (L, Q), Δ -> (lq_back(data(A), L, Q, Δ...),)
end


text:
using Symbolics: Term

"Index mapping variables to integers"
struct IndexDict
    index::Dict{Sym, Int}
end

IndexDict(vars::Vector{<:Sym}) = IndexDict(Dict(v => i for (i, v) in enumerate(vars)))

struct SymbolicSATProblem
    variables # ::Vector{<:Sym}
    clauses # ::Vector{<:Term}
    index::IndexDict
    p::SATProblem
end

function process(d::IndexDict, var::Sym)
    if !haskey(d.index, var)
        error("$var is not in the problem")
    end

    return [ d.index[var] ]
end

"Output a clause c from a Dict of variables c, as a vector of integers"
function process(d::IndexDict, clause::Term)

    if clause.f == ¬
        return [ -(d.index[clause.arguments[1]]) ]

    elseif clause.f == ∨
        return reduce(vcat, process.(Ref(d), clause.arguments))

    else
        error("Malformed")
    end

end

function process(d::IndexDict, clauses)
    return process.(Ref(d.index), clauses)
end

function SymbolicSATProblem(variables::Vector{<:Sym}, symbolic_clauses)

    d = IndexDict(variables)

    clauses = process.(Ref(d), symbolic_clauses)

    p = SATProblem(length(variables), clauses)

    # @show variables
    # @show symbolic_clauses
    # @show d
    # @show p

    return SymbolicSATProblem(variables, symbolic_clauses, d, p)
end

# SymbolicSATProblem(m::Model) = SymbolicSATProblem(boolean_variables(m), clauses(m))

function decode(vars, results::Vector{Int})
    return Dict(v => (i < 0 ? false : true) for (v, i) in zip(vars, results))
end

function solve(p::SymbolicSATProblem)
    status, results = solve(p.p)

    (status == :unsat) && return status, missing

    return status, decode(p.variables, results)

end

decode(p::SymbolicSATProblem, results::Vector{Int}) = decode(p.variables, results)

all_solutions(p::SymbolicSATProblem) = decode.(Ref(p), all_solutions(p.p))


text:
function RBMs.zerosum!(rbm::DenseConvRBM)
    RBMs.zerosum_visible!(rbm)
    RBMs.zerosum_hidden!(rbm)
    return rbm
end

function RBMs.zerosum_visible!(rbm::DenseConvRBM)
    if rbm.visible_dense isa RBMs.Potts
        RBMs.zerosum!(rbm.visible_dense)
        RBMs.zerosum!(rbm.w_dense; dims = 1)
    end
    if rbm.visible_conv isa RBMs.Potts
        RBMs.zerosum!(rbm.visible_conv)
        RBMs.zerosum!(rbm.w_conv; dims = 1)
    end
    return rbm
end

function RBMs.zerosum_hidden!(rbm::DenseConvRBM)
    if hidden(rbm) isa RBMs.Potts
        RBMs.zerosum!(hidden(rbm))
        RBMs.zerosum!(rbm.w_dense; dims = ndims(rbm.w_dense) - ndims(hidden(rbm)) + 1)
        RBMs.zerosum!(rbm.w_conv; dims = ndims(rbm.w_conv) - ndims(hidden(rbm)) + 1)
    end
    return rbm
end


text:
import .Utils
import DocStringExtensions

DocStringExtensions.@template (FUNCTIONS, METHODS) =
    """
    $(DocStringExtensions.TYPEDSIGNATURES)
    $(DocStringExtensions.DOCSTRING)
    """

# We want these enums to be made available in the HELICS api.
# Manually typing these out is verbose and annoying
# We make the enums uppercase since they are essentially constant integer values
# for e.g. `Lib.helics_iteration_request` -> `HELICS.HELICS_ITERATION_REQUEST`
#          `Lib.helics_iteration_request_no_iteration` -> `HELICS.HELICS_ITERATION_REQUEST_NO_ITERATION`
#          `Lib.helics_iteration_request_force_iteration` -> `HELICS.HELICS_ITERATION_REQUEST_FORCE_ITERATION`
#          `Lib.helics_iteration_request_iterate_if_needed` -> `HELICS.HELICS_ITERATION_REQUEST_ITERATE_IF_NEEDED`
# and so on ...

for enum_name in [
                 Lib.helics_iteration_request,
                 Lib.helics_iteration_result,
                 Lib.helics_federate_state,
                 Lib.helics_data_type,
                 Lib.helics_core_type,
                 Lib.helics_federate_flags,
                 Lib.helics_log_levels,
                 Lib.helics_error_types,
                 Lib.helics_properties,
                 Lib.helics_handle_options,
                 Lib.helics_filter_type,
                 ]
    # Hacks to not populate namespace
    eval(:(const $(Symbol(uppercase(split(String(Symbol(enum_name)), ".")[end]))) = $enum_name))

    for (_sym, ans) in Lib.CEnum.name_value_pairs(enum_name)
        eval( :(const $(Symbol(uppercase(String(_sym)))) = Lib.$_sym) )
    end

end

# Generate the docstring for these enumerations.
# e.g. the following generates documentation like this:
# HELICS.HELICS_ITERATION_REQUEST — Type.
#
#    HELICS_ITERATION_REQUEST_NO_ITERATION: 0
#    HELICS_ITERATION_REQUEST_FORCE_ITERATION: 1
#    HELICS_ITERATION_REQUEST_ITERATE_IF_NEEDED: 2
#
# TODO: this can be further simplified with a macro that loops over the list as done previously

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_ITERATION_REQUEST)), "\n") )
"""
HELICS.HELICS_ITERATION_REQUEST

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_ITERATION_RESULT)), "\n") )
"""
HELICS.HELICS_ITERATION_RESULT

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_FEDERATE_STATE)), "\n") )
"""
HELICS.HELICS_FEDERATE_STATE

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_DATA_TYPE)), "\n") )
"""
HELICS.HELICS_DATA_TYPE

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_CORE_TYPE)), "\n") )
"""
HELICS.HELICS_CORE_TYPE

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_FEDERATE_FLAGS)), "\n") )
"""
HELICS.HELICS_FEDERATE_FLAGS

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_LOG_LEVELS)), "\n") )
"""
HELICS.HELICS_LOG_LEVELS

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_ERROR_TYPES)), "\n") )
"""
HELICS.HELICS_ERROR_TYPES

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_PROPERTIES)), "\n") )
"""
HELICS.HELICS_PROPERTIES

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_HANDLE_OPTIONS)), "\n") )
"""
HELICS.HELICS_HANDLE_OPTIONS

@doc """
$( join(map(x -> "- `" * uppercase(String(x[1])) * "`: " * string(x[2]), Lib.CEnum.name_value_pairs(HELICS.HELICS_FILTER_TYPE)), "\n") )
"""
HELICS.HELICS_FILTER_TYPE

const HELICS_TIME = Union{Int, Float64}

"""
"""
function helicsFederateRegisterEndpoint(fed::Federate, name::String, kind::String)::Endpoint
    return Utils.@invoke_and_check Lib.helicsFederateRegisterEndpoint(fed, name, kind)
end

"""
"""
function helicsFederateRegisterGlobalEndpoint(fed::Federate, name::String, kind::String)::Endpoint
    return Utils.@invoke_and_check Lib.helicsFederateRegisterGlobalEndpoint(fed, name, kind)
end

"""
"""
function helicsFederateGetEndpoint(fed::Federate, name::String)::Endpoint
    Utils.@invoke_and_check Lib.helicsFederateGetEndpoint(fed, name)
end

"""
"""
function helicsFederateGetEndpointByIndex(fed::Federate, index::Int)::Endpoint
    return Utils.@invoke_and_check Lib.helicsFederateGetEndpointByIndex(fed, index)
end

"""
"""
function helicsEndpointSetDefaultDestination(endpoint::Endpoint, dest::String)
    Utils.@invoke_and_check Lib.helicsEndpointSetDefaultDestination(endpoint, dest)
end

"""
"""
function helicsEndpointGetDefaultDestination(endpoint::Endpoint)::String
    return Lib.helicsEndpointGetDefaultDestination(endpoint) |> unsafe_string
end

"""
"""
function helicsEndpointSendMessageRaw(endpoint::Endpoint, dest::String, data::String)
    inputDataLength = length(data)
    data = pointer(data)
    Utils.@invoke_and_check Lib.helicsEndpointSendMessageRaw(endpoint, dest, data, inputDataLength)
end

"""
"""
function helicsEndpointSendEventRaw(endpoint::Endpoint, dest::String, data::String, time::HELICS.HELICS_TIME)
    inputDataLength = length(data)
    data = pointer(data)
    Utils.@invoke_and_check Lib.helicsEndpointSendEventRaw(endpoint, dest, data, inputDataLength, time)
end

"""
"""
function helicsEndpointSendMessage(endpoint::Endpoint, message::Message)
    Utils.@invoke_and_check Lib.helicsEndpointSendMessage(endpoint, message)
end

"""
"""
function helicsEndpointSendMessageObject(endpoint::Endpoint, message::Message)
    Utils.@invoke_and_check Lib.helicsEndpointSendMessageObject(endpoint, message)
end

"""
"""
function helicsEndpointSubscribe(endpoint::Endpoint, key::String)
    Utils.@invoke_and_check Lib.helicsEndpointSubscribe(endpoint, key)
end

"""
"""
function helicsFederateHasMessage(fed::Federate)::Bool
    return Lib.helicsFederateHasMessage(fed) == 1 ? true : false
end

"""
"""
function helicsEndpointHasMessage(endpoint::Endpoint)::Bool
    return Lib.helicsEndpointHasMessage(endpoint) == 1 ? true : false
end

"""
"""
function helicsFederatePendingMessages(fed::Federate)::Int
    return Lib.helicsFederatePendingMessages(fed)
end

"""
"""
function helicsEndpointPendingMessages(endpoint::Endpoint)::Int
    return Lib.helicsEndpointPendingMessages(endpoint)
end

"""
"""
function helicsEndpointGetMessage(endpoint::Endpoint)::Message
    return Lib.helicsEndpointGetMessage(endpoint)
end

"""
"""
function helicsEndpointGetMessageObject(endpoint::Endpoint)::Message
    return Lib.helicsEndpointGetMessageObject(endpoint)
end

"""
"""
function helicsFederateGetMessage(fed::Federate)::Message
    return Lib.helicsFederateGetMessage(fed)
end

"""
"""
function helicsFederateGetMessageObject(fed::Federate)::Message
    return Lib.helicsFederateGetMessageObject(fed)
end

"""
"""
function helicsEndpointGetType(endpoint::Endpoint)::String
    return Lib.helicsEndpointGetType(endpoint) |> unsafe_string
end

"""
"""
function helicsEndpointGetName(endpoint::Endpoint)::String
    return Lib.helicsEndpointGetName(endpoint) |> unsafe_string
end

"""
"""
function helicsFederateGetEndpointCount(fed::Federate)::Int
    return Lib.helicsFederateGetEndpointCount(fed)
end

"""
"""
function helicsEndpointGetInfo(_end::Endpoint)::String
    return Lib.helicsEndpointGetInfo(_end) |> unsafe_string
end

"""
"""
function helicsEndpointSetInfo(_end::Endpoint, info::String)
    Utils.@invoke_and_check Lib.helicsEndpointSetInfo(_end, info)
end

"""
"""
function helicsEndpointSetOption(_end::Endpoint, option::Int, value::Bool)
    Utils.@invoke_and_check Lib.helicsEndpointSetOption(_end, option, value ? 1 : 0)
end

"""
"""
function helicsEndpointGetOption(_end::Endpoint, option::Int)::Bool
    return Lib.helicsEndpointGetOption(_end, option)
end

"""
"""
function helicsFederateRegisterFilter(fed::Federate, kind::Union{Int, HELICS.HELICS_FILTER_TYPE}, name::String)::Filter
    return Utils.@invoke_and_check Lib.helicsFederateRegisterFilter(fed, kind, name)
end

"""
"""
function helicsFederateRegisterGlobalFilter(fed::Federate, kind::Union{Int, HELICS.HELICS_FILTER_TYPE}, name::String)::Filter
    return Utils.@invoke_and_check Lib.helicsFederateRegisterGlobalFilter(fed, kind, name)
end

"""
"""
function helicsFederateRegisterCloningFilter(fed::Federate, name::String)::Filter
    return Utils.@invoke_and_check Lib.helicsFederateRegisterCloningFilter(fed, name)
end

"""
"""
function helicsFederateRegisterGlobalCloningFilter(fed::Federate, name::String)::Filter
    return Utils.@invoke_and_check Lib.helicsFederateRegisterGlobalCloningFilter(fed, name)
end

"""
"""
function helicsCoreRegisterFilter(core::Core, kind::Union{Int, HELICS.HELICS_FILTER_TYPE}, name::String)::Filter
    return Utils.@invoke_and_check Lib.helicsCoreRegisterFilter(core, kind, name)
end

"""
"""
function helicsCoreRegisterCloningFilter(core::Core, name::String)::Filter
    return Utils.@invoke_and_check Lib.helicsCoreRegisterCloningFilter(core, name)
end

"""
"""
function helicsFederateGetFilterCount(fed::Federate)::Int
    return Lib.helicsFederateGetFilterCount(fed)
end

"""
"""
function helicsFederateGetFilter(fed::Federate, name::String)::Filter
    return Utils.@invoke_and_check Lib.helicsFederateGetFilter(fed, name)
end

"""
"""
function helicsFederateGetFilterByIndex(fed::Federate, index::Int)::Filter
    return Utils.@invoke_and_check Lib.helicsFederateGetFilterByIndex(fed, index)
end

"""
"""
function helicsFilterGetName(filt::Filter)::String
    return Lib.helicsFilterGetName(filt) |> unsafe_string
end

"""
"""
function helicsFilterSet(filt::Filter, prop::String, val::Float64)
    Utils.@invoke_and_check Lib.helicsFilterSet(filt, prop, val)
end

"""
"""
function helicsFilterSetString(filt::Filter, prop::String, val::String)
    Utils.@invoke_and_check Lib.helicsFilterSetString(filt, prop, val)
end

"""
"""
function helicsFilterAddDestinationTarget(filt::Filter, dest::String)
    Utils.@invoke_and_check Lib.helicsFilterAddDestinationTarget(filt, dest)
end

"""
"""
function helicsFilterAddSourceTarget(filt::Filter, source::String)
    Utils.@invoke_and_check Lib.helicsFilterAddSourceTarget(filt, source)
end

"""
"""
function helicsFilterAddDeliveryEndpoint(filt::Filter, deliveryEndpoint::String)
    Utils.@invoke_and_check Lib.helicsFilterAddDeliveryEndpoint(filt, deliveryEndpoint)
end

"""
"""
function helicsFilterRemoveTarget(filt::Filter, target::String)
    Utils.@invoke_and_check Lib.helicsFilterRemoveTarget(filt, target)
end

"""
"""
function helicsFilterRemoveDeliveryEndpoint(filt::Filter, deliveryEndpoint::String)
    Utils.@invoke_and_check Lib.helicsFilterRemoveDeliveryEndpoint(filt, deliveryEndpoint)
end

"""
"""
function helicsFilterGetInfo(filt::Filter)::String
    return Lib.helicsFilterGetInfo(filt) |> unsafe_string
end

"""
"""
function helicsFilterSetInfo(filt::Filter, info::String)
    Utils.@invoke_and_check Lib.helicsFilterSetInfo(filt, info)
end

"""
"""
function helicsFilterSetOption(filt::Filter, option::Int, value::Bool)
    Utils.@invoke_and_check Lib.helicsFilterSetOption(filt, option, value ? 1 : 0)
end

"""
"""
function helicsFilterGetOption(filt::Filter, option::Int)::Bool
    return Lib.helicsFilterGetOption(filt, option) == 1 ? true : false
end

"""
"""
function helicsFederateRegisterSubscription(fed::Federate, key::String, units::String="")::Subscription
    return Utils.@invoke_and_check Lib.helicsFederateRegisterSubscription(fed, key, units)
end

"""
"""
function helicsFederateRegisterPublication(fed::Federate, key::String, kind::Union{Int, HELICS.HELICS_DATA_TYPE}, units::String="")::Publication
    return Utils.@invoke_and_check Lib.helicsFederateRegisterPublication(fed, key, kind, units)
end

"""
"""
function helicsFederateRegisterTypePublication(fed::Federate, key::String, kind::String, units::String="")::Publication
    return Utils.@invoke_and_check Lib.helicsFederateRegisterTypePublication(fed, key, kind, units)
end

"""
"""
function helicsFederateRegisterGlobalPublication(fed::Federate, key::String, kind::Union{Int, HELICS.HELICS_DATA_TYPE}, units::String="")::Publication
    return Utils.@invoke_and_check Lib.helicsFederateRegisterGlobalPublication(fed, key, kind, units)
end

"""
"""
function helicsFederateRegisterGlobalTypePublication(fed::Federate, key::String, kind::String, units::String="")::Publication
    return Utils.@invoke_and_check Lib.helicsFederateRegisterGlobalTypePublication(fed, key, kind, units)
end

"""
"""
function helicsFederateRegisterInput(fed::Federate, key::String, kind::Union{Int, HELICS.HELICS_DATA_TYPE}, units::String="")::Input
    return Utils.@invoke_and_check Lib.helicsFederateRegisterInput(fed, key, kind, units)
end

"""
"""
function helicsFederateRegisterTypeInput(fed::Federate, key::String, kind::String, units::String="")::Input
    return Utils.@invoke_and_check Lib.helicsFederateRegisterTypeInput(fed, key, kind, units)
end

"""
"""
function helicsFederateRegisterGlobalInput(fed::Federate, key::String, kind::Union{Int, HELICS.HELICS_DATA_TYPE}, units::String="")::Input
    return Utils.@invoke_and_check Lib.helicsFederateRegisterGlobalInput(fed, key, kind, units)
end

"""
"""
function helicsFederateRegisterGlobalTypeInput(fed::Federate, key::String, kind::String, units::String="")::Input
    return Utils.@invoke_and_check Lib.helicsFederateRegisterGlobalTypeInput(fed, key, kind, units)
end

"""
"""
function helicsFederateGetPublication(fed::Federate, key::String)::Publication
    return Utils.@invoke_and_check Lib.helicsFederateGetPublication(fed, key)
end

"""
"""
function helicsFederateGetPublicationByIndex(fed::Federate, index::Int)::Publication
    return Utils.@invoke_and_check Lib.helicsFederateGetPublicationByIndex(fed, index)
end

"""
"""
function helicsFederateGetInput(fed::Federate, key::String)::Input
    return Utils.@invoke_and_check Lib.helicsFederateGetInput(fed, key)
end

"""
"""
function helicsFederateGetInputByIndex(fed::Federate, index::Int)::Input
    return Utils.@invoke_and_check Lib.helicsFederateGetInputByIndex(fed, index)
end

"""
"""
function helicsFederateGetSubscription(fed::Federate, key::String)::Subscription
    return Utils.@invoke_and_check Lib.helicsFederateGetSubscription(fed, key)
end

"""
"""
function helicsPublicationPublishRaw(pub::Publication, data)
    inputDataLength = length(data)
    data = pointer(data)
    Utils.@invoke_and_check Lib.helicsPublicationPublishRaw(pub, data, inputDataLength)
end

"""
"""
function helicsPublicationPublishString(pub::Publication, str::String)
    Utils.@invoke_and_check Lib.helicsPublicationPublishString(pub, str)
end

"""
"""
function helicsPublicationPublishInteger(pub::Publication, val::Int)
    Utils.@invoke_and_check Lib.helicsPublicationPublishInteger(pub, val)
end

"""
"""
function helicsPublicationPublishBoolean(pub::Publication, val::Bool)
    Utils.@invoke_and_check Lib.helicsPublicationPublishBoolean(pub, val ? 1 : 0)
end

"""
"""
function helicsPublicationPublishDouble(pub::Publication, val::Float64)
    Utils.@invoke_and_check Lib.helicsPublicationPublishDouble(pub, val)
end

"""
"""
function helicsPublicationPublishTime(pub::Publication, val::HELICS.HELICS_TIME)
    Utils.@invoke_and_check Lib.helicsPublicationPublishTime(pub, val)
end

"""
"""
function helicsPublicationPublishChar(pub::Publication, val::Char)
    Utils.@invoke_and_check Lib.helicsPublicationPublishChar(pub, val)
end

"""
"""
function helicsPublicationPublishComplex(pub::Publication, c::ComplexF64)
    Utils.@invoke_and_check Lib.helicsPublicationPublishComplex(pub, c.re, c.im)
end

"""
"""
function helicsPublicationPublishVector(pub::Publication, vectorInput::Vector{Float64})
    vectorLength = length(vectorInput)
    Utils.@invoke_and_check Lib.helicsPublicationPublishVector(pub, vectorInput, vectorLength)
end

"""
"""
function helicsPublicationPublishNamedPoint(pub::Publication, str::String, val::Float64)
    Utils.@invoke_and_check Lib.helicsPublicationPublishNamedPoint(pub, str, val)
end

"""
"""
function helicsPublicationAddTarget(pub::Publication, target::String)
    Utils.@invoke_and_check Lib.helicsPublicationAddTarget(pub, target)
end

"""
"""
function helicsInputAddTarget(ipt::Input, target::String)
    Utils.@invoke_and_check Lib.helicsInputAddTarget(ipt, target)
end

"""
"""
function helicsInputGetRawValueSize(ipt::Input)::Int
    return Lib.helicsInputGetRawValueSize(ipt)
end

"""
"""
function helicsInputGetRawValue(ipt::Input, data::T)::T where T<:Any
    maxlen = Lib.helicsInputGetRawValueSize(ipt)
    actualSize = Ref(maxlen)
    return Utils.@invoke_and_check Lib.helicsInputGetRawValue(ipt, data, maxlen, actualSize)
end

"""
"""
function helicsInputGetStringSize(ipt::Input)::Int
    return Lib.helicsInputGetStringSize(ipt)
end

"""
"""
function helicsInputGetString(ipt::Input)::String
    maxStringLen = helicsInputGetStringSize(ipt)
    outputString = repeat(" ", maxStringLen + 2)
    actualLength = Ref{Int32}(maxStringLen)
    Utils.@invoke_and_check Lib.helicsInputGetString(ipt, outputString, maxStringLen, actualLength)
    return outputString[1:(actualLength[]-1)]
end

"""
"""
function helicsInputGetInteger(ipt::Input)::Int
    return Utils.@invoke_and_check Lib.helicsInputGetInteger(ipt)
end

"""
"""
function helicsInputGetBoolean(ipt::Input)::Bool
    r = Utils.@invoke_and_check Lib.helicsInputGetBoolean(ipt)
    return r == 1 ? true : false
end

"""
"""
function helicsInputGetDouble(ipt::Input)::Float64
    return Utils.@invoke_and_check Lib.helicsInputGetDouble(ipt)
end

"""
"""
function helicsInputGetTime(ipt::Input)::Float64
    return Utils.@invoke_and_check Lib.helicsInputGetTime(ipt)
end

"""
"""
function helicsInputGetChar(ipt::Input)::Char
    return Utils.@invoke_and_check Lib.helicsInputGetChar(ipt)
end

"""
"""
function helicsInputGetComplexObject(ipt::Input)::ComplexF64
    r = Utils.@invoke_and_check Lib.helicsInputGetComplexObject(ipt)
    return r.real + im * r.imag
end

"""
"""
function helicsInputGetComplex(ipt::Input)::ComplexF64
    real = Ref{Float64}(0)
    imag = Ref{Float64}(0)
    Utils.@invoke_and_check Lib.helicsInputGetComplex(ipt, real, imag)
    return real[] + im * imag[]
end

"""
"""
function helicsInputGetVectorSize(ipt::Input)::Int64
    return Lib.helicsInputGetVectorSize(ipt)
end

"""
"""
function helicsInputGetVector(ipt::Input)::Vector{Float64}
    maxlen = Cint(helicsInputGetVectorSize(ipt))
    data = Vector{Float64}(undef, maxlen)
    actualSize = Ref(maxlen)
    Utils.@invoke_and_check Lib.helicsInputGetVector(ipt, data, maxlen, actualSize)
    return data[1:actualSize[]]
end

"""
"""
function helicsInputGetNamedPoint(ipt::Input)::Tuple{String, Float64}
    maxStringLen = helicsInputGetStringSize(ipt)
    outputString = repeat(" ", maxStringLen + 2)
    actualLength = Ref{Int32}(maxStringLen)
    val = Ref{Float64}(0.0)
    Utils.@invoke_and_check Lib.helicsInputGetNamedPoint(ipt, outputString, maxStringLen, actualLength, val)
    return outputString[1:(actualLength[]-1)], val[]
end

"""
"""
function helicsInputSetDefaultRaw(ipt::Input, data)
    inputDataLength = length(data)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultRaw(ipt, data, inputDataLength)
end

"""
"""
function helicsInputSetDefaultString(ipt::Input, str::String)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultString(ipt, str)
end

"""
"""
function helicsInputSetDefaultInteger(ipt::Input, val::Int)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultInteger(ipt, val)
end

"""
"""
function helicsInputSetDefaultBoolean(ipt::Input, val::Bool)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultBoolean(ipt, val ? 1 : 0)
end

"""
"""
function helicsInputSetDefaultTime(ipt::Input, val::HELICS.HELICS_TIME)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultTime(ipt, val)
end

"""
"""
function helicsInputSetDefaultChar(ipt::Input, val::Char)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultChar(ipt, val)
end

"""
"""
function helicsInputSetDefaultDouble(ipt::Input, val::Float64)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultDouble(ipt, val)
end

"""
"""
function helicsInputSetDefaultComplex(ipt::Input, c::ComplexF64)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultComplex(ipt, c.re, c.im)
end

"""
"""
function helicsInputSetDefaultVector(ipt::Input, vectorInput::Vector{Float64})
    vectorLength = length(vectorInput)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultVector(ipt, vectorInput, vectorLength)
end

"""
"""
function helicsInputSetDefaultNamedPoint(ipt::Input, str::String, val::Float64)
    Utils.@invoke_and_check Lib.helicsInputSetDefaultNamedPoint(ipt, str, val)
end

"""
"""
function helicsInputGetType(ipt::Input)::String
    return Lib.helicsInputGetType(ipt) |> unsafe_string
end

"""
"""
function helicsInputGetPublicationType(ipt::Input)::String
    return Lib.helicsInputGetPublicationType(ipt) |> unsafe_string
end

"""
"""
function helicsPublicationGetType(pub::Publication)::String
    return Lib.helicsPublicationGetType(pub) |> unsafe_string
end

"""
"""
function helicsInputGetKey(ipt::Input)::String
    return Lib.helicsInputGetKey(ipt) |> unsafe_string
end

"""
"""
function helicsSubscriptionGetKey(ipt::Input)::String
    return Lib.helicsSubscriptionGetKey(ipt) |> unsafe_string
end

"""
"""
function helicsPublicationGetKey(pub::Publication)::String
    return Lib.helicsPublicationGetKey(pub) |> unsafe_string
end

"""
"""
function helicsInputGetUnits(ipt::Input)::String
    return Lib.helicsInputGetUnits(ipt) |> unsafe_string
end

"""
"""
function helicsPublicationGetUnits(pub::Publication)::String
    return Lib.helicsPublicationGetUnits(pub) |> unsafe_string
end

"""
"""
function helicsInputGetInfo(inp::Input)::String
    return Lib.helicsInputGetInfo(inp) |> unsafe_string
end

"""
"""
function helicsInputSetInfo(inp::Input, info::String)
    Utils.@invoke_and_check Lib.helicsInputSetInfo(inp, info)
end

"""
"""
function helicsPublicationGetInfo(pub::Publication)::String
    return Lib.helicsPublicationGetInfo(pub) |> unsafe_string
end

"""
"""
function helicsPublicationSetInfo(pub::Publication, info::String)
    Utils.@invoke_and_check Lib.helicsPublicationSetInfo(pub, info)
end

"""
"""
function helicsInputGetOption(inp::Input, option::Int)::Bool
    return Lib.helicsInputGetOption(inp, option::Int) == 1 ? true : false
end

"""
"""
function helicsInputSetOption(inp::Input, option::Int, value::Bool)
    Utils.@invoke_and_check Lib.helicsInputSetOption(inp, option, value ? 1 : 0)
end

"""
"""
function helicsPublicationGetOption(pub::Publication, option::Int)::Bool
    return Lib.helicsPublicationGetOption(pub, option) == 1 ? true : false
end

"""
"""
function helicsPublicationSetOption(pub::Publication, option::Int, val::Bool)
    Utils.@invoke_and_check Lib.helicsPublicationSetOption(pub, option, val ? 1 : 0)
end

"""
"""
function helicsInputIsUpdated(ipt::Input)::Bool
    return Lib.helicsInputIsUpdated(ipt) == 1 ? true : false
end

"""
"""
function helicsInputLastUpdateTime(ipt::Input)::Float64
    return Lib.helicsInputLastUpdateTime(ipt)
end

"""
"""
function helicsFederateGetPublicationCount(fed::Federate)::Int
    return Lib.helicsFederateGetPublicationCount(fed)
end

"""
"""
function helicsFederateGetInputCount(fed::Federate)::Int
    return Lib.helicsFederateGetInputCount(fed)
end

"""
Get a version string for HELICS
"""
function helicsGetVersion()::String
    return Lib.helicsGetVersion() |> unsafe_string
end

"""
Return an initialized error object
"""
function helicsErrorInitialize()::Lib.helics_error
    return Lib.helicsErrorInitialize()
end

"""
Clear an error object
"""
function helicsErrorClear(err::Lib.helics_error)
    Lib.helicsErrorClear(err)
end

"""
Returns true if core/broker type specified is available in current compilation.

# Arguments

- `type`: a string representing a core type

# Returns

- a `Bool`

possible options include "test","zmq","udp","ipc","interprocess","tcp","default", "mpi"

"""
function helicsIsCoreTypeAvailable(kind::String)::Bool
    return Lib.helicsIsCoreTypeAvailable(kind) == 1 ? true : false
end

"""
Create a core object

# Arguments

- `type`: the type of the core to create
- `name`: the name of the core , may be a nullptr or empty string to have a name automatically assigned
- `initString`: an initialization string to send to the core-the format is similar to command line arguments. Typical options include a broker address  --broker="XSSAF" or the number of federates or the address

# Returns

- a [`Core`](@ref) object if the core is invalid err will contain some indication

"""
function helicsCreateCore(kind::String, name::String, initString::String)::Core
    return Utils.@invoke_and_check Lib.helicsCreateCore(kind, name, initString)
end

"""
"""
function helicsCreateCoreFromArgs(kind::String, name::String, argc::Int, argv::Vector{<:AbstractString})::Core
    return Utils.@invoke_and_check Lib.helicsCreateCoreFromArgs(kind, name, argc, argv)
end

"""
Create a new reference to an existing core

this will create a new broker object that references the existing broker it must be freed as well

# Arguments

- `core`: an existing [`Core`](@ref)

# Returns

- a new reference to the same [`Core`](@ref)
"""
function helicsCoreClone(core::Core)::Core
    return Utils.@invoke_and_check Lib.helicsCoreClone(core)
end

"""
Check if a core object is a valid object

# Arguments

- `core`: the [`Core`](@ref) object to test
"""
function helicsCoreIsValid(core::Core)::Bool
    return Lib.helicsCoreIsValid(core) == 1 ? true : false
end

"""
Create a broker object

# Arguments

- `type`: the type of the broker to create
- `name`: the name of the broker , may be a nullptr or empty string to have a name automatically assigned
- `initString`: an initialization string to send to the core-the format is similar to command line arguments. Typical options include a broker address  --broker="XSSAF" if this is a subbroker or the number of federates or the address

# Returns

- a [`Broker`](@ref) object, will be NULL if there was an error indicated in the err object

"""
function helicsCreateBroker(kind::String, name::String, initString::String)::Broker
    return Utils.@invoke_and_check Lib.helicsCreateBroker(kind, name, initString)
end

"""
"""
function helicsCreateBrokerFromArgs(kind::String, name::String, argc::Int, argv::Vector{<:AbstractString})::Broker
    return Utils.@invoke_and_check Lib.helicsCreateBrokerFromArgs(kind, name, argc, argv)
end

"""
Create a new reference to an existing broker

this will create a new broker object that references the existing broker it must be freed as well

# Arguments

- `broker`: an existing [`Broker`](@ref)

# Returns

- a new reference to the same broker
"""
function helicsBrokerClone(broker::Broker)::Broker
    return Utils.@invoke_and_check Lib.helicsBrokerClone(broker)
end

"""
Check if a broker object is a valid object

# Arguments

- `broker`: the [`Broker`](@ref) object to test
"""
function helicsBrokerIsValid(broker::Broker)::Bool
    return Lib.helicsBrokerIsValid(broker) == 1 ? true : false
end

"""
Check if a broker is connected. A connected broker implies is attached to cores or cores could reach out to communicate. return 0 if not connected , something else if it is connected.
"""
function helicsBrokerIsConnected(broker::Broker)::Bool
    return Lib.helicsBrokerIsConnected(broker) == 1 ? true : false
end

"""
Link a named publication and named input using a broker

# Arguments

- `broker`: the [`Broker`](@ref) to generate the connection from
- `source`: the name of the publication (cannot be NULL)
- `target`: the name of the target to send the publication data (cannot be NULL)

"""
function helicsBrokerDataLink(broker::Broker, source::String, target::String)
    Utils.@invoke_and_check Lib.helicsBrokerDataLink(broker, source, target)
end

"""
"""
function helicsBrokerAddSourceFilterToEndpoint(broker::Broker, filter::String, endpoint::String)
    Utils.@invoke_and_check Lib.helicsBrokerAddSourceFilterToEndpoint(broker, filter, endpoint)
end

"""
"""
function helicsBrokerAddDestinationFilterToEndpoint(broker::Broker, filter::String, endpoint::String)
    Utils.@invoke_and_check Lib.helicsBrokerAddDestinationFilterToEndpoint(broker, filter, endpoint)
end

"""
Wait for the broker to disconnect

# Arguments

- `broker`: the broker to wait for
- `msToWait`: the time out in millisecond (<0 for infinite timeout)

# Returns

- `true` if the disconnect was successful, `false` if there was a timeout

"""
function helicsBrokerWaitForDisconnect(broker::Broker, msToWait::Int)::Bool
    r = Utils.@invoke_and_check Lib.helicsBrokerWaitForDisconnect(broker, msToWait)
    return r == 1 ? true : false
end

"""
Check if a core is connected. A connected core implies is attached to federate or federates could be attached to it.

# Returns

- `false` if not connected, `true` if it is connected
"""
function helicsCoreIsConnected(core::Core)::Bool
    return Lib.helicsCoreIsConnected(core) == 1 ? true : false
end

"""
Link a named publication and named input using a core

# Arguments

- `core`: the core to generate the connection from
- `source`: the name of the publication (cannot be NULL)
- `target`: the named of the target to send the publication data (cannot be NULL)

"""
function helicsCoreDataLink(core::Core, source::String, target::String)
    Utils.@invoke_and_check Lib.helicsCoreDataLink(core, source, target)
end

"""
Link a named filter to a source endpoint

# Arguments

- `core`: the core to generate the connection from
- `filter`: the name of the filter (cannot be NULL)
- `endpoint`: the name of the endpoint to filter the data from (cannot be NULL)

"""
function helicsCoreAddSourceFilterToEndpoint(core::Core, filter::String, endpoint::String)
    Utils.@invoke_and_check Lib.helicsCoreAddSourceFilterToEndpoint(core, filter, endpoint)
end

"""
"""
function helicsCoreAddDestinationFilterToEndpoint(core::Core, filter::String, endpoint::String)
    Utils.@invoke_and_check Lib.helicsCoreAddDestinationFilterToEndpoint(core, filter, endpoint)
end

"""
Get an identifier for the broker

# Arguments

- `broker`: the broker to query

# Returns

- a string containing the identifier for the broker

"""
function helicsBrokerGetIdentifier(broker::Broker)::String
    return Lib.helicsBrokerGetIdentifier(broker) |> unsafe_string
end

"""
Get an identifier for the core

# Arguments

- `core`: the core to query

# Returns

- a string with the identifier of the core

"""
function helicsCoreGetIdentifier(core::Core)::String
    return Lib.helicsCoreGetIdentifier(core) |> unsafe_string
end

"""
Get the network address associated with a broker

# Arguments

- `broker`: the broker to query

# Returns

- a string with the network address of the broker

"""
function helicsBrokerGetAddress(broker::Broker)::String
    return Lib.helicsBrokerGetAddress(broker) |> unsafe_string
end

"""
Set the core to ready for init

this function is used for cores that have filters but no federates so there needs to be a direct signal to the core to trigger the federation initialization

# Arguments

- `core`: the core object to enable init values for

"""
function helicsCoreSetReadyToInit(core::Core)
    Utils.@invoke_and_check Lib.helicsCoreSetReadyToInit(core)
end

"""
Get an identifier for the core

# Arguments

- `core`: the core to query

# Returns

- a void enumeration indicating any error condition

"""
function helicsCoreDisconnect(core::Core)
    Utils.@invoke_and_check Lib.helicsCoreDisconnect(core)
end

"""
Get an existing [`Federate`](@ref) from a core by name

the federate must have been created by one of the other functions and at least one of the objects referencing the created
    federate must still be active in the process

# Arguments

- `fedName`: the name of the federate to retrieve

# Returns

- NULL if no fed is available by that name otherwise a [`Federate`](@ref) with that name
"""
function helicsGetFederateByName(fedName::String)::CombinationFederate
    return Utils.@invoke_and_check Lib.helicsGetFederateByName(fedName)
end

"""
Disconnect a broker

# Arguments

- `broker`: the broker to disconnect

"""
function helicsBrokerDisconnect(broker::Broker)
    Utils.@invoke_and_check Lib.helicsBrokerDisconnect(broker)
end

"""
Disconnect and free a broker
"""
function helicsFederateDestroy(fed::Federate)
    Lib.helicsFederateDestroy(fed)
end

"""
Disconnect and free a broker
"""
function helicsBrokerDestroy(broker::Broker)
    Lib.helicsBrokerDestroy(broker)
end

"""
Disconnect and free a core
"""
function helicsCoreDestroy(core::Core)
    Lib.helicsCoreDestroy(core)
end

"""
Release the memory associated with a core
"""
function helicsCoreFree(core::Core)
    Lib.helicsCoreFree(core)
end

"""
Release the memory associated with a broker
"""
function helicsBrokerFree(broker::Broker)
    Lib.helicsBrokerFree(broker)
end

"""
Create a value federate from a [`FederateInfo`](@ref) object

[`Federate`](@ref) objects can be used in all functions that take a [`Federate`](@ref) object as an argument

# Arguments

- `fedName`: the name of the federate to create, can NULL or an empty string to use the default name from fi or an assigned name
- `fi`: the [`FederateInfo`](@ref) object that contains details on the federate

# Returns

- an opaque value [`Federate`](@ref)

"""
function helicsCreateValueFederate(fedName::String, fi::FederateInfo)::ValueFederate
    return Utils.@invoke_and_check Lib.helicsCreateValueFederate(fedName, fi)
end

"""
Create a value federate from a JSON file, JSON string, or TOML file

[`Federate`](@ref) objects can be used in all functions that take a [`Federate`](@ref) object as an argument

# Arguments

- `configFile`:  a JSON file or a JSON string or TOML file that contains setup and configuration information

# Returns

- an opaque value [`Federate`](@ref)

"""
function helicsCreateValueFederateFromConfig(configFile::String)::ValueFederate
    return Utils.@invoke_and_check Lib.helicsCreateValueFederateFromConfig(configFile)
end

"""
Create a [`MessageFederate`](@ref) from a [`FederateInfo`](@ref) object

[`MessageFederate`](@ref) objects can be used in all functions that take a [`MessageFederate`](@ref) or [`Federate`](@ref) object as an argument

# Arguments

- `fedName`: the name of the federate to create
- `fi`: the [`FederateInfo`](@ref) object that contains details on the federate

# Returns

- an opaque [`MessageFederate`](@ref)

"""
function helicsCreateMessageFederate(fedName::String, fi::FederateInfo)::MessageFederate
    return Utils.@invoke_and_check Lib.helicsCreateMessageFederate(fedName, fi)
end

"""
Create a [`MessageFederate`](@ref) from a JSON file or JSON string or TOML file

[`MessageFederate`](@ref) objects can be used in all functions that take a [`MessageFederate`](@ref) or [`Federate`](@ref) object as an argument

# Arguments

- `configFile`:  a Config(JSON,TOML) file or a JSON string that contains setup and configuration information

# Returns

- an opaque [`MessageFederate`](@ref)

"""
function helicsCreateMessageFederateFromConfig(configFile::String)::MessageFederate
    return Utils.@invoke_and_check Lib.helicsCreateMessageFederateFromConfig(configFile)
end

"""
Create a [`CombinationFederate`](@ref) from a [`FederateInfo`](@ref) object

[`CombinationFederate`](@ref) are both [`ValueFederate`](@ref) and [`MessageFederate`](@ref), objects can be used in all functions that take a [`Federate`](@ref), [`MessageFederate`](@ref) or [`ValueFederate`](@ref) object as an argument

# Arguments

- `fedName`: a string with the name of the federate, can be NULL or an empty string to pull the default name from fi
- `fi`: the [`FederateInfo`](@ref) object that contains details on the federate

# Returns

- an opaque [`ValueFederate`](@ref), nullptr if the object creation failed

"""
function helicsCreateCombinationFederate(fedName::String, fi::FederateInfo)::CombinationFederate
    return Utils.@invoke_and_check Lib.helicsCreateCombinationFederate(fedName, fi)
end

"""
Create a [`CombinationFederate`](@ref) from a JSON file or JSON string

[`CombinationFederate`](@ref) are both [`ValueFederate`](@ref) and [`MessageFederate`](@ref), objects can be used in all functions that take a [`Federate`](@ref), [`MessageFederate`](@ref) or [`ValueFederate`](@ref) object as an argument

# Arguments

- `configFile`:  a JSON file or a JSON string or TOML file that contains setup and configuration information

# Returns

- an opaque [`CombinationFederate`](@ref)

"""
function helicsCreateCombinationFederateFromConfig(configFile::String)::CombinationFederate
    return Utils.@invoke_and_check Lib.helicsCreateCombinationFederateFromConfig(configFile)
end

"""
Create a new reference to an existing federate

this will create a new [`Federate`](@ref) object that references the existing federate it must be freed as well

# Arguments

- `fed`: an existing [`Federate`](@ref)

# Returns

- a new reference to the same federate
"""
function helicsFederateClone(fed::T)::T where T <: Federate
    return Utils.@invoke_and_check Lib.helicsFederateClone(fed)
end

"""
Create a [`FederateInfo`](@ref) object for specifying federate information when constructing a federate

# Returns

- a [`FederateInfo`](@ref) object which is a reference to the created object

"""
function helicsCreateFederateInfo()::FederateInfo
    return Lib.helicsCreateFederateInfo()
end

"""
Create a [`FederateInfo`](@ref) object from an existing one and clone the information

# Arguments

- `fi`: a [`FederateInfo`](@ref) object to duplicate

# Returns

- a helics_federate_info object which is a reference to the created object

"""
function helicsFederateInfoClone(fi::FederateInfo)::FederateInfo
    return Utils.@invoke_and_check Lib.helicsFederateInfoClone(fi)
end

"""
Load a [`FederateInfo`](@ref) from command line arguments

# Arguments

- `fi`: a [`FederateInfo`](@ref) object
- `argc`: the number of command line arguments
- `argv`: an array of strings from the command line

"""
function helicsFederateInfoLoadFromArgs(fi::FederateInfo, argc::Int, argv::Vector{<:AbstractString})
    Utils.@invoke_and_check Lib.helicsFederateInfoLoadFromArgs(fi, argc, argv)
end

"""
Delete the memory associated with a [`FederateInfo`](@ref) object
"""
function helicsFederateInfoFree(fi::FederateInfo)
    Lib.helicsFederateInfoFree(fi)
end

"""
Check if a federate_object is valid

# Returns

- `true` if the federate is a valid active federate, `false` otherwise
"""
function helicsFederateIsValid(fed::Federate)::Bool
    return Lib.helicsFederateIsValid(fed) == 1 ? true : false
end

"""
Set the name of the core to link to for a federate

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `corename`: the identifier for a core to link to

"""
function helicsFederateInfoSetCoreName(fi::FederateInfo, corename::String)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetCoreName(fi, corename)
end

"""
Set the initialization string for the core usually in the form of command line arguments

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `coreInit`: a string with the core initialization strings

"""
function helicsFederateInfoSetCoreInitString(fi::FederateInfo, coreInit::String)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetCoreInitString(fi, coreInit)
end

"""
Set the core type by integer code

valid values available by definitions in api-data.h

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `coretype`: an numerical code for a core type see /ref helics_core_type

"""
function helicsFederateInfoSetCoreType(fi::FederateInfo, coretype::Union{Int, HELICS.HELICS_CORE_TYPE})
    Utils.@invoke_and_check Lib.helicsFederateInfoSetCoreType(fi, coretype)
end

"""
Set the core type from a string

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `coretype`: a string naming a core type

"""
function helicsFederateInfoSetCoreTypeFromString(fi::FederateInfo, coretype::String)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetCoreTypeFromString(fi, coretype)
end

"""
Set the name or connection information for a broker

this is only used if the core is automatically created, the broker information will be transferred to the core for connection

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `broker`: a string which defined the connection information for a broker either a name or an address

"""
function helicsFederateInfoSetBroker(fi::FederateInfo, broker::Broker)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetBroker(fi, broker)
end

"""
Set the port to use for the broker

this is only used if the core is automatically created, the broker information will be transferred to the core for connection this will only be useful for network broker connections

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `brokerPort`: the integer port number to use for connection with a broker

"""
function helicsFederateInfoSetBrokerPort(fi::FederateInfo, brokerPort::Int)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetBrokerPort(fi, brokerPort)
end

"""
Set the local port to use

this is only used if the core is automatically created, the port information will be transferred to the core for connection

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `localPort`: a string with the port information to use as the local server port can be a number or "auto" or "os_local"

"""
function helicsFederateInfoSetLocalPort(fi::FederateInfo, localPort::Int)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetLocalPort(fi, localPort)
end

"""
Get a property index for use in [`helicsFederateInfoSetFlagOption`](@ref), [`helicsFederateInfoSetTimeProperty`](@ref), [`helicsFederateInfoSetIntegerProperty`](@ref)

# Arguments

- `val`: a string with the property name

# Returns

- an int with the property code (-1) if not a valid property

"""
function helicsGetPropertyIndex(val::String)::Int
    return Lib.helicsGetPropertyIndex(val)
end

"""
Get an option index for use in [`helicsPublicationSetOption`](@ref), [`helicsInputSetOption`](@ref), [`helicsEndpointSetOption`](@ref), [`helicsFilterSetOption`](@ref), and the corresponding get functions

# Arguments

- `val`: a string with the option name

# Returns

- an int with the option index (-1) if not a valid property

"""
function helicsGetOptionIndex(val::String)::Int
    return Lib.helicsGetOptionIndex(val)
end

"""
Set a flag in the info structure

valid flags are available [`HELICS_FEDERATE_FLAGS`](@ref)

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `flag`: a numerical index for a flag
- `value`: the desired value of the flag `true` or `false`

"""
function helicsFederateInfoSetFlagOption(fi::FederateInfo, flag::Union{Int, HELICS.HELICS_FEDERATE_FLAGS}, value::Bool)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetFlagOption(fi, flag, value ? 1 : 0)
end

"""
Set the separator character in the info structure

the separator character is the separation character for local publications/endpoints in creating their global name. for example if the separator character is '/'  then a local endpoint would have a globally reachable name of fedName/localName

# Arguments

- `fi`: the [`FederateInfo`](@ref) object to alter
- `separator`: the character to use as a separator

"""
function helicsFederateInfoSetSeparator(fi::FederateInfo, separator::Char)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetSeparator(fi, separator)
end

"""
"""
function helicsFederateInfoSetTimeProperty(fi::FederateInfo, timeProperty::Union{Int, HELICS.HELICS_PROPERTIES}, propertyValue::HELICS.HELICS_TIME)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetTimeProperty(fi, timeProperty, propertyValue)
end

"""
"""
function helicsFederateInfoSetIntegerProperty(fi::FederateInfo, intProperty::Union{Int, HELICS.HELICS_PROPERTIES}, propertyValue::Int)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetIntegerProperty(fi, intProperty, propertyValue)
end

"""
Load interfaces from a file

# Arguments

- `fed`: the federate to which to load interfaces
- `file`: the name of a file to load the interfaces from either JSON, or TOML

"""
function helicsFederateRegisterInterfaces(fed::Federate, file::String)
    Utils.@invoke_and_check Lib.helicsFederateRegisterInterfaces(fed, file)
end

"""
Finalize the federate this function halts all communication in the federate and disconnects it from the core
"""
function helicsFederateFinalize(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateFinalize(fed)
end

"""
Finalize the federate in an async call
"""
function helicsFederateFinalizeAsync(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateFinalizeAsync(fed)
end

"""
Complete the asynchronous finalize call
"""
function helicsFederateFinalizeComplete(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateFinalizeComplete(fed)
end

"""
Release the memory associated withe a federate
"""
function helicsFederateFree(fed::Federate)
    Lib.helicsFederateFree(fed)
end

"""
Call when done using the helics library,  this function will ensure the threads are closed properly if possible
    this should be the last call before exiting,
"""
function helicsCloseLibrary()
    Lib.helicsCloseLibrary()
end

"""
Enter the initialization state of a federate

the initialization state allows initial values to be set and received if the iteration is requested on entry to the execution state. This is a blocking call and will block until the core allows it to proceed.

# Arguments

- `fed`: the federate to operate on

"""
function helicsFederateEnterInitializingMode(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateEnterInitializingMode(fed)
end

"""
Non blocking alternative to [`helicsFederateEnterInitializingMode`](@ref).
The function [`helicsFederateFinalize`](@ref) must be called to finish the operation

# Arguments

- `fed`: the federate to operate on

"""
function helicsFederateEnterInitializingModeAsync(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateEnterInitializingModeAsync(fed)
end

"""
Check if the current Asynchronous operation has completed

# Arguments

- `fed`: the federate to operate on

# Returns

- `false` if not completed, `true` if completed
"""
function helicsFederateIsAsyncOperationCompleted(fed::Federate)::Bool
    r = Utils.@invoke_and_check Lib.helicsFederateIsAsyncOperationCompleted(fed)
    return r == 1 ? true : false
end

"""
Finalize the entry to initialize mode that was initiated with [`helicsFederateEnterInitializingModeAsync`](@ref)

# Arguments

- `fed`: the federate desiring to complete the initialization step function
"""
function helicsFederateEnterInitializingModeComplete(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateEnterInitializingModeComplete(fed)
end

"""
Request that the federate enter the Execution mode

this call is blocking until granted entry by the core object for an asynchronous alternative call [`helicsFederateEnterExecutingModeAsync`](@ref) on return from this call the federate will be at time 0.

# Arguments

- `fed`: a federate to change modes

"""
function helicsFederateEnterExecutingMode(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateEnterExecutingMode(fed)
end

"""
Request that the federate enter the Execution mode

this call is non-blocking and will return immediately.
Call [`helicsFederateEnterExecutingModeComplete`](@ref) to finish the call sequence.

# Arguments

- `fed`: the federate object to complete the call

"""
function helicsFederateEnterExecutingModeAsync(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateEnterExecutingModeAsync(fed)
end

"""
Complete the call to [`helicsFederateEnterExecutingModeAsync`](@ref)

# Arguments

- `fed`: the federate object to complete the call

"""
function helicsFederateEnterExecutingModeComplete(fed::Federate)
    Utils.@invoke_and_check Lib.helicsFederateEnterExecutingModeComplete(fed)
end

"""
Request an iterative time

this call allows for finer grain control of the iterative process then [`helicsFederateRequestTime`](@ref) it takes a time and iteration request and return a time and iteration status.

# Arguments

- `fed`: the federate to make the request of
- `iterate`: the requested iteration mode

# Returns

- an iteration structure with field containing the time and iteration status

"""
function helicsFederateEnterExecutingModeIterative(fed::Federate, iterate::Union{Int, HELICS.HELICS_ITERATION_REQUEST})::HELICS.HELICS_ITERATION_RESULT
    return Utils.@invoke_and_check Lib.helicsFederateEnterExecutingModeIterative(fed, iterate)
end

"""
"""
function helicsFederateEnterExecutingModeIterativeAsync(fed::Federate, iterate::Union{Int, HELICS.HELICS_ITERATION_REQUEST})::HELICS.HELICS_ITERATION_RESULT
    return Utils.@invoke_and_check Lib.helicsFederateEnterExecutingModeIterativeAsync(fed, iterate)
end

"""
Complete the asynchronous iterative call into ExecutionModel

# Arguments

- `fed`: the federate to make the request of

# Returns

- an iteration object containing the iteration time and iteration_status

"""
function helicsFederateEnterExecutingModeIterativeComplete(fed::Federate)::HELICS.HELICS_ITERATION_RESULT
    return Utils.@invoke_and_check Lib.helicsFederateEnterExecutingModeIterativeComplete(fed)
end

"""
Get the current state of a federate

# Arguments

- `fed`: the [`Federate`](@ref) to query

# Returns

- state the resulting state if void return helics_ok
"""
function helicsFederateGetState(fed::Federate)::HELICS.HELICS_FEDERATE_STATE
    return Utils.@invoke_and_check Lib.helicsFederateGetState(fed)
end

"""
Get the core object associated with a federate

# Arguments

- `fed`: a federate object

# Returns

- a core object, nullptr if invalid

"""
function helicsFederateGetCoreObject(fed::Federate)::Core
    return Utils.@invoke_and_check Lib.helicsFederateGetCoreObject(fed)
end

"""
Request the next time for federate execution

# Arguments

- `fed`: the federate to make the request of
- `requestTime`: the next requested time

# Returns

- the time granted to the federate
    invalid
"""
function helicsFederateRequestTime(fed::Federate, requestTime::HELICS.HELICS_TIME)::Float64
    return Utils.@invoke_and_check Lib.helicsFederateRequestTime(fed, requestTime)
end

"""
Request the next time step for federate execution

feds should have setup the period or minDelta for this to work well but it will request the next time step which is the current time plus the minimum time step.

# Arguments

- `fed`: the federate to make the request of

# Returns

- the time granted to the federate
"""
function helicsFederateRequestNextStep(fed::Federate)::Float64
    return Utils.@invoke_and_check Lib.helicsFederateRequestNextStep(fed)
end

"""
Request an iterative time

this call allows for finer grain control of the iterative process then [`helicsFederateRequestTime`](@ref) it takes a time and iteration request and return a time and iteration status.

# Arguments

- `fed`: the federate to make the request of
- `requestTime`: the next desired time
- `iterate`: the requested iteration mode

# Returns

- the granted time
- the iteration specification of the result

"""
function helicsFederateRequestTimeIterative(fed::Federate, requestTime::HELICS.HELICS_TIME, iterate::Union{Int, HELICS.HELICS_ITERATION_REQUEST})::Tuple{Float64, HELICS.HELICS_ITERATION_RESULT}
    outIteration = Ref{HELICS.HELICS_ITERATION_RESULT}(0)
    t = Utils.@invoke_and_check Lib.helicsFederateRequestTimeIterative(fed, requestTime, iterate, outIteration)
    return t, outIteration
end

"""
Request the next time for federate execution in an asynchronous call
Call [`helicsFederateRequestTimeComplete`](@ref) to finish the call

# Arguments

- `fed`: the federate to make the request of
- `requestTime`: the next requested time

"""
function helicsFederateRequestTimeAsync(fed::Federate, requestTime::HELICS.HELICS_TIME)
    Utils.@invoke_and_check Lib.helicsFederateRequestTimeAsync(fed, requestTime)
end

"""
Complete an asynchronous requestTime call

# Arguments

- `fed`: the federate to make the request of

# Returns

- the time granted to the federate
"""
function helicsFederateRequestTimeComplete(fed::Federate)::Float64
    return Utils.@invoke_and_check Lib.helicsFederateRequestTimeComplete(fed)
end

"""
Request an iterative time through an asynchronous call

this call allows for finer grain control of the iterative process then [`helicsFederateRequestTime`](@ref) it takes a time an iteration request and returns a time and iteration status call [`helicsFederateRequestTimeIterativeComplete`](@ref) to finish the process.

# Arguments

- `fed`: the federate to make the request of
- `requestTime`: the next desired time
- `iterate`: the requested iteration mode

# Returns

- a void object with a return code of the result

"""
function helicsFederateRequestTimeIterativeAsync(fed::Federate, requestTime::HELICS.HELICS_TIME, iterate::Union{Int, HELICS.HELICS_ITERATION_REQUEST})
    Utils.@invoke_and_check Lib.helicsFederateRequestTimeIterativeAsync(fed, requestTime, iterate)
end

"""
Complete an iterative time request asynchronous call

# Arguments

- `fed`: the federate to make the request of

# Returns

- the granted time
- `outIterate`  the iteration specification of the result

"""
function helicsFederateRequestTimeIterativeComplete(fed::Federate)::Tuple{Float64, HELICS.HELICS_ITERATION_RESULT}
    return outIterate = Ref{HELICS.HELICS_ITERATION_RESULT}(0)
    t = Utils.@invoke_and_check Lib.helicsFederateRequestTimeIterativeComplete(fed, outIterate)
    return t, outIterate[]
end

"""
Get the name of the federate

# Arguments

- `fed`: the [`Federate`](@ref) to query

# Returns

- a string with the name

"""
function helicsFederateGetName(fed::Federate)::String
    return Lib.helicsFederateGetName(fed) |> unsafe_string
end

"""
Set a time based property for a federate

# Arguments

- `fed`: the [`Federate`](@ref) set the property for
- `timeProperty`: a integer code for a time property
- `time`: the requested value of the property

"""
function helicsFederateSetTimeProperty(fed::Federate, timeProperty::Union{Int, HELICS.HELICS_PROPERTIES}, time::HELICS.HELICS_TIME)
    Utils.@invoke_and_check Lib.helicsFederateSetTimeProperty(fed, timeProperty, time)
end

"""
Set a flag for the federate

# Arguments

- `fed`: the federate to alter a flag for
- `flag`: the flag to change
- `flagValue`: the new value of the flag 0 for false !=0 for true

"""
function helicsFederateSetFlagOption(fed::Federate, flag::Int, flagValue::Bool)
    Utils.@invoke_and_check Lib.helicsFederateSetFlagOption(fed, flag, flagValue ? 1 : 0)
end

"""
Set the separator character in a federate

the separator character is the separation character for local publications/endpoints in creating their global name. for example if the separator character is '/'  then a local endpoint would have a globally reachable name of fedName/localName.

# Arguments

- `fed`: the [`FederateInfo`](@ref) object to alter
- `separator`: the character to use as a separator

"""
function helicsFederateSetSeparator(fed::Federate, separator::Char)
    Utils.@invoke_and_check Lib.helicsFederateSetSeparator(fed, separator)
end

"""
Set an integer based property of a federate

# Arguments

- `fed`: the federate to change the property for
- `intProperty`: the property to set
- `propertyVal`: the value of the property

"""
function helicsFederateSetIntegerProperty(fed::Federate, intProperty::Union{Int, HELICS.HELICS_PROPERTIES}, propertyVal::Int)
    Utils.@invoke_and_check Lib.helicsFederateSetIntegerProperty(fed, intProperty, propertyVal)
end

"""
Get the current value of a time based property in a federate

# Arguments

- `fed`: the federate query
- `timeProperty`: the property to query

"""
function helicsFederateGetTimeProperty(fed::Federate, timeProperty::Union{Int, HELICS.HELICS_PROPERTIES})::Float64
    return Utils.@invoke_and_check Lib.helicsFederateGetTimeProperty(fed, timeProperty)
end

"""
Get a flag value for a federate

# Arguments

- `fed`: the federate to get the flag for
- `flag`: the flag to query

# Returns

- the value of the flag

"""
function helicsFederateGetFlagOption(fed::Federate, flag::Int)::Bool
    return Utils.@invoke_and_check Lib.helicsFederateGetFlagOption(fed, flag)
end

"""
Set the logging level for the federate

debug and trace only do anything if they were enabled in the compilation

# Arguments

- `fed`: the federate to get the flag for
- `intProperty`: a code for the property to set [`HELICS_HANDLE_OPTIONS`](@ref)

# Returns

- the value of the property

"""
function helicsFederateGetIntegerProperty(fed::Federate, intProperty::Union{Int, HELICS.HELICS_HANDLE_OPTIONS})::Int
    return Utils.@invoke_and_check Lib.helicsFederateGetIntegerProperty(fed, intProperty)
end

"""
Get the current time of the federate

# Arguments

- `fed`: the [`Federate`](@ref) to query

# Returns

- the current time of the federate

"""
function helicsFederateGetCurrentTime(fed::Federate)::Float64
    return Utils.@invoke_and_check Lib.helicsFederateGetCurrentTime(fed)
end

"""
Set a federation global value through a federate

this overwrites any previous value for this name

# Arguments

- `fed`: the federate to set the global through
- `valueName`: the name of the global to set
- `value`: the value of the global

"""
function helicsFederateSetGlobal(fed::Federate, valueName::String, value::String)
    Utils.@invoke_and_check Lib.helicsFederateSetGlobal(fed, valueName, value)
end

"""
Set a global value in a core

this overwrites any previous value for this name

# Arguments

- `core`: the core to set the global through
- `valueName`: the name of the global to set
- `value`: the value of the global

"""
function helicsCoreSetGlobal(core::Core, valueName::String, value::String)
    Utils.@invoke_and_check Lib.helicsCoreSetGlobal(core, valueName, value)
end

"""
Set a federation global value

this overwrites any previous value for this name

# Arguments

- `broker`: the broker to set the global through
- `valueName`: the name of the global to set
- `value`: the value of the global

"""
function helicsBrokerSetGlobal(broker::Broker, valueName::String, value::String)
    Utils.@invoke_and_check Lib.helicsBrokerSetGlobal(broker, valueName, value)
end

"""
Create a [`Query`](@ref)

a [`Query`](@ref) consists of a target and query string

# Arguments

- `target`: the name of the target to query
- `query`: the query string to make of the target

"""
function helicsCreateQuery(target::String, query::String)::Query
    return Lib.helicsCreateQuery(target, query)
end

"""
Execute a query

the call will block until the [`Query`](@ref) finishes which may require communication or other delays

# Arguments

- `query`: the [`Query`](@ref) to use in the query
- `fed`: a federate to send the [`Query`](@ref) through

# Returns

- a string. String will remain valid until the [`Query`](@ref) is freed or executed again
the return will be nullptr if fed or [`Query`](@ref) is an invalid object, the return string will be "#invalid" if the [`Query`](@ref) itself was invalid

"""
function helicsQueryExecute(query::Query, fed::Federate)::String
    r = Utils.@invoke_and_check Lib.helicsQueryExecute(query, fed)
    return r |> unsafe_string
end

"""
Execute a [`Query`](@ref) directly on a core

the call will block until the [`Query`](@ref) finishes which may require communication or other delays

# Arguments

- `query`: the [`Query`](@ref) to use in the query
- `core`: the core to send the [`Query`](@ref) to

# Returns

- a string. String will remain valid until the [`Query`](@ref) is freed or executed again
the return will be nullptr if fed or [`Query`](@ref) is an invalid object, the return string will be "#invalid" if the [`Query`](@ref) itself was invalid

"""
function helicsQueryCoreExecute(query::Query, core::Core)::String
    r = Utils.@invoke_and_check Lib.helicsQueryCoreExecute(query, core)
    return r |> unsafe_string
end

"""
Execute a [`Query`](@ref) directly on a broker

the call will block until the [`Query`](@ref) finishes which may require communication or other delays

# Arguments

- `query`: the [`Query`](@ref) to use in the query
- `broker`: the broker to send the [`Query`](@ref) to

# Returns

- a string. String will remain valid until the [`Query`](@ref) is freed or executed again
the return will be nullptr if fed or [`Query`](@ref) is an invalid object, the return string will be "#invalid" if the [`Query`](@ref) itself was invalid

"""
function helicsQueryBrokerExecute(query::Query, broker::Broker)::String
    r = Utils.@invoke_and_check Lib.helicsQueryBrokerExecute(query, broker)
    return r |> unsafe_string
end

"""
Execute a [`Query`](@ref) in a non-blocking call

# Arguments

- `query`: the [`Query`](@ref) to use in the query
- `fed`: a federate to send the [`Query`](@ref) through

"""
function helicsQueryExecuteAsync(query::Query, fed::Federate)
    Utils.@invoke_and_check Lib.helicsQueryExecuteAsync(query, fed)
end

"""
Complete the return from a [`Query`](@ref) called with [`helicsQueryExecuteAsync`](@ref)

the function will block until the [`Query`](@ref) completes [`helicsQueryIsCompleted`](@ref) can be called to determine if a [`Query`](@ref) has completed or not.

# Arguments

- `query`: the [`Query`](@ref) to complete execution of

# Returns

- a string. String will remain valid until the [`Query`](@ref) is freed or executed again
the return will be nullptr if [`Query`](@ref) is an invalid object

"""
function helicsQueryExecuteComplete(query::Query)::String
    r = Utils.@invoke_and_check Lib.helicsQueryExecuteComplete(query)
    return r |> unsafe_string
end

"""
Check if an asynchronously executed [`Query`](@ref) has completed

this function should usually be called after a QueryExecuteAsync function has been called.

# Arguments

- `query`: the [`Query`](@ref) to check if completed

# Returns

- will return `true` if an asynchronous [`Query`](@ref) has complete or a regular [`Query`](@ref) call was made with a result and `false` if an asynchronous [`Query`](@ref) has not completed or is invalid

"""
function helicsQueryIsCompleted(query::Query)::Bool
    return Lib.helicsQueryIsCompleted(query) == 1 ? true : false
end

"""
Free the memory associated with a [`Query`](@ref)
"""
function helicsQueryFree(query::Query)
    Lib.helicsQueryFree(query)
end

"""
Function to do some housekeeping work

this runs some cleanup routines and tries to close out any residual thread that haven't been shutdown yet.
"""
function helicsCleanupLibrary()
    Lib.helicsCleanupLibrary()
end

function helicsBrokerAddLoggingCallback(broker::Broker, logger)
    Utils.@invoke_and_check Lib.helicsBrokerAddLoggingCallback(broker, logger)
end

function helicsCoreAddLoggingCallback(core::Core, logger)
    Utils.@invoke_and_check Lib.helicsCoreAddLoggingCallback(core, logger)
end

function helicsFederateAddLoggingCallback(fed::Federate, logger)
    Utils.@invoke_and_check Lib.helicsFederateAddLoggingCallback(fed, logger)
end

function helicsFederateCreateMessageObject(fed::Federate)::Message
    Utils.@invoke_and_check Lib.helicsFederateCreateMessageObject(fed)
end

"""
"""
function helicsMessageGetSource(message::Message)::String
    return Lib.helicsMessageGetSource(message) |> unsafe_string
end

"""
"""
function helicsMessageGetDestination(message::Message)::String
    return Lib.helicsMessageGetDestination(message) |> unsafe_string
end

"""
"""
function helicsMessageGetOriginalSource(message::Message)::String
    return Lib.helicsMessageGetOriginalSource(message) |> unsafe_string
end

"""
"""
function helicsMessageGetOriginalDestination(message::Message)::String
    return Lib.helicsMessageGetOriginalDestination(message) |> unsafe_string
end

"""
"""
function helicsMessageGetTime(message::Message)::Float64
    return Lib.helicsMessageGetTime(message)
end

"""
"""
function helicsMessageGetString(message::Message)::String
    return Lib.helicsMessageGetString(message) |> unsafe_string
end

"""
"""
function helicsMessageGetMessageID(message::Message)::Int
    return Lib.helicsMessageGetMessageID(message)
end

"""
"""
function helicsMessageCheckFlag(message::Message, flag::Union{Int, HELICS.HELICS_FEDERATE_FLAGS})::Bool
    return Lib.helicsMessageCheckFlag(message, flag) == 1 ? true : false
end

"""
"""
function helicsMessageGetRawDataSize(message::Message)::Int
    return Lib.helicsMessageGetRawDataSize(message)
end

"""
"""
function helicsMessageGetRawData(message::Message)::String
    maxlen = Cint(helicsMessageGetRawDataSize(message))
    data = Vector{Cchar}(undef, maxlen)
    actualSize = Ref(maxlen)
    Utils.@invoke_and_check Lib.helicsMessageGetRawData(message, Ref(data), maxlen, actualSize)
    return String(Vector{UInt8}(data))
end

"""
"""
function helicsMessageGetRawDataPointer(message::Message)::Ptr{Cvoid}
    return Lib.helicsMessageGetRawDataPointer(message)
end

"""
"""
function helicsMessageIsValid(message::Message)::Bool
    return Lib.helicsMessageIsValid(message) == 1 ? true : false
end

"""
"""
function helicsMessageSetSource(message::Message, src::String)
    Utils.@invoke_and_check Lib.helicsMessageSetSource(message, src)
end

"""
"""
function helicsMessageSetDestination(message::Message, dest::String)
    Utils.@invoke_and_check Lib.helicsMessageSetDestination(message, dest)
end

"""
"""
function helicsMessageSetOriginalSource(message::Message, src::String)
    Utils.@invoke_and_check Lib.helicsMessageSetOriginalSource(message, src)
end

"""
"""
function helicsMessageSetOriginalDestination(message::Message, dest::String)
    Utils.@invoke_and_check Lib.helicsMessageSetOriginalDestination(message, dest)
end

"""
"""
function helicsMessageSetTime(message::Message, time::HELICS.HELICS_TIME)
    Utils.@invoke_and_check Lib.helicsMessageSetTime(message, time)
end

"""
"""
function helicsMessageResize(message::Message, newSize::Int)
    Utils.@invoke_and_check Lib.helicsMessageResize(message, newSize)
end

"""
"""
function helicsMessageReserve(message::Message, reserveSize::Int)
    Utils.@invoke_and_check Lib.helicsMessageReserve(message, reserverSize)
end

"""
"""
function helicsMessageSetMessageID(message::Message, messageID::Int32)
    Utils.@invoke_and_check helicsMessageSetMessageID(message, messageID)
end

"""
"""
function helicsMessageClearFlags(message::Message)
    Lib.helicsMessageClearFlags(message)
end

"""
"""
function helicsMessageSetFlagOption(message::Message, flag::Union{Int, HELICS_FEDERATE_FLAGS}, flagValue::Bool)
    Utils.@invoke_and_check Lib.helicsMessageSetFlagOption(message, flag, flagValue)
end

"""
"""
function helicsMessageSetString(message::Message, str::String)
    Utils.@invoke_and_check Lib.helicsMessageSetString(message, str)
end

"""
"""
function helicsMessageSetData(message::Message, data::String)
    inputDataLength = length(data)
    data = pointer(data)
    Utils.@invoke_and_check Lib.helicsMessageSetData(message, data, inputDataLength)
end

"""
"""
function helicsMessageAppendData(message::Message, data::String)
    inputDataLength = length(data)
    data = pointer(data)
    Utils.@invoke_and_check Lib.helicsMessageAppendData(message, data, inputDataLength)
end

function helicsInputGetInjectionUnits(ipt::Input)::String
    return Lib.helicsInputGetInjectionUnits(ipt) |> unsafe_string
end

function helicsFederateRegisterFromPublicationJSON(fed::Federate, json::String)
    Utils.@invoke_and_check Lib.helicsFederateRegisterFromPublicationJSON(fed, json)
end

function helicsCoreSetLogFile(core::Core, logFileName::String)
    Utils.@invoke_and_check Lib.helicsCoreSetLogFile(core, logFileName)
end

function helicsBrokerSetLogFile(broker::Broker, logFileName::String)
    Utils.@invoke_and_check Lib.helicsBrokerSetLogFile(broker::Broker, logFileName::String)
end

function helicsFederateClearMessages(fed::Federate)
    Lib.helicsFederateClearMessages(fed)
end

function helicsFederateClearUpdates(fed::Federate)
    Lib.helicsFederateClearUpdates(fed)
end

function helicsFederateLogInfoMessage(fed::Federate, logmessage::String)
    Utils.@invoke_and_check Lib.helicsFederateLogInfoMessage(fed, logmessage)
end

function helicsInputGetExtractionUnits(ipt::Input)::String
    return Lib.helicsInputGetExtractionUnits(ipt) |> unsafe_string
end

function helicsFederateSetLogFile(fed::Federate, logFile::String)
    Utils.@invoke_and_check Lib.helicsFederateSetLogFile(fed, logFile)
end

function helicsFederateLogLevelMessage(fed::Federate, loglevel::Int, logmessage::String)
    Utils.@invoke_and_check Lib.helicsFederateLogLevelMessage(fed, loglevel, logmessage)
end

function helicsCoreGetAddress(core::Core)::String
    return Lib.helicsCoreGetAddress(core) |> unsafe_string
end

function helicsInputClearUpdate(ipt::Input)
    Lib.helicsInputClearUpdate(ipt)
end

helicsFederateSetLoggingCallback(fed::Federate, logger, userdata) = error("Not implemented.")

function helicsFederateLogErrorMessage(fed::Federate, logmessage::String)
    Utils.@invoke_and_check Lib.helicsFederateLogErrorMessage(fed, logmessage)
end

function helicsCoreWaitForDisconnect(core::Core, msWait::Int)
    Utils.@invoke_and_check Lib.helicsCoreWaitForDisconnect(core, msWait)
end

function helicsFederateLogDebugMessage(fed::Federate, logmessage::String)
    Utils.@invoke_and_check Lib.helicsFederateLogDebugMessage(fed, logmessage)
end

helicsBrokerSetLoggingCallback(broker::Broker, logger, userdata) = error("Not implemented.")

function helicsFederateInfoSetBrokerKey(fi::FederateInfo, brokerKey::String)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetBrokerKey(fi, brokerKey)
end

function helicsFederateLogWarningMessage(fed::Federate, logmessage::String)
    Utils.@invoke_and_check Lib.helicsFederateLogWarningMessage(fed, logmessage)
end

function helicsFederatePublishJSON(fed::Federate, json::String)
    Utils.@invoke_and_check Lib.helicsFederatePublishJSON(fed, json)
end

function helicsFederateRequestTimeAdvance(fed::Federate, timeDelta::HELICS.HELICS_TIME)::Float64
    return Utils.@invoke_and_check Lib.helicsFederateRequestTimeAdvance(fed, timeDelta)
end

function helicsEndpointClearMessages(endpoint::Endpoint)
    Lib.helicsEndpointClearMessages(endpoint)
end

helicsCoreSetLoggingCallback(core::Core, logger, userdata) = error("Not implemented")

function helicsFederateInfoSetBrokerInitString(fi::FederateInfo, brokerInit::String)
    Utils.@invoke_and_check Lib.helicsFederateInfoSetBrokerInitString(fi::FederateInfo, brokerInit)
end

function helicsBrokerMakeConnections(broker::Broker, file::String)
    Utils.@invoke_and_check Lib.helicsBrokerMakeConnections(broker, file)
end

function helicsCoreMakeConnections(core::Core, file::String)
    Utils.@invoke_and_check Lib.helicsCoreMakeConnections(core, file)
end

function helicsInputSetMinimumChange(inp::Input, tolerance::Float64)
    Utils.@invoke_and_check Lib.helicsInputSetMinimumChange(inp, tolerance)
end


text:
using PolyhedralRelaxations, JuMP, Cbc, Test

"""
    example_trig(; verbose = true)

``trig'' is an MINLPLib test instance with trigonometric functions
(see http://www.minlplib.org/trig.html). The best known primal feasible
objective value for this problem is -3.76250036. This example illustrates
how to obtain lower bounds to the optimal objective value using the MILP and
LP relaxations obtained using the PolyhedralRelaxations package.

trig is a non-linear non-convex minimization problem with one decision variable
and one inequality constraint.

Variables: -2 <= x <= 5; start = 1
Constraint: 5 * sin(x) - x <= 0.0
Objective: Minimize sin(11 * x) - cos(13 * x) + sin(17 * x) + cos(19 * x)

We reformulate this problem using additional variables as
Variables:
    -2 <= x <= 5; start = 1
    -1 <= y[1:5] <= 1
Constraints:
    5 * y[1] - x <= 0.0
    y[1] = sin(x)
    y[2] = sin(11 * x)
    y[3] = cos(13 * x)
    y[4] = sin(17 * x)
    y[5] = cos(19 * x)
Objective: Minimize y[2] + y[3] - y[4] - y[5]

"""

function example_trig(; verbose = true)
    best_known_objective = -3.76250036
    cbc_optimizer = JuMP.optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    error_tolerances = [NaN64, 1e-1, 1e-2]
    # base partition holds the inflection points and the end-points for the trigonometric functions
    base_partition = Dict{Int,Vector{Float64}}()
    base_partition[1] = [-2.0, 0.0, π, 5.0]
    base_partition[2] = [-2.0, collect(-7*π/11:π/11:17*π/11)..., 5.0]
    base_partition[3] = [-2.0, collect(-15*π/26:π/13:41*π/26)..., 5.0]
    base_partition[4] = [-2.0, collect(-10*π/17:π/17:27*π/17)..., 5.0]
    base_partition[5] = [-2.0, collect(-23*π/38:π/19:59*π/38)..., 5.0]
    # functions
    functions = Dict{Int,Function}()
    functions[1] = x -> sin(x)
    functions[2] = x -> sin(11 * x)
    functions[3] = x -> cos(13 * x)
    functions[4] = x -> sin(17 * x)
    functions[5] = x -> cos(19 * x)
    for i = 1:length(error_tolerances)
        err_tol = error_tolerances[i]
        milp = Model(cbc_optimizer)
        @variable(milp, -2.0 <= x <= 5.0)
        @variable(milp, -1.0 <= y[1:5] <= 1.0)

        # construct MILP relaxations
        for j = 1:5
            f = functions[j]
            p = deepcopy(base_partition[j])
            construct_univariate_relaxation!(
                milp,
                f,
                x,
                y[j],
                p,
                true,
                error_tolerance = err_tol,
            )
        end
        @constraint(milp, 5 * y[1] - x <= 0.0)
        @objective(milp, Min, y[2] + y[3] - y[4] - y[5])
        optimize!(milp)
        relaxation_objective = objective_value(milp)
        @test relaxation_objective ≤ best_known_objective
        relative_gap =
            abs(best_known_objective - relaxation_objective) / abs(relaxation_objective)
        if verbose
            println(
                "Optimal solution: $best_known_objective; MILP for error tolerance $err_tol: $relaxation_objective; relative gap: $relative_gap",
            )
        end
    end

    for i = 1:length(error_tolerances)
        err_tol = error_tolerances[i]
        # construct LP relaxations
        lp = Model(cbc_optimizer)
        @variable(lp, -2.0 <= x <= 5.0)
        @variable(lp, -1.0 <= y[1:5] <= 1.0)
        for j = 1:5
            f = functions[j]
            p = deepcopy(base_partition[j])
            construct_univariate_relaxation!(
                lp,
                f,
                x,
                y[j],
                p,
                false,
                error_tolerance = err_tol,
            )
        end
        @constraint(lp, 5 * y[1] - x <= 0.0)
        @objective(lp, Min, y[2] + y[3] - y[4] - y[5])
        optimize!(lp)
        relaxation_objective = objective_value(lp)
        @test relaxation_objective ≤ best_known_objective
        relative_gap =
            abs(best_known_objective - relaxation_objective) / abs(relaxation_objective)
        if verbose
            println(
                "Optimal solution: $best_known_objective; LP for error tolerance $err_tol: $relaxation_objective; relative gap: $relative_gap",
            )
        end
    end

end

example_trig()