text:
function parseInput!(input_data)
    lines = split(input_data, '\n')

    firstLine = split(lines[1], ' ')

    numberOfItems = parse(Int, firstLine[1])
    capacity = parse(Int, firstLine[2])

    items = Array{Union{Item}}(undef, numberOfItems);

    @assert numberOfItems + 2 <= length(lines)

    for i in 1:numberOfItems
        itemArray = split(lines[i+1], ' ')
        item = Item(i, parse(Int, itemArray[1]), parse(Int, itemArray[2]))
        items[i] = item
    end

    return InputData(items, Item[], numberOfItems, capacity)
end

function printSolution(solution::Solution)
    println(solution.value, " ", solution.optimality ? 1 : 0)
    for taken in solution.content
        print(taken ? 1 : 0, " ")
    end
    println()
end

function parseFile!(filename)

    if filename == ""
        throw(ArgumentError("You must specify a data file"))
    end

    toReturn = nothing

    open(filename, "r") do openedFile
        toReturn = parseInput!(read(openedFile, String))
    end

    return toReturn
end

max_stars_repo_name: corail-research/SeaPearlZoo

max_issues_repo_name: corail-research/SeaPearlZoo

max_forks_repo_name: corail-research/SeaPearlZoo


text:
##############################################################################
#
# In this test problem, we consider a globalized Newton method.
# The scenario considers two different stopping criteria to solve the linesearch.
#
# i) This example illustrates how the "structure" handling the algorithmic
# parameters can be passed to the solver of the subproblem.
#
# ii) This algorithm handles a sub-stopping defined by passing the stopping as a
# keyword argument. Note that when a stopping is used multiple times, it has to
# be reinitialized.
#
# iii) It also shows how we can reuse the information and avoid unnecessary evals.
# Here, the objective function of the main problem and sub-problem are the same.
# Warning: the structure onedoptim however does not allow keeping the gradient
# of the main problem. This issue can be corrected by using a specialized State.
#
#############################################################################

#include("backls.jl")
#contains the rosenbrock and backtracking_ls functions, and the onedoptim struct
#using LinearAlgebra, NLPModels, Stopping

##############################################################################
#
# Newton method with LineSearch
#
#############################################################################
function global_newton(stp       :: NLPStopping,
                       onedsolve :: Function,
                       ls_func   :: Function;
                       prms = nothing)

    #Notations
    state = stp.current_state; nlp = stp.pb
    #Initialization
    xt = state.x; d = zeros(size(xt))

    #First call
    OK = update_and_start!(stp, x = xt, fx = obj(nlp, xt),
                                gx = grad(nlp, xt), Hx = hess(nlp, xt))

    #Initialize the sub-Stopping with the main Stopping as keyword argument
    h = onedoptim(x -> obj(nlp, xt + x * d),
                  x -> dot(d, grad(nlp, xt + x * d)))
    lsstp = LS_Stopping(h, LSAtT(1.0), main_stp = stp, optimality_check = ls_func)

    #main loop
    while !OK
        #Compute the Newton direction
        d = Symmetric(state.Hx, :L) \ (-state.gx)

        #Prepare the substopping
        #We reinitialize the stopping before each new use
        #rstate = true, force a reinialization of the State as well
        reinit!(lsstp, rstate = true, x = 1.0, g₀=-dot(state.gx,d), h₀=state.fx)
        lsstp.pb = onedoptim(x -> obj(nlp, xt + x * d),
                             x -> dot(d, grad(nlp, xt + x * d)))

        #solve subproblem
        onedsolve(lsstp, prms)

        if status(lsstp) == :Optimal
         alpha = lsstp.current_state.x
         #update
         xt = xt + alpha * d
         #Since the onedoptim and the nlp have the same objective function,
         #we save one evaluation.
         update!(stp.current_state, fx = lsstp.current_state.ht)
        else
         stp.meta.fail_sub_pb = true
        end

        OK = update_and_stop!(stp, x = xt, gx = grad(nlp, xt), Hx = hess(nlp, xt))

    end

    return stp
end

##############################################################################
#
# Newton method with LineSearch
# Buffer version
#
#############################################################################
function global_newton(stp :: NLPStopping, prms)

 lf = :ls_func   ∈ fieldnames(typeof(prms)) ? prms.ls_func : armijo
 os = :onedsolve ∈ fieldnames(typeof(prms)) ? prms.onedsolve : backtracking_ls

 return global_newton(stp, os, lf; prms = prms)
end

##############################################################################
#
#
#
mutable struct PrmUn

    #parameters of the unconstrained minimization
    armijo_prm  :: Float64 #Armijo parameter
    wolfe_prm   :: Float64 #Wolfe parameter
    onedsolve   :: Function #1D solver
    ls_func     :: Function

    #parameters of the 1d minimization
    back_update :: Float64 #backtracking update

    function PrmUn(;armijo_prm  :: Float64 = 0.01,
                    wolfe_prm   :: Float64 = 0.99,
                    onedsolve   :: Function = backtracking_ls,
                    ls_func     :: Function = (x,y)-> armijo(x,y, τ₀ = armijo_prm),
                    back_update :: Float64 = 0.5)
        return new(armijo_prm,wolfe_prm,onedsolve,ls_func,back_update)
    end
end

ext: jl


text:
function isValidLabel(abstractNode::N)::Bool where N <: AbstractCGNode
    id = String(abstractNode.id)
    return all(t -> t != uppercase(id), _invalidIds) && match(_validLabelRegex, id) != nothing
end

# Fastest way I can think to convert the data into a dict
#TODO: Probably should be made more efficient...definitely should be made more efficient
function _convertNodeToDict(abstractNode::N)::Dict{String, Any} where N <: AbstractCGNode
    cp = deepcopy(abstractNode)
    data = length(cp.data) != 0 ? JSON2.write(cp.data) : "{}"
    ser = JSON2.read(JSON2.write(abstractNode), Dict{String, Any})
    ser["data"] = base64encode(data)
    return ser
end

#TODO: Refactor, #HACK :D (but it works!)
function _convertDictToSession(dict::Dict{String, Any})::Session
    data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
    session = Session(
        Symbol(dict["id"]),
        Symbol(dict["robotId"]),
        Symbol(dict["userId"]),
        dict["name"],
        dict["description"],
        data,
        dict["createdTimestamp"],
        dict["lastUpdatedTimestamp"])
    return session
end
#TODO: Refactor, #HACK :D (but it works!)
function _convertDictToRobot(dict::Dict{String, Any})::Robot
    data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
    robot = Robot(
        Symbol(dict["id"]),
        Symbol(dict["userId"]),
        dict["name"],
        dict["description"],
        data,
        dict["createdTimestamp"],
        dict["lastUpdatedTimestamp"])
    return robot
end
#TODO: Refactor, #HACK :D (but it works!)
function _convertDictToUser(dict::Dict{String, Any})::User
    data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
    user = User(
        Symbol(dict["id"]),
        dict["name"],
        dict["description"],
        data,
        dict["createdTimestamp"],
        dict["lastUpdatedTimestamp"])
    return user
end

function createUser(dfg::CloudGraphsDFG, user::User)::User
    Symbol(dfg.userId) != user.id && error("DFG user ID must match user's ID")
    !isValidLabel(user) && error("Node cannot have an ID '$(user.id)'.")

    props = _convertNodeToDict(user)
    retNode = _createNode(dfg.neo4jInstance, ["USER", String(user.id)], props, nothing)
    return user
end

function createRobot(dfg::CloudGraphsDFG, robot::Robot)::Robot
    Symbol(dfg.robotId) != robot.id && error("DFG robot ID must match robot's ID")
    Symbol(dfg.userId) != robot.userId && error("DFG user ID must match robot's user ID")
    !isValidLabel(robot) && error("Node cannot have an ID '$(robot.id)'.")

    # Find the parent
    parents = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER:$(dfg.userId))")
    length(parents) == 0 && error("Cannot find user '$(dfg.userId)'")
    length(parents) > 1 && error("Found multiple users '$(dfg.userId)'")

    # Already exists?
    length(_getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.userId):$(robot.id))")) != 0 &&
        error("Robot '$(robot.id)' already exists for user '$(robot.userId)'")

    props = _convertNodeToDict(robot)
    retNode = _createNode(dfg.neo4jInstance, ["ROBOT", String(robot.userId), String(robot.id)], props, parents[1], :ROBOT)
    return robot
end

function createSession(dfg::CloudGraphsDFG, session::Session)::Session
    Symbol(dfg.robotId) != session.robotId && error("DFG robot ID must match session's robot ID")
    Symbol(dfg.userId) != session.userId && error("DFG user ID must match session's->robot's->user ID")
    !isValidLabel(session) && error("Node cannot have an ID '$(session.id)'.")

    # Find the parent
    parents = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.robotId):$(dfg.userId))")
    length(parents) == 0 && error("Cannot find robot '$(dfg.robotId)' for user '$(dfg.userId)'")
    length(parents) > 1 && error("Found multiple robots '$(dfg.robotId)' for user '$(dfg.userId)'")

    # Already exists?
    length(_getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(session.userId):$(session.robotId):$(session.id))")) != 0 &&
        error("Session '$(session.id)' already exists for robot '$(session.robotId)' and user '$(session.userId)'")

    props = _convertNodeToDict(session)
    retNode = _createNode(dfg.neo4jInstance, ["SESSION", String(session.userId), String(session.robotId), String(session.id)], props, parents[1], :SESSION)
    return session
end

"""
$(SIGNATURES)
Shortcut method to create the user, robot, and session if it doesn't already exist.
"""
function createDfgSessionIfNotExist(dfg::CloudGraphsDFG)::Session
    strip(dfg.userId) == "" && error("User ID is not populated in DFG.")
    strip(dfg.robotId) == "" && error("Robot ID is not populated in DFG.")
    strip(dfg.sessionId) == "" && error("Session ID is not populated in DFG.")
    user = User(Symbol(dfg.userId), dfg.userId, "Description for $(dfg.userId)", Dict{Symbol, String}())
    robot = Robot(Symbol(dfg.robotId), Symbol(dfg.userId), dfg.robotId, "Description for $(dfg.userId):$(dfg.robotId)", Dict{Symbol, String}())
    session = Session(Symbol(dfg.sessionId), Symbol(dfg.robotId), Symbol(dfg.userId), dfg.sessionId, dfg.description, Dict{Symbol, String}())

    _getNodeCount(dfg.neo4jInstance, [dfg.userId, "USER"]) == 0 && createUser(dfg, user)
    _getNodeCount(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"]) == 0 && createRobot(dfg, robot)
    if _getNodeCount(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"]) == 0
        return createSession(dfg, session)
    else
        return getSession(dfg)
    end
end

"""
$(SIGNATURES)
List all sessions for the specified DFG's robot and user.
Returns nothing if it isn't found.
"""
function lsSessions(dfg::CloudGraphsDFG)::Vector{Session}
    sessionNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(dfg.robotId):$(dfg.userId))")
    return map(s -> _convertDictToSession(Neo4j.getnodeproperties(s)), sessionNodes)
end

"""
$(SIGNATURES)
List all robots for the specified DFG's user.
Returns nothing if it isn't found.
"""
function lsRobots(dfg::CloudGraphsDFG)::Vector{Robot}
    robotNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.userId))")
    return map(s -> _convertDictToRobot(Neo4j.getnodeproperties(s)), robotNodes)
end

"""
$(SIGNATURES)
List all users.
Returns nothing if it isn't found.
"""
function lsUsers(dfg::CloudGraphsDFG)::Vector{User}
    userNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER)")
    return map(s -> _convertDictToUser(Neo4j.getnodeproperties(s)), userNodes)
end

"""
$(SIGNATURES)
Get a session specified by userId:robotId:sessionId.
Returns nothing if it isn't found.
"""
function getSession(dfg::CloudGraphsDFG, userId::Symbol, robotId::Symbol, sessionId::Symbol)::Union{Session, Nothing}
    !isValidLabel(userId) && error("Can't retrieve session with user ID '$(userId)'.")
    !isValidLabel(robotId) && error("Can't retrieve session with robot ID '$(robotId)'.")
    !isValidLabel(sessionId) && error("Can't retrieve session with session ID '$(sessionId)'.")
    sessionNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(sessionId):$(robotId):$(userId))")
    length(sessionNode) == 0 && return nothing
    length(sessionNode) > 1 && error("There look to be $(length(sessionNode)) sessions identified for $(sessionId):$(robotId):$(userId)")
    return _convertDictToSession(Neo4j.getnodeproperties(sessionNode[1]))
end

"""
$(SIGNATURES)
Get the session specified by the DFG object.
Returns nothing if it isn't found.
"""
function getSession(dfg::CloudGraphsDFG)::Union{Nothing, Session}
    return getSession(dfg, Symbol(dfg.userId), Symbol(dfg.robotId), Symbol(dfg.sessionId))
end

"""
$(SIGNATURES)
Get a robot specified by userId:robotId.
Returns nothing if it isn't found.
"""
function getRobot(dfg::CloudGraphsDFG, userId::Symbol, robotId::Symbol)::Union{Robot, Nothing}
    !isValidLabel(userId) && error("Can't retrieve robot with user ID '$(userId)'.")
    !isValidLabel(robotId) && error("Can't retrieve robot with robot ID '$(robotId)'.")
    robotNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(robotId):$(userId))")
    length(robotNode) == 0 && return nothing
    length(robotNode) > 1 && error("There look to be $(length(robotNode)) robots identified for $(robotId):$(userId)")
    return _convertDictToRobot(Neo4j.getnodeproperties(robotNode[1]))
end

"""
$(SIGNATURES)
Get the robot specified by the DFG object.
Returns nothing if it isn't found.
"""
function getRobot(dfg::CloudGraphsDFG)::Union{Nothing, Robot}
    return getRobot(dfg, Symbol(dfg.userId), Symbol(dfg.robotId))
end

"""
$(SIGNATURES)
Get a user specified by userId.
Returns nothing if it isn't found.
"""
function getUser(dfg::CloudGraphsDFG, userId::Symbol)::Union{User, Nothing}
    !isValidLabel(userId) && error("Can't retrieve user with user ID '$(userId)'.")
    userNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER:$(userId))")
    length(userNode) == 0 && return nothing
    length(userNode) > 1 && error("There look to be $(length(userNode)) robots identified for $(userId)")
    return _convertDictToUser(Neo4j.getnodeproperties(userNode[1]))
end

"""
$(SIGNATURES)
Get the user specified by the DFG object.
Returns nothing if it isn't found.
"""
function getUser(dfg::CloudGraphsDFG)::Union{Nothing, User}
    return getUser(dfg, Symbol(dfg.userId))
end

"""
    $(SIGNATURES)
DANGER: Clears the whole session from the database.
"""
function clearSession!!(dfg::CloudGraphsDFG)::Nothing
    # Perform detach+deletion
    _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId)) detach delete node ")

    # Clearing history
    dfg.addHistory = Symbol[]
    return nothing
end

"""
    $(SIGNATURES)
DANGER: Clears the whole robot + sessions from the database.
"""
function clearRobot!!(dfg::CloudGraphsDFG)::Nothing
    # Perform detach+deletion
    _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:$(dfg.userId):$(dfg.robotId)) detach delete node ")

    # Clearing history
    dfg.addHistory = Symbol[]
    return nothing
end

"""
    $(SIGNATURES)
DANGER: Clears the whole user + robot + sessions from the database.
"""
function clearUser!!(dfg::CloudGraphsDFG)::Nothing
    # Perform detach+deletion
    _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:$(dfg.userId)) detach delete node ")

    # Clearing history
    dfg.addHistory = Symbol[]
    return nothing
end

"""
    $(SIGNATURES)
DANGER: Copies and overwrites the destination session.
If no destination specified then it creates a unique one.
"""
function copySession!(sourceDFG::CloudGraphsDFG, destDFG::Union{Nothing, CloudGraphsDFG})::CloudGraphsDFG
    if destDFG == nothing
        destDFG = _getDuplicatedEmptyDFG(sourceDFG)
    end
    _copyIntoGraph!(sourceDFG, destDFG, union(listVariables(sourceDFG), listFactors(sourceDFG)), true)
    return destDFG
end
"""
    $(SIGNATURES)
DANGER: Copies the source to a new unique destination.
"""
copySession!(sourceDFG::CloudGraphsDFG)::CloudGraphsDFG = copySession!(sourceDFG, nothing)


text:
module Assignment

export find_best_assignment, find_kbest_assignments

import Base: <, >, <=, >=, ==, !=
using DataStructures

include("solution.jl")
include("solve.jl")
include("solve_kbest.jl")

end


text:
macro server_capability(name::Symbol, revision::Int)

    has_func = :(@inline $name(rev) = rev >= $revision)
    rev_func_name = Symbol(name, "_rev")
    rev_func = :(@inline $rev_func_name() = $revision)
    return  quote
                $(esc(has_func))
                $(esc(rev_func))
            end

end

@server_capability has_temporary_tables 50264
@server_capability has_total_rows_in_progress 51554
@server_capability has_block_info 51903
@server_capability has_client_info 54032
@server_capability has_server_timezone 54058
@server_capability has_quota_key 54060
@server_capability has_server_display_name 54372
@server_capability has_version_patch 54401
@server_capability has_server_logs 54406
@server_capability has_column_dafaults_metadata 54410
@server_capability has_client_write_info 54420

const CLIENT_NAME = "ClickHouseJL"
const DBMS_VER_MAJOR = 19
const DBMS_VER_MINOR = 11
const DBMS_VER_REV = 54423
const DBMS_DEFAULT_BUFFER_SIZE = 1048576
const DBMS_DEFAULT_CONNECT_TIMEOUT = 5
const DBMS_DEFAULT_MAX_INSERT_BLOCK = 100000


text:
"""
    mkmat(n,log10κ=5,T=Float32)

construct a matrix of size `(n,n)` and eltype `T` with log-spaced
singular values from `10^(-log10κ)` to 1.
"""
function mkmat(n, log10κ, ::Type{T}) where {T}
    if T <: Real
        q1,_ = qr(randn(n,n))
        q2,_ = qr(randn(n,n))
    else
        q1,_ = qr(randn(ComplexF64,n,n))
        q2,_ = qr(randn(ComplexF64,n,n))
    end
    DT = real(T)
    s = 10.0 .^(-shuffle(0:(n-1))*log10κ/(n-1))
    A = T.(Matrix(q1)*Diagonal(s)*Matrix(q2)')
end

# matrix with simple eigenvalues, separated by at least `dmin`
function mkmat_simple(n, dmin, ::Type{T}) where {T}
    if dmin > 1 / (2*n)
        throw(ArgumentError("unrealistic value of dmin, I give up."))
    end
    dmin1 = 0.0
    local ews
    while dmin1 < dmin
        ews = rand(n)
        dmin1 = minimum(abs.((ews .- ews') + I))
    end
    X = rand(n,n)
    # println("cond X: ",cond(X))
    A1 = X * diagm(0 => ews) * inv(X)
    A = T.(A1)
end

function mkmat_multiple(n, k, target, dmin, ::Type{T}) where {T <: Real}
    if dmin > 1 / (2*(n-k))
        throw(ArgumentError("unrealistic value of dmin, I give up."))
    end
    dmin1 = 0.0
    local ews
    while dmin1 < dmin
        ews = rand(n-k)
        dmin1 = minimum(abs.((ews .- ews') + I))
    end
    append!(ews,fill(Float64(target),k))
    X = rand(n,n)
    # println("cond X: ",cond(X))
    A1 = X * diagm(0 => ews) * inv(X)
    A = T.(A1)
end
function mkmat_multiple(n, k, target, dmin, ::Type{T}) where {T <: Complex}
    dmin1 = 0.0
    local ews
    while dmin1 < dmin
        ews = rand(ComplexF64,n-k)
        dmin1 = minimum(abs.((ews .- ews') + I))
    end
    append!(ews,fill(ComplexF64(target),k))
    X = rand(ComplexF64,n,n)
    # println("cond X: ",cond(X))
    A1 = X * diagm(0 => ews) * inv(X)
    A = T.(A1)
end

"""
construct a matrix similar to one with one Jordan block of size `k`,
eigenvalue `w1` and other eigenvalues random, likely simple, in [0,1).
"""
function mkmat_defective(n, k, w1, ::Type{T}) where {T <: Real}
    # putting defective ones at end seems to make final location more random
    ews = vcat(rand(n-k), w1 * ones(k))
    Ts = diagm(0=>ews) + diagm(1 => vcat(zeros(n-k), ones(k-1)))
    X = rand(n,n)
    # println("cond X: ",cond(X))
    A1 = X * Ts * inv(X)
    A = T.(A1)
end
function mkmat_defective(n, k, w1, ::Type{T}) where {T <: Complex}
    ews = vcat(rand(ComplexF64, n-k), w1 * ones(ComplexF64, k))
    Ts = diagm(0=>ews) + diagm(1 => vcat(zeros(n-k), ones(k-1)))
    X = rand(ComplexF64,n,n)
    A1 = X * Ts * inv(X)
    A = T.(A1)
end

"""
construct a matrix with a cluster of eigenvalues with specified condition.
`lbdiag` specifies whether lower block is normal. (Otherwise it is likely
to have worse condition than the cluster of interest, which may be
undesirable.)
"""
function mkmat_cond(n, targets, cond, ::Type{T}; lbdiag=false) where T
    if (cond < 1)
        throw(ArgumentError("condition cannot be < 1"))
    end
    k = length(targets)
    Tw = (T <: Real) ? Float64 : ComplexF64
    A11 = diagm(0=>Tw.(targets)) + triu(rand(Tw,k,k),1)
    ews = rand(n-k)
    if lbdiag
        A22 = diagm(0=>rand(Tw,n-k))
    else
        A22 = triu(rand(Tw,n-k,n-k))
    end
    R = rand(Tw,k,n-k)
    condr = sqrt(cond^2 - 1.0)
    lmul!(condr/opnorm(R,2),R)
    A12 = -A11 * R + R * A22
    U,_ = qr(randn(Tw,n,n))
    At = [A11 A12; zeros(Tw,n-k,k) A22]
    # norm(A12) / norm(R) might be a good estimate for sep(A11,A22)
    A = T.(U' * At * U)
end


text:
#include("../dataset_reader.jl")
const dataset3_labels = Float64[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const dataset3_nfeatures = 11
const dataset3_filename1 = "winequality-red.csv"
const dataset3_filename2 = "winequality-white.csv"

function get_numerical_dataset3(path::String="")
    data_red = get_data(joinpath(path, dataset3_filename1); dlm=";")
    data_white = get_data(joinpath(path, dataset3_filename2); dlm=";")

    data = vcat(data_red, data_white)

    conversion_pattern = [Float64, Float64, Float64, Float64, Float64, Float64, Float64, Float64, Float64, Float64, Float64, Float64]
    convert_values(data, conversion_pattern)
end

function get_dataset3(path::String=""; shuffle::Bool=false, test_perc::Float64=0.2,
                      normalise::Bool=false, separate_test_data::Bool=true, balance_classes::Bool=false)
    data = get_numerical_dataset3(path)
    separate_dataset(data, test_percentage=test_perc, shuffle=shuffle, normalise=normalise,
                     separate_test_data=separate_test_data, balance_classes=balance_classes)
end

function get_dataset3_setup(path::String=""; normalise::Bool=false, separate::Bool=false,
                            balance::Bool=false)
    dataset = get_dataset3(path, normalise=normalise, separate_test_data=separate, balance_classes=balance)
    if separate
        SeparatedDataset(dataset..., dataset3_labels, dataset3_nfeatures, 4)
    else
        Dataset(dataset..., dataset3_labels, dataset3_nfeatures, 4)
    end
end

max_stars_repo_name: Lucas-Muniz/MNEAT

max_issues_repo_name: Lucas-Muniz/MNEAT

max_forks_repo_name: Lucas-Muniz/MNEAT


text:
export BrickletLCD128x64ReadPixelsLowLevel
struct BrickletLCD128x64ReadPixelsLowLevel
    pixels_length::Integer
    pixels_chunk_offset::Integer
    pixels_chunk_data::Vector{Bool}
end

export BrickletLCD128x64DisplayConfiguration
struct BrickletLCD128x64DisplayConfiguration
    contrast::Integer
    backlight::Integer
    invert::Bool
    automatic_draw::Bool
end

export BrickletLCD128x64TouchPosition
struct BrickletLCD128x64TouchPosition
    pressure::Integer
    x::Integer
    y::Integer
    age::Integer
end

export BrickletLCD128x64TouchPositionCallbackConfiguration
struct BrickletLCD128x64TouchPositionCallbackConfiguration
    period::Integer
    value_has_to_change::Bool
end

export BrickletLCD128x64TouchGesture
struct BrickletLCD128x64TouchGesture
    gesture::Integer
    duration::Integer
    pressure_max::Integer
    x_start::Integer
    y_start::Integer
    x_end::Integer
    y_end::Integer
    age::Integer
end

export BrickletLCD128x64TouchGestureCallbackConfiguration
struct BrickletLCD128x64TouchGestureCallbackConfiguration
    period::Integer
    value_has_to_change::Bool
end

export BrickletLCD128x64GUIButton
struct BrickletLCD128x64GUIButton
    active::Bool
    position_x::Integer
    position_y::Integer
    width::Integer
    height::Integer
    text::String
end

export BrickletLCD128x64GUIButtonPressedCallbackConfiguration
struct BrickletLCD128x64GUIButtonPressedCallbackConfiguration
    period::Integer
    value_has_to_change::Bool
end

export BrickletLCD128x64GUISlider
struct BrickletLCD128x64GUISlider
    active::Bool
    position_x::Integer
    position_y::Integer
    length::Integer
    direction::Integer
    value::Integer
end

export BrickletLCD128x64GUISliderValueCallbackConfiguration
struct BrickletLCD128x64GUISliderValueCallbackConfiguration
    period::Integer
    value_has_to_change::Bool
end

export BrickletLCD128x64GUITabConfiguration
struct BrickletLCD128x64GUITabConfiguration
    change_tab_config::Integer
    clear_gui::Bool
end

export BrickletLCD128x64GUITabText
struct BrickletLCD128x64GUITabText
    active::Bool
    text::String
end

export BrickletLCD128x64GUITabIcon
struct BrickletLCD128x64GUITabIcon
    active::Bool
    icon::Vector{Bool}
end

export BrickletLCD128x64GUITabSelectedCallbackConfiguration
struct BrickletLCD128x64GUITabSelectedCallbackConfiguration
    period::Integer
    value_has_to_change::Bool
end

export BrickletLCD128x64GUIGraphConfiguration
struct BrickletLCD128x64GUIGraphConfiguration
    active::Bool
    graph_type::Integer
    position_x::Integer
    position_y::Integer
    width::Integer
    height::Integer
    text_x::String
    text_y::String
end

export BrickletLCD128x64GUIGraphDataLowLevel
struct BrickletLCD128x64GUIGraphDataLowLevel
    data_length::Integer
    data_chunk_offset::Integer
    data_chunk_data::Vector{Integer}
end

export BrickletLCD128x64SPITFPErrorCount
struct BrickletLCD128x64SPITFPErrorCount
    error_count_ack_checksum::Integer
    error_count_message_checksum::Integer
    error_count_frame::Integer
    error_count_overflow::Integer
end

export BrickletLCD128x64Identity
struct BrickletLCD128x64Identity
    uid::String
    connected_uid::String
    position::Char
    hardware_version::Vector{Integer}
    firmware_version::Vector{Integer}
    device_identifier::Integer
end

export BrickletLCD128x64
"""
7.1cm (2.8") display with 128x64 pixel and touch screen
"""
mutable struct BrickletLCD128x64 <: TinkerforgeDevice
    ipcon::IPConnection
    deviceInternal::PyObject

    """
    Creates an object with the unique device ID *uid* and adds it to
    the IP Connection *ipcon*.
    """
    function BrickletLCD128x64(uid::String, ipcon::IPConnection)
        package = pyimport("tinkerforge.bricklet_lcd_128x64")
        deviceInternal = package.BrickletLCD128x64(uid, ipcon.ipconInternal)

        return new(ipcon, deviceInternal)
    end
end

export write_pixels_low_level
"""
    $(SIGNATURES)

Writes pixels to the specified window.

The pixels are written into the window line by line top to bottom
and each line is written from left to right.

If automatic draw is enabled (default) the pixels are directly written to
the screen. Only pixels that have actually changed are updated on the screen,
the rest stays the same.

If automatic draw is disabled the pixels are written to an internal buffer and
the buffer is transferred to the display only after :func:`Draw Buffered Frame`
is called. This can be used to avoid flicker when drawing a complex frame in
multiple steps.

Automatic draw can be configured with the :func:`Set Display Configuration`
function.
"""
function write_pixels_low_level(device::BrickletLCD128x64, x_start, y_start, x_end, y_end, pixels_length, pixels_chunk_offset, pixels_chunk_data)
    device.deviceInternal.write_pixels_low_level(x_start, y_start, x_end, y_end, pixels_length, pixels_chunk_offset, pixels_chunk_data)
end

export read_pixels_low_level
"""
    $(SIGNATURES)

Reads pixels from the specified window.

The pixels are read from the window line by line top to bottom
and each line is read from left to right.

If automatic draw is enabled (default) the pixels that are read are always the
same that are shown on the display.

If automatic draw is disabled the pixels are read from the internal buffer
(see :func:`Draw Buffered Frame`).

Automatic draw can be configured with the :func:`Set Display Configuration`
function.
"""
function read_pixels_low_level(device::BrickletLCD128x64, x_start, y_start, x_end, y_end)
    return device.deviceInternal.read_pixels_low_level(x_start, y_start, x_end, y_end)
end

export clear_display
"""
    $(SIGNATURES)

Clears the complete content of the display.

If automatic draw is enabled (default) the pixels are directly cleared.

If automatic draw is disabled the the internal buffer is cleared and
the buffer is transferred to the display only after :func:`Draw Buffered Frame`
is called. This can be used to avoid flicker when drawing a complex frame in
multiple steps.

Automatic draw can be configured with the :func:`Set Display Configuration`
function.
"""
function clear_display(device::BrickletLCD128x64)
    device.deviceInternal.clear_display()
end

export set_display_configuration
"""
    $(SIGNATURES)

Sets the configuration of the display.

If automatic draw is set to *true*, the display is automatically updated with every
call of :func:`Write Pixels` and :func:`Write Line`. If it is set to false, the
changes are written into an internal buffer and only shown on the display after
a call of :func:`Draw Buffered Frame`.
"""
function set_display_configuration(device::BrickletLCD128x64, contrast, backlight, invert, automatic_draw)
    device.deviceInternal.set_display_configuration(contrast, backlight, invert, automatic_draw)
end

export get_display_configuration
"""
    $(SIGNATURES)

Returns the configuration as set by :func:`Set Display Configuration`.
"""
function get_display_configuration(device::BrickletLCD128x64)
    return device.deviceInternal.get_display_configuration()
end

export write_line
"""
    $(SIGNATURES)

Writes text to a specific line with a specific position.

For example: (1, 10, "Hello") will write *Hello* in the middle of the
second line of the display.

The display uses a special 5x7 pixel charset. You can view the characters
of the charset in Brick Viewer.

If automatic draw is enabled (default) the text is directly written to
the screen. Only pixels that have actually changed are updated on the screen,
the rest stays the same.

If automatic draw is disabled the text is written to an internal buffer and
the buffer is transferred to the display only after :func:`Draw Buffered Frame`
is called. This can be used to avoid flicker when drawing a complex frame in
multiple steps.

Automatic draw can be configured with the :func:`Set Display Configuration`
function.

This function is a 1:1 replacement for the function with the same name
in the LCD 20x4 Bricklet. You can draw text at a specific pixel position
and with different font sizes with the :func:`Draw Text` function.
"""
function write_line(device::BrickletLCD128x64, line, position, text)
    device.deviceInternal.write_line(line, position, text)
end

export draw_buffered_frame
"""
    $(SIGNATURES)

Draws the currently buffered frame. Normally each call of :func:`Write Pixels` and
:func:`Write Line` draws directly onto the display. If you turn automatic draw off
(:func:`Set Display Configuration`), the data is written in an internal buffer and
only transferred to the display by calling this function. This can be used to
avoid flicker when drawing a complex frame in multiple steps.

Set the `force complete redraw` to *true* to redraw the whole display
instead of only the changed parts. Normally it should not be necessary to set this to
*true*. It may only become necessary in case of stuck pixels because of errors.
"""
function draw_buffered_frame(device::BrickletLCD128x64, force_complete_redraw)
    device.deviceInternal.draw_buffered_frame(force_complete_redraw)
end

export get_touch_position
"""
    $(SIGNATURES)

Returns the last valid touch position:

* Pressure: Amount of pressure applied by the user
* X: Touch position on x-axis
* Y: Touch position on y-axis
* Age: Age of touch press (how long ago it was)
"""
function get_touch_position(device::BrickletLCD128x64)
    return device.deviceInternal.get_touch_position()
end

export set_touch_position_callback_configuration
"""
    $(SIGNATURES)

The period is the period with which the :cb:`Touch Position` callback
is triggered periodically. A value of 0 turns the callback off.

If the `value has to change`-parameter is set to true, the callback is only
triggered after the value has changed. If the value didn't change within the
period, the callback is triggered immediately on change.

If it is set to false, the callback is continuously triggered with the period,
independent of the value.
"""
function set_touch_position_callback_configuration(device::BrickletLCD128x64, period, value_has_to_change)
    device.deviceInternal.set_touch_position_callback_configuration(period, value_has_to_change)
end

export get_touch_position_callback_configuration
"""
    $(SIGNATURES)

Returns the callback configuration as set by
:func:`Set Touch Position Callback Configuration`.
"""
function get_touch_position_callback_configuration(device::BrickletLCD128x64)
    return device.deviceInternal.get_touch_position_callback_configuration()
end

export get_touch_gesture
"""
    $(SIGNATURES)

Returns one of four touch gestures that can be automatically detected by the Bricklet.

The gestures are swipes from left to right, right to left, top to bottom and bottom to top.

Additionally to the gestures a vector with a start and end position of the gesture is
provided. You can use this vector do determine a more exact location of the gesture (e.g.
the swipe from top to bottom was on the left or right part of the screen).

The age parameter corresponds to the age of gesture (how long ago it was).
"""
function get_touch_gesture(device::BrickletLCD128x64)
    return device.deviceInternal.get_touch_gesture()
end

export set_touch_gesture_callback_configuration
"""
    $(SIGNATURES)

The period is the period with which the :cb:`Touch Gesture` callback
is triggered periodically. A value of 0 turns the callback off.

If the `value has to change`-parameter is set to true, the callback is only
triggered after the value has changed. If the value didn't change within the
period, the callback is triggered immediately on change.

If it is set to false, the callback is continuously triggered with the period,
independent of the value.
"""
function set_touch_gesture_callback_configuration(device::BrickletLCD128x64, period, value_has_to_change)
    device.deviceInternal.set_touch_gesture_callback_configuration(period, value_has_to_change)
end

export get_touch_gesture_callback_configuration
"""
    $(SIGNATURES)

Returns the callback configuration as set by
:func:`Set Touch Gesture Callback Configuration`.
"""
function get_touch_gesture_callback_configuration(device::BrickletLCD128x64)
    return device.deviceInternal.get_touch_gesture_callback_configuration()
end

export draw_line
"""
    $(SIGNATURES)

Draws a white or black line from (x, y)-start to (x, y)-end.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function draw_line(device::BrickletLCD128x64, position_x_start, position_y_start, position_x_end, position_y_end, color)
    device.deviceInternal.draw_line(position_x_start, position_y_start, position_x_end, position_y_end, color)
end

export draw_box
"""
    $(SIGNATURES)

Draws a white or black box from (x, y)-start to (x, y)-end.

If you set fill to true, the box will be filled with the
color. Otherwise only the outline will be drawn.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function draw_box(device::BrickletLCD128x64, position_x_start, position_y_start, position_x_end, position_y_end, fill, color)
    device.deviceInternal.draw_box(position_x_start, position_y_start, position_x_end, position_y_end, fill, color)
end

export draw_text
"""
    $(SIGNATURES)

Draws a text at the pixel position (x, y).

You can use one of 9 different font sizes and draw the text in white or black.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function draw_text(device::BrickletLCD128x64, position_x, position_y, font, color, text)
    device.deviceInternal.draw_text(position_x, position_y, font, color, text)
end

export set_gui_button
"""
    $(SIGNATURES)

Draws a clickable button at position (x, y) with the given text.

You can use up to 12 buttons.

The x position + width has to be within the range of 1 to 128 and the y
position + height has to be within the range of 1 to 64.

The minimum useful width/height of a button is 3.

You can enable a callback for a button press with
:func:`Set GUI Button Pressed Callback Configuration`. The callback will
be triggered for press and release-events.

The button is drawn in a separate GUI buffer and the button-frame will
always stay on top of the graphics drawn with :func:`Write Pixels`. To
remove the button use :func:`Remove GUI Button`.

If you want an icon instead of text, you can draw the icon inside of the
button with :func:`Write Pixels`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_button(device::BrickletLCD128x64, index, position_x, position_y, width, height, text)
    device.deviceInternal.set_gui_button(index, position_x, position_y, width, height, text)
end

export get_gui_button
"""
    $(SIGNATURES)

Returns the button properties for a given `Index` as set by :func:`Set GUI Button`.

Additionally the `Active` parameter shows if a button is currently active/visible
or not.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_button(device::BrickletLCD128x64, index)
    return device.deviceInternal.get_gui_button(index)
end

export remove_gui_button
"""
    $(SIGNATURES)

Removes the button with the given index.

You can use index 255 to remove all buttons.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function remove_gui_button(device::BrickletLCD128x64, index)
    device.deviceInternal.remove_gui_button(index)
end

export set_gui_button_pressed_callback_configuration
"""
    $(SIGNATURES)

The period is the period with which the :cb:`GUI Button Pressed` callback
is triggered periodically. A value of 0 turns the callback off.

If the `value has to change`-parameter is set to true, the callback is only
triggered after the value has changed. If the value didn't change within the
period, the callback is triggered immediately on change.

If it is set to false, the callback is continuously triggered with the period,
independent of the value.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_button_pressed_callback_configuration(device::BrickletLCD128x64, period, value_has_to_change)
    device.deviceInternal.set_gui_button_pressed_callback_configuration(period, value_has_to_change)
end

export get_gui_button_pressed_callback_configuration
"""
    $(SIGNATURES)

Returns the callback configuration as set by
:func:`Set GUI Button Pressed Callback Configuration`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_button_pressed_callback_configuration(device::BrickletLCD128x64)
    return device.deviceInternal.get_gui_button_pressed_callback_configuration()
end

export get_gui_button_pressed
"""
    $(SIGNATURES)

Returns the state of the button for the given index.

The state can either be pressed (true) or released (false).

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_button_pressed(device::BrickletLCD128x64, index)
    return device.deviceInternal.get_gui_button_pressed(index)
end

export set_gui_slider
"""
    $(SIGNATURES)

Draws a slider at position (x, y) with the given length.

You can use up to 6 sliders.

If you use the horizontal direction, the x position + length has to be
within the range of 1 to 128 and the y position has to be within
the range of 0 to 46.

If you use the vertical direction, the y position + length has to be
within the range of 1 to 64 and the x position has to be within
the range of 0 to 110.

The minimum length of a slider is 8.

The parameter value is the start-position of the slider, it can
be between 0 and length-8.

You can enable a callback for the slider value with
:func:`Set GUI Slider Value Callback Configuration`.

The slider is drawn in a separate GUI buffer and it will
always stay on top of the graphics drawn with :func:`Write Pixels`. To
remove the button use :func:`Remove GUI Slider`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_slider(device::BrickletLCD128x64, index, position_x, position_y, length, direction, value)
    device.deviceInternal.set_gui_slider(index, position_x, position_y, length, direction, value)
end

export get_gui_slider
"""
    $(SIGNATURES)

Returns the slider properties for a given `Index` as set by :func:`Set GUI Slider`.

Additionally the `Active` parameter shows if a button is currently active/visible
or not.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_slider(device::BrickletLCD128x64, index)
    return device.deviceInternal.get_gui_slider(index)
end

export remove_gui_slider
"""
    $(SIGNATURES)

Removes the slider with the given index.

You can use index 255 to remove all slider.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function remove_gui_slider(device::BrickletLCD128x64, index)
    device.deviceInternal.remove_gui_slider(index)
end

export set_gui_slider_value_callback_configuration
"""
    $(SIGNATURES)

The period is the period with which the :cb:`GUI Slider Value` callback
is triggered periodically. A value of 0 turns the callback off.

If the `value has to change`-parameter is set to true, the callback is only
triggered after the value has changed. If the value didn't change within the
period, the callback is triggered immediately on change.

If it is set to false, the callback is continuously triggered with the period,
independent of the value.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_slider_value_callback_configuration(device::BrickletLCD128x64, period, value_has_to_change)
    device.deviceInternal.set_gui_slider_value_callback_configuration(period, value_has_to_change)
end

export get_gui_slider_value_callback_configuration
"""
    $(SIGNATURES)

Returns the callback configuration as set by
:func:`Set GUI Slider Value Callback Configuration`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_slider_value_callback_configuration(device::BrickletLCD128x64)
    return device.deviceInternal.get_gui_slider_value_callback_configuration()
end

export get_gui_slider_value
"""
    $(SIGNATURES)

Returns the current slider value for the given index.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_slider_value(device::BrickletLCD128x64, index)
    return device.deviceInternal.get_gui_slider_value(index)
end

export set_gui_tab_configuration
"""
    $(SIGNATURES)

Sets the general configuration for tabs. You can configure the tabs to only
accept clicks or only swipes (gesture left/right and right/left) or both.

Additionally, if you set `Clear GUI` to true, all of the GUI elements (buttons,
slider, graphs) will automatically be removed on every tab change.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_tab_configuration(device::BrickletLCD128x64, change_tab_config, clear_gui)
    device.deviceInternal.set_gui_tab_configuration(change_tab_config, clear_gui)
end

export get_gui_tab_configuration
"""
    $(SIGNATURES)

Returns the tab configuration as set by :func:`Set GUI Tab Configuration`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_tab_configuration(device::BrickletLCD128x64)
    return device.deviceInternal.get_gui_tab_configuration()
end

export set_gui_tab_text
"""
    $(SIGNATURES)

Adds a text-tab with the given index.

You can use up to 10 tabs.

A text-tab with the same index as a icon-tab will overwrite the icon-tab.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_tab_text(device::BrickletLCD128x64, index, text)
    device.deviceInternal.set_gui_tab_text(index, text)
end

export get_gui_tab_text
"""
    $(SIGNATURES)

Returns the text for a given index as set by :func:`Set GUI Tab Text`.

Additionally the `Active` parameter shows if the tab is currently active/visible
or not.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_tab_text(device::BrickletLCD128x64, index)
    return device.deviceInternal.get_gui_tab_text(index)
end

export set_gui_tab_icon
"""
    $(SIGNATURES)

Adds a icon-tab with the given index. The icon can have a width of 28 pixels
with a height of 6 pixels. It is drawn line-by-line from left to right.

You can use up to 10 tabs.

A icon-tab with the same index as a text-tab will overwrite the text-tab.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_tab_icon(device::BrickletLCD128x64, index, icon)
    device.deviceInternal.set_gui_tab_icon(index, icon)
end

export get_gui_tab_icon
"""
    $(SIGNATURES)

Returns the icon for a given index as set by :func:`Set GUI Tab Icon`.

Additionally the `Active` parameter shows if the tab is currently active/visible
or not.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_tab_icon(device::BrickletLCD128x64, index)
    return device.deviceInternal.get_gui_tab_icon(index)
end

export remove_gui_tab
"""
    $(SIGNATURES)

Removes the tab with the given index.

You can use index 255 to remove all tabs.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function remove_gui_tab(device::BrickletLCD128x64, index)
    device.deviceInternal.remove_gui_tab(index)
end

export set_gui_tab_selected
"""
    $(SIGNATURES)

Sets the tab with the given index as selected (drawn as selected on the display).

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_tab_selected(device::BrickletLCD128x64, index)
    device.deviceInternal.set_gui_tab_selected(index)
end

export set_gui_tab_selected_callback_configuration
"""
    $(SIGNATURES)

The period is the period with which the :cb:`GUI Tab Selected` callback
is triggered periodically. A value of 0 turns the callback off.

If the `value has to change`-parameter is set to true, the callback is only
triggered after the value has changed. If the value didn't change within the
period, the callback is triggered immediately on change.

If it is set to false, the callback is continuously triggered with the period,
independent of the value.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_tab_selected_callback_configuration(device::BrickletLCD128x64, period, value_has_to_change)
    device.deviceInternal.set_gui_tab_selected_callback_configuration(period, value_has_to_change)
end

export get_gui_tab_selected_callback_configuration
"""
    $(SIGNATURES)

Returns the callback configuration as set by
:func:`Set GUI Tab Selected Callback Configuration`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_tab_selected_callback_configuration(device::BrickletLCD128x64)
    return device.deviceInternal.get_gui_tab_selected_callback_configuration()
end

export get_gui_tab_selected
"""
    $(SIGNATURES)

Returns the index of the currently selected tab.
If there are not tabs, the returned index is -1.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_tab_selected(device::BrickletLCD128x64)
    return device.deviceInternal.get_gui_tab_selected()
end

export set_gui_graph_configuration
"""
    $(SIGNATURES)

Sets the configuration for up to four graphs.

The graph type can be dot-, line- or bar-graph.

The x and y position are pixel positions.

You can add a text for the x and y axis.
The text is drawn at the inside of the graph and it can overwrite some
of the graph data. If you need the text outside of the graph you can
leave this text here empty and use :func:`Draw Text` to draw the caption
outside of the graph.

The data of the graph can be set and updated with :func:`Set GUI Graph Data`.

The graph is drawn in a separate GUI buffer and the graph-frame and data will
always stay on top of the graphics drawn with :func:`Write Pixels`. To
remove the graph use :func:`Remove GUI Graph`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_graph_configuration(device::BrickletLCD128x64, index, graph_type, position_x, position_y, width, height, text_x, text_y)
    device.deviceInternal.set_gui_graph_configuration(index, graph_type, position_x, position_y, width, height, text_x, text_y)
end

export get_gui_graph_configuration
"""
    $(SIGNATURES)

Returns the graph properties for a given `Index` as set by :func:`Set GUI Graph Configuration`.

Additionally the `Active` parameter shows if a graph is currently active/visible
or not.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_graph_configuration(device::BrickletLCD128x64, index)
    return device.deviceInternal.get_gui_graph_configuration(index)
end

export set_gui_graph_data_low_level
"""
    $(SIGNATURES)

Sets the data for a graph with the given index. You have to configure the graph with
:func:`Set GUI Graph Configuration` before you can set the first data.

The graph will show the first n values of the data that you set, where
n is the width set with :func:`Set GUI Graph Configuration`. If you set
less then n values it will show the rest of the values as zero.

The maximum number of data-points you can set is 118 (which also corresponds to the
maximum width of the graph).

You have to scale your values to be between 0 and 255. 0 will be shown
at the bottom of the graph and 255 at the top.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_graph_data_low_level(device::BrickletLCD128x64, index, data_length, data_chunk_offset, data_chunk_data)
    device.deviceInternal.set_gui_graph_data_low_level(index, data_length, data_chunk_offset, data_chunk_data)
end

export get_gui_graph_data_low_level
"""
    $(SIGNATURES)

Returns the graph data for a given index as set by :func:`Set GUI Graph Data`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_graph_data_low_level(device::BrickletLCD128x64, index)
    return device.deviceInternal.get_gui_graph_data_low_level(index)
end

export remove_gui_graph
"""
    $(SIGNATURES)

Removes the graph with the given index.

You can use index 255 to remove all graphs.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function remove_gui_graph(device::BrickletLCD128x64, index)
    device.deviceInternal.remove_gui_graph(index)
end

export remove_all_gui
"""
    $(SIGNATURES)

Removes all GUI elements (buttons, slider, graphs, tabs).

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function remove_all_gui(device::BrickletLCD128x64)
    device.deviceInternal.remove_all_gui()
end

export set_touch_led_config
"""
    $(SIGNATURES)

Sets the touch LED configuration. By default the LED is on if the
LCD is touched.

You can also turn the LED permanently on/off or show a heartbeat.

If the Bricklet is in bootloader mode, the LED is off.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_touch_led_config(device::BrickletLCD128x64, config)
    device.deviceInternal.set_touch_led_config(config)
end

export get_touch_led_config
"""
    $(SIGNATURES)

Returns the configuration as set by :func:`Set Touch LED Config`

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_touch_led_config(device::BrickletLCD128x64)
    return device.deviceInternal.get_touch_led_config()
end

export get_spitfp_error_count
"""
    $(SIGNATURES)

Returns the error count for the communication between Brick and Bricklet.

The errors are divided into

* ACK checksum errors,
* message checksum errors,
* framing errors and
* overflow errors.

The errors counts are for errors that occur on the Bricklet side. All
Bricks have a similar function that returns the errors on the Brick side.
"""
function get_spitfp_error_count(device::BrickletLCD128x64)
    return device.deviceInternal.get_spitfp_error_count()
end

export set_bootloader_mode
"""
    $(SIGNATURES)

Sets the bootloader mode and returns the status after the requested
mode change was instigated.

You can change from bootloader mode to firmware mode and vice versa. A change
from bootloader mode to firmware mode will only take place if the entry function,
device identifier and CRC are present and correct.

This function is used by Brick Viewer during flashing. It should not be
necessary to call it in a normal user program.
"""
function set_bootloader_mode(device::BrickletLCD128x64, mode)
    return device.deviceInternal.set_bootloader_mode(mode)
end

export get_bootloader_mode
"""
    $(SIGNATURES)

Returns the current bootloader mode, see :func:`Set Bootloader Mode`.
"""
function get_bootloader_mode(device::BrickletLCD128x64)
    return device.deviceInternal.get_bootloader_mode()
end

export set_write_firmware_pointer
"""
    $(SIGNATURES)

Sets the firmware pointer for :func:`Write Firmware`. The pointer has
to be increased by chunks of size 64. The data is written to flash
every 4 chunks (which equals to one page of size 256).

This function is used by Brick Viewer during flashing. It should not be
necessary to call it in a normal user program.
"""
function set_write_firmware_pointer(device::BrickletLCD128x64, pointer)
    device.deviceInternal.set_write_firmware_pointer(pointer)
end

export write_firmware
"""
    $(SIGNATURES)

Writes 64 Bytes of firmware at the position as written by
:func:`Set Write Firmware Pointer` before. The firmware is written
to flash every 4 chunks.

You can only write firmware in bootloader mode.

This function is used by Brick Viewer during flashing. It should not be
necessary to call it in a normal user program.
"""
function write_firmware(device::BrickletLCD128x64, data)
    return device.deviceInternal.write_firmware(data)
end

export set_status_led_config
"""
    $(SIGNATURES)

Sets the status LED configuration. By default the LED shows
communication traffic between Brick and Bricklet, it flickers once
for every 10 received data packets.

You can also turn the LED permanently on/off or show a heartbeat.

If the Bricklet is in bootloader mode, the LED is will show heartbeat by default.
"""
function set_status_led_config(device::BrickletLCD128x64, config)
    device.deviceInternal.set_status_led_config(config)
end

export get_status_led_config
"""
    $(SIGNATURES)

Returns the configuration as set by :func:`Set Status LED Config`
"""
function get_status_led_config(device::BrickletLCD128x64)
    return device.deviceInternal.get_status_led_config()
end

export get_chip_temperature
"""
    $(SIGNATURES)

Returns the temperature as measured inside the microcontroller. The
value returned is not the ambient temperature!

The temperature is only proportional to the real temperature and it has bad
accuracy. Practically it is only useful as an indicator for
temperature changes.
"""
function get_chip_temperature(device::BrickletLCD128x64)
    return device.deviceInternal.get_chip_temperature()
end

export reset
"""
    $(SIGNATURES)

Calling this function will reset the Bricklet. All configurations
will be lost.

After a reset you have to create new device objects,
calling functions on the existing ones will result in
undefined behavior!
"""
function reset(device::BrickletLCD128x64)
    device.deviceInternal.reset()
end

export write_uid
"""
    $(SIGNATURES)

Writes a new UID into flash. If you want to set a new UID
you have to decode the Base58 encoded UID string into an
integer first.

We recommend that you use Brick Viewer to change the UID.
"""
function write_uid(device::BrickletLCD128x64, uid)
    device.deviceInternal.write_uid(uid)
end

export read_uid
"""
    $(SIGNATURES)

Returns the current UID as an integer. Encode as
Base58 to get the usual string version.
"""
function read_uid(device::BrickletLCD128x64)
    return device.deviceInternal.read_uid()
end

export get_identity
"""
    $(SIGNATURES)

Returns the UID, the UID where the Bricklet is connected to,
the position, the hardware and firmware version as well as the
device identifier.

The position can be 'a', 'b', 'c', 'd', 'e', 'f', 'g' or 'h' (Bricklet Port).
A Bricklet connected to an :ref:`Isolator Bricklet <isolator_bricklet>` is always at
position 'z'.

The device identifier numbers can be found :ref:`here <device_identifier>`.
|device_identifier_constant|
"""
function get_identity(device::BrickletLCD128x64)
    return device.deviceInternal.get_identity()
end

export write_pixels
"""
Writes pixels to the specified window.

The pixels are written into the window line by line top to bottom
and each line is written from left to right.

If automatic draw is enabled (default) the pixels are directly written to
the screen. Only pixels that have actually changed are updated on the screen,
the rest stays the same.

If automatic draw is disabled the pixels are written to an internal buffer and
the buffer is transferred to the display only after :func:`Draw Buffered Frame`
is called. This can be used to avoid flicker when drawing a complex frame in
multiple steps.

Automatic draw can be configured with the :func:`Set Display Configuration`
function.
"""
function write_pixels(device::BrickletLCD128x64, x_start, y_start, x_end, y_end, pixels)

        x_start = UInt8(x_start)
    y_start = UInt8(y_start)
    x_end = UInt8(x_end)
    y_end = UInt8(y_end)
    pixels = convert(Vector{Bool}, pixels)

    if length(pixels) > 65535
        throw(TinkerforgeInvalidParameterError("Pixels can be at most 65535 items long"))
    end

    pixels_length = length(pixels)
    pixels_chunk_offset = 0

    if pixels_length == 0
        pixels_chunk_data = [False] * 448
        ret = write_pixels_low_level(device, x_start, y_start, x_end, y_end, pixels_length, pixels_chunk_offset, pixels_chunk_data)
    else
        lock(device.stream_lock) do
            while pixels_chunk_offset < pixels_length
                pixels_chunk_data = create_chunk_data(pixels, pixels_chunk_offset, 448, False)
                ret = write_pixels_low_level(device, x_start, y_start, x_end, y_end, pixels_length, pixels_chunk_offset, pixels_chunk_data)
                pixels_chunk_offset += 448
            end
        end
    end

        return ret
end

export read_pixels
"""
Reads pixels from the specified window.

The pixels are read from the window line by line top to bottom
and each line is read from left to right.

If automatic draw is enabled (default) the pixels that are read are always the
same that are shown on the display.

If automatic draw is disabled the pixels are read from the internal buffer
(see :func:`Draw Buffered Frame`).

Automatic draw can be configured with the :func:`Set Display Configuration`
function.
"""
function read_pixels(device::BrickletLCD128x64, x_start, y_start, x_end, y_end)

        x_start = UInt8(x_start)
    y_start = UInt8(y_start)
    x_end = UInt8(x_end)
    y_end = UInt8(y_end)

    lock(device.stream_lock) do
        ret = read_pixels_low_level(device, x_start, y_start, x_end, y_end)
            pixels_length = ret.pixels_length
        pixels_out_of_sync = ret.pixels_chunk_offset != 0

        pixels_data = ret.pixels_chunk_data

        while !pixels_out_of_sync && length(pixels_data) < pixels_length
            ret = read_pixels_low_level(device, x_start, y_start, x_end, y_end)
                pixels_length = ret.pixels_length
            pixels_out_of_sync = ret.pixels_chunk_offset != length(pixels_data)
            pixels_data += ret.pixels_chunk_data
        end

        if pixels_out_of_sync # discard remaining stream to bring it back in-sync
            while ret.pixels_chunk_offset + 480 < pixels_length
                ret = read_pixels_low_level(device, x_start, y_start, x_end, y_end)
                    pixels_length = ret.pixels_length
            end

            throw(TinkerforgeStreamOutOfSyncError("Pixels stream is out-of-sync"))
        end
    end

        return pixels_data[:pixels_length]
end

export set_gui_graph_data
"""
Sets the data for a graph with the given index. You have to configure the graph with
:func:`Set GUI Graph Configuration` before you can set the first data.

The graph will show the first n values of the data that you set, where
n is the width set with :func:`Set GUI Graph Configuration`. If you set
less then n values it will show the rest of the values as zero.

The maximum number of data-points you can set is 118 (which also corresponds to the
maximum width of the graph).

You have to scale your values to be between 0 and 255. 0 will be shown
at the bottom of the graph and 255 at the top.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function set_gui_graph_data(device::BrickletLCD128x64, index, data)

        index = UInt8(index)
    data = convert(Vector{UInt8}, data)

    if length(data) > 65535
        throw(TinkerforgeInvalidParameterError("Data can be at most 65535 items long"))
    end

    data_length = length(data)
    data_chunk_offset = 0

    if data_length == 0
        data_chunk_data = [0] * 59
        ret = set_gui_graph_data_low_level(device, index, data_length, data_chunk_offset, data_chunk_data)
    else
        lock(device.stream_lock) do
            while data_chunk_offset < data_length
                data_chunk_data = create_chunk_data(data, data_chunk_offset, 59, 0)
                ret = set_gui_graph_data_low_level(device, index, data_length, data_chunk_offset, data_chunk_data)
                data_chunk_offset += 59
            end
        end
    end

        return ret
end

export get_gui_graph_data
"""
Returns the graph data for a given index as set by :func:`Set GUI Graph Data`.

.. versionadded:: 2.0.2\$nbsp;(Plugin)
"""
function get_gui_graph_data(device::BrickletLCD128x64, index)

        index = UInt8(index)

    lock(device.stream_lock) do
        ret = get_gui_graph_data_low_level(device, index)
            data_length = ret.data_length
        data_out_of_sync = ret.data_chunk_offset != 0

        data_data = ret.data_chunk_data

        while !data_out_of_sync && length(data_data) < data_length
            ret = get_gui_graph_data_low_level(device, index)
                data_length = ret.data_length
            data_out_of_sync = ret.data_chunk_offset != length(data_data)
            data_data += ret.data_chunk_data
        end

        if data_out_of_sync # discard remaining stream to bring it back in-sync
            while ret.data_chunk_offset + 59 < data_length
                ret = get_gui_graph_data_low_level(device, index)
                    data_length = ret.data_length
            end

            throw(TinkerforgeStreamOutOfSyncError("Data stream is out-of-sync"))
        end
    end

        return data_data[:data_length]
end

export register_callback
"""
Registers the given *function* with the given *callback_id*.
"""
function register_callback(device::BrickletLCD128x64, callback_id, function_)
    if isnothing(function_)
        device.registered_callbacks.pop(callback_id, None)
    else
        device.registered_callbacks[callback_id] = function_
    end
end


text:
"""
`BufferedInputStream{T}` provides buffered reading from a source of type `T`.

Any type `T` wrapped in a `BufferedInputStream` must implement:

    BufferedStreams.readbytes!(source::T, buffer::Vector{UInt8}, from::Int, to::Int)

This function should:

* refill `buffer` starting at `from` and not filling past `to`.
* return the number of bytes read.

Failure to read any new data into the buffer is interpreted as eof.
"""
mutable struct BufferedInputStream{T} <: IO
    source::T
    buffer::Vector{UInt8}

    # Position of the next byte to be read in buffer;
    # `position ≤ 0` indicates that the stream is closed.
    position::Int

    # Number of bytes available in buffer.
    # I.e. buffer[1:available] is valid data.
    available::Int

    # If positive, preserve and move buffer[anchor:available]
    # when refilling the buffer.
    anchor::Int

    # If `true`, buffered data `buffer[anchor:available]` is not shifted.
    immobilized::Bool
end

function BufferedInputStream(source::T, bufsize::Integer = default_buffer_size) where T
    if bufsize ≤ 0
        throw(ArgumentError("buffer size must be positive"))
    end
    return BufferedInputStream{T}(source, Vector{UInt8}(undef, bufsize), 1, 0, 0, false)
end

function Base.show(io::IO, stream::BufferedInputStream{T}) where T
    bufsize = length(stream.buffer)
    filled = stream.available - stream.position + 1
    if isopen(stream)
        print(io,
            summary(stream), "(<",
            _datasize(bufsize), " buffer, ",
            round(Int, filled / bufsize * 100), "% filled",
            stream.immobilized ? ", data immobilized" : "", ">)")
    else
        print(io, summary(stream), "(<closed>)")
    end
end

"""
Refill the buffer, optionally moving and retaining part of the data.
"""
function fillbuffer!(stream::BufferedInputStream)
    if eof(stream.source)
        return 0
    end

    shiftdata!(stream)
    margin = length(stream.buffer) - stream.available
    if margin == 0
        resize!(stream.buffer, length(stream.buffer) * 2)
    end

    nbytes = readbytes!(
        stream.source,
        stream.buffer,
        stream.available + 1,
        length(stream.buffer))
    stream.available += nbytes
    return nbytes
end

# Shift data to be kept.
function shiftdata!(stream::BufferedInputStream)
    if stream.immobilized
        return 0
    else
        if stream.anchor > 0 && stream.available - stream.anchor + 1 > 0
            shift = stream.anchor - 1
            n = stream.available - shift
            copyto!(stream.buffer, 1, stream.buffer, stream.anchor, n)
            stream.anchor -= shift
        elseif stream.available - stream.position + 1 > 0
            shift = stream.position - 1
            n = stream.available - shift
            copyto!(stream.buffer, 1, stream.buffer, stream.position, n)
        else
            # no data to be kept
            @assert stream.position > stream.available
            shift = stream.available
        end
        stream.position -= shift
        stream.available -= shift
        return shift
    end
end

@inline function Base.eof(stream::BufferedInputStream)
    if stream.position > stream.available
        return fillbuffer!(stream) == 0
    else
        return false
    end
end

@inline function Base.bytesavailable(stream::BufferedInputStream)
    if eof(stream)
        return 0
    else
        return stream.available - stream.position + 1
    end
end

@inline function Base.readavailable(stream::BufferedInputStream)
    read(stream, bytesavailable(stream))
end

@inline function Base.skip(stream::BufferedInputStream, n_::Integer)
    n0 = n = convert(Int, n_)
    if n < 0
        throw(ArgumentError("n must be non-negative in skip(::BufferedInputStream, n)"))
    end

    while stream.position + n > stream.available + 1
        n -= stream.available - stream.position + 1
        stream.position = stream.available + 1
        if fillbuffer!(stream) < 1
            throw(EOFError())
        end
    end
    stream.position += n
    return n0
end

@inline function checkopen(stream::BufferedInputStream)
    if !isopen(stream)
        error("buffered input stream is already closed")
    end
end

"""
Return the next byte from the input stream without advancing the position.
"""
@inline function peek(stream::BufferedInputStream)
    checkopen(stream)
    if stream.position > stream.available
        if fillbuffer!(stream) < 1
            throw(EOFError())
        end
    end
    @inbounds c = stream.buffer[stream.position]
    return c
end

"""
Fills `buffer` with bytes from `stream`'s buffer without advancing the
position.

Unless the buffer is empty, we do not re-fill it. Therefore the number of bytes
read is limited to the minimum of `nb` and the remaining bytes in the buffer.
"""
function peekbytes!(stream::BufferedInputStream,
                    buffer::AbstractArray{UInt8},
                    nb=length(buffer))
    checkopen(stream)
    if stream.position > stream.available
        if fillbuffer!(stream) < 1
            throw(EOFError())
        end
    end
    nb = min(nb, stream.available - stream.position + 1)
    copyto!(buffer, 1, stream.buffer, stream.position, nb)
    return nb
end

@inline function Base.read(stream::BufferedInputStream, ::Type{UInt8})
    checkopen(stream)
    if stream.position > stream.available
        if fillbuffer!(stream) < 1
            throw(EOFError())
        end
    end
    @inbounds c = stream.buffer[stream.position]
    stream.position += 1
    return c
end

# fast multi-byte data readers
for T in [Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, Float16, Float32, Float64]
    @eval begin
        @inline function Base.read(stream::BufferedInputStream, ::Type{$(T)})
            checkopen(stream)
            if !ensurebuffered!(stream, $(sizeof(T)))
                throw(EOFError())
            end
            ptr::Ptr{$(T)} = pointer(stream)
            ret = unsafe_load(ptr)
            stream.position += $(sizeof(T))
            return ret
        end
    end
end

if isdefined(Base, :unsafe_read)
    function Base.unsafe_read(stream::BufferedInputStream, ptr::Ptr{UInt8}, nb::UInt)
        p = ptr
        p_end = ptr + nb
        while p < p_end
            if ensurebuffered!(stream, 1)
                n = min(p_end - p, available_bytes(stream))
                ccall(:memcpy, Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), p, pointer(stream), n)
                p += n
                stream.position += n
            else
                throw(EOFError())
            end
        end
        return nothing
    end
end

# Special purpose readuntil for plain bytes.
function Base.readuntil(stream::BufferedInputStream, delim::UInt8)
    checkopen(stream)
    anchor!(stream)
    while true
        p0 = pointer(stream.buffer, stream.position)
        p1 = ccall(:memchr, Ptr{UInt8}, (Ptr{UInt8}, Cint, Csize_t),
                   p0, delim, stream.available - stream.position + 1)
        if p1 != C_NULL
            stream.position += p1 - p0
            break
        else
            stream.position = stream.available + 1
            nb = fillbuffer!(stream)
            if nb == 0
                chunk = takeanchored!(stream)
                return chunk
            end
        end
    end
    chunk = stream.buffer[upanchor!(stream):stream.position]
    stream.position += 1
    return chunk
end

function readbytes!(stream::BufferedInputStream,
                    buffer::AbstractArray{UInt8},
                    nb=length(buffer))
    return readbytes!(stream, buffer, 1, nb)
end

function readbytes!(stream::BufferedInputStream,
                    buffer::AbstractArray{UInt8},
                    from::Int, to::Int)
    p = from
    while !eof(stream) && p ≤ to
        @assert ensurebuffered!(stream, 1)
        n = min(to - p + 1, stream.available - stream.position + 1)
        copyto!(buffer, p, stream.buffer, stream.position, n)
        p += n
        stream.position += n
    end
    return p - from
end

function Base.ismarked(stream::BufferedInputStream)
    return stream.anchor > 0
end

function Base.mark(stream::BufferedInputStream)
    stream.anchor = stream.position
    return stream.anchor
end

function Base.unmark(stream::BufferedInputStream)
    if !ismarked(stream)
        return false
    end
    stream.anchor = 0
    return true
end

function Base.reset(stream::BufferedInputStream)
    if !ismarked(stream)
        error("buffered stream is not marked")
    end
    anchor = stream.anchor
    stream.position = anchor
    unmark(stream)
    return anchor
end

"""
Return true if the stream is anchored.
"""
function isanchored(stream::BufferedInputStream)
    return stream.anchor > 0
end

"""
Set the buffer's anchor to its current position.
"""
function anchor!(stream::BufferedInputStream)
    stream.anchor = stream.position
end

"""
Remove and return a buffer's anchor.
"""
function upanchor!(stream::BufferedInputStream)
    anchor = stream.anchor
    stream.anchor = 0
    return anchor
end

"""
Copy and return a byte array from the anchor up to, but not including the
current position, also removing the anchor.
"""
function takeanchored!(stream::BufferedInputStream)
    if stream.position - 1 > stream.available
        throw(EOFError())
    end
    chunk = stream.buffer[stream.anchor:stream.position - 1]
    upanchor!(stream)
    return chunk
end

function Base.position(stream::BufferedInputStream)
    return position(stream.source) - stream.available + stream.position - 1
end

function Base.seek(stream::BufferedInputStream{T}, pos::Integer) where T
    if applicable(seek, stream.source, pos)
        upanchor!(stream)
        source_position = position(stream.source)
        # is the new position within the buffer?
        if source_position - stream.available <= pos <= source_position
            stream.position = 1 + pos - (source_position - stream.available)
        else
            seek(stream.source, pos)
            stream.position = 1
            stream.available = 0
        end
    else
        throw(ArgumentError(
            string("Can't seek in input stream with source of type ", T)))
        # TODO: Allow seeking forwards by just reading and discarding input
    end
end

function Base.isopen(stream::BufferedInputStream)
    return stream.position > 0
end

function Base.close(stream::BufferedInputStream)
    if !isopen(stream)
        return
    end
    if applicable(close, stream.source)
        close(stream.source)
    end
    stream.position = 0
    empty!(stream.buffer)
    return
end

function Base.pointer(stream::BufferedInputStream, index::Integer=1)
    return pointer(stream.buffer, stream.position + index - 1)
end

@inline function available_bytes(stream::BufferedInputStream)
    return stream.available - stream.position + 1
end

@inline function ensurebuffered!(stream::BufferedInputStream, nb::Integer)
    if available_bytes(stream) < nb
        fillbuffer!(stream)
        if available_bytes(stream) < nb
            return false
        end
    end
    return true
end


text:
#==============================================================================
    Code for solving the Hamiltonian Jacobi Bellman for
	   a Ramsey model with a Diffusion process for productivity

	Based on Matlab code from Ben Moll:
        http://www.princeton.edu/~moll/HACTproject.htm

        Updated to Julia 1.0.0
==============================================================================#

using Distributions, Plots, LinearAlgebra, SparseArrays
using Random

Random.seed!(1234)

γ= 2.0 #gamma parameter for CRRA utility
ρ = 0.05 #the discount rate
α = 0.3 # the curvature of the production function (cobb-douglas)
δ = 0.05 # the depreciation rate

#  Now the agents misspecify θ and σ^2
global θ_g = 0.25
global σ_sq_g = 0.008

# Z our state variable follows this process
	#= for this process:
	 		dlog(z) = -θ⋅log(z)dt + σ⋅dw
		and
			log(z)∼N(0,var) where var = σ^2/(2⋅θ) =#
var = 0.07
μ_z = exp(var/2)
corr = 0.9
θ = -log(corr)
σ_sq = 2*θ*var

#=============================================================================
 	k our capital follows a process that depends on z,

	using the regular formula for capital accumulation
	we would have:
		(1+ρ)k_{t+1} = k_{t}⋅f'(k_{t}) + (1-δ)k_{t}
	where:
		f(k_{t}) = z⋅k^{α} so f'(k_{t}) = (α)⋅z⋅k^{α-1}
	so in steady state where k_{t+1} = k_{t}
		(1+ρ)k = α⋅z⋅k^{α} + (1-δ)k
		k = [(α⋅z)/(ρ+δ)]^{1/(1-α)}

=============================================================================#
#K_starting point, for mean of z process

k_st = ((α⋅μ_z)/(ρ+δ))^(1/(1-α))

# create the grid for k
H = 100 #number of points on grid
k_min = 0.3*k_st # min value
k_max = 3*k_st # max value
k = LinRange(k_min, k_max, H)
dk = (k_max-k_min)/(H-1)

# create the grid for z
J = 40
z_min = μ_z*0.8
z_max = μ_z*1.2
z = LinRange(z_min, z_max, J)
dz = (z_max-z_min)/(J-1)
dz_sq = dz^2

# Check the pdf to make sure our grid isn't cutting off the tails of
	# our distribution
y = pdf.(LogNormal(0, var), z)
plot(z,y, grid=false,
		xlabel="z", ylabel="Probability",
		legend=false, color="purple", title="PDF of z")
#png("PDF_Z")

#create matrices for k and z
z= convert(Array, z)'
kk = k*ones(1,J)
zz = ones(H,1)*z

# use Ito's lemma to find the drift and variance of our optimization equation

μ = (-θ*log.(z).+σ_sq/2).*z # the drift from Ito's lemma
Σ_sq = σ_sq.*z.^2 #the variance from Hto's lemma

maxit = 100
ε = 0.1^(6)
Δ = 1000

# They will update this based on a draw from a binomial

Dist = Bernoulli(.5)

T = 1000 #periods of time for updating

global μ_g = (-θ_g*log.(z).+σ_sq_g/2).*z # the drift from Ito's lemma
Σ_sq_g = σ_sq_g.*z.^2 #the variance from Ito's lemma

# set up all of these empty matrices for Misspecfication loop
Vaf_2, Vab_2,c_2 = [zeros(H,J) for i in 1:6]

# set up all of these empty matrices
Vaf_1, Vab_1, c_1 = [zeros(H,J) for i in 1:6]

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

    Now we are going to construct a matrix summarizing the evolution of V_z

    This comes from the following discretized Bellman equation:

    ρv_ij = u(c_ij) + v_k(zF(k_{i}) -δk-c) + v_z⋅μ(z)
                                   + 1/2(v_{zz})σ^2(z)

                                   or

    ρv_ij = u(c_ij) + v_k(zF(k_{i}) -δk-c) + ((v_{i,j+1}-v_{i,j})/Δz)μ(z)
                            + 1/2((v_{i,j+1}-2v_{i,j}+v_{ij-1})/Δz^2)σ^2(z)

    Assume forward difference because of boundary conditions

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

 yy = (-Σ_sq/dz_sq - μ/dz)
 χ = Σ_sq/(2*dz_sq)
 ζ = μ/dz + Σ_sq/(2*dz_sq)

 # Define the diagonals of this matrix
 updiag = zeros(H,1)
 	for j = 1:J
		global updiag =[updiag; repeat([ζ[j]], H, 1)]
	end
 updiag =(updiag[:])

 centerdiag=repeat([χ[1]+yy[1]],H,1)
	for j = 2:J-1
		global centerdiag = [centerdiag; repeat([yy[j]], H, 1)]
	end
 centerdiag=[centerdiag; repeat([yy[J]+ζ[J]], H, 1)]
 centerdiag = centerdiag[:]

lowdiag = repeat([χ[2]], H, 1)
	for j=3:J
		global lowdiag = [lowdiag; repeat([χ[j]],H,1)]
	end
lowdiag=lowdiag[:]

# spdiags in Matlab allows for automatic trimming/adding of zeros
    # spdiagm does not do this

B_switch = sparse(Diagonal(centerdiag))+ [zeros(H,H*J);  sparse(Diagonal(lowdiag)) zeros(H*(J-1), H)]+ sparse(Diagonal(updiag))[(H+1):end,1:(H*J)]

# Now it's time to solve the model, first put in a guess for the value function
v0 = (zz.*kk.^α).^(1-γ)/(1-γ)/ρ
v_1=v0

dist = [] # set up empty array for the convergence criteria

for n = 1:maxit
    V=v_1

    #Now set up the forward difference

    Vaf_1[1:H-1,:] = (V[2:H, :] - V[1:H-1,:])/dk
    Vaf_1[H,:] = (z.*k_max.^α .- δ.*k_max).^(-γ) # imposes a constraint

    #backward difference
    Vab_1[2:H,:] = (V[2:H, :] - V[1:H-1,:])/dk
    Vab_1[1,:] = (z.*k_min.^α .- δ.*k_min).^(-γ)

    #H_concave = Vab .> Vaf # indicator for whether the value function is concave

    # Consumption and savings functions
    cf = Vaf_1.^(-1/γ)
    sf = zz .* kk.^α - δ.*kk - cf

    # consumption and saving backwards difference

    cb = Vab_1.^(-1.0/γ)
    sb = zz .* kk.^α - δ.*kk - cb
    #println(sb)
    #consumption and derivative of the value function at the steady state

    c0 = zz.*kk.^α - δ.*kk
    Va0 = c0.^(-γ)

    # df chooses between the forward or backward difference

    Hf = sf.>0 # positive drift will ⇒ forward difference
    Hb = sb.<0 # negative drift ⇒ backward difference
    H0=(1.0.-Hf-Hb) # at steady state

    Va_upwind = Vaf_1.*Hf + Vab_1.*Hb + Va0.*H0 # need to include SS term

    global c_1 = Va_upwind.^(-1/γ)
    u = (c_1.^(1-γ))/(1-γ)

    # Now to constuct the A matrix
    X = -min.(sb, 0)/dk
    #println(X)
    Y = -max.(sf, 0)/dk + min.(sb, 0)/dk
    Z = max.(sf, 0)/dk

    updiag_k = 0
       for j = 1:J
           updiag_k =[updiag_k; Z[1:H-1,j]; 0]
       end

    centerdiag_k=reshape(Y, H*J,1)
    centerdiag_k=(centerdiag_k[:])

   lowdiag_k = X[2:H, 1]
       for j = 2:J
           lowdiag_k = [lowdiag_k; 0; X[2:H,j]]
       end

   # Upated using julia 1.0.0
  AA = sparse(Diagonal(centerdiag_k))+ [zeros(1, H*J); sparse(Diagonal(lowdiag_k)) zeros(H*J-1,1)] + sparse(Diagonal(updiag_k))[2:end, 1:(H*J)] # trim first element

  A = AA + B_switch
  B = (1/Δ + ρ)*sparse(1.0I, H*J, H*J) - A

  u_stacked= reshape(u, H*J, 1)
  V_stacked = reshape(V,H*J, 1)# trim off rows of zeros

  b = u_stacked + (V_stacked./Δ)

  V_stacked = B\b

  V = reshape(V_stacked, H, J)

  V_change = V-v_1

  global v_1= V

  # need push function to add to an already existing array
  push!(dist, findmax(abs.(V_change))[1])
  if dist[n].< ε
      println("Value Function Converged Iteration=")
      println(n)
      break
  end

end

# calculate the savings for kk
ss_1 = zz.*kk.^α - δ.*kk - c_1

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

    Now we are going to construct a matrix summarizing the evolution of V_z

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

 yy_g = (-Σ_sq_g/dz_sq - μ_g/dz)
 χ_g = Σ_sq_g/(2*dz_sq)
 ζ_g = μ_g/dz + Σ_sq_g/(2*dz_sq)

 # Define the diagonals of this matrix
 updiag_g = zeros(H,1)
 	for j = 1:J
		global updiag_g =[updiag_g; repeat([ζ_g[j]], H, 1)]
	end
 updiag_g =(updiag_g[:])

 centerdiag_g=repeat([χ_g[1]+yy_g[1]],H,1)
	for j = 2:J-1
		global centerdiag_g = [centerdiag_g; repeat([yy_g[j]], H, 1)]
	end
 centerdiag_g=[centerdiag_g; repeat([yy_g[J]+ζ_g[J]], H, 1)]
 centerdiag_g = centerdiag_g[:]

lowdiag_g = repeat([χ_g[2]], H, 1)
	for j=3:J
		global lowdiag_g = [lowdiag_g; repeat([χ_g[j]],H,1)]
	end
lowdiag_g=lowdiag_g[:]

# spdiags in Matlab allows for automatic trimming/adding of zeros
    # spdiagm does not do this

global B_switch_g = sparse(Diagonal(centerdiag_g))+ [zeros(H,H*J);  sparse(Diagonal(lowdiag_g)) zeros(H*(J-1), H)]+ sparse(Diagonal(updiag_g))[(H+1):end,1:(H*J)] # trim off rows of zeros

# set up empty cells for storing parameters
V_tot = []
V_all=[]
drifts=[]

guess_σ=[]
guess_θ=[]

for t = 1:T
    indicator = rand(Dist)
	global B_switch_g = B_switch_g
	global θ_g = θ_g
	global σ_sq_g = σ_sq_g
	push!(guess_θ, θ_g)
	push!(guess_σ, σ_sq_g)
	push!(drifts, μ_g)
	global dist =[]

	# Now it's time to solve the model, first put in a guess for the value function
	v0 = (zz.*kk.^α).^(1-γ)/(1-γ)/ρ
	global v_2=v0

    for n = 1:maxit
    V=v_2

    #Now set up the forward difference

    Vaf_2[1:H-1,:] = (V[2:H, :] - V[1:H-1,:])/dk
    Vaf_2[H,:] = (z.*k_max.^α .- δ.*k_max).^(-γ) # imposes a constraint

    #backward difference
    Vab_2[2:H,:] = (V[2:H, :] - V[1:H-1,:])/dk
    Vab_2[1,:] = (z.*k_min.^α .- δ.*k_min).^(-γ)
    #H_concave = Vab .> Vaf # indicator for whether the value function is concave

    # Consumption and savings functions
    cf = Vaf_2.^(-1/γ)
    sf = zz .* kk.^α - δ.*kk - cf

    # consumption and saving backwards difference

    cb = Vab_2.^(-1.0/γ)
    sb = zz .* kk.^α - δ.*kk - cb
    #println(sb)
    #consumption and derivative of the value function at the steady state

    c0 = zz.*kk.^α - δ.*kk
    Va0 = c0.^(-γ)

    # df chooses between the forward or backward difference

    Hf = sf.>0 # positive drift will ⇒ forward difference
    Hb = sb.<0 # negative drift ⇒ backward difference
    H0=(1.0.-Hf-Hb) # at steady state

    Va_upwind = Vaf_2.*Hf + Vab_2.*Hb + Va0.*H0 # need to include SS term

    global c_2 = Va_upwind.^(-1/γ)
    u = (c_2.^(1-γ))/(1-γ)

    # Now to constuct the A matrix
    X = -min.(sb, 0)/dk
    #println(X)
    Y = -max.(sf, 0)/dk + min.(sb, 0)/dk
    Z = max.(sf, 0)/dk

    updiag_k = 0
       for j = 1:J
           updiag_k =[updiag_k; Z[1:H-1,j]; 0]
       end
    updiag_k =(updiag_k[:])

    centerdiag_k=reshape(Y, H*J, 1)
    centerdiag_k = (centerdiag_k[:]) # for tuples

    lowdiag_k = X[2:H, 1]
       for j = 2:J
           lowdiag_k = [lowdiag_k; 0; X[2:H,j]]
       end
    lowdiag_k=(lowdiag_k)

    # spdiags in Matlab allows for automatic trimming/adding of zeros
       # spdiagm does not do this
    AA = sparse(Diagonal(centerdiag_k))+ [zeros(1, H*J); sparse(Diagonal(lowdiag_k)) zeros(H*J-1,1)] + sparse(Diagonal(updiag_k))[2:end, 1:(H*J)] # trim first element

    A = AA + B_switch_g
    B = (1/Δ + ρ)*sparse(I,H*J,H*J) - A

    u_stacked= reshape(u, H*J, 1)
    V_stacked = reshape(V,H*J, 1)

    b = u_stacked + (V_stacked./Δ)

    V_stacked = B\b

    V = reshape(V_stacked, H, J)

    V_change = V-v_2

    global v_2= V

    # need push function to add to an already existing array
    push!(dist, findmax(abs.(V_change))[1])
    if dist[n].< ε
      #println("loop")
      #println(t)
      break
    end
    end

    if indicator == 1
		θ_noise = θ + rand(Normal(0,.01),1)[1] # create noisy observations
		σ_sq_noise = σ_sq + rand(Normal(0,.001),1)[1]
		θ_g = θ_g + .01(θ_noise-θ_g) #update using RLS and noisy obs
		σ_sq_g = σ_sq_g + .01(σ_sq_noise-σ_sq_g)

	#use updated paramters to calculate Bswitch_g
	  global μ_g = (-θ_g*log.(z).+σ_sq_g/2).*z # the drift from Ito's lemma
	  Σ_sq_g = σ_sq_g.*z.^2 #the variance from Ito's lemma

	  yy_g = (-Σ_sq_g/dz_sq - μ_g/dz)
	  χ_g = Σ_sq_g/(2*dz_sq)
	  ζ_g = μ_g/dz + Σ_sq_g/(2*dz_sq)

	  # Define the diagonals of this matrix
	  updiag_g = zeros(H,1)
	   for j = 1:J
		   updiag_g =[updiag_g; repeat([ζ_g[j]], H, 1)]
	   end
	  updiag_g =(updiag_g[:])

	  centerdiag_g=repeat([χ_g[1]+yy_g[1]],H,1)
	   for j = 2:J-1
		   centerdiag_g = [centerdiag_g; repeat([yy_g[j]], H, 1)]
	   end
	  centerdiag_g=[centerdiag_g; repeat([yy_g[J]+ζ_g[J]], H, 1)]
	  centerdiag_g = centerdiag_g[:]

	 lowdiag_g = repeat([χ_g[2]], H, 1)
	   for j=3:J
		   lowdiag_g = [lowdiag_g; repeat([χ_g[j]],H,1)]
	   end
	 lowdiag_g=lowdiag_g[:]

	 # spdiags in Matlab allows for automatic trimming/adding of zeros
		 # spdiagm does not do this

	  B_switch_g = sparse(Diagonal(centerdiag_g))+ [zeros(H,H*J);  sparse(Diagonal(lowdiag_g)) zeros(H*(J-1), H)]+ sparse(Diagonal(updiag_g))[(H+1):end,1:(H*J)] # trim off rows of zeros

    end

    push!(V_tot, v_2[:,20])
	push!(V_all, v_2)
end

# calculate the savings for kk
ss_2 = zz.*kk.^α - δ.*kk - c_2

# Plot the savings vs. k
plot(k, ss_2, grid=false,
		xlabel="k", ylabel="s(k,z)",
        xlims=(k_min,k_max),
		legend=false, title="Optimal Savings Policies")
plot!(k, zeros(H,1))
#png("OptimalSavings")

plot(k, v_2, grid=false,
		xlabel="k", ylabel="V(k)",
		xlims=(k_min,k_max),
		legend=false, title="Computed Value Function over k")
#png("Value_function_vs_k_2")

plot(k, v_1, grid=false,
		xlabel="k", ylabel="V(k)",
		xlims=(k_min,k_max),
		legend=false, title="True Value Function over k")
#png("Value_function_vs_k_1")

z = LinRange(z_min, z_max, J)
plot(z, v_2', grid=false,
		xlabel="z", ylabel="V(z)",
		xlims=(z_min,z_max),
		legend=false, title="Computed Value Function over z")
#png("Value_function_vs_z_2")

plot(z, v_1', grid=false,
		xlabel="z", ylabel="V(z)",
		xlims=(z_min,z_max),
		legend=false, title="True Value")
#png("Value_function_vs_z_1")

plot(k, V_tot, grid=false,
		xlabel="k", ylabel="V(k)",
		xlims=(k_min,k_max),
		legend=false, title="Value Function Median values of z over k")
#png("Value_function_vs_k_2_median")

plot(k, V_tot[1], grid=false, label="Period 1", line=:dashdot)
plot!(k,V_tot[50], label="Period 50", line=:dot)
plot!(k,V_tot[1000], label="Period 1,000")
plot!(k,v_1[:,20],
		xlabel="k", ylabel="V(k)",
		xlims=(k_min,k_max), title="Value Function over k for Median z",
        legend=:bottomright, label="True Value", line=:dash,color=:black)
png("Value_functions_median_Z")

plot(z,V_all[1][50,:], grid=false, label="Period 1")
plot!(z,V_all[200][50,:], label="Period 200", line=:dashdot)
plot!(z,V_all[500][50,:], label="Period 500", line=:dot)
plot!(z,V_all[750][50,:], label="Period 750", line=:dashdot)
plot!(z,V_all[1000][50,:], label="Period 1,000")
plot!(z,v_1[50,:],
		xlabel="z", ylabel="V(z)", title="Value Function over z for Median k",
        legend=:bottomright, label="True Value",
		line=:dash, color=:black)
png("Value_functions_median_k")

plot([1:length(guess_σ)], guess_σ, grid=false,
		xlabel="Periods", ylabel="\$ \\sigma\$",
		title="\$ \\textrm{Estimate of } \\sigma \\textrm{ over Time}\$", label="Misspecfication")
plot!([1:length(guess_σ)],ones(length(guess_σ)).*σ_sq,
		label="True Value", color=:black, line=:dash, legend=:bottomright)
png("sigma")

plot([1:length(guess_σ)], guess_θ, grid=false,
		xlabel="Periods", ylabel="\$ \\theta\$",
		title="\$ \\textrm{Estimate of } \\theta \\textrm{ over Time}\$", label="Misspecfication",
		legend=:bottomright)
plot!([1:length(guess_σ)],ones(length(guess_σ)).*θ,
		label="True Value", color=:black, line=:dash)
png("theta")


text:
# HTML
# ----

abstract type HTMLFormat <: WeaveFormat end

render_code(docformat::HTMLFormat, code) =
    highlight_code(MIME("text/html"), code, docformat.highlight_theme)

render_termchunk(docformat::HTMLFormat, chunk) =
    should_render(chunk) ? highlight_term(MIME("text/html"), chunk.output, docformat.highlight_theme) : ""

# Julia markdown
# --------------

Base.@kwdef mutable struct WeaveHTML <: HTMLFormat
    description = "Weave-style HTML"
    extension = "html"
    codestart = '\n'
    codeend = '\n'
    termstart = codestart
    termend = codeend
    outputstart = "
"
    outputend = "
\n"
    mimetypes = ["image/png", "image/jpg", "image/svg+xml",
                "text/html", "text/markdown", "text/plain"]
    fig_ext = ".png"
    out_width = nothing
    out_height = nothing
    fig_pos = nothing
    fig_env = nothing
    # specials
    template = nothing
    stylesheet = nothing
    highlight_theme = nothing
end
register_format!("md2html", WeaveHTML())

function set_format_options!(docformat::WeaveHTML; template = nothing, css = nothing, highlight_theme = nothing, _kwargs...)
    template_path = isnothing(template) ? normpath(TEMPLATE_DIR, "md2html.tpl") : template
    docformat.template = get_mustache_template(template_path)
    stylesheet_path = isnothing(css) ? normpath(STYLESHEET_DIR, "skeleton.css") : css
    docformat.stylesheet = read(stylesheet_path, String)
    docformat.highlight_theme = get_highlight_theme(highlight_theme)
end

# very similar to tex version of function
function render_chunk(docformat::WeaveHTML, chunk::DocChunk)
    out = IOBuffer()
    io = IOBuffer()
    for inline in chunk.content
        if isa(inline, InlineText)
            write(io, inline.content)
        elseif !isempty(inline.rich_output)
            clear_buffer_and_format!(io, out, WeaveMarkdown.html)
            write(out, addlines(inline.rich_output, inline))
        elseif !isempty(inline.figures)
            write(io, inline.figures[end])
        elseif !isempty(inline.output)
            write(io, addlines(inline.output, inline))
        end
    end
    clear_buffer_and_format!(io, out, WeaveMarkdown.html)
    return take2string!(out)
end

render_output(docformat::WeaveHTML, output) = Markdown.htmlesc(output)

function render_figures(docformat::WeaveHTML, chunk)
    fignames = chunk.figures
    caption = chunk.options[:fig_cap]
    width = chunk.options[:out_width]
    height = chunk.options[:out_height]
    f_pos = chunk.options[:fig_pos]
    f_env = chunk.options[:fig_env]
    result = ""
    figstring = ""

    # Set size
    attribs = ""
    isnothing(width) || (attribs = "width=\"$width\"")
    (!isempty(attribs) && !isnothing(height)) && (attribs *= ",")
    isnothing(height) || (attribs *= " height=\"$height\" ")

    if !isnothing(caption)
        result *= """
\n"""
    end

    for fig in fignames
        figstring *= """\n"""
    end

    result *= figstring

    if !isnothing(caption)
        result *= """

$caption

          """
    end

    if !isnothing(caption)
        result *= "
\n"
    end

    return result
end

function render_doc(docformat::WeaveHTML, body, doc; css = nothing)
    _, weave_source = splitdir(abspath(doc.source))
    weave_version, weave_date = weave_info()

    return Mustache.render(
        docformat.template;
        body = body,
        stylesheet = docformat.stylesheet,
        highlight_stylesheet = get_highlight_stylesheet(MIME("text/html"), docformat.highlight_theme),
        header_script = doc.header_script,
        weave_source = weave_source,
        weave_version = weave_version,
        weave_date = weave_date,
        [Pair(Symbol(k), v) for (k, v) in doc.header]...,
    )
end

# Pandoc
# ------

Base.@kwdef mutable struct Pandoc2HTML <: HTMLFormat
    description = "HTML via intermediate Pandoc Markdown (requires Pandoc 2)"
    extension = "md"
    codestart = '\n'
    codeend = '\n'
    termstart = codestart
    termend = codeend
    outputstart = '\n'
    outputend = '\n'
    mimetypes = ["image/png", "image/svg+xml", "image/jpg", "text/html", "text/markdown", "text/plain"]
    fig_ext = ".png"
    out_width = nothing
    out_height = nothing
    fig_pos = nothing
    fig_env = nothing
    # specials
    preserve_header = true
    template_path = nothing
    stylesheet_path = nothing
    highlight_theme = nothing
    pandoc_options = String[]
end
register_format!("pandoc2html", Pandoc2HTML())

function set_format_options!(docformat::Pandoc2HTML; template = nothing, css = nothing, highlight_theme = nothing, pandoc_options = String[], _kwargs...)
    docformat.template_path =
        isnothing(template) ? normpath(TEMPLATE_DIR, "pandoc2html.html") : template
    docformat.stylesheet_path =
        isnothing(css) ? normpath(STYLESHEET_DIR, "pandoc2html_skeleton.css") : css
    docformat.highlight_theme = get_highlight_theme(highlight_theme)
    docformat.pandoc_options = pandoc_options
end

render_figures(docformat::Pandoc2HTML, chunk) = render_figures(Pandoc(), chunk)


text:
using HTTP
using JSON2
# CORS headers that show what kinds of complex requests are allowed to API
headers = [
    "Access-Control-Allow-Origin" => "*",
    "Access-Control-Allow-Headers" => "*",
    "Access-Control-Allow-Methods" => "POST, GET, OPTIONS"
]

#=
JSONHandler minimizes code by automatically converting the request body
to JSON to pass to the other service functions automatically. JSONHandler
recieves the body of the response from the other service funtions and sends
back a success response code
=#
function JSONHandler(req::HTTP.Request)
    # first check if there's any request body
    body = IOBuffer(HTTP.payload(req))
    if eof(body)
        # no request body
        response_body = handle(ANIMAL_ROUTER, req)
    else
        # there's a body, so pass it on to the handler we dispatch to
        response_body = handle(ANIMAL_ROUTER, req)
    end
    return HTTP.Response(200, JSON2.write(response_body))
end

#= CorsHandler: handles preflight request with the OPTIONS flag
If a request was recieved with the correct headers, then a response will be
sent back with a 200 code, if the correct headers were not specified in the request,
then a CORS error will be recieved on the client side

Since each request passes throught the CORS Handler, then if the request is
not a preflight request, it will simply go to the JSONHandler to be passed to the
correct service function =#
function CorsHandler(req)
    if HTTP.hasheader(req, "OPTIONS")
        return HTTP.Response(200, headers = headers)
    else
        return JSONHandler(req)
    end
end

mutable struct Animal
    id::Int
    userId::Base.UUID
    type::String
    name::String
end

# use a plain `Dict` as a "data store"
const ANIMALS = Dict{Int, Animal}()
const NEXT_ID = Ref(0)
function getNextId()
    id = NEXT_ID[]
    NEXT_ID[] += 1
    return id
end

# "service" functions to actually do the work
function createAnimal(req::HTTP.Request)
    animal = JSON2.read(IOBuffer(HTTP.payload(req)), Animal)
    animal.id = getNextId()
    ANIMALS[animal.id] = animal
    return HTTP.Response(200, JSON2.write(animal))
end

function getAnimal(req::HTTP.Request)
    return HTTP.Response(200, "ok")
end

function updateAnimal(req::HTTP.Request)
    animal = JSON2.read(IOBuffer(HTTP.payload(req)), Animal)
    ANIMALS[animal.id] = animal
    return HTTP.Response(200, JSON2.write(animal))
end

function deleteAnimal(req::HTTP.Request)
    animalId = HTTP.URIs.splitpath(req.target)[5] # /api/zoo/v1/animals/10, get 10
    delete!(ANIMALS, parse(Int, animal.id))
    return HTTP.Response(200)
end

# define REST endpoints to dispatch to "service" functions
const ANIMAL_ROUTER = HTTP.Router()

HTTP.@register(ANIMAL_ROUTER, "POST", "/api/zoo/v1/animals", createAnimal)
# note the use of `*` to capture the path segment "variable" animal id
HTTP.@register(ANIMAL_ROUTER, "GET", "/api/zoo/v1/animals", getAnimal)
HTTP.@register(ANIMAL_ROUTER, "PUT", "/api/zoo/v1/animals", updateAnimal)
HTTP.@register(ANIMAL_ROUTER, "DELETE", "/api/zoo/v1/animals/*", deleteAnimal)

HTTP.serve(CorsHandler, "0.0.0.0", 8080)