text:
struct StridedTaggedImage{T, O} <: AbstractTIFF{T, 1} where {O <: Unsigned}
    data::Vector{AbstractArray{T, 2}}

    ifds::Vector{IFD{O}}
end

function StridedTaggedImage(data::AbstractArray{T, 2}, ifd::IFD{O}) where {T, O}
    StridedTaggedImage([data], IFD{O}[ifd])
end

Base.size(t::StridedTaggedImage) = length(t.data)
Base.getindex(img::StridedTaggedImage, i) = getindex(img.data, i)


text:
@enum Align begin
    A_left
    A_right
    A_center
    A_equal
end

const alignmap = Dict("<" => A_left,
                      ">" => A_right,
                      "^" => A_center,
                      "=" => A_equal,
                      "" => A_right)

@enum Sign begin
    S_plus
    S_minus
    S_space
end

const signmap = Dict("+" => S_plus,
                     "-" => S_minus,
                     " " => S_space,
                     "" => S_minus)

@enum FormatType begin
    T_b
    T_c
    T_d
    T_o
    T_x
    T_X
    T_n
    T_e
    T_E
    T_f
    T_F
    T_g
    T_G
    T_percent
    T_float
end

const typemap = Dict("b" => T_b,
                     "c" => T_c,
                     "d" => T_d,
                     "o" => T_o,
                     "x" => T_x,
                     "X" => T_X,
                     "n" => T_n,
                     "e" => T_e,
                     "E" => T_E,
                     "f" => T_f,
                     "F" => T_F,
                     "g" => T_g,
                     "G" => T_G,
                     "%" => T_percent)

struct FormatSpecNumber
    fill::Char
    align::Align
    sign::Sign
    hash::Bool
    width::Int
    precision::Int
    type::FormatType
end

"""
fmt(fmtstr, x)

return `x` formated according to `fmtstr`

This function is called by format and can be overloaded to add suport for diferent types
"""
function fmt end

fmt(fmtstr::AbstractString, x::Union{Integer, AbstractFloat}) = spec_format(parse_format(fmtstr, x), x)
fmt(fmtstr::AbstractString, x) = string(x)

"""
    parse_format(fmtstr, x)

    parse the `fmtstr` for the type of `x`
"""
function parse_format end

parse_format(fmtstr::AbstractString, ::T) where {T<:Integer} = parse_format_number(fmtstr, T_d, "bcdoxXneEfFgGn%", T)
parse_format(fmtstr::AbstractString, ::T) where {T<:AbstractFloat} = parse_format_number(fmtstr, T_float, "neEfFgGn%", T)

function parse_format_number(fmtstr::AbstractString, defaulttype::FormatType, validtypes::String, type)
    rgx = r"((?<fill>.?)(?<align>[<>=^]))?(?<sign>[+\- ]?)(?<hash>#?)(?<zero>0?)(?<width>[0-9]*)(?<precision>\.[0-9]+)?(?<type>.?)"

    m = match(rgx, fmtstr)

    #error handling
    if m === nothing
        error("format \"$fmtstr\" is not understood")
    end

    if(m.offset != 1)
        error("\"$(fmtstr)\" is not understood")
    end

    if(!occursin(m.captures[9], validtypes))
        error("unkown type: \"$(m.captures[9])\" for $type")
    end

    if(length(m.match) != length(fmtstr))
        error("\"$(fmtstr[(length(m.match)+1):end])\" is not understood at idx $(length(m.match) + 1)")
    end

    # Get parameters
    fill = ' ' #default value
    align = A_right #default value
    if m.captures[6] == "0" #changes default
        fill = '0'
        align = A_equal
    end
    if !(m.captures[1] === nothing)
        fill = length(m.captures[2]) > 0 ? m.captures[2][1] : ' '
        align = alignmap[m.captures[3]]
    end
    sign = signmap[m.captures[4]]
    hash = (m.captures[5] == "#")
    if m.captures[7] == ""
        width = -1
    else
        width = parse(Int, m.captures[7])
    end
    if m.captures[8] === nothing
        precision = -1
    elseif m.captures[8] == "."
        error("format specifier missing precision: \"$fmtstr\" ")
    else
        precision = parse(Int, m.captures[8][2:end])
    end
    if m.captures[9] == ""
        type = defaulttype
    else
        type = typemap[m.captures[9]]
    end

    FormatSpecNumber(
        fill,
        align,
        sign,
        hash,
        width,
        precision,
        type
    )
end

function spec_format(fmt::FormatSpecNumber, x::Number)
    sign, number = separate_sign(x)

    sign   = format_sign(sign, fmt.sign)
    number = format_number(number, fmt.precision, fmt.type, fmt.hash)

    combine(sign, number, fmt.fill, fmt.width, fmt.align)
end

function separate_sign(x)
    x < 0 ?
        ("-", -x) :
        ("+",  x)
end

function format_sign(sign, signfmt)
    if sign == "-"
        return sign
    else
        if signfmt == S_minus
            return ""
        elseif signfmt == S_plus
            return "+"
        elseif signfmt == S_space
            return " "
        end
    end
end

function format_number(number, precision, type, hash::Bool)
    # floating point representations
    fprecision = precision >= 0 ? precision : 6
    if type == T_e
        return Ryu.writeexp(number, fprecision)
    elseif type == T_E
        return uppercase(Ryu.writeexp(number, fprecision, false, false, false, UInt8('E')))
    elseif type == T_f
        return Ryu.writefixed(number, fprecision)
    elseif type == T_F
        return uppercase(Ryu.writefixed(number, fprecision))
    elseif type == T_percent
        return Ryu.writefixed(number*100, fprecision) * "%"
    elseif type == T_g
        return generalformat(number, fprecision, false)
    elseif type == T_G
        return generalformat(number, fprecision, true)
    elseif type == T_float
        if precision == -1
            return string(number) #when no precision is given print the default formatting
        else
            return generalformat(number, fprecision, false, true)
        end
    end
    # integer representations
    if precision != -1
        error("precision not allowed in integer format specifier")
    end
    if type == T_b
        return format_integer(number, _Bin(), hash)
    elseif type == T_o
        return format_integer(number, _Oct(), hash)
    elseif type == T_d
        return format_integer(number, _Dec(), hash)
    elseif type == T_x
        return format_integer(number, _Hex(), hash)
    elseif type == T_X
        return format_integer(number, _HEX(), hash)
    elseif type == T_c
        return format_char(number)
    end
    string(number)
end

"""
combine(sign, number, fill, width, align)

creates a string with `sign` and `number` of size `width` (or more if number
is too big) using `fill` as padding according to `signtype` and `align`
"""
function combine(sign::AbstractString,
                 number::AbstractString,
                 fill::Char,
                 width::Integer,
                 align::Align)
    number_len = length(number)
    fill_len = max(0, width-number_len-length(sign))

    if align == A_center
        l_len = fill_len ÷ 2
        l_fill = fill ^ l_len
        r_fill = fill ^ (fill_len - l_len)

        return l_fill * sign * number * r_fill
    elseif align == A_left
        return sign * number * (fill ^ fill_len)
    elseif align == A_right
        return (fill ^ fill_len) * sign * number
    elseif align == A_equal
        return sign * (fill ^ fill_len) * number
    end
end

# sci -> Ryu.writeexp
# floatingpoint -> Ryu.writefixed
# generalformat -> Ryu.shortest
# StringVector to create a UInt8 vector

ext: jl


text:
# The concept and trivial implementation of graph visitors

abstract type AbstractGraphVisitor end

# trivial implementation

# invoked when a vertex v is encountered for the first time
# this function returns whether to continue search
discover_vertex!(vis::AbstractGraphVisitor, v) = true

# invoked when the algorithm is about to examine v's neighbors
open_vertex!(vis::AbstractGraphVisitor, v) = nothing

# invoked when a neighbor is discovered & examined
examine_neighbor!(vis::AbstractGraphVisitor, u, v, color::Int, ecolor::Int) = nothing

# invoked when an edge is discovered & examined
examine_edge!(vis::AbstractGraphVisitor, e, color::Int) = nothing

# invoked when all of v's neighbors have been examined
close_vertex!(vis::AbstractGraphVisitor, v) = nothing

mutable struct TrivialGraphVisitor <: AbstractGraphVisitor
end

# This is the common base for BreadthFirst and DepthFirst
abstract type AbstractGraphVisitAlgorithm end

###########################################################
#
#   General algorithms based on graph traversal
#
###########################################################

# List vertices by the order of being discovered

mutable struct VertexListVisitor{V} <: AbstractGraphVisitor
    vertices::Vector{V}

    function VertexListVisitor{V}(n::Integer=0) where {V}
        vs = Array{V}(undef, 0)
        sizehint!(vs, n)
        new{V}(vs)
    end
end

function discover_vertex!(visitor::VertexListVisitor{V}, v::V) where {V}
    push!(visitor.vertices, v)
    true
end

function visited_vertices(
    graph::AbstractGraph{V,E},
    alg::AbstractGraphVisitAlgorithm,
    sources) where {V,E}

    visitor = VertexListVisitor{V}(num_vertices(graph))
    traverse_graph(graph, alg, sources, visitor)
    visitor.vertices::Vector{V}
end

# Print visit log

mutable struct LogGraphVisitor{S<:IO} <: AbstractGraphVisitor
    io::S
end

function discover_vertex!(vis::LogGraphVisitor, v)
    println(vis.io, "discover vertex: $v")
    true
end

open_vertex!(vis::LogGraphVisitor, v) = println(vis.io, "open vertex: $v")
close_vertex!(vis::LogGraphVisitor, v) = println(vis.io, "close vertex: $v")

function examine_neighbor!(vis::LogGraphVisitor, u, v, vcolor::Int, ecolor::Int)
    println(vis.io, "examine neighbor: $u -> $v (vertexcolor = $vcolor, edgecolor= $ecolor)")
end

function examine_edge!(vis::LogGraphVisitor, e, color::Int)
    println(vis.io, "examine edge: $e")
end

function traverse_graph_withlog(g::AbstractGraph, alg::AbstractGraphVisitAlgorithm, sources, io::IO)
    visitor = LogGraphVisitor(io)
    traverse_graph(g, alg, sources, visitor)
end

traverse_graph_withlog(g::AbstractGraph, alg::AbstractGraphVisitAlgorithm,
    sources) = traverse_graph_withlog(g, alg, sources, STDOUT)


text:
include("test_functions_5taxon_read.jl")

tests = ["F","G","H","J","I"];
wrong = AbstractString[];

function whichtree(t::String)
    if(t == "tree")
        tree = "(((6:0.1,4:1.5)1:0.2,7:0.2)5:0.1,8:0.1,10:0.1);" # normal tree
    elseif(t == "C")
        tree = "((((6:0.1,4:1.5),(7:0.2)11#H1),11#H1),8:0.1,10:0.1);" # Case C: bad triangle II
    elseif(t == "F")
        tree = "(((6:0.1,(4)11#H1)1:0.2,(11#H1,7))5:0.1,8:0.1,10:0.1);" # Case F: bad diamond I
    elseif(t == "G")
        tree = "((((6:0.1,4:1.5)1:0.2,(7)11#H1)5:0.1,(11#H1,8)),10:0.1);" # Case G
    elseif(t == "H")
        tree = "((((6,4),#H1),7),(8)#H1,10);" # Case H
    elseif(t == "J")
        tree = "((((6)#H1,4),7),8,(#H1,10));" # Case J
    elseif(t == "D")
        tree = "((((6,4))#H1,(#H1,7)),8,10);" # Case D Bad triangle I
    elseif(t == "E")
        tree = "(((((8,10))#H1,7),#H1),6,4);" # Case E Bad triangle I
    elseif(t == "I")
        tree = "((((8,10))#H1,7),6,(4,#H1));" # Case I Bad diamond II
    else
        error("not a known 5 taxon network case")
    end
    return tree
end

for t in tests
    #println("running $(t)")
    net = nothing;
    tree = whichtree(t)
    net = readTopologyLevel1(tree);
    if(t == "tree")
        try
            testTree(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    elseif(t == "C")
        try
            testCaseC(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    elseif(t == "F")
        try
            testCaseF(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    elseif(t == "G")
        try
            testCaseG(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    elseif(t == "H")
        try
            testCaseH(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    elseif(t == "J")
        try
            testCaseJ(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    elseif(t == "D")
        try
            testCaseD(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    elseif(t == "E")
        try
            testCaseE(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    elseif(t == "I")
        try
            testCaseI(net)
        catch
            println("error in $(t)")
            push!(wrong,t);
        end
    else
        error("not a known 5 taxon network case")
    end
end

## if(!isempty(wrong))
##     for t in wrong
##         println("running $(t)")
##         net = nothing;
##         tree = whichtree(t)
##         f = open("prueba_tree.txt","w")
##         write(f,tree)
##         close(f)
##         net = readTopologyUpdate("prueba_tree.txt");
##         if(t == "tree")
##             testTree(net)
##         elseif(t == "C")
##             testCaseC(net)
##         elseif(t == "F")
##             testCaseF(net)
##         elseif(t == "G")
##             testCaseG(net)
##         elseif(t == "H")
##             testCaseH(net)
##         elseif(t == "J")
##             testCaseJ(net)
##         elseif(t == "D")
##             testCaseD(net)
##         elseif(t == "E")
##             testCaseE(net)
##         elseif(t == "I")
##             testCaseI(net)
##         else
##             error("not a known 5 taxon network case")
##         end
##     end
## else
##     println("----------NO ERRORS!----------");
## end

@test isempty(wrong)


text:
Base.@kwdef mutable struct AlgorithmParameters
    nbGranular :: Cint = 20
    mu :: Cint = 25
    lambda :: Cint = 40
    nbElite :: Cint = 4
    nbClose :: Cint = 5
    targetFeasible :: Cdouble = 0.2
    penaltyIncrease :: Cdouble = 1.20
    penaltyDecrease :: Cdouble = 0.85
    repairProb :: Cdouble = 0.5
    seedRNG :: Cint = 0
    nbIter :: Cint = 20000
    timeLimit :: Cdouble = Cdouble(typemax(Cint))
    isRoundingInteger :: Cchar = 1
end

mutable struct C_SolutionRoute
    length :: Cint
    path :: Ptr{Cint}
end

mutable struct C_Solution
    cost :: Cdouble
    time :: Cdouble
    n_routes :: Cint
    routes :: Ptr{C_SolutionRoute}
end

mutable struct RoutingSolution
    cost :: Float64
    time :: Float64
    routes :: Vector{Vector{Int}}

    function RoutingSolution(sol_ptr::Ptr{C_Solution})
        sol = unsafe_load(sol_ptr)
        routes = Vector{Vector{Int}}(undef, sol.n_routes)
        for i in eachindex(routes)
            r = unsafe_load(sol.routes, i)
            path = unsafe_wrap(Array, r.path, r.length)
            routes[i] = unsafe_wrap(Array, r.path, r.length) .+ 1 # customer numbering offset
        end
        return new(sol.cost, sol.time, routes)
    end
end

function convert_destroy(c_sol_ptr::Ptr{C_Solution})
    # copy C structs to Julia structs
    j_sol = RoutingSolution(c_sol_ptr)

    # don't forget to delete pointers, important to avoid memory leaks
    ccall((:delete_solution, LIBHGSCVRP), Cvoid, (Ptr{C_Solution},), c_sol_ptr)

    return j_sol
end

"""
    function c_api_solve_cvrp(
        n::Integer,
        x::Vector{Cdouble},
        y::Vector{Cdouble},
        service_time::Vector{Cdouble},
        demand::Vector{Cdouble},
        vehicle_capacity::Integer,
        maximum_number_of_vehicles::Integer,
        parameters::AlgorithmParameters,
        verbose::Bool
    )

`x`, `y`, `service_time`, `demand` all require at least `n` elements.
Anything after `n` elements are ignored.

The first element should be the depot.
The next `n-1` elements should be customers.

"""
function c_api_solve_cvrp(
    n::Integer,
    x::Vector,
    y::Vector,
    service_time::Vector,
    demand::Vector,
    vehicle_capacity::Integer,
    maximum_number_of_vehicles::Integer,
    parameters::AlgorithmParameters,
    verbose::Bool
)
    c_solution_ptr = ccall(
        (:solve_cvrp, LIBHGSCVRP),
        Ptr{C_Solution},
        (
            Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
            Cint, Cint, Ptr{AlgorithmParameters}, Cchar
        ),
        n, Cdouble.(x), Cdouble.(y), Cdouble.(service_time), Cdouble.(demand),
        vehicle_capacity, maximum_number_of_vehicles, Ref(parameters), verbose
    )

    return convert_destroy(c_solution_ptr)
end

function c_api_solve_cvrp_dist_mtx(
    n::Integer,
    x::Vector,
    y::Vector,
    dist_mtx::Matrix, # row-first matrix as in C
    service_time::Vector,
    demand::Vector,
    vehicle_capacity::Integer,
    maximum_number_of_vehicles::Integer,
    parameters::AlgorithmParameters,
    verbose::Bool
)
    c_solution_ptr = ccall(
        (:solve_cvrp_dist_mtx, LIBHGSCVRP),
        Ptr{C_Solution},
        (
            Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
            Cint, Cint, Ptr{AlgorithmParameters}, Cchar
        ),
        n, Cdouble.(x), Cdouble.(y), Cdouble.(dist_mtx), Cdouble.(service_time), Cdouble.(demand),
        vehicle_capacity, maximum_number_of_vehicles, Ref(parameters), verbose
    )

    return convert_destroy(c_solution_ptr)
end


text:
# groundstate with UniformCMPS
function groundstate(H::LocalHamiltonian, Ψ₀::UniformCMPS;
                        optalg = ConjugateGradient(; verbosity = 2, gradtol = 1e-7),
                        eigalg = defaulteigalg(Ψ₀),
                        linalg = defaultlinalg(Ψ₀),
                        finalize! = OptimKit._finalize!,
                        kwargs...)

    δ = 1
    function retract(x, d, α)
        ΨL, = x
        QL = ΨL.Q
        RLs = ΨL.Rs
        KL = copy(QL)
        for R in RLs
            mul!(KL, R', R, +1/2, 1)
        end

        dRs = d
        RdR = zero(QL)
        for (R, dR) in zip(RLs, dRs)
            mul!(RdR, R', dR, true, true)
        end

        RLs = RLs .+ α .* dRs
        KL = KL - (α/2) * (RdR - RdR')
        QL = KL
        for R in RLs
            mul!(QL, R', R, -1/2, 1)
        end

        ΨL = InfiniteCMPS(QL, RLs; gauge = :left)
        ρR, λ, infoR = rightenv(ΨL, ρR; eigalg = eigalg, linalg = linalg, kwargs...)
        rmul!(ρR, 1/tr(ρR[]))
        ρL = one(ρR)
        HL, E, e, hL, infoL =
            leftenv(H, (ΨL, ρL, ρR); eigalg = eigalg, linalg = linalg, kwargs...)

        if infoR.converged == 0 || infoL.converged == 0
            @warn "step $α : not converged, energy = $E"
            @show infoR
            @show infoL
        end

        return (ΨL, ρR, HL, E, e, hL), d
    end

    transport!(v, x, d, α, xnew) = v # simplest possible transport

    function inner(x, d1, d2)
        return 2*real(sum(dot.(d1, d2)))
    end

    function precondition(x, d)
        ΨL, ρR, = x
        dRs = d
        return dRs .* Ref(posreginv(ρR[0], δ))
    end

    function fg(x)
        (ΨL, ρR, HL, E, e, hL) = x

        gradQ, gradRs = gradient(H, (ΨL, one(ρR), ρR), HL, zero(HL); kwargs...)

        Rs = ΨL.Rs

        dRs = .-(Rs) .* Ref(gradQ) .+ gradRs

        return E, dRs
    end

    scale!(d, α) = rmul!.(d, α)
    add!(d1, d2, α) = axpy!.(α, d2, d1)

    function _finalize!(x, E, d, numiter)
        normgrad2 = real(inner(x, d, d))
        δ = max(1e-12, 1e-3*normgrad2)
        return finalize!(x, E, d, numiter)
    end

    ΨL, = leftgauge(Ψ₀; kwargs...)
    ρR, λ, infoR = rightenv(ΨL; kwargs...)
    ρL = one(ρR)
    rmul!(ρR, 1/tr(ρR[]))
    HL, E, e, hL, infoL = leftenv(H, (ΨL,ρL,ρR); kwargs...)
    x = (ΨL, ρR, HL, E, e, hL)

    if infoR.converged == 0 || infoL.converged == 0
        @warn "initial point not converged, energy = $E"
        @show infoR
        @show infoL
    end

    x, E, grad, numfg, history =
        optimize(fg, x, optalg; retract = retract,
                                precondition = precondition,
                                finalize! = _finalize!,
                                inner = inner, transport! = transport!,
                                scale! = scale!, add! = add!,
                                isometrictransport = true)
    (ΨL, ρR, HL, E, e, hL) = x
    normgrad = sqrt(inner(x, grad, grad))
    return ΨL, one(ρR), ρR, E, e, normgrad, numfg, history
end

function groundstate(H::LocalHamiltonian, Ψ₀::FourierCMPS;
                        optalg = ConjugateGradient(; verbosity = 2, gradtol = 1e-7),
                        eigalg = defaulteigalg(Ψ₀),
                        linalg = defaultlinalg(Ψ₀),
                        finalize! = OptimKit._finalize!,
                        test = false,
                        kwargs...)

    δ = 1
    function retract(x, d, α)
        ΨL, ρR, HL, = x
        QL = ΨL.Q
        RLs = ΨL.Rs
        dK, dRs = d

        RdR = sum(adjoint.(RLs) .* dRs)
        dRdR = sum(adjoint.(dRs) .* dRs)

        QL = QL + α * dK - α * RdR - (α * α / 2) * dRdR
        RLs = RLs .+ α .* dRs

        ΨL = InfiniteCMPS(QL, RLs; gauge = :left)
        ρR, λ, infoR = rightenv(ΨL, ρR; eigalg = eigalg, linalg = linalg, kwargs...)
        rmul!(ρR, 1/tr(ρR[0]))
        ρL = one(ρR)
        HL, E, e, hL, infoL =
            leftenv(H, (ΨL, ρL, ρR); eigalg = eigalg, linalg = linalg, kwargs...)

        if infoR.converged == 0 || infoL.converged == 0
            @warn "step $α : not converged, energy = $E"
            @show infoR
            @show infoL
        end

        return (ΨL, ρR, HL, E, e, hL), d
    end

    transport!(v, x, d, α, xnew) = v # simplest possible transport

    function inner(x, d1, d2)
        dK1, dRs1 = d1
        dK2, dRs2 = d2
        return 2*real(dot(dK1, dK2)) + 2*real(sum(dot.(dRs1, dRs2)))
    end

    function fg(x)
        ΨL, ρR, HL, E, e, hL = x

        gradQ, gradRs = gradient(H, (ΨL, one(ρR), ρR), HL, zero(HL); kwargs...)

        Q = ΨL.Q
        RLs = ΨL.Rs

        dK = truncate!((gradQ - gradQ')/2; Kmax = nummodes(Q))
        dRs = truncate!.((.-(RLs)) .* (gradQ,) .+ gradRs; Kmax = nummodes(RLs[1]))

        return E, (dK, dRs)
    end

    function scale!(d, α)
        dK, dRs = d
        dK = rmul!(dK, α)
        dRs = rmul!.(dRs, α)
        return (dK, dRs)
    end

    function add!(d1, d2, α)
        dK1, dR1s = d1
        dK2, dR2s = d2
        axpy!(α, dK2, dK1)
        axpy!.(α, dR2s, dR1s)
        return (dK1, dR1s)
    end

    # TODO: make this work and test this
    # function precondition(x, d)
    #     ΨL, ρR, = x
    #     dK, dRs = d
    #     ρinv = posreginv(ρR[0], δ)
    #     dKρinv = sylvester(inv(ρinv), inv(ρinv), dK)
    #     return (dKρinv, dRs .* Ref(ρinv))
    # end

    function _finalize!(x, E, d, numiter)
        normgrad2 = real(inner(x, d, d))
        δ = max(1e-12, 1e-1*normgrad2)
        return finalize!(x, E, d, numiter)
    end

    ΨL = Ψ₀
    ρR, λ, infoR = rightenv(ΨL; kwargs...)
    ρL = one(ρR)
    @assert norm(LeftTransfer(ΨL)(ρL)) < 1e-12
    rmul!(ρR, 1/tr(ρR[0]))
    HL, E, e, hL, infoL = leftenv(H, (ΨL,ρL,ρR); kwargs...)
    x = (ΨL, ρR, HL, E, e, hL)

    if infoR.converged == 0 || infoL.converged == 0
        @warn "initial point not converged, energy = $E"
        @show infoR
        @show infoL
    end

    if test
        return optimtest(fg, x; alpha = -0.1:0.01:0.1, retract = retract, inner = inner)
    end

    x, E, grad, numfg, history =
        optimize(fg, x, optalg; retract = retract,
                                # precondition = precondition, # TODO
                                finalize! = _finalize!,
                                inner = inner, transport! = transport!,
                                scale! = scale!, add! = add!,
                                isometrictransport = true)
    (ΨL, ρR, HL, E, e, hL) = x
    normgrad = sqrt(inner(x, grad, grad))
    return ΨL, one(ρR), ρR, E, e, normgrad, numfg, history
end


text:
module PackageName

include("visualise.jl")
using PIPPlottingModule
# package code goes here

end # module


text:
# Finite difference discretisation

"""
    FDDiscretisation

Finite difference discretisation on square domain

TODO per-thread scratch space
"""
struct FDDiscretisation{N} <: Sampling{N}
    n::Int
    h::Float64# Axis steps
    Ds::Any
    scratch::Any

    function FDDiscretisation{2}(n::Int, h::Float64, order = 2)
        Ds = [fdop(n, h, 2order + 1, 1), fdop(n, h, 2order + 1, 2)]
        scratch = [Array{Complex{Float64}}(undef, n, n) for j = 1:Threads.nthreads()]
        new(n, h, Ds, scratch)
    end
end

function fdop(N, h, slength, order)
    ud = (slength - 1) ÷ 2
    D = BandedMatrix{Float64}(undef, (N, N), (ud, ud))
    weights = Array{Float64}(undef, N + 2)
    for j = 1:N
        stencil = (0:N+1) ∩ (j-ud:j+ud)
        weights .= 0
        weights[stencil.+1] = DiffEqOperators.calculate_weights(order, h * j, h * stencil)
        D[j, stencil∩(1:N)] = weights[(stencil∩(1:N)).+1]
    end
    D
end

# Maximum domain size l, limit maximum V to nyquist T
# TODO update to take maximum V, account for hbm
# TODO copy the keyword syntax for Range
FDDiscretisation(s::Superfluid{N}, n, l = Inf) where {N} =
    FDDiscretisation{N}(n, min(l / (n + 1), sqrt(√2 * π / n)))
FDDiscretisation(n, l) = FDDiscretisation(default(:superfluid), n, l)

Base.show(io::IO, ::MIME"text/plain", d::FDDiscretisation{N}) where {N} =
    print(io, "FDDiscretisation{$N}($(d.n), $(d.h))")

function D!(y, d::FDDiscretisation{2}, a, u, n, axis)
    if axis == 1
        y .+= a .* (d.Ds[n] * u)
    elseif axis == 2
        y .+= a .* (u * d.Ds[n]')
    else
        error("Non-existent axis")
    end
end

# TODO test that interpolating z at r gives r
interpolant(d::FDDiscretisation{2}, r::Complex{Float64}) = finterp(d, r, 0.0)

# TODO Sparse matrices for small a
function finterp(d::FDDiscretisation{2}, r::Complex{Float64}, a)
    z = argand(d)
    u = zero(z)
    # TODO less brute force
    ixs = sort(eachindex(z), by = k -> abs(z[k] - r))[1:4]
    for j in ixs
        @. u +=
            exp(-abs2(z - z[j]) / 2a^2) *
            (d.h - abs(real(z[j] - r))) *
            (d.h - abs(imag(z[j] - r))) / d.h^2
    end
end

"""
    matrices(s::Superfluid{2}, d::FDDiscretisation{2}, Ω, ψ, syms::Vararg{Symbol})

Like operators, but as matrices instead of functions.

The argument ψ is an order parameter to linearise U about.
This is the kind of place that TensArrays will be useful.
"""
function matrices(s::Superfluid{2}, d::FDDiscretisation{2}, Ω, ψ, syms::Vararg{Symbol})
    x, y = daxes(d)
    ∂, ∂² = d.Ds
    V = diagm(0 => sample(s.V, d)[:])
    eye = Matrix(I, d.n, d.n)
    ρ = diagm(0 => abs2.(ψ[:]))

    T = -s.hbm * kron(eye, ∂²) / 2 - s.hbm * kron(∂², eye) / 2
    U = s.C / d.h^2 * ρ
    J = -1im * (repeat(x, 1, d.n)[:] .* kron(∂, eye) - repeat(y, d.n, 1)[:] .* kron(eye, ∂))
    L = T + V + U - Ω * J
    H = T + V + U / 2 - Ω * J

    ops = Dict(:V => V, :T => T, :U => U, :J => J, :L => L, :H => H)
    isempty(syms) ? ops : [ops[j] for j in syms]
end


text:
import LinearAlgebra.dot

function dot(xv::StridedVector{DoubleFloat{T}}, yv::StridedVector{DoubleFloat{T}})  where {N, T <: AbstractFloat}
    _dot(xv,yv,Vec{Npref,T})
end

function _dot(xv::StridedVector{DoubleFloat{T}}, yv::StridedVector{DoubleFloat{T}},::Type{Vec{N,T}}) where {N, T <: AbstractFloat}
     n = length(xv)
    (length(yv) == n) || throw(DimensionMismatch("arguments must have equal lengths"))
    z = zero(T)
    shi = Vec{N,T}(0)
    slo = Vec{N,T}(0)
    nd,nr = divrem(n, N)
    @inbounds begin
        for i in 1:nd
            i0=(i-1)*N

            xhi = vgethi(xv,i0,Vec{N,T})
            xlo = vgetlo(xv,i0,Vec{N,T})
            yhi = vgethi(yv,i0,Vec{N,T})
            ylo = vgetlo(yv,i0,Vec{N,T})

            zhi, zlo = dfvmul(xhi, xlo, yhi, ylo)
            shi, slo = dfvadd(shi, slo, zhi, zlo)
        end
    end
    shi, slo = add_(shi, slo) # this should canonicalize all at once
    s = DoubleFloat((shi[1], slo[1]))
    for j=2:N
        s += DoubleFloat((shi[j], slo[j]))
        end
    (nr == 0) && return s
    @inbounds begin
        @simd for i in (nd*N)+1:n
            s += xv[i]*yv[i]
        end
    end
    s
end

function dot(xv::StridedVector{Complex{DoubleFloat{T}}},
             yv::StridedVector{Complex{DoubleFloat{T}}})  where {N, T <: AbstractFloat}
    _dot(xv,yv,Vec{Npref,T})
end

function _dot(xv::StridedVector{Complex{DoubleFloat{T}}}, yv::StridedVector{Complex{DoubleFloat{T}}},::Type{Vec{N,T}},tA=:c) where {N, T <: AbstractFloat}
     n = length(xv)
    (length(yv) == n) || throw(DimensionMismatch("arguments must have equal lengths"))
    z = zero(T)
    srhi = Vec{N,T}(0)
    srlo = Vec{N,T}(0)
    sihi = Vec{N,T}(0)
    silo = Vec{N,T}(0)
    nd,nr = divrem(n, N)
    @inbounds begin
        for i in 1:nd
            i0=(i-1)*N

            xrhi = vgethire(xv,i0,Vec{N,T})
            xrlo = vgetlore(xv,i0,Vec{N,T})
            yrhi = vgethire(yv,i0,Vec{N,T})
            yrlo = vgetlore(yv,i0,Vec{N,T})
            xihi = vgethiim(xv,i0,Vec{N,T})
            xilo = vgetloim(xv,i0,Vec{N,T})
            yihi = vgethiim(yv,i0,Vec{N,T})
            yilo = vgetloim(yv,i0,Vec{N,T})

            z1hi, z1lo = dfvmul(xrhi, xrlo, yrhi, yrlo)
            z2hi, z2lo = dfvmul(xrhi, xrlo, yihi, yilo)
            z3hi, z3lo = dfvmul(xihi, xilo, yrhi, yrlo)
            z4hi, z4lo = dfvmul(xihi, xilo, yihi, yilo)

            if tA == :c
                zrhi, zrlo = dfvadd(z1hi, z1lo, z4hi, z4lo)
                zihi, zilo = dfvsub(z2hi, z2lo, z3hi, z3lo)
            else
                zrhi, zrlo = dfvsub(z1hi, z1lo, z4hi, z4lo)
                zihi, zilo = dfvadd(z2hi, z2lo, z3hi, z3lo)
            end

            srhi, srlo = dfvadd(srhi, srlo, zrhi, zrlo)
            sihi, silo = dfvadd(sihi, silo, zihi, zilo)
        end
    end
    srhi, srlo = add_(srhi, srlo) # this should canonicalize all at once
    sihi, silo = add_(sihi, silo)
    sr = DoubleFloat((srhi[1], srlo[1]))
    si = DoubleFloat((sihi[1], silo[1]))
    for j=2:N
        sr += DoubleFloat((srhi[j], srlo[j]))
        si += DoubleFloat((sihi[j], silo[j]))
    end
    s = complex(sr,si)
    (nr == 0) && return s
    @inbounds begin
        if tA == :c
            @simd for i in (nd*N)+1:n
                s += conj(xv[i])*yv[i]
            end
        else
            @simd for i in (nd*N)+1:n
                s += xv[i]*yv[i]
            end
        end
    end
    s
end

# partial-vector versions to support level 2 and 3 methods

# warning: unsafe indexing
function _dot(n::Integer,
              xv::StridedVecOrMat{DoubleFloat{T}}, ix1::Integer,
              yv::StridedVecOrMat{DoubleFloat{T}}, iy1::Integer,
              ::Type{Vec{N,T}}) where {N, T <: AbstractFloat}
    z = zero(T)
    shi = Vec{N,T}(0)
    slo = Vec{N,T}(0)
    nd,nr = divrem(n, N)
    ixoff = ix1-1
    iyoff = iy1-1
    @inbounds begin
        for i in 1:nd
            i0=(i-1)*N
            ix0=ixoff + i0
            iy0=iyoff + i0

            xhi = vgethi(xv,ix0,Vec{N,T})
            xlo = vgetlo(xv,ix0,Vec{N,T})
            yhi = vgethi(yv,iy0,Vec{N,T})
            ylo = vgetlo(yv,iy0,Vec{N,T})

            zhi, zlo = dfvmul(xhi, xlo, yhi, ylo)
            shi, slo = dfvadd(shi, slo, zhi, zlo)
        end
    end
    shi, slo = add_(shi, slo) # this should canonicalize all at once
    s = DoubleFloat((shi[1], slo[1]))
    for j=2:N
        s += DoubleFloat((shi[j], slo[j]))
        end
    (nr == 0) && return s
    @inbounds begin
        @simd for i in (nd*N)+1:n
            s += xv[ixoff+i]*yv[iyoff+i]
        end
    end
    s
end

function _dot(n::Integer,
              xv::StridedVecOrMat{Complex{DoubleFloat{T}}}, ix1::Integer,
              yv::StridedVecOrMat{Complex{DoubleFloat{T}}}, iy1::Integer,
              ::Type{Vec{N,T}},tA=:c) where {N, T <: AbstractFloat}
    z = zero(T)
    srhi = Vec{N,T}(0)
    srlo = Vec{N,T}(0)
    sihi = Vec{N,T}(0)
    silo = Vec{N,T}(0)
    nd,nr = divrem(n, N)
    ixoff = ix1-1
    iyoff = iy1-1
    @inbounds begin
        for i in 1:nd
            i0=(i-1)*N
            ix0=ixoff + i0
            iy0=iyoff + i0

            xrhi = vgethire(xv,ix0,Vec{N,T})
            xrlo = vgetlore(xv,ix0,Vec{N,T})
            yrhi = vgethire(yv,iy0,Vec{N,T})
            yrlo = vgetlore(yv,iy0,Vec{N,T})
            xihi = vgethiim(xv,ix0,Vec{N,T})
            xilo = vgetloim(xv,ix0,Vec{N,T})
            yihi = vgethiim(yv,iy0,Vec{N,T})
            yilo = vgetloim(yv,iy0,Vec{N,T})

            z1hi, z1lo = dfvmul(xrhi, xrlo, yrhi, yrlo)
            z2hi, z2lo = dfvmul(xrhi, xrlo, yihi, yilo)
            z3hi, z3lo = dfvmul(xihi, xilo, yrhi, yrlo)
            z4hi, z4lo = dfvmul(xihi, xilo, yihi, yilo)

            if tA == :c
                zrhi, zrlo = dfvadd(z1hi, z1lo, z4hi, z4lo)
                zihi, zilo = dfvsub(z2hi, z2lo, z3hi, z3lo)
            else
                zrhi, zrlo = dfvsub(z1hi, z1lo, z4hi, z4lo)
                zihi, zilo = dfvadd(z2hi, z2lo, z3hi, z3lo)
            end

            srhi, srlo = dfvadd(srhi, srlo, zrhi, zrlo)
            sihi, silo = dfvadd(sihi, silo, zihi, zilo)
        end
    end
    srhi, srlo = add_(srhi, srlo) # this should canonicalize all at once
    sihi, silo = add_(sihi, silo)
    sr = DoubleFloat((srhi[1], srlo[1]))
    si = DoubleFloat((sihi[1], silo[1]))
    for j=2:N
        sr += DoubleFloat((srhi[j], srlo[j]))
        si += DoubleFloat((sihi[j], silo[j]))
    end
    s = complex(sr,si)
    (nr == 0) && return s
    @inbounds begin
        if tA == :c
            @simd for i in (nd*N)+1:n
                s += conj(xv[ixoff+i])*yv[iyoff+i]
            end
        else
            @simd for i in (nd*N)+1:n
                s += xv[ixoff+i]*yv[iyoff+i]
            end
        end
    end
    s
end

# reference version for testing
function dot1(x::StridedVector{T}, y::StridedVector{T}) where {T}
    n = length(x)
    (length(y) == n) || throw(ArgumentError("arguments must have equal lengths"))
    s = zero(T)
    @inbounds begin
        @simd for i in eachindex(x)
            s += x[i]*y[i]
        end
    end
    s
end


text:
@testset "Chores" begin

  # need expanding
  chore_result = Process("chore 1")
  @test chore_result.Name == "chore 1"
  @test name(chore_result) == "chore 1"

  chore_2 = JSON.parse("""
  {
    "Name": "chore 2"
  }
  """)

  @test name(Chore(chore_2)) == "chore 2"
  @test Chore(chore_2) != chore_result

end


text:
# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder

name = "SFML"
version = v"2.5.1"

# Collection of sources required to build SFML
sources = [
    GitSource(
        "https://github.com/SFML/SFML.git",
        "2f11710abc5aa478503a7ff3f9e654bd2078ebab",
    ),
    ArchiveSource(
        "https://www.sfml-dev.org/files/SFML-2.5.1-linux-gcc-64-bit.tar.gz",
        "34ad106e4592d2ec03245db5e8ad8fbf85c256d6ef9e337e8cf5c4345dc583dd",
    )
]

# Bash recipe for building across all platforms
script = raw"""
# build SFML
cd ${WORKSPACE}/srcdir

if [[ "${target}" == *linux* ]]; then

cd SFML-2.5.1/
mkdir -p ${prefix}
cp -r ./include/* ${prefix}/include
cp -r ./lib/* ${prefix}/lib
install_license ./share/SFML/license.md

else

cd SFML
mkdir build && cd build

CMAKE_FLAGS="-DCMAKE_INSTALL_PREFIX=${prefix} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TARGET_TOOLCHAIN} -DCMAKE_BUILD_TYPE=Release"

# if [[ "${target}" == *-linux-* ]]; then
#     apk add eudev-dev
# fi

if [[ "${target}" == *apple* ]]; then
CMAKE_FLAGS="${CMAKE_FLAGS} -DSFML_DEPENDENCIES_INSTALL_PREFIX=${WORKSPACE}/destdir/Frameworks"
fi

if [[ "${target}" == *mingw* ]] && [[ ${nbits} == 64 ]]; then
CMAKE_FLAGS="${CMAKE_FLAGS} -DOPENAL_LIBRARY=${WORKSPACE}/srcdir/SFML/extlibs/bin/x64/openal32.dll"
fi

if [[ "${target}" == *mingw* ]] && [[ ${nbits} == 32 ]]; then
CMAKE_FLAGS="${CMAKE_FLAGS} -DOPENAL_LIBRARY=${WORKSPACE}/srcdir/SFML/extlibs/bin/x86/openal32.dll"
fi

cmake .. ${CMAKE_FLAGS}
make
make install
install_license ../license.md

fi

"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = [
    Platform("x86_64", "linux"; libc="glibc", cxxstring_abi="cxx11"),
    Platform("x86_64", "macos"),
    Platform("i686", "windows"),
    Platform("x86_64", "windows")
]
platforms = expand_cxxstring_abis(platforms; skip=!Sys.iswindows)

# The products that we will ensure are always built
products = [
    LibraryProduct(["libsfml-window", "sfml-window"], :libsfml_window),
    LibraryProduct(["libsfml-audio", "sfml-audio"], :libsfml_audio),
    LibraryProduct(["libsfml-network", "sfml-network"], :libsfml_network),
    LibraryProduct(["libsfml-system", "sfml-system"], :libsfml_system),
    LibraryProduct(["libsfml-graphics", "sfml-graphics"], :libsfml_graphics)
]

# Dependencies that must be installed before this package can be built
dependencies = [
    Dependency("Libglvnd_jll"),
    Dependency("Ogg_jll"),
    Dependency("FLAC_jll"),
    Dependency("FreeType2_jll"),
    Dependency("libvorbis_jll"),
    Dependency("Xorg_libXrandr_jll"),
    Dependency("Xorg_libX11_jll"),
    Dependency("OpenAL_jll")
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies)

max_stars_repo_name: blenessy/Yggdrasil

max_issues_repo_name: blenessy/Yggdrasil

max_forks_repo_name: blenessy/Yggdrasil


text:
module RunTests

include("capture_output.jl")
include("findtestfiles.jl")
include("io.jl")
include("parameterize.jl")
include("run_tests.jl")
include("skipif.jl")
include("testmodule.jl")
include("xfail.jl")

end


text:
using Pkg
Pkg.activate(@__DIR__)
Pkg.instantiate()

using Inflate
using BenchmarkTools
using CodecZlib
using LightGraphs
using StatsBase
using Random
using Printf

# Generate an incompressible vector of length `n`.
incompressible(n) = rand(UInt8, n)

# Generate a Huffman compressible vector of length `n`.
function huffman_compressible(n)
    w = 0.844 .^ (0:255)
    return sample(StatsBase.shuffle(UInt8.(0:255)), Weights(w), n)
end

# Generate a runlength compressible vector of length `n`.
function runlength(n)
    x = UInt8[]
    while length(x) < n
        m = min(sample(1:32), n - length(x))
        append!(x, fill(rand(UInt8), m))
    end
    return x
end

# Generate a LightGraphs save file of length approximately `n`.
# Note: the balance between nodes and edges needs to be adjusted for
# savefiles smaller than about 620 bytes.
function graph(n)
    buf = IOBuffer()
    m = ceil(Int, n / (2 * (log10(n) - 1)))
    local x
    for k = 1:4
        g = erdos_renyi(m ÷ 10, m, is_directed = true)
        savegraph(buf, g, "", LGFormat())
        x = take!(buf)
        m = m * n ÷ length(x)
    end
    return x
end

sizes = Dict(:small => 1_000, :medium => 100_000, :large => 10_000_000)
generators = Dict(:incompressible => incompressible,
                  :huffman => huffman_compressible,
                  :runlength => runlength,
                  :graph => graph)
compressors = Dict(:deflate => DeflateCompressor,
                   :zlib => ZlibCompressor,
                   :gzip => GzipCompressor)
zlib_decompressors = Dict(:deflate => DeflateDecompressor,
                          :zlib => ZlibDecompressor,
                          :gzip => GzipDecompressor)
inflate_decompressors = Dict(:deflate => inflate,
                             :zlib => inflate_zlib,
                             :gzip => inflate_gzip)
zlib_decompressor_streams = Dict(:deflate => DeflateDecompressorStream,
                                 :zlib => ZlibDecompressorStream,
                                 :gzip => GzipDecompressorStream)
inflate_decompressor_streams = Dict(:deflate => InflateStream,
                                    :zlib => InflateZlibStream,
                                    :gzip => InflateGzipStream)

function run_tests()
    results = Dict{Any, Float64}()
    for data_size in keys(sizes)
        for data_type in keys(generators)
            Random.seed!(13)
            x = generators[data_type](sizes[data_size])
            for format in keys(compressors)
                compressed = transcode(compressors[format], x)
                zlib_decompressor = zlib_decompressors[format]
                inflate_decompressor = inflate_decompressors[format]
                GC.gc()
                t1 = @belapsed transcode($zlib_decompressor, $compressed)
                GC.gc()
                t2 = @belapsed $inflate_decompressor($compressed)
                results[(data_size, data_type, format, :zlib, :in_memory)] = t1
                results[(data_size, data_type, format, :inflate, :in_memory)] = t2
                zlib_decompressor_stream = zlib_decompressor_streams[format]
                inflate_decompressor_stream = inflate_decompressor_streams[format]
                GC.gc()
                t3 = @belapsed read($zlib_decompressor_stream(stream)) setup=(stream = IOBuffer($compressed)) evals=1
                GC.gc()
                t4 = @belapsed read($inflate_decompressor_stream(stream)) setup=(stream = IOBuffer($compressed)) evals=1
                results[(data_size, data_type, format, :zlib, :streaming)] = t3
                results[(data_size, data_type, format, :inflate, :streaming)] = t4
            end
        end
    end
    return results
end

function print_results(results, mode)
    mode_string = replace(string(mode), "_" => " ")
    println("Results for $(mode_string):")
    for data_type in [:incompressible, :huffman, :runlength, :graph]
        for data_size in [:small, :medium, :large]
            for format in [:deflate, :zlib, :gzip]
                t1 = results[(data_size, data_type, format, :zlib, mode)]
                t2 = results[(data_size, data_type, format, :inflate, mode)]
                @printf("%6s %14s %7s: %4.1f %8.1f %8.1f \n",
                        data_size, data_type, format, t2 / t1, 1e6*t1, 1e6*t2)
            end
        end
    end
end

function print_markdown_row(row)
    println(join(vcat("", row, ""), " | "))
end

function print_markdown_table(results, mode)
    mode_string = Dict(:in_memory => "In Memory", :streaming => "Streaming")[mode]
    data_types = [:incompressible, :huffman, :runlength, :graph]
    # TODO: Find a proper way to retrieve the version number from Pkg.
    version = Pkg.API.Context().env.manifest["Inflate"][1]["version"]
    print_markdown_row(vcat(version, mode_string, fill("", 4)))
    print_markdown_row(fill("-", 6))
    print_markdown_row(vcat("", "", string.(data_types)))
    for data_size in [:small, :medium, :large]
        row = [string(data_size)]
        for format in [:deflate, :zlib, :gzip]
            push!(row, string(format))
            for data_type in data_types
                t1 = results[(data_size, data_type, format, :zlib, mode)]
                t2 = results[(data_size, data_type, format, :inflate, mode)]
                push!(row, @sprintf("%4.1f", t2 / t1))
            end
            print_markdown_row(row)
            row = [""]
        end
    end
end

results = run_tests()
print_results(results, :in_memory)
print_results(results, :streaming)
print_markdown_table(results, :in_memory)
print_markdown_table(results, :streaming)


text:
# ---
# title: 682. Baseball Game
# id: problem682
# author: zhwang
# date: 2022-03-26
# difficulty: Easy
# categories: Stack
# link: <https://leetcode.com/problems/baseball-game/description/>
# hidden: true
# ---
#
# You are keeping score for a baseball game with strange rules. The game
# consists of several rounds, where the scores of past rounds may affect future
# rounds' scores.
#
# At the beginning of the game, you start with an empty record. You are given a
# list of strings `ops`, where `ops[i]` is the `ith` operation you must apply to
# the record and is one of the following:
#
#   1. An integer `x` \- Record a new score of `x`.
#   2. `"+"` \- Record a new score that is the sum of the previous two scores. It is guaranteed there will always be two previous scores.
#   3. `"D"` \- Record a new score that is double the previous score. It is guaranteed there will always be a previous score.
#   4. `"C"` \- Invalidate the previous score, removing it from the record. It is guaranteed there will always be a previous score.
#
# Return _the sum of all the scores on the record_.
#
#
#
# **Example 1:**
#
#
#
#     Input: ops = ["5","2","C","D","+"]
#     Output: 30
#     Explanation:
#     "5" - Add 5 to the record, record is now [5].
#     "2" - Add 2 to the record, record is now [5, 2].
#     "C" - Invalidate and remove the previous score, record is now [5].
#     "D" - Add 2 * 5 = 10 to the record, record is now [5, 10].
#     "+" - Add 5 + 10 = 15 to the record, record is now [5, 10, 15].
#     The total sum is 5 + 10 + 15 = 30.
#
#
# **Example 2:**
#
#
#
#     Input: ops = ["5","-2","4","C","D","9","+","+"]
#     Output: 27
#     Explanation:
#     "5" - Add 5 to the record, record is now [5].
#     "-2" - Add -2 to the record, record is now [5, -2].
#     "4" - Add 4 to the record, record is now [5, -2, 4].
#     "C" - Invalidate and remove the previous score, record is now [5, -2].
#     "D" - Add 2 * -2 = -4 to the record, record is now [5, -2, -4].
#     "9" - Add 9 to the record, record is now [5, -2, -4, 9].
#     "+" - Add -4 + 9 = 5 to the record, record is now [5, -2, -4, 9, 5].
#     "+" - Add 9 + 5 = 14 to the record, record is now [5, -2, -4, 9, 5, 14].
#     The total sum is 5 + -2 + -4 + 9 + 5 + 14 = 27.
#
#
# **Example 3:**
#
#
#
#     Input: ops = ["1"]
#     Output: 1
#
#
#
#
# **Constraints:**
#
#   * `1 <= ops.length <= 1000`
#   * `ops[i]` is `"C"`, `"D"`, `"+"`, or a string representing an integer in the range `[-3 * 104, 3 * 104]`.
#   * For operation `"+"`, there will always be at least two previous scores on the record.
#   * For operations `"C"` and `"D"`, there will always be at least one previous score on the record.
#
#
## @lc code=start
using LeetCode

function calculate_points(ops::Vector{String})::Int
    scores = Int[]
    for op in ops
        if op == "+"
            push!(scores, last(scores) + scores[end - 1])
        elseif op == "D"
            push!(scores, last(scores) * 2)
        elseif op == "C"
            pop!(scores)
        else
            push!(scores, parse(Int, op))
        end
    end
    return sum(scores)
end

## @lc code=end


text:
# This file is a part of Julia. License is MIT: https://julialang.org/license

"""
    Experimental

!!! warning
    Types, methods, or macros defined in this module are experimental and subject
    to change and will not have deprecations. Caveat emptor.
"""
module Experimental

using Base: Threads, sync_varname
using Base.Meta

"""
    Const(A::Array)

Mark an Array as constant/read-only. The invariant guaranteed is that you will not
modify an Array (through another reference) within an `@aliasscope` scope.

!!! warning
    Experimental API. Subject to change without deprecation.
"""
struct Const{T,N} <: DenseArray{T,N}
    a::Array{T,N}
end

Base.IndexStyle(::Type{<:Const}) = IndexLinear()
Base.size(C::Const) = size(C.a)
Base.axes(C::Const) = axes(C.a)
@eval Base.getindex(A::Const, i1::Int) =
    (Base.@inline; Core.const_arrayref($(Expr(:boundscheck)), A.a, i1))
@eval Base.getindex(A::Const, i1::Int, i2::Int, I::Int...) =
  (Base.@inline; Core.const_arrayref($(Expr(:boundscheck)), A.a, i1, i2, I...))

"""
    @aliasscope expr

Allows the compiler to assume that all `Const`s are not being modified through stores
within this scope, even if the compiler can't prove this to be the case.

!!! warning
    Experimental API. Subject to change without deprecation.
"""
macro aliasscope(body)
    sym = gensym()
    quote
        $(Expr(:aliasscope))
        $sym = $(esc(body))
        $(Expr(:popaliasscope))
        $sym
    end
end

function sync_end(c::Channel{Any})
    if !isready(c)
        # there must be at least one item to begin with
        close(c)
        return
    end
    nremaining::Int = 0
    while true
        event = take!(c)
        if event === :__completion__
            nremaining -= 1
            if nremaining == 0
                break
            end
        else
            nremaining += 1
            schedule(Task(()->begin
                try
                    wait(event)
                    put!(c, :__completion__)
                catch e
                    close(c, e)
                end
            end))
        end
    end
    close(c)
    nothing
end

"""
    Experimental.@sync

Wait until all lexically-enclosed uses of `@async`, `@spawn`, `@spawnat` and `@distributed`
are complete, or at least one of them has errored. The first exception is immediately
rethrown. It is the responsibility of the user to cancel any still-running operations
during error handling.

!!! Note
    This interface is experimental and subject to change or removal without notice.
"""
macro sync(block)
    var = esc(sync_varname)
    quote
        let $var = Channel(Inf)
            v = $(esc(block))
            sync_end($var)
            v
        end
    end
end

"""
    Experimental.@optlevel n::Int

Set the optimization level (equivalent to the `-O` command line argument)
for code in the current module. Submodules inherit the setting of their
parent module.

Supported values are 0, 1, 2, and 3.

The effective optimization level is the minimum of that specified on the
command line and in per-module settings. If a `--min-optlevel` value is
set on the command line, that is enforced as a lower bound.
"""
macro optlevel(n::Int)
    return Expr(:meta, :optlevel, n)
end

"""
    Experimental.@max_methods n::Int

Set the maximum number of potentially-matching methods considered when running inference
for methods defined in the current module. This setting affects inference of calls with
incomplete knowledge of the argument types.

Supported values are `1`, `2`, `3`, `4`, and `default` (currently equivalent to `3`).
"""
macro max_methods(n::Int)
    0 < n < 5 || error("We must have that `1 <= max_methods <= 4`, but `max_methods = $n`.")
    return Expr(:meta, :max_methods, n)
end

"""
    Experimental.@compiler_options optimize={0,1,2,3} compile={yes,no,all,min} infer={yes,no} max_methods={default,1,2,3,...}

Set compiler options for code in the enclosing module. Options correspond directly to
command-line options with the same name, where applicable. The following options
are currently supported:

  * `optimize`: Set optimization level.
  * `compile`: Toggle native code compilation. Currently only `min` is supported, which
    requests the minimum possible amount of compilation.
  * `infer`: Enable or disable type inference. If disabled, implies [`@nospecialize`](@ref).
  * `max_methods`: Maximum number of matching methods considered when running type inference.
"""
macro compiler_options(args...)
    opts = Expr(:block)
    for ex in args
        if isa(ex, Expr) && ex.head === :(=) && length(ex.args) == 2
            if ex.args[1] === :optimize
                push!(opts.args, Expr(:meta, :optlevel, ex.args[2]::Int))
            elseif ex.args[1] === :compile
                a = ex.args[2]
                a = #a === :no  ? 0 :
                    #a === :yes ? 1 :
                    #a === :all ? 2 :
                    a === :min ? 3 : error("invalid argument to \"compile\" option")
                push!(opts.args, Expr(:meta, :compile, a))
            elseif ex.args[1] === :infer
                a = ex.args[2]
                a = a === false || a === :no  ? 0 :
                    a === true  || a === :yes ? 1 : error("invalid argument to \"infer\" option")
                push!(opts.args, Expr(:meta, :infer, a))
            elseif ex.args[1] === :max_methods
                a = ex.args[2]
                a = a === :default ? 3 :
                  a isa Int ? ((0 < a < 5) ? a : error("We must have that `1 <= max_methods <= 4`, but `max_methods = $a`.")) :
                  error("invalid argument to \"max_methods\" option")
                push!(opts.args, Expr(:meta, :max_methods, a))
            else
                error("unknown option \"$(ex.args[1])\"")
            end
        else
            error("invalid option syntax")
        end
    end
    return opts
end

"""
    Experimental.@force_compile

Force compilation of the block or function (Julia's built-in interpreter is blocked from executing it).

# Examples

```
julia> occursin("interpreter", string(stacktrace(begin
           # with forced compilation
           Base.Experimental.@force_compile
           backtrace()
       end, true)))
false

julia> occursin("interpreter", string(stacktrace(begin
           # without forced compilation
           backtrace()
       end, true)))
true
```
"""
macro force_compile() Expr(:meta, :force_compile) end

# UI features for errors

"""
    Experimental.register_error_hint(handler, exceptiontype)

Register a "hinting" function `handler(io, exception)` that can
suggest potential ways for users to circumvent errors.  `handler`
should examine `exception` to see whether the conditions appropriate
for a hint are met, and if so generate output to `io`.
Packages should call `register_error_hint` from within their
`__init__` function.

For specific exception types, `handler` is required to accept additional arguments:

- `MethodError`: provide `handler(io, exc::MethodError, argtypes, kwargs)`,
  which splits the combined arguments into positional and keyword arguments.

When issuing a hint, the output should typically start with `\\n`.

If you define custom exception types, your `showerror` method can
support hints by calling [`Experimental.show_error_hints`](@ref).

# Example

```
julia> module Hinter

       only_int(x::Int)      = 1
       any_number(x::Number) = 2

       function __init__()
           Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs
               if exc.f == only_int
                    # Color is not necessary, this is just to show it's possible.
                    print(io, "\\nDid you mean to call ")
                    printstyled(io, "`any_number`?", color=:cyan)
               end
           end
       end

       end
```

Then if you call `Hinter.only_int` on something that isn't an `Int` (thereby triggering a `MethodError`), it issues the hint:

```
julia> Hinter.only_int(1.0)
ERROR: MethodError: no method matching only_int(::Float64)
Did you mean to call `any_number`?
Closest candidates are:
    ...
```

!!! compat "Julia 1.5"
    Custom error hints are available as of Julia 1.5.
!!! warning
    This interface is experimental and subject to change or removal without notice.
    To insulate yourself against changes, consider putting any registrations inside an
    `if isdefined(Base.Experimental, :register_error_hint) ... end` block.
"""
function register_error_hint(@nospecialize(handler), @nospecialize(exct::Type))
    list = get!(Vector{Any}, _hint_handlers, exct)
    push!(list, handler)
    return nothing
end

const _hint_handlers = IdDict{Type,Vector{Any}}()

"""
    Experimental.show_error_hints(io, ex, args...)

Invoke all handlers from [`Experimental.register_error_hint`](@ref) for the particular
exception type `typeof(ex)`. `args` must contain any other arguments expected by
the handler for that type.

!!! compat "Julia 1.5"
    Custom error hints are available as of Julia 1.5.
!!! warning
    This interface is experimental and subject to change or removal without notice.
"""
function show_error_hints(io, ex, args...)
    hinters = get(_hint_handlers, typeof(ex), nothing)
    isnothing(hinters) && return
    for handler in hinters
        try
            Base.invokelatest(handler, io, ex, args...)
        catch err
            tn = typeof(handler).name
            @error "Hint-handler $handler for $(typeof(ex)) in $(tn.module) caused an error"
        end
    end
end

# OpaqueClosure
include("opaque_closure.jl")

"""
    Experimental.@overlay mt [function def]

Define a method and add it to the method table `mt` instead of to the global method table.
This can be used to implement a method override mechanism. Regular compilation will not
consider these methods, and you should customize the compilation flow to look in these
method tables (e.g., using [`Core.Compiler.OverlayMethodTable`](@ref)).

"""
macro overlay(mt, def)
    def = macroexpand(__module__, def) # to expand @inline, @generated, etc
    if !isexpr(def, [:function, :(=)])
        error("@overlay requires a function Expr")
    end
    if isexpr(def.args[1], :call)
        def.args[1].args[1] = Expr(:overlay, mt, def.args[1].args[1])
    elseif isexpr(def.args[1], :where)
        def.args[1].args[1].args[1] = Expr(:overlay, mt, def.args[1].args[1].args[1])
    else
        error("@overlay requires a function Expr")
    end
    esc(def)
end

let new_mt(name::Symbol, mod::Module) = begin
        ccall(:jl_check_top_level_effect, Cvoid, (Any, Cstring), mod, "@MethodTable")
        ccall(:jl_new_method_table, Any, (Any, Any), name, mod)
    end
    @eval macro MethodTable(name::Symbol)
        esc(:(const $name = $$new_mt($(quot(name)), $(__module__))))
    end
end

"""
    Experimental.@MethodTable(name)

Create a new MethodTable in the current module, bound to `name`. This method table can be
used with the [`Experimental.@overlay`](@ref) macro to define methods for a function without
adding them to the global method table.
"""
:@MethodTable

end

max_stars_repo_name: BSnelling/julia

max_issues_repo_name: BSnelling/julia

max_forks_repo_name: BSnelling/julia


text:
abstract type CognitiveAtlas end

mutable struct ConceptAPI
    alias::String
    id_concept_class::String
    last_updated::Int
    creation_time::Int
    definition_text::String
    id::String
    id_user::String
    def_id::String
    event_stamp::String
    def_id_user::String
    # TODO: date time
#    def_event_stamp:: "2011-08-10T20:41:26",
    name::String
end

mutable struct DisorderAPI
    id::String
    id_protocol::String
    id_user::String
    flag_for_curator::Int
#  TODO date time
#    event_stamp:: "2013-11-20T15:38:08"
    name::String
    definition::String
end


text:
# conversion
float(x::CLogarithmic) = Complex(exp(Complex(float(x.abs.log), float(x.angle))))
(::Type{T})(x::CLogarithmic) where {T<:AbstractFloat} = (y=float(x.abs); T(iszero(x.angle) ? y : x.angle≈π ? -y : throw(DomainError(x))))
(::Type{T})(x::CLogarithmic) where {T<:Complex} = T(exp(Complex(x.abs.log, x.angle)))
big(x::CLogarithmic) = CLogarithmic(big(x.abs), big(x.angle))

# type functions
widen(::Type{CLogarithmic{T}}) where {T} = CLogarithmic{widen(T)}
big(::Type{CLogarithmic{T}}) where {T} = CLogarithmic{big(T)}

# special values
zero(::Type{CLogarithmic{T}}) where {T} = CLogarithmic(zero(ULogarithmic{T}))
one(::Type{CLogarithmic{T}}) where {T} = CLogarithmic(one(ULogarithmic{T}))

# predicates
iszero(x::CLogarithmic) = iszero(x.abs)
isone(x::CLogarithmic) = isone(x.abs) && iszero(x.angle)
isinf(x::CLogarithmic) = isinf(x.abs)
isfinite(x::CLogarithmic) = isfinite(x.abs)
isnan(x::CLogarithmic) = isnan(x.abs)

# sign
sign(x::CLogarithmic{T}) where {T} = iszero(x) ? zero(x) : CLogarithmic{T}(uexp(T,0), x.angle)
abs(x::CLogarithmic) = x.abs
angle(x::CLogarithmic) = x.angle
real(x::CLogarithmic) = x.abs * cos(x.angle)
imag(x::CLogarithmic) = x.abs * sin(x.angle)

# ordering
(==)(x::CLogarithmic, y::CLogarithmic) = x.abs==y.abs && x.angle==y.angle
isequal(x::CLogarithmic, y::CLogarithmic) = isequal(x.abs, y.abs) && isequal(x.angle, y.angle)


text:
# %% Comparison with analytically resolved models
using DrWatson
@quickactivate :FractalDimension # uses DynamicalSystems, PyPlot
include(srcdir("style.jl"))

datas = Vector(undef, 6)
labels = Vector{String}(undef, 6)

N = Int(1e5)

datas[1] = :roessler_periodic
labels[1] = "periodic"

# datas[2] = :torus2
# labels[2] = "2-torus"
datas[2] = :henonheiles_quasi
labels[2] = "quasiperiodic"

datas[3] = :koch
labels[3] = "Koch snowflake"

datas[4] = :kaplanyorke_map
labels[4] = "Kaplan-Yorke map"

datas[5] = :uniform_sphere
labels[5] = "sphere"

datas[6] = :standardmap_chaotic
labels[6] = "SM (uniform)"

qH = 0
qC = 2
Cmethod = "standard" # bueno or standard. Decides εmax for correlation sum.

# Calculate values for H, C
eHs, eCs, Hs, Cs = [Vector{Float64}[] for i in 1:4]
for i in 1:length(datas)
    # These are the parameters that change between the different elements of the plot
    data = datas[i]

    # Here we simply pack all parameters into a dictionary
    # (other parameters are (probably) globals)
    params = @strdict N qH qC data
    if Cmethod ≠ "standard"
        params["Cmethod"] = Cmethod
    end
    if data == :koch
        params["maxk"] = 7
        delete!(params, "N")
    end

    # This is the main call that calculates everything
    output, s = produce_or_load(
        datadir("main"), params, make_C_H;
        prefix = string(data), suffix = "jld2", force = false,
        ignores = ["data"], storepatch = false,
    )
    @unpack eH, eC, H, C = output
    push!(eHs, eH); push!(Hs, H); push!(eCs, eC); push!(Cs, C)
end

# Do the actual plot
legendtitle = "analytically known \$\\Delta\$"

fig, axs = mainplot(
    Hs, Cs, eHs, eCs, labels, legendtitle;
    qH, qC, tol = 0.25,

    # For this plot we use standard regression fit because
    # the estimate is already so accurate, we don't want to
    # have the unecessary larger confidence intervals from the
    # logarithmic correction
    dimension_fit_C = FractalDimension.linear_regression_fit_glm,
)

wsave(plotsdir("paper", "analytic"), fig)

max_stars_repo_name: Datseris/FractalDimension

max_issues_repo_name: Datseris/FractalDimension

max_forks_repo_name: Datseris/FractalDimension


text:
# write.jl: Silo variable writing
export silowrite

function silowrite{T1<:AbstractString, T2<:Real}(file::silofile, name::T1, x_values::Vector{T2}, y_values::Vector{T2}; optlist::Ptr{DBoptlist}=Ptr{DBoptlist}(0))
  const N = length(x_values)
  if length(y_values) != N
    error("silowrite requires x and y-values to be of the same length.")
  end
  y_values_eltype = eltype(y_values)
#   from libsilo_h:
#   [:Int64 => DB_LONG_LONG, :Int32 => DB_INT, :Int16 =>DB_SHORT, :Int8=>DB_CHAR, :Float32 => DB_FLOAT, :Float64 => DB_DOUBLE]
  if haskey(DBdatatype.JuliaSilotypemap, y_values_eltype)
    datatype = DBdatatype.JuliaSilotypemap[y_values_eltype]
  else
    error("Datatype $y_values_eltype currently unsupported.")
  end
  return DBPutCurve(file.dbfile, name, x_values, y_values, datatype, N, optlist)
end
silowrite{T1<:AbstractString, T2<:Real}(file::silofile, name::T1, y_values::Vector{T2}) = silowrite(file, name, convert(Vector{eltype(y_values)},1:length(y_values)), y_values)


text:
@deprecate ConsistencyTest(estimator::CalibrationErrorEstimator, data...) ConsistencyTest(
    estimator, CalibrationErrors.predictions_targets(data...)...
)

@deprecate DistributionFreeSKCETest(estimator::SKCE, data...; kwargs...) DistributionFreeSKCETest(
    estimator, CalibrationErrors.predictions_targets(data...)...; kwargs...
)

@deprecate AsymptoticSKCETest(skce::UnbiasedSKCE, data...) AsymptoticSKCETest(
    skce.kernel, data...
)
@deprecate AsymptoticSKCETest(kernel::Kernel, data...) AsymptoticSKCETest(
    kernel, CalibrationErrors.predictions_targets(data...)...
)

@deprecate AsymptoticBlockSKCETest(skce::BlockUnbiasedSKCE, data...) AsymptoticBlockSKCETest(
    skce.kernel, skce.blocksize, data...
)
@deprecate AsymptoticBlockSKCETest(kernel::Kernel, blocksize::Int, data...) AsymptoticBlockSKCETest(
    kernel, blocksize, CalibrationErrors.predictions_targets(data...)...
)

@deprecate AsymptoticCMETest(estimator::UCME, data...) AsymptoticCMETest(
    estimator, CalibrationErrors.predictions_targets(data...)...
)


text:
struct SPPort end
struct SPConfig end
struct SPEventSet end

const Port = Ref{SPPort}
const PortP = Ref{Ptr{SPPort}}
const Config = Ref{SPConfig}
const ConfigP = Ref{Ptr{SPConfig}}

@enum SPReturn begin
    SP_OK = 0
    SP_ERR_ARG = -1
    SP_ERR_FAIL = -2
    SP_ERR_MEM = -3
    SP_ERR_SUPP = -4
end

@enum SPMode begin
    SP_MODE_READ = 1
    SP_MODE_WRITE = 2
    SP_MODE_READ_WRITE = 3
end

@enum SPEvent begin
    SP_EVENT_RX_READY = 1
    SP_EVENT_TX_READY = 2
    SP_EVENT_ERROR = 4
end

@enum SPBuffer begin
    SP_BUF_INPUT = 1
    SP_BUF_OUTPUT = 2
    SP_BUF_BOTH = 3
end

@enum SPParity begin
    SP_PARITY_INVALID = -1
    SP_PARITY_NONE = 0
    SP_PARITY_ODD = 1
    SP_PARITY_EVEN = 2
    SP_PARITY_MARK = 3
    SP_PARITY_SPACE = 4
end

@enum SPrts begin
    SP_RTS_INVALID = -1
    SP_RTS_OFF = 0
    SP_RTS_ON = 1
    SP_RTS_FLOW_CONTROL = 2
end

@enum SPcts begin
    SP_CTS_INVALID = -1
    SP_CTS_IGNORE = 0
    SP_CTS_FLOW_CONTROL = 1
end

@enum SPdtr begin
    SP_DTR_INVALID = -1
    SP_DTR_OFF = 0
    SP_DTR_ON = 1
    SP_DTR_FLOW_CONTROL = 2
end

@enum SPdsr begin
    SP_DSR_INVALID = -1
    SP_DSR_IGNORE = 0
    SP_DSR_FLOW_CONTROL = 1
end

@enum SPXonXoff begin
    SP_XONXOFF_INVALID = -1
    SP_XONXOFF_DISABLED = 0
    SP_XONXOFF_IN = 1
    SP_XONXOFF_OUT = 2
    SP_XONXOFF_INOUT = 3
end

@enum SPFlowControl begin
    SP_FLOWCONTROL_NONE = 0
    SP_FLOWCONTROL_XONXOFF = 1
    SP_FLOWCONTROL_RTSCTS = 2
    SP_FLOWCONTROL_DTRDSR = 3
end

@enum SPSignal begin
    SP_SIG_CTS = 1
    SP_SIG_DSR = 2
    SP_SIG_DCD = 4
    SP_SIG_RI = 8
end

@enum SPTransport begin
    SP_TRANSPORT_NATIVE
    SP_TRANSPORT_USB
    SP_TRANSPORT_BLUETOOTH
end

# Define constructors to make conversion to Int explicit, since falling back to
# `convert` from a missing constructor is now deprecated behavior.
SPReturn(x::SPReturn) = SPReturn(Int(x))
SPMode(x::SPMode) = SPMode(Int(x))
SPEvent(x::SPEvent) = SPEvent(Int(x))
SPBuffer(x::SPBuffer) = SPBuffer(Int(x))
SPParity(x::SPParity) = SPParity(Int(x))
SPrts(x::SPrts) = SPrts(Int(x))
SPcts(x::SPcts) = SPcts(Int(x))
SPdtr(x::SPdtr) = SPdtr(Int(x))
SPdsr(x::SPdsr) = SPdsr(Int(x))
SPXonXoff(x::SPXonXoff) = SPXonXoff(Int(x))
SPFlowControl(x::SPFlowControl) = SPFlowControl(Int(x))
SPSignal(x::SPSignal) = SPSignal(Int(x))
SPTransport(x::SPTransport) = SPTransport(Int(x))

function handle_error(ret::SPReturn, location::AbstractString)
    ret >= SP_OK && return

    msg = "From $location:\n\nlibserialport returned $ret - "

    if ret == SP_ERR_ARG
        msg *= "Function was called with invalid arguments."
    elseif ret == SP_ERR_FAIL
        # Note: these functions only return valid info after SP_ERR_FAIL
        # Don't use them elsewhere
        println("OS error code $(sp_last_error_code()): $(sp_last_error_message())")
        msg *= "Host OS reported a failure."
    elseif ret == SP_ERR_MEM
        msg *= "Memory allocation failed."
    elseif ret == SP_ERR_SUPP
        msg *= "No support for the requested operation in the current OS, driver or device."
    else
        error("Unknown SPReturn value")
    end

    error(msg)
end

function loc()
    f = eval(@__FILE__)
    l = eval(@__LINE__)
    return "$f: $l"
end

# enum sp_return sp_get_port_by_name(const char *portname, struct sp_port **port_ptr);
function sp_get_port_by_name(portname::AbstractString)
    portp = PortP()
    ret = ccall((:sp_get_port_by_name, libserialport), SPReturn,
                (Ptr{UInt8}, PortP), portname, portp)
    handle_error(ret, loc())
    portp[]
end

# void sp_free_port(struct sp_port *port);
function sp_free_port(port::Port)
    ccall((:sp_free_port, libserialport), Nothing, (Port,), port)
end

# enum sp_return sp_list_ports(struct sp_port ***list_ptr);
function sp_list_ports()
    ports = Ref{Ptr{Ptr{SPPort}}}()
    ret = ccall((:sp_list_ports, libserialport),
                SPReturn, (Ref{Ptr{Ptr{SPPort}}},), ports)
    handle_error(ret, loc())
    return ports[]
end

# enum sp_return sp_copy_port(const struct sp_port *port, struct sp_port **copy_ptr);
function sp_copy_port(port::Port)
    port_copy = PortP()
    ret = ccall((:sp_copy_port, libserialport), SPReturn,
                (Port, PortP), port, port_copy)
    handle_error(ret, loc())
    return port_copy[]
end

# void sp_free_port_list(struct sp_port **ports);
function sp_free_port_list(ports::PortP)
    ccall((:sp_free_port_list, libserialport), Nothing, (PortP,), ports)
end

# enum sp_return sp_open(struct sp_port *port, enum sp_mode flags);
function sp_open(port::Port, mode::SPMode)
    ret = ccall((:sp_open, libserialport), SPReturn, (Port, SPMode), port, mode)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_close(struct sp_port *port);
function sp_close(port::Port)
    ret = ccall((:sp_close, libserialport), SPReturn, (Port,), port)
    handle_error(ret, loc())
    ret
end

# char *sp_get_port_name(const struct sp_port *port);
function sp_get_port_name(port::Port)
    cname = ccall((:sp_get_port_name, libserialport), Ptr{UInt8}, (Port,), port)
    name = cname != C_NULL ? unsafe_string(cname) : ""
end

# char *sp_get_port_description(const struct sp_port *port);
function sp_get_port_description(port::Port)
    d = ccall((:sp_get_port_description, libserialport), Ptr{UInt8}, (Port,), port)
    desc = d != C_NULL ? unsafe_string(d) : ""
end

# enum sp_transport sp_get_port_transport(const struct sp_port *port);
function sp_get_port_transport(port::Port)
    ccall((:sp_get_port_transport, libserialport), SPTransport, (Port,), port)
end

# enum sp_return sp_get_port_usb_bus_address(const struct sp_port *port, int *usb_bus, int *usb_address);
function sp_get_port_usb_bus_address(port::Port)

    if sp_get_port_transport(port) != SP_TRANSPORT_USB
        @warn "Port does not use USB transport"
        return
    end

    usb_bus = Ref{Cint}()
    usb_address = Ref{Cint}()
    ret = ccall((:sp_get_port_usb_bus_address, libserialport), SPReturn,
                (Port, Ref{Cint}, Ref{Cint}), port, usb_bus, usb_address)

    if ret == SP_ERR_SUPP
        return -1, -1
    end

    handle_error(ret, loc())
    return usb_bus[], usb_address[]
end

# enum sp_return sp_get_port_usb_vid_pid(const struct sp_port *port, int *usb_vid, int *usb_pid);
function sp_get_port_usb_vid_pid(port::Port)

    if sp_get_port_transport(port) != SP_TRANSPORT_USB
        @warn "Port does not use USB transport"
        return
    end

    vid = Ref{Cint}()
    pid = Ref{Cint}()
    ret = ccall((:sp_get_port_usb_vid_pid, libserialport), SPReturn,
                (Port, Ref{Cint}, Ref{Cint}), port, vid, pid)

    if ret == SP_ERR_SUPP
        return -1, -1
    end

    handle_error(ret, loc())
    return vid[], pid[]
end

# char *sp_get_port_usb_manufacturer(const struct sp_port *port);
function sp_get_port_usb_manufacturer(port::Port)
    m = ccall((:sp_get_port_usb_manufacturer, libserialport),
              Ptr{UInt8}, (Port,), port)
    manufacturer = (m != C_NULL) ? unsafe_string(m) : ""
end

# char *sp_get_port_usb_product(const struct sp_port *port);
function sp_get_port_usb_product(port::Port)
    p = ccall((:sp_get_port_usb_product, libserialport),
              Ptr{UInt8}, (Port,), port)
    product = (p != C_NULL) ? unsafe_string(p) : ""
end

# char *sp_get_port_usb_serial(const struct sp_port *port);
function sp_get_port_usb_serial(port::Port)
    s = ccall((:sp_get_port_usb_serial, libserialport),
              Ptr{UInt8}, (Port,), port)
    serial = (s != C_NULL) ? unsafe_string(s) : ""
end

# char *sp_get_port_bluetooth_address(const struct sp_port *port);
function sp_get_port_bluetooth_address(port::Port)
    a = ccall((:sp_get_port_bluetooth_address, libserialport),
              Ptr{UInt8}, (Port,), port)
    address = (a != C_NULL) ? unsafe_string(a) : ""
end

if Sys.iswindows()
     # TODO: on Windows, result should be Ref{HANDLE}
    sp_get_port_handle(port::Port) = error("Returning port handle not supported on Windows")
else
    # enum sp_return sp_get_port_handle(const struct sp_port *port, void *result_ptr);
    function sp_get_port_handle(port::Port)
        # For Linux and OS X
        result = Ref{Cint}(0)
        ret = ccall((:sp_get_port_handle, libserialport), SPReturn,
                    (Port, Ref{Cint}), port, result)
        handle_error(ret, loc())
        result[]
    end
end

# enum sp_return sp_new_config(struct sp_port_config **config_ptr);
function sp_new_config()
    pc = ConfigP()
    ret = ccall((:sp_new_config, libserialport), SPReturn, (ConfigP,), pc)
    handle_error(ret, loc())
    pc[]
end

# void sp_free_config(struct sp_port_config *config);
function sp_free_config(config::Config)
    ccall((:sp_free_config, libserialport), Nothing, (Config,), config)
end

function sp_get_config(port::Port)
    config = sp_new_config()
    ret = ccall((:sp_get_config, libserialport), SPReturn,
                (Port, Config), port, config)
    handle_error(ret, loc())
    config
end

# enum sp_return sp_set_config(struct sp_port *port, const struct sp_port_config *config);
function sp_set_config(port::Port, config::Config)
    ret = ccall((:sp_set_config, libserialport), SPReturn,
                (Port, Config), port, config)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_baudrate(struct sp_port *port, int baudrate);
function sp_set_baudrate(port::Port, baudrate::Integer)
    ret = ccall((:sp_set_baudrate, libserialport), SPReturn,
                (Port, Cint), port, Cint(baudrate))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_baudrate(const struct sp_port_config *config, int *baudrate_ptr);
function sp_get_config_baudrate(config::Config)
    baudrate = Ref{Cint}()
    ret = ccall((:sp_get_config_baudrate, libserialport), SPReturn,
                (Config, Ref{Cint}), config, baudrate)
    handle_error(ret, loc())
    baudrate[]
end

# enum sp_return sp_set_config_baudrate(struct sp_port_config *config, int baudrate);
function sp_set_config_baudrate(config::Config, baudrate::Integer)
    ret = ccall((:sp_set_config_baudrate, libserialport), SPReturn,
                (Config, Cint), config, Cint(baudrate))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_bits(struct sp_port *port, int bits);
function sp_set_bits(port::Port, bits::Integer)
    @assert 5 <= bits <= 8
    ret = ccall((:sp_set_bits, libserialport), SPReturn,
                (Port, Cint), port, Cint(bits))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_bits(const struct sp_port_config *config, int *bits_ptr);
function sp_get_config_bits(config::Config)
    bits = Ref{Cint}()
    ret = ccall((:sp_get_config_bits, libserialport), SPReturn,
                (Config, Ref{Cint}), config, bits)
    handle_error(ret, loc())
    bits[]
end

# enum sp_return sp_set_config_bits(struct sp_port_config *config, int bits);
function sp_set_config_bits(config::Config, bits::Integer)
    ret = ccall((:sp_set_config_bits, libserialport), SPReturn,
                (Config, Cint), config, Cint(bits))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_parity(struct sp_port *port, enum sp_parity parity);
function sp_set_parity(port::Port, parity::SPParity)
    ret = ccall((:sp_set_parity, libserialport), SPReturn,
                (Port, SPParity), port, parity)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_parity(const struct sp_port_config *config, enum sp_parity *parity_ptr);
function sp_get_config_parity(config::Config)
    parity = Ref{SPParity}()
    ret = ccall((:sp_get_config_parity, libserialport), SPReturn,
                (Config, Ref{SPParity}), config, parity)
    handle_error(ret, loc())
    parity[]
end

# enum sp_return sp_set_config_parity(struct sp_port_config *config, enum sp_parity parity);
function sp_set_config_parity(config::Config, parity::SPParity)
    ret = ccall((:sp_set_config_parity, libserialport), SPReturn,
                (Config, SPParity), config, parity)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_stopbits(struct sp_port *port, int stopbits);
function sp_set_stopbits(port::Port, stopbits::Integer)
    ret = ccall((:sp_set_stopbits, libserialport), SPReturn,
                (Port, Cint), port, Cint(stopbits))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_stopbits(const struct sp_port_config *config, int *stopbits_ptr);
function sp_get_config_stopbits(config::Config)
    bits = Ref{Cint}()
    ret = ccall((:sp_get_config_stopbits, libserialport), SPReturn,
                (Config, Ref{Cint}), config, bits)
    handle_error(ret, loc())
    bits[]
end

# enum sp_return sp_set_config_stopbits(struct sp_port_config *config, int stopbits);
function sp_set_config_stopbits(config::Config, stopbits::Integer)
    ret = ccall((:sp_set_config_stopbits, libserialport), SPReturn,
                (Config, Cint), config, Cint(stopbits))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_rts(struct sp_port *port, enum sp_rts rts);
function sp_set_rts(port::Port, rts::SPrts)
    ret = ccall((:sp_set_rts, libserialport), SPReturn,
                (Port, SPrts), port, rts)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_rts(const struct sp_port_config *config, enum sp_rts *rts_ptr);
function sp_get_config_rts(config::Config)
    rts = Ref{SPrts}()
    ret = ccall((:sp_get_config_rts, libserialport), SPReturn,
                (Config, Ref{SPrts}), config, rts)
    handle_error(ret, loc())
    rts[]
end

# enum sp_return sp_set_config_rts(struct sp_port_config *config, enum sp_rts rts);
function sp_set_config_rts(config::Config, rts::SPrts)
    ret = ccall((:sp_set_config_rts, libserialport), SPReturn,
                (Config, SPrts), config, SPrts(rts))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_cts(struct sp_port *port, enum sp_cts cts);
function sp_set_cts(port::Port, cts::SPcts)
    ret = ccall((:sp_set_cts, libserialport), SPReturn,
                (Port, SPcts), port, cts)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_cts(const struct sp_port_config *config, enum sp_cts *cts_ptr);
function sp_get_config_cts(config::Config)
    cts = Ref{SPcts}()
    ret = ccall((:sp_get_config_cts, libserialport), SPReturn,
                (Config, Ref{SPcts}), config, cts)
    handle_error(ret, loc())
    cts[]
end

# enum sp_return sp_set_config_cts(struct sp_port_config *config, enum sp_cts cts);
function sp_set_config_cts(config::Config, cts::SPcts)
    ret = ccall((:sp_set_config_cts, libserialport), SPReturn,
                (Config, SPcts), config, SPcts(cts))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_dtr(struct sp_port *port, enum sp_dtr dtr);
function sp_set_dtr(port::Port, dtr::SPdtr)
    ret = ccall((:sp_set_dtr, libserialport), SPReturn,
                (Port, SPdtr), port, dtr)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_dtr(const struct sp_port_config *config, enum sp_dtr *dtr_ptr);
function sp_get_config_dtr(config::Config)
    dtr = Ref{SPdtr}()
    ret = ccall((:sp_get_config_dtr, libserialport), SPReturn,
                (Config, Ref{SPdtr}), config, dtr)
    handle_error(ret, loc())
    dtr[]
end

# enum sp_return sp_set_config_dtr(struct sp_port_config *config, enum sp_dtr dtr);
function sp_set_config_dtr(config::Config, dtr::SPdtr)
    ret = ccall((:sp_set_config_dtr, libserialport), SPReturn,
                (Config, SPdtr), config, SPdtr(dtr))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_dsr(struct sp_port *port, enum sp_dsr dsr);
function sp_set_dsr(port::Port, dsr::SPdsr)
    ret = ccall((:sp_set_dsr, libserialport), SPReturn,
                (Port, SPdsr), port, dsr)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_dsr(const struct sp_port_config *config, enum sp_dsr *dsr_ptr);
function sp_get_config_dsr(config::Config)
    dsr = Ref{SPdsr}()
    ret = ccall((:sp_get_config_dsr, libserialport), SPReturn,
                (Config, Ref{SPdsr}), config, dsr)
    handle_error(ret, loc())
    dsr[]
end

# enum sp_return sp_set_config_dsr(struct sp_port_config *config, enum sp_dsr dsr);
function sp_set_config_dsr(config::Config, dsr::SPdsr)
    ret = ccall((:sp_set_config_dsr, libserialport), SPReturn,
                (Config, SPdsr), config, SPdsr(dsr))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_xon_xoff(struct sp_port *port, enum sp_xonxoff xon_xoff);
function sp_set_xon_xoff(port::Port, xon_xoff::SPXonXoff)
    ret = ccall((:sp_set_xon_xoff, libserialport), SPReturn,
                (Port, SPXonXoff), port, xon_xoff)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_config_xon_xoff(const struct sp_port_config *config, enum sp_xonxoff *xon_xoff_ptr);
function sp_get_config_xon_xoff(config::Config)
    xon_xoff = Ref{SPXonXoff}()
    ret = ccall((:sp_get_config_xon_xoff, libserialport), SPReturn,
                (Config, Ref{SPXonXoff}), config, xon_xoff)
    handle_error(ret, loc())
    xon_xoff[]
end

# enum sp_return sp_set_config_xon_xoff(struct sp_port_config *config, enum sp_xonxoff xon_xoff);
function sp_set_config_xon_xoff(config::Config, xon_xoff::SPXonXoff)
    ret = ccall((:sp_set_config_xon_xoff, libserialport), SPReturn,
                (Config, SPXonXoff), config, SPXonXoff(xon_xoff))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_config_flowcontrol(struct sp_port_config *config, enum sp_flowcontrol flowcontrol);
function sp_set_config_flowcontrol(config::Config, flowcontrol::SPFlowControl)
    ret = ccall((:sp_set_config_flowcontrol, libserialport), SPReturn,
                (Config, SPFlowControl), config, flowcontrol)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_set_flowcontrol(struct sp_port *port, enum sp_flowcontrol flowcontrol);
function sp_set_flowcontrol(port::Port, flowcontrol::SPFlowControl)
    ret = ccall((:sp_set_flowcontrol, libserialport), SPReturn,
                (Port, SPFlowControl), port, flowcontrol)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_blocking_read(struct sp_port *port, void *buf, size_t count, unsigned int timeout_ms);
function sp_blocking_read(port::Port, nbytes::Integer, timeout_ms::Integer)
    buffer = zeros(UInt8, nbytes)

    # If the read succeeds, the return value is the number of bytes read.
    ret = ccall((:sp_blocking_read, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t, Cuint),
                port, buffer, Csize_t(nbytes), Cuint(timeout_ms))
    handle_error(ret, loc())

    return Int(ret), buffer
end

# enum sp_return sp_blocking_read_next(struct sp_port *port, void *buf, size_t count, unsigned int timeout_ms);
function sp_blocking_read_next(port::Port, nbytes::Integer, timeout_ms::Integer)
    buffer = zeros(UInt8, nbytes)

    # If the read succeeds, the return value is the number of bytes read.
    ret = ccall((:sp_blocking_read_next, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t, Cuint),
                port, buffer, Csize_t(nbytes), Cuint(timeout_ms))
    handle_error(ret, loc())

    return Int(ret), buffer
end

# enum sp_return sp_nonblocking_read(struct sp_port *port, void *buf, size_t count);
function sp_nonblocking_read(port::Port, nbytes::Integer)
    buffer = zeros(UInt8, nbytes)

    # If the read succeeds, the return value is the number of bytes read.
    ret = ccall((:sp_nonblocking_read, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t), port, buffer, Csize_t(nbytes))
    handle_error(ret, loc())

    return Int(ret), buffer
end

# enum sp_return sp_blocking_write(struct sp_port *port, const void *buf, size_t count, unsigned int timeout_ms);
function sp_blocking_write(port::Port, buffer::Array{UInt8}, timeout_ms::Integer)
    ret = ccall((:sp_blocking_write, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t, Cuint),
                port, pointer(buffer), length(buffer), Cuint(timeout_ms))
    handle_error(ret, loc())
    ret
end

function sp_blocking_write(port::Port, buffer::Ptr{UInt8}, timeout_ms::Integer)
    ret = ccall((:sp_blocking_write, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t, Cuint),
                port, buffer, sizeof(buffer), Cuint(timeout_ms))
    handle_error(ret, loc())
    ret
end

function sp_blocking_write(port::Port, buffer::String, timeout_ms::Integer)
    ret = ccall((:sp_blocking_write, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t, Cuint),
                port, buffer, length(buffer), Cuint(timeout_ms))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_nonblocking_write(struct sp_port *port, const void *buf, size_t count);
function sp_nonblocking_write(port::Port, buffer::Array{UInt8})
    ret = ccall((:sp_nonblocking_write, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t), port, pointer(buffer), length(buffer))
    handle_error(ret, loc())
    ret
end

function sp_nonblocking_write(port::Port, buffer::Ptr{UInt8})
    ret = ccall((:sp_nonblocking_write, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t), port, buffer, sizeof(buffer))
    handle_error(ret, loc())
    ret
end

function sp_nonblocking_write(port::Port, buffer::String)
    ret = ccall((:sp_nonblocking_write, libserialport), SPReturn,
                (Port, Ptr{UInt8}, Csize_t), port, buffer, sizeof(buffer))
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_input_waiting(struct sp_port *port);
"""
Returns the number of bytes in the input buffer or an error code.
"""
function sp_input_waiting(port::Port)
    ret = ccall((:sp_input_waiting, libserialport), SPReturn, (Port,), port)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_output_waiting(struct sp_port *port);
"""
Returns the number of bytes in the output buffer or an error code.
"""
function sp_output_waiting(port::Port)
    ret = ccall((:sp_output_waiting, libserialport), SPReturn, (Port,), port)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_flush(struct sp_port *port, enum sp_buffer buffers);
function sp_flush(port::Port, buffers::SPBuffer)
    ret = ccall((:sp_flush, libserialport), SPReturn,
                (Port, SPBuffer), port, buffers)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_drain(struct sp_port *port);
function sp_drain(port::Port)
    ret = ccall((:sp_drain, libserialport), SPReturn, (Port,), port)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_new_event_set(struct sp_event_set **result_ptr);
function sp_new_event_set()
    event_set = Ref{Ptr{SPEventSet}}()
    ret = ccall((:sp_new_event_set, libserialport), SPReturn,
                (Ref{Ptr{SPEventSet}},), event_set)
    handle_error(ret, loc())
    event_set[]
end

# enum sp_return sp_add_port_events(struct sp_event_set *event_set, const struct sp_port *port, enum sp_event mask);
function sp_add_port_events(event_set::Ref{SPEventSet}, port::Port, mask::SPEvent)
    ret = ccall((:sp_add_port_events, libserialport), SPReturn,
                (Ref{SPEventSet}, Port, SPEvent), event_set, port, mask)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_wait(struct sp_event_set *event_set, unsigned int timeout_ms);
function sp_wait(event_set::Ref{SPEventSet}, timeout_ms::Integer)
    ret = ccall((:sp_wait, libserialport), SPReturn,
                (Ref{SPEventSet}, Cuint), event_set, timeout_ms)
    handle_error(ret, loc())
    ret
end

# void sp_free_event_set(struct sp_event_set *event_set);
function sp_free_event_set(event_set::Ref{SPEventSet})
    ret = ccall((:sp_free_event_set, libserialport), SPReturn,
                (Ref{SPEventSet},), event_set)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_get_signals(struct sp_port *port, enum sp_signal *signal_mask);
function sp_get_signals(port::Port, signal_mask::Ref{SPSignal})
    ret = ccall((:sp_get_signals, libserialport), SPReturn,
                (Port, Ref{SPSignal}), port, signal_mask)
    handle_error(ret, loc())
    signal_mask[]
end

# enum sp_return sp_start_break(struct sp_port *port);
function sp_start_break(port::Port)
    ret = ccall((:sp_start_break, libserialport), SPReturn, (Port,), port)
    handle_error(ret, loc())
    ret
end

# enum sp_return sp_end_break(struct sp_port *port);
function sp_end_break(port::Port)
    ret = ccall((:sp_end_break, libserialport), SPReturn, (Port,), port)
    handle_error(ret, loc())
    ret
end

# int sp_last_error_code(void);
function sp_last_error_code()
    ccall((:sp_last_error_code, libserialport), Cint, ())
end

# char *sp_last_error_message(void);
function sp_last_error_message()
    msg = ccall((:sp_last_error_message, libserialport), Ptr{UInt8}, ())
    msg_jl = unsafe_string(msg)
    _sp_free_error_message(msg)
    return msg_jl
end

# void sp_free_error_message(char *message);
function _sp_free_error_message(message::Ptr{UInt8})
    ccall((:sp_free_error_message, libserialport), Nothing, (Ptr{UInt8},), message)
end

# Due to ccall's incomplete variadic argument support, the following two
# functions are not (yet) wrapped.
# void sp_set_debug_handler(void (*handler)(const char *format, ...));
# void sp_default_debug_handler(const char *format, ...);

# int sp_get_major_package_version(void);
function sp_get_major_package_version()
    ccall((:sp_get_major_package_version, libserialport), Cint, ())
end

# int sp_get_minor_package_version(void);
function sp_get_minor_package_version()
    ccall((:sp_get_minor_package_version, libserialport), Cint, ())
end

# int sp_get_micro_package_version(void);
function sp_get_micro_package_version()
    ccall((:sp_get_micro_package_version, libserialport), Cint, ())
end

# const char *sp_get_package_version_string(void);
function sp_get_package_version_string()
    ver = ccall((:sp_get_package_version_string, libserialport), Ptr{UInt8}, ())
    unsafe_string(ver)
end

# int sp_get_current_lib_version(void);
function sp_get_current_lib_version()
    ccall((:sp_get_current_lib_version, libserialport), Cint, ())
end

# int sp_get_revision_lib_version(void);
function sp_get_revision_lib_version()
    ccall((:sp_get_revision_lib_version, libserialport), Cint, ())
end

# int sp_get_age_lib_version(void);
function sp_get_age_lib_version()
    ccall((:sp_get_age_lib_version, libserialport), Cint, ())
end

# const char *sp_get_lib_version_string(void);
function sp_get_lib_version_string()
    ver = ccall((:sp_get_lib_version_string, libserialport), Ptr{UInt8}, ())
    unsafe_string(ver)
end


text:
using Flux
using Flux: onehotbatch, onecold, crossentropy
using JLD2, FileIO
using Statistics: mean

include("modulos/dataset_DL.jl");
include("modulos/graphics.jl");

#===============================================================================
COSAS:
    · en toFloatArray no me deja hacer un assert de una comp de ints:  MethodError: objects of type Int64 are not callable
    · ¿por qué filtros de 3x3?
    · ¿Están bien las capas convolucionales?

===============================================================================#

interval = 8;
(train_imgs, train_labels,
    test_imgs, test_labels) = getInputs("datasets", interval);

#=
for image in train_imgs
    display(image);
end;
for image in test_imgs
    display(image);
end;
=#

train_imgs = toFloatArray(train_imgs);
test_imgs = toFloatArray(test_imgs);

println("Tamaño de la matriz de entrenamiento: ", size(train_imgs))
println("Tamaño de la matriz de test:          ", size(test_imgs))

batch_size = 64;
gruposIndicesBatch = Iterators.partition(1:size(train_imgs,4), batch_size);
println("He creado ", length(gruposIndicesBatch), " grupos de indices para distribuir los patrones en batches");

train_set = [( train_imgs[:,:,:,indicesBatch], Array(onehotbatch(train_labels[indicesBatch], 0:2)) ) for indicesBatch in gruposIndicesBatch]

test_set = (test_imgs, onehotbatch(test_labels, 0:2));

train_imgs = nothing;
test_imgs = nothing;
GC.gc();

funcionTransferenciaCapasConvolucionales = relu;

# Definimos la red con la funcion Chain, que concatena distintas capas

modelo = Chain(
    Conv((5, 5), 3=>16, pad=(1,1), funcionTransferenciaCapasConvolucionales),
    MaxPool((2,2)),
    Conv((5, 5), 16=>32, pad=(1,1), funcionTransferenciaCapasConvolucionales),
    MaxPool((2,2)),
    Conv((5, 5), 32=>32, pad=(1,1), funcionTransferenciaCapasConvolucionales),
    MaxPool((2,2)),
    x -> reshape(x, :, size(x, 4)),
    Dense(9248, 3),
    softmax
);

# Vamos a probar la RNA y poner algunos datos de cada capa
# Usaremos como entrada varios patrones de un batch
numBatchCoger = 1; numImagenEnEseBatch = [12, 6];

entradaCapa = train_set[numBatchCoger][1][:,:,:,numImagenEnEseBatch];
numCapas = length(params(modelo));
println("La RNA tiene ", numCapas, " capas:");
for numCapa in 1:numCapas
    println("   Capa ", numCapa, ": ", modelo[numCapa]);
    # Le pasamos la entrada a esta capa
    global entradaCapa # Esta linea es necesaria porque la variable entradaCapa es global y se modifica en este bucle
    capa = modelo[numCapa];
    salidaCapa = capa(entradaCapa);
    println("      La salida de esta capa tiene dimension ", size(salidaCapa));
    entradaCapa = salidaCapa;
end;

modelo(train_set[numBatchCoger][1][:,:,:,numImagenEnEseBatch]);

loss(x, y) = crossentropy(modelo(x), y)
accuracy(batch) = mean(onecold(modelo(batch[1])) .== onecold(batch[2]))

function calculate_loss(array_of_tuples)
    return loss(array_of_tuples[1], array_of_tuples[2]);
end;

println("Ciclo 0: Precision en el conjunto de entrenamiento: ", 100*mean(accuracy.(train_set)), " %");

opt = ADAM(0.001);

println("Comenzando entrenamiento...")
mejorPrecision = -Inf;
criterioFin = false;
numCiclo = 0;
numCicloUltimaMejora = 0;
mejorModelo = nothing;
num_ciclos = [];
trainingLosses = [];
testLosses = [];
trainingAccuracies = [];
testAccuracies = [];

while (!criterioFin)

    global numCicloUltimaMejora, numCiclo, mejorPrecision, mejorModelo,
        criterioFin, num_ciclos, trainingLosses, testLosses,
        trainingAccuracies, testAccuracies;

    Flux.train!(loss, params(modelo), train_set, opt);

    numCiclo += 1;

    precisionEntrenamiento = mean(accuracy.(train_set));
    println("Ciclo ", numCiclo, ": Precision en el conjunto de entrenamiento: ", 100*precisionEntrenamiento, " %");
    precisionTest = accuracy(test_set);
    println("   Precision en el conjunto de test: ", 100*precisionTest, " %");

    # Calculamos las métricas
    push!(num_ciclos, numCiclo);
    push!(trainingAccuracies, precisionEntrenamiento);
    push!(testAccuracies, precisionTest);
    push!(trainingLosses, mean(calculate_loss.(train_set)));
    push!(testLosses, mean(calculate_loss(test_set)));

    if (precisionEntrenamiento > mejorPrecision)
        mejorPrecision = precisionEntrenamiento;
        println("   Mejora en el conjunto de entrenamiento -> Precision en el conjunto de test: ", 100*precisionTest, " %");
        mejorModelo = deepcopy(modelo);
        numCicloUltimaMejora = numCiclo;
    end;
#=
    if (numCiclo - numCicloUltimaMejora >= 5) && (opt.eta > 1e-6)
        opt.eta /= 10.0
        println("   No se ha mejorado en 5 ciclos, se baja la tasa de aprendizaje a ", opt.eta);
        numCicloUltimaMejora = numCiclo;
    end;
=#
    if (precisionEntrenamiento >= 0.999)
        println("   Se para el entenamiento por haber llegado a una precision de 99.9%")
        criterioFin = true;
    end;

    if (numCiclo - numCicloUltimaMejora >= 10)
        println("   Se para el entrenamiento por no haber mejorado la precision en el conjunto de entrenamiento durante 10 ciclos")
        criterioFin = true;
    end;
end;

print_train_results(num_ciclos, trainingLosses, testLosses, trainingAccuracies, testAccuracies);


text:
function stderrs{T <: Model}(::Type{T}, inputs::Vector, weights::Vector)
    se = sqrt.(diag(cov(T, inputs, weights)))
    return se
end