text:
# declaration of basic types
abstract type AbstractScene end
abstract type AbstractSceneObject end
abstract type AbstractMaterial end
abstract type AbstractUIApp end
abstract type AbstractUIVariable end
abstract type AbstractUIValidation end
abstract type AbstractUIControl end

export  AbstractScene,
        AbstractSceneObject,
        AbstractMaterial,
        AbstractUIApp,
        AbstractUIVariable,
        AbstractUIValidation,
        AbstractUIControl

# place holders for function names
prop() = 0
prop!() = 0

function updateVariable!(app::AbstractUIApp, var::AbstractUIVariable)
    @error "abstract function - should not reach here"
end

#---------------------------------------------------------------
#   utility methods to convert image data into Base64 representation suted for JavaScript.
#---------------------------------------------------------------
show_png(io, x) = show(io, MIME"image/png"(), x)
show_svg(io, x) = show(io, MIME"image/svg+xml"(), x)

base64png(img) = "data:image/png;base64,$(Base64.base64encode(show_png, img))"
base64svg(img) = "data:image/svg+xml;base64,$(Base64.base64encode(show_svg, img))"

# #---------------------------------------------------------------
# #   Transform to AffineMat that can be used in the renderer
# #---------------------------------------------------------------
# function tr2affine(tr::Transform)
#     return AffineMap(tr[1:3,1:3], Geometry.origin(tr))
# end

unitX3(::Type{T} = Float64) where {T<:Real} = SVector{3,T}(1.0, 0.0, 0.0)
unitY3(::Type{T} = Float64) where {T<:Real} = SVector(0.0, 1.0, 0.0)
unitZ3(::Type{T} = Float64) where {T<:Real} = SVector(0.0, 0.0, 1.0)
zero3(::Type{T} = Float64)  where {T<:Real} = SVector(0.0, 0.0, 0.0)
identityTransform(::Type{T} = Float64) where {T<:Real} = AffineMap(SMatrix{3, 3, T, 9}(I), zero(SVector{3, T}))

#---------------------------------------------------------------
# utility function that given a vector, return 2 orthogonal vectors to that one
#---------------------------------------------------------------
function get_orthogonal_vectors(direction::SVector{3, T}) where {T<:Real}
    axis1 = normalize(direction)

    dp = dot(unitX3(T), axis1)
    # check the special case where the input vector is parallel to the X axis
    if	( dp >= one(T) - eps(T))
        # axis1 = unitX(T);
        axis2 = unitY3(T);
        axis3 = unitZ3(T);
        return (axis2, axis3)
    elseif  ( dp <= -one(T) + eps(T))
        # axis1 = -unitX(T);
        axis2 = -unitY3(T);
        axis3 = -unitZ3(T);
        return (axis2, axis3)
    end

    axis3 = normalize(cross(axis1, unitX3(T)))
    axis2 = normalize(cross(axis3, axis1))
    return (axis2, axis3)
end

function rotation(axis1::SVector{3, T}, axis2::SVector{3, T}, axis3::SVector{3, T}) where {T<:Real}
    # this is a COLUMN MAJOR initialization, where axis N is column N
    return SMatrix{3,3,T,9}(
        axis1[1], axis1[2], axis1[3],
        axis2[1], axis2[2], axis2[3],
        axis3[1], axis3[2], axis3[3]
    )
end

function transform(origin::SVector{3, T}, forward::SVector{3, T} = unitZ3(T)) where {T<:Real}
    forward = normalize(forward)
    right, up = get_orthogonal_vectors(forward)
    return AffineMap(rotation(right, up, forward), origin)
end
function lookAt(position::SVector{3, T}, target::SVector{3, T} = zero3(T)) where {T<:Real}
    forward = target - position
    return transform(position, forward)
end

dataPath() = joinpath(dirname(@__FILE__), "Data")

#---------------------------------------------------------------
#   Color conversion methods
#---------------------------------------------------------------
function to_color(c::Tuple{Int64, Int64, Int64})
    return Colors.RGBA((c ./ 255)...)
end

function to_color(c::Tuple{Float64, Int64, Int64})
    return Colors.RGBA(c...)
end

function to_color(c::Colors.RGBA)
    return c
end

function to_color(c::Colors.RGB)
    return RGBA(c)
end

function to_color(c::Symbol)
    return return to_color(Colors.color_names[string(c)])
end

#---------------------------------------------------------------
#   Examples Utilities
#---------------------------------------------------------------
fixFolderSeperator(fn::String) = replace(fn, '\\' => '/')

"""
    rootFolder()

return the root folder for this package.
"""
function rootFolder()
    return fixFolderSeperator(abspath(joinpath(dirname(@__FILE__), "..")))
end

"""
    examplesFolder()

return the examples folder (~/examples) for this package.
"""
function examplesFolder()
    return fixFolderSeperator(abspath(joinpath(rootFolder(), "examples")))
end

"""
    examplesList()

Display a list of avalable examples.
"""
function examplesList()
    folder = examplesFolder();

    files = readdir(folder, join=false)

    all_jl_files = [fn for fn in files if splitext(basename(fn))[2] == ".jl"]

    println("-"^60)
    println("Examples List for Twinkle  [$(folder)]")
    println("-"^60)
    for f in all_jl_files
        len = length(f)
        if (len < 20)
            spaces = " "^(20 - len)
        else
            spaces = ""
        end
        println("    $(f)$(spaces)       to run:  julia> Twinkle.runExample(\"$(splitext(f)[1])\")")
    end

end

"""
    runExample(example::String)

Run the specific example. [example] is given without extension.
For example, if we have an example file named "Controls.jl" the command to run it is:
Twinkle.runExample("Controls")
"""
function runExample(example::String)
    fn = fixFolderSeperator(joinpath(examplesFolder(), "$(splitext(example)[1]).jl"))

    @show fn
    code = "include(\"$fn\")"
    @show code
    exp = Meta.parse(code)
    return eval(exp)
end


text:
module Utils

using OnlineLearning

export fitlearner!

function fitlearner!(l::Learner, x, y, passes=1, batchsize=1, dumps=100; offset=:none)
    n = size(x, 1)

    batchesperpass, r = divrem(n, batchsize)
    if r!= 0
        warn("n is not divisible by batchsize, so some observations won't ever be used")
    end

    losses = Float64[]

    dumpinterval = dumps<=0? typemax(Int) : div(passes * batchesperpass, dumps)

    for pass in 1:passes
      for i in 1:batchesperpass
        r1= (i-1)*batchsize + 1
        r2 = i * batchsize
        if offset == :none
            update!(l, x[r1:r2, :], y[r1:r2])
        else
            update!(l, x[r1:r2, :], y[r1:r2], offset=offset[r1:r2])
        end

        if dumpinterval == 0 || i % dumpinterval == 0
            push!(losses, loss(l, x, y, offset=offset))
        end
      end
    end

    return (losses, dumpinterval)

end

end

ext: jl


text:
# abstract supertype of specific semidiscretizations such as
# - SemidiscretizationHyperbolic for hyperbolic conservation laws
# - SemidiscretizationEulerGravity for Euler with self-gravity
abstract type AbstractSemidiscretization end

# abstract supertype of specific equations such as the compressible Euler equations
abstract type AbstractEquations{NDIMS, NVARS} end

# abstract supertype of specific SBP bases such as a Lobatto-Legendre nodal basis
abstract type AbstractBasisSBP{RealT<:Real} end

# abstract supertype of mortar methods, e.g. using L² projections
abstract type AbstractMortar{RealT<:Real} end

# abstract supertype of mortar methods using L² projection
# which will be specialized for different SBP bases
abstract type AbstractMortarL2{RealT<:Real} <: AbstractMortar{RealT} end

# abstract supertype of functionality related to the analysis of
# numerical solutions, e.g. the calculation of errors
abstract type SolutionAnalyzer{RealT<:Real} end

# abstract supertype of grid-transfer methods used for AMR,
# e.g. refinement and coarsening based on L² projections
abstract type AdaptorAMR{RealT<:Real} end

# abstract supertype of AMR grid-transfer operations using L² projections
# which will be specialized for different SBP bases
abstract type AdaptorL2{RealT<:Real} <: AdaptorAMR{RealT} end

# TODO: Taal decide, which abstract types shall be defined here?

struct BoundaryConditionPeriodic end

"""
    boundary_condition_periodic = BoundaryConditionPeriodic()

A singleton struct indicating periodic boundary conditions.
"""
const boundary_condition_periodic = BoundaryConditionPeriodic()

Base.show(io::IO, ::BoundaryConditionPeriodic) = print(io, "boundary_condition_periodic")


text:
using Documenter, DifferentialEvolution

makedocs(
    sitename="DifferentialEvolution.jl", modules = [DifferentialEvolution])


text:
import Base.length
import Base.propertynames
import Base.getproperty
import Base.setproperty!
import Base.push!
import Base.iterate
using DataStructures

struct HashVector{V}
    dict::OrderedDict{Symbol, Union{Int, Vector{Int}}}
    data::Vector{V}

    HashVector{V}() where V =
        new{V}(OrderedDict{Symbol, Union{Int, Vector{Int}}}(), Vector{V}())

    HashVector{V}(data::Vector{V}) where V =
        new{V}(OrderedDict{Symbol, Union{Int, Vector{Int}}}(), data)
end

length(hv::HashVector) = length(getfield(hv, :dict))
propertynames(hv::HashVector) = keys(getfield(hv, :dict))

function getproperty(hv::HashVector{V}, key::Symbol) where V
    id = getfield(hv, :dict)[key]
    if isa(id, Array)
        return view(getfield(hv, :data), id)
    end
    return getfield(hv, :data)[id]
end

function setproperty!(hv::HashVector{V}, key::Symbol, value::V) where V
    id = get(getfield(hv, :dict), key, nothing)

    # Avoid raising an error here to simplify patch functions
    isnothing(id)  &&  (return value)

    @assert !isa(id, Array) "Can't set a scalar value to a Vector{$V}: try with dot (broadcast) notation."
    getfield(hv, :data)[id] = value
end

function push!(hv::HashVector{V}, k::Symbol, v::V) where V
    dict = getfield(hv, :dict)
    data = getfield(hv, :data)

    i1 = length(data) + 1
    if k in keys(dict)
        if isa(dict[k], Int)
            dict[k] = [dict[k], i1]
        else
            push!(dict[k], i1)
        end
    else
        dict[k] = i1
    end
    push!(getfield(hv, :data), v)
end

function iterate(hv::HashVector{V}, state...) where V
    out = iterate(getfield(hv, :dict), state...)
    isnothing(out)  &&  (return nothing)
    return (out[1][1] => getproperty(hv, out[1][1]), out[2])
end

#=
hv = HashVector{Int}()
push!(hv, :a, 1)
push!(hv, :b, 2)
push!(hv, :b, 3)

@assert length(hv) == 2
@assert all(propertynames(hv) .== [:a, :b])

@assert hv.a == 1
@assert hv.b == [2,3]

hv.a = 10
@assert hv.a == 10

hv.b .= 10
@assert hv.b == [10,10]

for (key, val) in hv
    println(key, "  ", val)
end
=#

#=
function HashVector(in_dict::AbstractDict{Symbol, T}) where T
    Eltypes = unique(eltype.(values(in_dict)))
    @assert length(Eltypes) == 1
    V = Eltypes[1]
    dict = OrderedDict{Symbol, Union{Int, Vector{Int}}}()
    data = Vector{V}()
    for (key, values) in in_dict
        i0 = length(data) + 1
        i1 = i0 + length(values) - 1
        append!(data, values)
        if isa(values, AbstractArray)
            dict[key] = collect(i0:i1)
        else
            dict[key] = i0
        end
    end
    return HashVector{V}(dict, data)
end

function force_vector(hv::HashVector{V}, k::Symbol) where V
    dict = getfield(hv, :dict)
    @assert k in keys(dict)
    if isa(dict[k], Int)
        dict[k] = [dict[k]]
    end
    return hv
end

=#


text:
function add_file_and_commit(repo::GitRepo, filename::AbstractString, content=nothing)
    idx, sig = GitIndex(repo), default_signature(repo)
    touch_test(workdir(repo), filename, content)
    push!(idx, filename)
    commit(repo, "New commit", sig, sig)
end

with_new_repo("test a new repo is fully merged") do test_repo, path
    @test is_fully_merged(GitIndex(test_repo))
end

with_standard_test_repo(
"test that a fully merged repo only contains staged idx entries") do test_repo, path
    idx = GitIndex(test_repo)
    @test is_fully_merged(idx)
    for entry in idx
        @test entry.stage == 0 # is staged
    end
end

with_merged_test_repo(
"test soft reset a repo wth unmerged entries throws") do test_repo, path
    idx = GitIndex(test_repo)
    @test is_fully_merged(idx) == false
    # head commit
    hc  = lookup(GitReference, test_repo, head(test_repo))
    fcp = parents(hc)[1]
    @test_throws LibGitError{:Object,:Unmerged} reset!(test_repo, fcp, :soft)
end

with_merged_test_repo(
"test commit against a repo with unmerged entries throws") do test_repo, path
    idx = GitIndex(test_repo)
    @test is_fully_merged(idx) == false
    author = Signature("test", "test@test.com")
    @test_throws LibGitError{:Index,:Unmerged} commit(test_repo,
        "try commit unmerged entries", author, author)
end

with_standard_test_repo(
"test can retrieve the branch begin merged") do test_repo, _
    b1 = "9fd738e8f7967c078dceed8190330fc8648ee56a";
    b2 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
    touch_test(path(test_repo), "MERGE_HEAD", "$b1\n$b2\n")
    @test currentstate(test_repo) === :merge
end

#=
for detach_head in (true, false)
    with_standard_test_repo(
    "test can merge repo non-ff : $detach_head") do test_repo, _
        b1name = "first branch file.txt";
        b2name = "second branch file.txt";
        sbname = "first+second branch file.txt";

        b1 = create_branch(test_repo, "FirstBranch")
        checkout!(b1)

        original_tree_count = GitTree(tip(b1)) |> length

        # commit with ONE new file to both first and
        # second branches
        add_file_and_commit(test_repo, sbname)

        b2 = create_branch(test_repo, "SecondBranch")

        if detach_head
            # detaches head
            checkout!(test_repo, tip(b2))
        else
            checkout!(b2)
        end

        # Commit with one new file to second branch
        # first branch and second branch now point to different
        # commits that both have thhe same parent commit
        add_file_and_commit(test_repo, b2name)

        status, commit = merge!(test_repo, tip(b1))

        @test status === :nonfastforward
        @test isa(commit, GitCommit)
        #=
        @show Oid(test_repo[head(test_repo)])
        @show parent_count(test_repo[res])
        @show is_head_detached(test_repo) == detach_head

        if !detach_head
            # ensure HEAD is still attached and points to second branch
            @show canonical_name(b2)
            @show symbolic_target(head(test_repo))
        end
        =#
    end
end
=#

with_standard_test_repo(
"test is up to date merged") do test_repo, _
    b1name = "first branch file.txt"
    b2name = "second branch file.txt"
    sbname = "first+second branch file.txt"

    b1 = create_branch(test_repo, "first_branch")
    checkout!(b1)
    add_file_and_commit(test_repo, b1name)

    b2 = create_branch(test_repo, "second_branch")
    checkout!(b2)

    status, cmmt = merge!(test_repo, b1)

    @test status === :uptodate
    @test is(cmmt, nothing)
end

for detach_head in (false,) #true XXX: detached head does not work (merge conflict)
    with_standard_test_repo(
     "test can ff repos : $detach_head") do test_repo, _

            b1name = "first branch file.txt";
            sbname = "first+second branch file.txt";

            # reset the index and the working tree
            reset!(test_repo, "HEAD", :hard)

            # clean up the working directory
            remove_untracked!(test_repo)

            b1 = create_branch(test_repo, "FirstBranch")
            checkout!(b1)

            # commit with ONE new file to both b1 & b2
            add_file_and_commit(test_repo, sbname)

            b2 = create_branch(test_repo, "SecondBranch")

            # commit with ONE new file to first branch
            # b2 is now 1 behind  b1
            add_file_and_commit(test_repo, b1name)

            if detach_head
                checkout!(test_repo, tip(b2))
            else
                checkout!(b2)
            end

            @test is_head_detached(test_repo) == detach_head

            status, cmmt = merge!(test_repo, tip(b1))

            @test status === :fastforward

            @test tip(b1) == cmmt
            @test tip(b1) == test_repo[head(test_repo)]
            @test is_fully_merged(GitIndex(test_repo))
            @test is_head_detached(test_repo) == detach_head
            if !detach_head
                # ensure HEAD is still attached and points to second branch
                @test name(head(test_repo)) == canonical_name(b2)
            end
    end
end

with_standard_test_repo(
"test conflicting merge repos") do test_repo, _
    b1name = "first branch file.txt"
    b2name = "second branch file.txt"
    sbname = "first+second branch file.txt"

    b1 = create_branch(test_repo, "FirstBranch")
    checkout!(b1)

    # Commit with ONE new file to both first and second branch
    add_file_and_commit(test_repo, sbname)

    b2 = create_branch(test_repo, "SecondBranch")

    # Commit with ONE new file to first branch
    # first branch moves  forward as it is checked out
    # second branch stays back one
    add_file_and_commit(test_repo, b1name)
    add_file_and_commit(test_repo, sbname, "The first branches comment")

    checkout!(b2)

    # Commit with ONE new file to second branch
    # first branch and second branch now point to separate commits
    # that both have the same parent commit
    add_file_and_commit(test_repo, b2name)
    add_file_and_commit(test_repo, sbname, "The second branches comment")

    status, cmmt = merge!(test_repo, tip(b1))

    @test status == :conflicts
    @test is(cmmt, nothing)

    idx = GitIndex(test_repo)
    @test has_conflicts(idx)
    @test length(conflicts(test_repo, idx)) == 1
end

with_standard_test_repo(
"test conflicting merge repos binary") do test_repo, _
    b1name = "first branch file.txt"
    b2name = "second branch file.txt"
    sbname = "first+second branch file.txt"

    b1 = create_branch(test_repo, "FirstBranch")
    checkout!(b1)

    # commit with ONE new file to both first and second branch
    add_file_and_commit(test_repo, sbname)

    b2 = create_branch(test_repo, "SecondBranch")

    # commit with ONE new file to first branch
    # first branch moves forward as it is checked out,
    # second branch stays back one
    add_file_and_commit(test_repo, b1name)
    # change file in first branch
    add_file_and_commit(test_repo, sbname, "\0The first branches comment\0")

    checkout!(b2)
    # commit with ONE new file to the second branch
    # b1 and b2 now point to separte commits that both have the same parent commit
    add_file_and_commit(test_repo, b2name)
    add_file_and_commit(test_repo, sbname, "\0The second branches comment\0")

    status, cmmt = merge!(test_repo, tip(b1))

    @test status == :conflicts

    idx = GitIndex(test_repo)
    @test has_conflicts(idx)

    cs = conflicts(test_repo, idx)
    @test length(cs) == 1

    ancestor, ours, theirs = cs[1]

    #TODO: this is not getting picked up as a binary delta
    #@test delta(diff(test_repo, ours.id, theirs.id)).isbinary == true
end

# test can ff commit
for (detach_head, ffstrategy) in [(true, :default),
                                 (true, :fastforwardonly),
                                 (false, :default),
                                 (false, :fastforwardonly)]
    with_merge_test_repo(
        "test can ff merge commit: $detach_head, $ffstrategy") do test_repo, _
            if detach_head
                checkout!(test_repo, test_repo[head(test_repo)])
            end
            commit_to_merge = tip(lookup_branch(test_repo, "fast_forward"))
            status, cmmt = merge!(test_repo, commit_to_merge,
                                  AnyDict(:ffstrategy => ffstrategy))
            @test status === :fastforward
            @test Oid(cmmt) == Oid("4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53")
            @test detach_head == is_head_detached(test_repo)
    end
end

for (detach_head, ffstrategy, estatus) in [(true, :default, :nonfastforward),
                                          (true, :nofastforward, :nonfastforward),
                                          (false, :default, :nonfastforward),
                                          (false, :nofastforward, :nonfastforward)]
    with_merge_test_repo(
        "test can non ff merge commit: $detach_head, $ffstrategy, $estatus") do test_repo, _
            if detach_head
                checkout!(test_repo, test_repo[head(test_repo)])
            end
            commit_to_merge = tip(lookup_branch(test_repo, "normal_merge"))
            status, cmmt = merge!(test_repo, commit_to_merge, AnyDict(:ffstrategy => ffstrategy))

            @test status === estatus
            @test detach_head == is_head_detached(test_repo)
    end
end

with_merge_test_repo(
"test merge reports checkout progress") do test_repo, _
    commit_to_merge = tip(lookup_branch(test_repo, "normal_merge"))
    was_called = false
    opts = AnyDict(:progress => (args...) -> begin
        was_called = true
    end)
    res = merge!(test_repo, commit_to_merge, opts)
    @test was_called
end

#TODO: progress works, notify does not
with_merge_test_repo(
"test merge reports checkout notifications") do test_repo, _
    commit_to_merge = tip(lookup_branch(test_repo, "normal_merge"))
    was_called = false
    checkout_notify = :none
    opts = AnyDict(:notify => (why, path, diffs...) -> begin
        was_called = true
        checkout_notify = why
    end)
    res = merge!(test_repo, commit_to_merge, opts)
    #@show was_called, checkout_notify
end

with_merge_test_repo(
"test ff merge reports checkout progress") do test_repo, _
    commit_to_merge = tip(lookup_branch(test_repo, "fast_forward"))
    was_called = false
    opts = AnyDict(:progress => (args...) -> begin
        was_called = true
    end)
    res = merge!(test_repo, commit_to_merge, opts)
    @test was_called
end

#TODO: progress works, notify does not
with_merge_test_repo(
"test ff merge reports checkout notifications") do test_repo, _
    commit_to_merge = tip(lookup_branch(test_repo, "fast_forward"))
    was_called = false
    checkout_notify = :none
    opts = AnyDict(:notify => (path, notify) -> begin
        was_called = true
        checkout_notify = notify
    end)
    res = merge!(test_repo, commit_to_merge, opts)
    #@show was_called, checkout_notify
end

#TODO: this does not work
with_merge_test_repo(
"test merge can detect renames") do test_repo, _
    #=
    The environment is set up such that:
    file b.txt is edited in the "rename" branch and
    edited and renamed in the "rename_source" branch.
    The edits are automergable.
    We can rename "rename_source" into "rename"
    if rename detection is enabled,
    but the merge will fail with conflicts if this
    change is not detected as a rename.
    =#
    current_branch = checkout!(test_repo, "rename_source")
    @test current_branch != nothing

    #run(`git --git-dir=$(path(test_repo)) branch`)

    branch_to_merge = lookup_branch(test_repo, "rename")
    @test branch_to_merge != nothing

    status, _ = merge!(test_repo, branch_to_merge)
    #@show status
end

with_merge_test_repo(
"test ff non ff merge throws") do test_repo, _
    commit_to_merge = tip(lookup_branch(test_repo, "normal_merge"))
    #TODO: better errors
    @test_throws ErrorException merge!(test_repo, commit_to_merge,
                                       AnyDict(:ffstrategy => :fastforwardonly))
end

with_merge_test_repo(
"test can force ff merge through config") do test_repo, _
    GitConfig(test_repo)["merge.ff"] = "only"
    commit_to_merge = tip(lookup_branch(test_repo, "normal_merge"))
    #TODO: better errors
    @test_throws ErrorException merge!(test_repo, commit_to_merge)
end

with_merge_test_repo(
"test can merge and not commit") do test_repo, _
    commit_to_merge = tip(lookup_branch(test_repo, "normal_merge"))

    status, cmmt = merge!(test_repo, commit_to_merge)

    @test status === :nonfastforward
    @test is(cmmt, nothing)

    idx = GitIndex(test_repo)
    @test count(entry -> entry.stage == 0, idx) == 2
    @test idx["b.txt"].stage  == 0 # is taged entry
end

with_merge_test_repo(
"test can force non ff merge") do test_repo, _
    commit_to_merge = tip(lookup_branch(test_repo,"fast_forward"))
    opts = AnyDict(:ffstrategy => :nofastforward)
    status, cmmt = merge!(test_repo, commit_to_merge, opts)
    @test status === :nonfastforward
    #TODO: I don't see how we can ever get a non null commit here
    #@test cmmt == Oid("f58f780d5a0ae392efd4a924450b1bbdc0577d32")
    @test is_fully_merged(test_repo)
end

with_merge_test_repo(
"test can force non ff merge through config") do test_repo, _
    GitConfig(test_repo)["merge.ff"] = "false"
    commit_to_merge = tip(lookup_branch(test_repo, "normal_merge"))
    status, cmmt = merge!(test_repo, commit_to_merge)
    @test status === :nonfastforward
    #@show cmmt
    @test is_fully_merged(test_repo)
end

with_merge_test_repo(
"test verify up to date merge") do test_repo, _
    commit_to_merge = tip(lookup_branch(test_repo, "master"))
    status, cmmt = merge!(test_repo, commit_to_merge, AnyDict(:strategy => :nofastforward))
    @test status === :uptodate
    @test is(cmmt, nothing)
    @test is_fully_merged(test_repo)
end

for (committish, strategy, estatus) in [
    ("refs/heads/normal_merge", :default, :nonfastforward),
    ("normal_merge", :default, :nonfastforward),
    (Oid("625186280ed2a6ec9b65d250ed90cf2e4acef957"), :default, :nonfastforward),
    ("fast_forward", :default, :fastforward)]

    with_merge_test_repo(
    "test can merge committish: $committish, $strategy, $estatus") do test_repo, _
        status, cmmt  = merge!(test_repo, committish, AnyDict(:ffstrategy => strategy))
        @test status === estatus
        @test is_fully_merged(test_repo)
    end
end

for (should_stage, strategy) in [(true, :fastforwardonly),
                                 (false, :fastforwardonly),
                                 (true, :nofastforward),
                                 (false, :nofastforward)]
    with_merge_test_repo(
    "test can merge with workdir conflicts throws: $should_stage, $strategy") do test_repo, _
        #=
        Merging the fast_forward branch results in a change to file
        b.txt. In this test we modify the file in the working directory
        and then attempt to perform a merge. We expect the merge to fail
        due to merge conflicts.
        =#
        committish_to_merge = "fast_forward"
        touch_test(workdir(test_repo), "b.txt", "this is an alternate change")
        #TODO: the libgit2 sharp stage concept is much cleaner, we should steal that
        if should_stage
            idx = GitIndex(test_repo)
            push!(idx, "b.txt")
            write!(idx)
        end
        if strategy === :fastforwardonly
            @test_throws LibGitError{:Checkout,:MergeConflict} merge!(test_repo,
                committish_to_merge, AnyDict(:ffstrategy => strategy))
        else
            @test_throws LibGitError{:Merge,:MergeConflict} merge!(test_repo,
                committish_to_merge, AnyDict(:ffstrategy => strategy))
        end
    end
end

for strategy in (:use_ours, :use_theirs)
    with_merge_test_repo(
        "test can specify conflict file strategy: $strategy") do test_repo, _

        conflict_file  = "a.txt"
        conflict_branchname = "conflicts"

        branch = lookup_branch(test_repo, conflict_branchname)
        @test branch != nothing

        opts = AnyDict(:strategy => strategy)
        status, cmmt = merge!(test_repo, branch, opts)

        @test status === :conflicts

        idx = GitIndex(test_repo)
        cs = conflicts(test_repo, idx)

        @test !isempty(cs)
        ancestor, ours, theirs= cs[1]
        blob = nothing
        if strategy === :use_theirs
            blob = lookup_blob(test_repo, theirs.id)
        elseif strategy === :use_ours
            blob = lookup_blob(test_repo, ours.id)
        else
            error("unexpected strategy")
        end
        @test blob != nothing
        @test bytestring(blob) == open(readall, joinpath(workdir(test_repo), conflict_file))
    end
end

for favor in (:favor_ours, :favor_theirs)
    with_merge_test_repo(
        "test merge can specify merge file flavor option") do test_repo, _

        conflict_file  = "a.txt"
        conflict_branchname = "conflicts"

        branch = lookup_branch(test_repo, conflict_branchname)

        @test branch != nothing

        opts = AnyDict(:automerge => favor)
        status, _ = merge!(test_repo, branch, opts)
        reload!(test_repo)

        @test status === :nonfastforward
        @test is_fully_merged(test_repo)

        idx = GitIndex(test_repo)

        blob = nothing
        if favor == :favor_theirs
            blob = lookup(test_repo, Oid("3dd9738af654bbf1c363f6c3bbc323bacdefa179"))
        elseif favor == :favor_ours
            blob = lookup(test_repo, Oid("610b16886ca829cebd2767d9196f3c4378fe60b5"))
        else
            error("unexpeted merge file test error")
        end
        @test blob != nothing

        entry = idx[conflict_file]
        @test entry != nothing

        @test Oid(blob) == Oid(entry)
        #@test bytestring(blob) == open(readall, joinpath(workdir(test_repo), conflict_file))
    end
end

for (branchname, strategy, estatus) in [
    ("refs/heads/normal_merge", :default, :nonfastforward),
    ("fast_forward", :default, :fastforward)]

    with_merge_test_repo(
    "test can merge branch: $branchname, $strategy, $estatus") do test_repo, _
        status, cmmt= merge!(test_repo, branchname, AnyDict(:ffstrategy => strategy))
        @test status === estatus
        @test is_fully_merged(test_repo)
    end
end

#=
with_merge_test_repo(
"test can merge into orphaned branch") do test_repo, _
    create_ref(test_repo, "HEAD", "refs/heads/orphan", force=true)
    # remove entried from the working dir
    idx = GitIndex(test_repo)
    remove_all!(idx, [entry.path for entry in idx])
    write!(idx)
    #assert that we have an empty working directory
    @test length(idx) == 0
    #run(`git --git-dir=$(path(test_repo)) status`)
    #merge!(test_repo, "master")
end
=#

sandboxed_test("merge-resolve", "test merge no conflict") do test_repo, path
    ours   = revparse(test_repo, "trivial-2alt")
    theirs = revparse(test_repo, "trivial-2alt-branch")
    base   = revparse(test_repo, merge_base(test_repo, ours, theirs))
    idx = merge!(test_repo, GitTree(ours), GitTree(theirs), GitTree(base))
    @test has_conflicts(idx) == false
end

sandboxed_test("merge-resolve", "test merge has conflicts") do test_repo, path
    ours   = revparse(test_repo, "trivial-4")
    theirs = revparse(test_repo, "trivial-4-branch")
    base   = revparse(test_repo, merge_base(test_repo, ours, theirs))
    idx = merge!(test_repo, GitTree(ours), GitTree(theirs), GitTree(base))
    @test has_conflicts(idx)
    @test sum(entry -> entry.stage > 0, idx) == 2
    @test idx["new-and-different.txt", 2] != nothing
    @test idx["new-and-different.txt", 3] != nothing
end


text:
#=
Use this method to receive incoming updates using long polling (wiki). An Array of Update objects is returned.

    Parameters	     Type	    Required	Description
    offset	         Integer	Optional	Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates. By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as soon as getUpdates is called with an offset higher than its update_id. The negative offset can be specified to retrieve updates starting from -offset update from the end of the updates queue. All previous updates will forgotten.
    limit	         Integer	Optional	Limits the number of updates to be retrieved. Values between 1—100 are accepted. Defaults to 100.
    timeout	         Integer	Optional	Timeout in seconds for long polling. Defaults to 0, i.e. usual short polling

Notes
1. This method will not work if an outgoing webhook is set up.
2. In order to avoid getting duplicate updates, recalculate offset after each server response.
=#
function getUpdates( offset::Int32, limit::Int32, timeout::Int32 )
end

#=
Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized Update. In case of an unsuccessful request, we will give up after a reasonable amount of attempts.
If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. https://www.example.com/<token>. Since nobody else knows your bot‘s token, you can be pretty sure it’s us.

    Parameters	 Type	    Required	Description
    url	         String	    Optional	HTTPS url to send updates to. Use an empty string to remove webhook integration
    certificate	 InputFile	Optional	Upload your public key certificate so that the root certificate in use can be checked. See our self-signed guide for details.

Notes
1. You will not be able to receive updates using getUpdates for as long as an outgoing webhook is set up.
2. To use a self-signed certificate, you need to upload your public key certificate using certificate parameter. Please upload as InputFile, sending a String will not work.
3. Ports currently supported for Webhooks: 443, 80, 88, 8443.
=#

function setWebhook( url::UTF8String, certificate::InputFile )
end

#=
Use this method to send text messages. On success, the sent Message is returned.

    Parameters	                 Type                        Required	   Description
    chat_id	                     Integer or String	         Yes	       Unique identifier for the target chat or username of the target channel (in the format @channelusername)
    text	                     String	                     Yes	       Text of the message to be sent
    parse_mode	                 String	                     Optional	   Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message.
    disable_web_page_preview	 Boolean	                 Optional	   Disables link previews for links in this message
    disable_notification	     Boolean	                 Optional	   Sends the message silently. iOS users will not receive a notification, Android users will receive a notification with no sound.
    reply_to_message_id	         Integer	                 Optional	   If the message is a reply, ID of the original message
    reply_markup	             InlineKeyboardMarkup or     Optional	   Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to hide reply keyboard or to force a reply from the user.
                                 ReplyKeyboardMarkup or
                                 ReplyKeyboardHide or
                                 ForceReply
=#
function sendMessage( chat_id::Union{ Int32, UTF8String }, text::UTF8String,
parse_mode::UTF8String, disable_web_page_preview::Bool, disable_notification::Bool,
reply_to_message_id::Int32, reply_markup::Union{ InlineKeyboardMarkup,
ReplyKeyboardMarkup, ReplyKeyboardHide, ForceReply } )
end

max_stars_repo_name: Fazendaaa/Julia-Telegram-Bot

max_issues_repo_name: Fazendaaa/Julia-Telegram-Bot

max_forks_repo_name: Fazendaaa/Julia-Telegram-Bot


text:
import TexTables: TableIndex, tuplefy

function test_constructor(name, x, y)
    pairs = Pair.(x,y)
    col = TableCol(name, x, y)
    @test col isa TableCol{1,1}

    # Check that all the constructors work
    @test begin
        col2 = TableCol(name, OrderedDict(pairs))
        col2 == col
    end

    @test begin
        col3 = TableCol(name, pairs...)
        col3 == col
    end

    # Build in a loop
    @test begin
        col4 = TableCol(name)
        for (key, val) in zip(x,y)
            col4[key] = val
        end
        col4 == col
    end
end

# This version does it with precisions
function test_constructor(name, x, y, p)
    pairs = Pair.(x,tuple.(y,p))

    col = TableCol(name, x, y, p)
    @test col isa TableCol{1,1}

    # Check that all the constructors work
    @test begin
        col2 = TableCol(name, OrderedDict(pairs))
        col2 == col
    end

    @test begin
        col3 = TableCol(name, pairs...)
        col3 == col
    end

    @test begin
        p1  = Pair.(x, y) |> OrderedDict
        p2  = Pair.(x, p) |> OrderedDict
        col4 = TableCol(name, p1, p2)
        col4 == col
    end

    # Build in a loop
    @test begin
        col5 = TableCol(name)
        for (key, val, se) in zip(x,y,p)
            col5[key] = val, se
        end
        col5 == col
    end
end

@testset "Constructing TableCols" begin

    @testset "Integer Values" begin
        # Data
        name = "test"
        x = ["key1", "key2", "key3"]
        y = [1, 2, 3]

        test_constructor(name, x, y)
    end

    @testset "Float Values with Precision" begin
        # Data
        name = "test"
        x = ["key1", "key2", "key3"]
        y = [1.0, 2.0, 3.0]
        p = [.2, .3, .3]

        test_constructor(name, x, y, p)
    end

    @testset "Construct With Mixed Types" begin

        name = "foo"
        x = ["key1", "key2", "key3", "key4"]
        y = [1, 2.2, (3.2, .24), "bar"]

        test_constructor(name, x, y)

    end

end

@testset "TableIndex" begin

    ####################################################################
    ################### Constructing TableIndex ########################
    ####################################################################
    x = TableIndex(1, "test")
    @test x.idx == (1,)
    @test x.name == (:test,)

    x = TableIndex(1, :test)
    @test x.idx == (1,)
    @test x.name == (:test,)

    ####################################################################
    ################### Comparing TableIndex Values for Sorting ########
    ####################################################################

    a = "a test"
    t = "test"
    z = "z test"

    # Sort Lexicographically on the levels
    @test TableIndex((1,1), (a, t)) < TableIndex((2,1), (a, t))
    @test TableIndex((1,1), (z, t)) < TableIndex((2,1), (a, t))
    @test TableIndex((2,1), (a, t)) < TableIndex((2,1), (z, t))
    @test TableIndex((2,1), (a, t)) <= TableIndex((2,1), (a, z))
    @test TableIndex((2,1), (a, t)) <= TableIndex((2,1), (a, t))
    @test TableIndex((2,1), (a, z)) > TableIndex((2,1), (a, a))

end


text:
"""
    Bond

Struct encapsulating the interactions between two lattice sites in matrix form.
* `sites    :: Tuple{Int64, Int64}` : indices of interacting lattice sites
* `exchange :: Matrix{Float64}`     : interaction matrix
"""
struct Bond
    sites    :: Tuple{Int64, Int64}
    exchange :: Matrix{Float64}
end

# generate a bond with vanishing interactions
function get_bond_empty(
    i :: Int64,
    j :: Int64
    ) :: Bond

    b = Bond((i, j), zeros(Float64, 3, 3))

    return b
end

# modify bond matrix in place
function add_bond!(
    J :: Float64,
    b :: Bond,
    μ :: Int64,
    ν :: Int64
    ) :: Nothing

    b.exchange[μ, ν] += J

    return nothing
end

# check if bonds are equal
function are_equal(
    b1 :: Bond,
    b2 :: Bond
    )  :: Bool

    equal = norm(b1.exchange .- b2.exchange) <= 1e-8

    return equal
end


text:
module RcLabView
import ..RchannelExperiments: plot_res
using ..RchannelExperiments

using Parameters
using LsqFit, VAWTools
import PyPlot
const P=PyPlot
import DataStructures: OrderedDict
import DSP

using ..RcSetup
import ..RcImages: median_filter
import Base.Dates: Time, Millisecond, millisecond

export ExpLabView, ExpLabViewResults

@with_kw struct ExpLabView
    file::String # output file of Lab computer
    zero_press_window::Tuple{Time,Time}
    equal_press_windows::Vector{Tuple{Time,Time}}
end
@with_kw struct ExpLabViewResults @deftype Vector{Float64}
    exl::ExpLabView
    time::Vector{Dates.Time} # Time of day
    t     # time since start of experiment (s)
    trun  # total time the water was running since start of experiment
          # note length(t)!=length(trun)==length(t[irun]) (s)
    irun::Vector{Int} # time[irun] where times with flowing water
    @assert length(t)==length(time)==length(Q)
    @assert length(trun)==length(irun)==length(t[irun])
    Q # discharge m^3/s
    press::OrderedDict{Port.PressPorts,Vector{Float64}} # pressure at different ports Pa
    press_std::OrderedDict{Port.PressPorts,Vector{Float64}} # std of press (if several sensors)

    press_raw::OrderedDict{Port.PressPorts,Vector{Float64}} # pressure at different ports Pa
    press_std_raw::OrderedDict{Port.PressPorts,Vector{Float64}} # std of press (if several sensors)
end

function ExpLabViewResults(ex)
    @unpack exl, experiment_start, setup, experiment_running = ex

    time, Q, press0 = prep(exl.file)
    ch = RcSetup.used_channels(setup)
    press0 = press0[:,ch]
    # time in secs:
    t = time2seconds(time, experiment_start)
    # set zero
    press0 = set_to_val(time, press0, exl.zero_press_window, 0)
    # correct
    press_out, means, vals, params, errs = correct_offsets(
        time, press0, exl.equal_press_windows)
    # assign columns of press_out to ports
    press = OrderedDict{Port.PressPorts,Vector{Float64}}()
    press_std = OrderedDict{Port.PressPorts,Vector{Float64}}()
    press_raw = OrderedDict{Port.PressPorts,Vector{Float64}}()
    press_std_raw = OrderedDict{Port.PressPorts,Vector{Float64}}()
    for (p,cs) in setup.port2channels
        length(cs)==0 && continue
        press_raw[p] = vec(mean(press_out[:,cs],2))
        press_std_raw[p] = vec(std(press_out[:,cs],2))
        # median filter to remove spikes
        tmp, tmp_std = press_raw[p], press_std_raw[p]
        n = 5
        if n>0
            tmp = median_filter(tmp, (n,))
            tmp_std = median_filter(tmp_std, (n,))
        end

        nn = 5
        if nn>2
            tmp = lowpass_filter2(tmp, nn)
            tmp_std = lowpass_filter2(tmp_std, nn)
        end

        press[p] = tmp
        press_std[p] = tmp_std
    end
    # figure out when water was running
    #irun = find((Q.>0.5/1000) .& (t.>=0))
    trun, irun = times_of_flow(time, ex)

    ExpLabViewResults(exl, time, t, trun, irun, Q, press, press_std,
                      press_raw, press_std_raw)
end

"""
    prep(exl::ExpLabView)

Returns
- time of day
- discharge
- pressures from all sensors
"""
function prep(file)
    # parse file-name to get time
    start = file2time(file)
    out = readdlm(file)
    time = start + Millisecond.(1000*out[:,1])
    Q = out[:,2]/1000 # m^3/s
    # the rest depends a bit on the setup
    press = out[:,3:end]
    press .= mBar_to_Pa.(press)
    return time, Q, press
end

"Transform pressure from mBar to Pascal"
mBar_to_Pa(p) = p*100

"Transform pressure from mBar to m H2O"
function mBar_to_m_h2o(p)
    # mbar -> Pa -> m
    p*100 /rho/g
end
Pa_to_m_h2o(p) = p/rho/g

m_h2o_to_Pa(p) = p*rho*g

"Transforms the labview file into a DateTime of the logging start"
file2datetime(file) = DateTime(splitext(splitdir(file)[2])[1], "yyyymmdd-HHMMSS")
file2time(file) = Time(file2datetime(file))

"""
    set_to_val(t, press, window, val=nothing)

Corrects the offsets in the pressure transducers.  Presumably to set
the zero.

- t -- time
- press -- pressure time series
- window -- time window which to use as reference of the 0,
            i.e. subtract the mean pressure during this time window
- val -- value to which to set the mean to.  Defaults to the mean of the mean.

"""
function set_to_val(t, press, window, val=nothing)
    window = time_window2index_window(t, window)
    means = mean(press[window,:],1)
    if val==nothing
        val = mean(means)
    end
    press .- means + val
end

"""
    correct_offsets(t, press, windows)

Correct the pressure with an offset which is dependent on the absolute
pressure.  This is needed for the pressure sensors.

Input
- t -- times as Vector{Time}
- press -- pressure time series
- windows -- time windows when all sensors should read the same pressure

For each window a mean pressure is calculated for each sensor `pm` and also the mean
of all means `mm`.  A line is then fitted between `pm` and `mm` and used to correct
each sensor.  (This means that the weight of each window is equal, i.e. independent of
its width.)

Return
- corrected pressure time series
- mean pressure value of each sensor at all windows
- mean pressure value of all sensors at all windows
- parameters (a,b) of line of best fit a*p+b
- errors (95%) for each sensor and each window

TODO: Maybe add ability to have the offset time dependent too.
"""
function correct_offsets(time, press, windows; verbose=false)
    nw = length(windows)
    ns = size(press,2)
    means = zeros(nw,ns)
    for (i,window) in enumerate(windows)
        window = time_window2index_window(time, window)
        @assert length(window)>0 "Window has zero length"
        means[i,:] =  mean(press[window,:],1)
    end
    @assert !any(isnan.(means)) "Found NaNs in mean"
    meanmeans = mean(means,2)[:]
    # assume a linear dependence
    offset(press, p) = p[1]*press + p[2]
    offsetinv(press, p) = (press -p[2])/p[1]
    function offsetJ(press,p)
        # Jacobian of offset
        J = Array{Float64}(length(press),length(p))
        J[:,1] = press    #doffset/dp[1]
        J[:,2] = ones(press)  #doffset/dp[2]
        J
    end

    # fit a and b such that error is minimized
    params = zeros(2,ns)
    errs = zeros(2,ns)
    press_out = similar(press)
    for j=1:ns
        xdata = means[:,j]
        ydata = meanmeans
        fit = curve_fit(offset, offsetJ, xdata, ydata, [1.0,0.0])
        if verbose
            @show j
            @show fit.converged
            @show fit.param
            @show estimate_errors(fit, 0.95)
            @show xdata
            @show ydata
            P.clf()
            P.scatter(xdata, ydata)
            P.plot(sort(xdata), offset(sort(xdata), fit.param))
            P.plot(sort(xdata), offset(sort(xdata), [1.0,0.0]))
            sleep(5)
        end
        @assert fit.converged "Could not find a line of best fit to correct pressure offsets"
        params[:,j] = fit.param
        # We can estimate errors on the fit parameters,
        # to get 95% confidence error bars:
        errs[:,j] = estimate_errors(fit, 0.95)
        # transform press
        ofs = p -> offset(p, fit.param)
        press_out[:,j] = ofs.(press[:,j])
    end
    return press_out, means, meanmeans, params, errs
end

#################
# Plotting

function plot_file(file, inds=1:10)
    time, Q, press0 = prep(file)
    start = file2time(file)
    t = Dates.value.(Dates.Millisecond.(time-start))/1000/60
    P.figure()
    ax = P.subplot(2,1,1)
    P.plot(t, Q*1000)
    ax = P.subplot(2,1,2,sharex=ax)
    P.plot(t, mBar_to_m_h2o.(press0[:,inds]))
end

function plot_res(ex, resl::ExpLabViewResults; ports=RcSetup.used_ports(ex.setup),
                  tscale=60, t0=[:exp,:logg,:trun, :time][3],
                  fig_axs=P.subplots(3,1,sharex="all"),
                  units=[:mH2O, :Pa, :Bar][1])
    fig,axs = fig_axs

    # value transform and y-label
    trans, yl1, yl2 = if units==:mH2O
        (Pa_to_m_h2o, "p (m H2O)", "Δp ()")
    elseif units==:Pa
        (identity, "p (Pa)", "Δp (Pa/m)")
    else
        error()
    end

    # time and indices
    tt,ii = if t0==:logg
        (resl.t-resl.t[1],
         1:length(resl.t))
    elseif t0==:exp
        (resl.t,
         1:length(resl.t))
    elseif t0==:trun
        (resl.trun,
         resl.irun)
    elseif t0==:time
        (Dates.value.(resl.time)/1000/60/60/24, # Convert time from milliseconds from day 0 to days from day 0
         1:length(resl.t))
    else
        error()
    end
    @show ports[1:end-1],ports[2:end]
    grad, grad_std, grad_ports = calculate_press_grad_all(ex, resl, ports[1:end-1], ports[2:end]);

    P.axes(axs[1])
    P.grid("on")
    P.plot(tt/tscale, resl.Q[ii]*1000)
    P.ylabel("Q (l/s)")
    P.title(ex.name)

    P.axes(axs[2])
    P.grid("on")
    for (k,v,vs) in zip(ports, [resl.press[p] for p in ports], [resl.press_std[p] for p in ports])
        t0==:time || P.fill_between(tt/tscale, trans.(v[ii]-vs[ii]), trans.(v[ii]+vs[ii]), alpha=0.3)
        t0==:time || P.plot(tt/tscale, trans.(v[ii]), label=string(k))
        t0==:time && error() # does not work P.plot_date(tt, trans.(v[ii]), label=string(k))
    end

    P.legend()
    P.ylabel(yl1)

    P.axes(axs[3])
    P.grid("on")

    for (i,p) in enumerate(grad_ports)
        P.fill_between(tt/tscale, trans.(grad[ii,i]-grad_std[ii,i]), trans.(grad[ii,i]+grad_std[ii,i]), alpha=0.3)
        P.plot(tt/tscale, trans.(grad[ii,i]), label=string(p))
    end

    P.ylabel(yl2)
    P.xlabel("Time")

    # P.xlim((93,97))
    # P.ylim((1.55,2.5))
end

end


text:
module GitForge

using Base.Iterators: Pairs
using Base.StackTraces: StackTrace

using Dates
using Dates: Period, UTC, now
using HTTP: HTTP
using JSON2: JSON2
using UUIDs: UUID

const AStr = AbstractString
const HEADERS = ["Content-Type" => "application/json"]

diag = false

function set_diag(value::Bool)
    global diag = value

    @info "GitForge: set diag to $diag"
end

function __init__()
    proj = read(joinpath(dirname(@__DIR__), "Project.toml"), String)
    pkgver = match(r"version = \"(.+)\"", proj)[1]
    push!(HEADERS, "User-Agent" => "Julia v$VERSION (GitForge v$pkgver)")
end

"""
The supertype of all other exceptions raised by API functions.
"""
abstract type ForgeError <: Exception end

include("forge.jl")
include("ratelimits.jl")
include("request.jl")
include("pagination.jl")
include("helpers.jl")
include("api.jl")
include(joinpath("forges", "GitHub", "GitHub.jl"))
include(joinpath("forges", "GitLab", "GitLab.jl"))
include(joinpath("forges", "Bitbucket", "Bitbucket.jl"))

end


text:
using Test

# Unit tests / verification
for submodule in ["Grids", "DomainDecomposition", "StateVecs", "PDEs"]
    println("Testing $submodule")
    include(joinpath(submodule, "runtests.jl"))
end


text:
## This uses the simplified kinsol interface.
## Results should match that in kinsol_kminTest.jl.

using Sundials

function sysfn(y, fy)
    fy[1] = y[1]^2 + y[2]^2 - 1.0
    fy[2] = y[2] - y[1]^2
end

res = Sundials.kinsol(sysfn, ones(2))


text:
@symbol_func function g(cur_reactor::AbstractReactor, cur_delta::AbstractSymbol)
  cur_factor = 9.0

  cur_factor -= 2 * cur_delta

  cur_factor -= _x_aa_of_pi(cur_delta)

  cur_factor /= 8

  cur_factor
end

@symbol_func function g(cur_reactor::AbstractReactor)
  cur_delta = cur_reactor.delta_95

  cur_factor = g(cur_reactor, cur_delta, is_direct_call)

  cur_factor
end

function _x_aa_of_pi(cur_delta::AbstractSymbol)
  cur_x_aa_of_pi = 1.0

  cur_x_aa_of_pi -= cur_delta ^ 2

  cur_x_aa_of_pi *= 0.3

  cur_x_aa_of_pi
end


text:
module BlackBoxOptim

export  Optimizer, PopulationOptimizer,
        bboptimize, compare_optimizers,

        DiffEvoOpt, de_rand_1_bin, de_rand_1_bin_radiuslimited,

        AdaptConstantsDiffEvoOpt, adaptive_de_rand_1_bin, adaptive_de_rand_1_bin_radiuslimited,

        SeparableNESOpt, separable_nes,
        XNESOpt, xnes,

        # Parameters
        Parameters,

        # Fitness

        # Evaluator
        ProblemEvaluator,

        # Problems
        Problems, FixedDimProblem, is_fixed_dimensional, is_any_dimensional,
        is_single_objective_problem, is_multi_objective_problem,
        search_space, eval1, evalall, anydim_problem, as_fixed_dim_problem,
        fitness_is_within_ftol, save_fitness_history_to_csv_file,

        # Archive
        TopListArchive, best_fitness, add_candidate!, best_candidate,
        last_top_fitness, delta_fitness,
        width_of_confidence_interval, fitness_improvement_potential,

        # Search spaces
        SearchSpace, FixedDimensionSearchSpace, ContinuousSearchSpace,
        RangePerDimSearchSpace, symmetric_search_space,
        numdims, mins, maxs, deltas, ranges, range_for_dim, diameters,
        rand_individual, rand_individuals, isinspace, rand_individuals_lhs,

        hat_compare, is_better, is_worse, same_fitness,
        popsize,
        FloatVectorFitness, float_vector_scheme_min, float_vector_scheme_max,
        FloatVectorPopulation,

        name

abstract Optimizer

include("parameters.jl")
include("fitness.jl")
include("archive.jl")

module Utils
  include("utilities/latin_hypercube_sampling.jl")
  include("utilities/assign_ranks.jl")
end

include("search_space.jl")
include("population.jl")
include("frequency_adaptation.jl")

# Problems for testing
include(joinpath("problems", "all_problems.jl"))

include("evaluator.jl")

function setup(o::Optimizer, evaluator::Evaluator)
  # Do nothing, override if you need to setup prior to the optimization loop
end

function finalize(o::Optimizer, evaluator::Evaluator)
  # Do nothing, override if you need to finalize something after the optimization loop
end

# The standard name function converts the type of the optimizer to a string
# and strips off trailing "Opt".
function name(o::Optimizer)
  s = string(typeof(o))
  if s[end-2:end] == "Opt"
    return s[1:end-3]
  else
    return s
  end
end

abstract PopulationOptimizer <: Optimizer

population(o::PopulationOptimizer) = o.population # Fallback method if sub-types have not implemented it.

# Our design is inspired by the object-oriented, ask-and-tell "optimizer API
# format" as proposed in:
#
#  Collette, Y., N. Hansen, G. Pujol, D. Salazar Aponte and
#  R. Le Riche (2010). On Object-Oriented Programming of Optimizers -
#  Examples in Scilab. In P. Breitkopf and R. F. Coelho, eds.:
#  Multidisciplinary Design Optimization in Computational Mechanics, Wiley,
#  pp. 527-565.
#  https://www.lri.fr/~hansen/collette2010Chap14.pdf
#
# but since Julia is not OO this is more reflected in certain patterns of how
# to specify and call optimizers. The basic ask-and-tell pattern is:
#
#   while !optimizer.stop
#     x = ask(optimizer)
#     y = f(x)
#     optimizer = tell(optimizer, x, y)
#   end
#
# after which the best solutions can be found by:
#
#   yopt, xopt = best(optimizer)
#
# We have extended this paradigm with the use of an archive that saves
# information on what we have learnt about the search space as well as the
# best solutions found. For most multi-objective optimization problems there
# is no single optimum. Instead there are many pareto optimal solutions.
# An archive collects information about the pareto optimal set or some
# approximation of it. Different archival strategies can be implemented.

has_ask_tell_interface(o::Optimizer) = true # Default is to have an ask and tell interface...

# Different optimization algorithms
include("random_search.jl")
include("differential_evolution.jl")
include("adaptive_differential_evolution.jl")
include("natural_evolution_strategies.jl")
include("resampling_memetic_search.jl")
include("simultaneous_perturbation_stochastic_approximation.jl")
include("generating_set_search.jl")
include("direct_search_with_probabilistic_descent.jl")

# End-user/interface functions
include("bboptimize.jl")

end # module BlackBoxOptim


text:
using PatternDispatch

immutable Tempo
  bpm::Real
  base::Real
end
Tempo(bpm) = Tempo(bpm, 1 // 4)

duration_tempo(duration, tempo) = error()

@patterns begin
  function (@inverse duration_tempo(duration, tempo))(e)
  	e::Event
  	@guard haskey(e, :duration)
  	@guard haskey(e, :tempo)
  	duration = e[:duration]
  	tempo = e[:tempo]
  end

  to_second(x) = error("Cannot convert to seconds: $(x)")
  to_second(duration_tempo(d, t)) = dt_to_second(d, t)
end

typealias DurationNum Union{Rational,Int}

function dt_to_second(d, tempo::Real)
  dt_to_second(d, Tempo(tempo))
end
function dt_to_second(d, tempo::Tempo)
  base = tempo.base
  bps(tempo.bpm) * beat_duration(d, base)
end

bps(d::DurationNum) = 60 // d
bps(d) = 60 / d

beat_duration(d::DurationNum, b::DurationNum) = d // b
beat_duration(d, b) = d / b


text:
module MyDistanceSimilarityPKG
using LinearAlgebra
include("CosineSimilarity.jl")
include("ManhattanDistance.jl")
include("EuclideanDistance.jl")
include("MinkowskiDistance.jl")
include("JaccardSimilarity.jl")

export greet

"""
This is my first attempt in Julia Package Development.
Any contribution and code review will be much appreciated! :)

Esta é a minha primeira tentativa em desenvolvimento de Pacotes em Julia.
Qualquer contribuição de code review será muito bem-vinda! :)
"""

greet() = print("Hello and thank you for your visit!")

end # module


text:
#!/usr/bin/env julia

using Luxor

using Test

using Random
Random.seed!(42)

function spirals()
    gsave()
    scale(.3)
    r = 200
    setcolor("gray")
    for i in 0:pi/8:2pi
        gsave()
        translate(r * cos(i), r * sin(i))
        rotate(i)
        julialogo()
        grestore()
    end
    grestore()
    juliacircles(20)
end

function expandingspirals()
    gsave()
    scale(.3)
    r = 200
    for i in pi:pi/12:6pi
        gsave()
        translate(i/3 * r * cos(i), i/3 * r * sin(i))
        scale(0.8)
        rotate(i)
        julialogo()
        grestore()
    end
    grestore()
end

function dropshadow()
    steps=20
    # white-gray ramp
    gramp = range(colorant"white", stop=colorant"gray60", length=steps)
    gsave()
    r = 200
    sethue("purple")
    rect(O, 5, 10, :stroke)
    setopacity(0.1)
    for i in 1:steps
        translate(-0.6, -0.5)
        julialogo(bodycolor=gramp[i], color=false)
    end
    julialogo()
    grestore()
end

function colorgrid()
    #cols = colormap("RdBu", 5; mid=0.5, logscale=false)
    #cols = sequential_palette(rand(10:360), 5, b=0.1)
    cols = distinguishable_colors(25)
    gsave()

    pagetiles = Tiler(500, 400, 5, 5)
    for (pos, n) in pagetiles
      gsave()
      setcolor(color(cols[n]))
      translate(pos)
      scale(0.3)
      julialogo(color=false)
      grestore()
    end

    grestore()
end

function boxes_and_rectangles(pt::Point)
    for i in 1:20
        randomhue()
        poly(box(pt + i, 10i, 10i, vertices=true), :stroke, close=true)
    end
    for i in 40:10:150
        randomhue()
        translate(i, 0)
        poly(squircle(O, 30, 30, vertices=true), :stroke)
    end
end

function draw_julia_logos(fname)
    Drawing(1600, 1600, fname)
    origin()
    background("white")

    translate(-500, -200)
    spirals()

    translate(750, 0)
    expandingspirals()

    translate(-1000, 500)
    dropshadow()

    translate(800, 50)
    colorgrid()

    translate(-700, 300)
    boxes_and_rectangles(O)

    @test finish() == true
    println("...finished test: output in $(fname)")
end

draw_julia_logos("julia-logo-draw.png")


text:
using ReverseDiff: JacobianTape, JacobianConfig, jacobian, jacobian!, compile

#########
# setup #
#########

# some objective functions to work with
f(a, b) = (a + b) * (a * b)'
g!(out, a, b) = A_mul_Bc!(out, a + b, a * b)

# pre-record JacobianTapes for `f` and `g` using inputs of shape 10x10 with Float64 elements
const f_tape = JacobianTape(f, (rand(10, 10), rand(10, 10)))
const g_tape = JacobianTape(g!, rand(10, 10), (rand(10, 10), rand(10, 10)))

# compile `f_tape` and `g_tape` into more optimized representations
const compiled_f_tape = compile(f_tape)
const compiled_g_tape = compile(g_tape)

# some inputs and work buffers to play around with
a, b = rand(10, 10), rand(10, 10)
inputs = (a, b)
output = rand(10, 10)
results = (similar(a, 100, 100), similar(b, 100, 100))
fcfg = JacobianConfig(inputs)
gcfg = JacobianConfig(output, inputs)

####################
# taking Jacobians #
####################

# with pre-recorded/compiled tapes (generated in the setup above) #
#-----------------------------------------------------------------#

# these should be the fastest methods, and non-allocating
jacobian!(results, compiled_f_tape, inputs)
jacobian!(results, compiled_g_tape, inputs)

# these should be the second fastest methods, and also non-allocating
jacobian!(results, f_tape, inputs)
jacobian!(results, g_tape, inputs)

# with a pre-allocated JacobianConfig #
#-------------------------------------#
# this is more flexible than a pre-recorded tape, but can be wasteful since the tape
# will be re-recorded for every call.

jacobian!(results, f, inputs, fcfg)

jacobian(f, inputs, fcfg)

jacobian!(results, g!, output, inputs, gcfg)

jacobian(g!, output, inputs, gcfg)

# without a pre-allocated JacobianConfig #
#----------------------------------------#
# convenient, but pretty wasteful since it has to allocate the JacobianConfig itself

jacobian!(results, f, inputs)

jacobian(f, inputs)

jacobian!(results, g!, output, inputs)

jacobian(g!, output, inputs)


text:
function KSolver(s::Symbol)
  if s == :minres
    return :MinresSolver
  elseif s == :minres_qlp
    return :MinresQlpSolver
  elseif s == :cg
    return :CgSolver
  elseif s == :qmr
    return :QmrSolver
  elseif s == :usymqr
    return :UsymqrSolver
  elseif s == :bicgstab
    return :BicgstabSolver
  end
end

ksolve!(
  KS::MinresSolver{T, S},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T, S} = minres!(
  KS,
  K,
  rhs,
  M = M,
  verbose = verbose,
  atol = zero(T),
  rtol = zero(T),
  ratol = atol,
  rrtol = rtol,
)

ksolve!(
  KS::MinresQlpSolver{T, S},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T, S} = minres_qlp!(KS, K, rhs, M = M, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::CgSolver{T, S},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T, S} = cg!(KS, K, rhs, M = M, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::QmrSolver{T, S},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T, S} = qmr!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::UsymqrSolver{T, S},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T, S} = usymqr!(KS, K, rhs, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::BicgstabSolver{T, S},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T, S} = bicgstab!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

function kscale!(rhs::AbstractVector{T}) where {T <: Real}
  rhsNorm = norm(rhs)
  if rhsNorm != zero(T)
    rhs ./= rhsNorm
  end
  return rhsNorm
end

function kunscale!(sol::AbstractVector{T}, rhsNorm::T) where {T <: Real}
  if rhsNorm != zero(T)
    sol .*= rhsNorm
  end
end

function update_kresiduals_history!(
  res::AbstractResiduals{T},
  K,
  sol::AbstractVector{T},
  rhs::AbstractVector{T},
) where {T <: Real}
  if typeof(res) <: ResidualsHistory
    mul!(res.KΔxy, K, sol) # krylov residuals
    res.Kres = res.KΔxy .- rhs
  end
end


text:
module DiffOpt

using LinearAlgebra
import LinearAlgebra: ⋅, dot, Diagonal
using SparseArrays
using JuMP

import LazyArrays
import BlockDiagonals

import MathOptInterface
const MOI = MathOptInterface
const MOIU = MathOptInterface.Utilities

using MathOptSetDistances
const MOSD = MathOptSetDistances

const VI = MOI.VariableIndex
const CI = MOI.ConstraintIndex

include("utils.jl")
include("product_of_sets.jl")
include("diff_opt.jl")
include("moi_wrapper.jl")
include("jump_moi_overloads.jl")

include("bridges.jl")

include("QuadraticProgram/QuadraticProgram.jl")
include("ConicProgram/ConicProgram.jl")

"""
    add_all_model_constructors(model)

Add all constructors of [`AbstractModel`](@ref) defined in this package to
`model` with [`add_model_constructor`](@ref).
"""
function add_all_model_constructors(model)
    add_model_constructor(model, QuadraticProgram.Model)
    add_model_constructor(model, ConicProgram.Model)
    return
end

export diff_optimizer

end # module


text:
const METHODS = [
    "GET",
    "POST",
    "PUT",
    "PATCH",
    "DELETE",
    "OPTIONS",
    "HEAD"
]

for METHOD in METHODS
    add_method! = Symbol("add_$(lowercase(METHOD))!")

    @eval function $add_method!(func::Function, app::App, path::AbstractString)
        paths = URIs.splitpath(path)

        if any(startswith.(paths, ':'))
            newpaths = map(paths) do str
                startswith(str, ':') ? "*" : str
            end
            newpath = "/" * join(newpaths, "/")

            handler = (request::HTTP.Request, body::BodyTypes) -> begin
                params = createparams(request, path)
                query = createquery(request)
                req = Req(request.method, path, params, query, body)
                return func(req)
            end

            HTTP.@register(app.router, $METHOD, newpath, handler)
        else
            handler = (request::HTTP.Request, body::BodyTypes) -> begin
                params = Params()
                query = createquery(request)
                req = Req(request.method, request.target, params, query, body)
                return func(req)
            end

            HTTP.@register(app.router, $METHOD, path, handler)
        end

        push!(app.routelist[$METHOD], path)
        return nothing
    end
end


text:
# This file was generated, do not modify it. # hide
select!(df, Not([:id, :date]))
schema(df)


text:
using Test
import MathOptInterface
const MOI = MathOptInterface
const MOIT = MOI.Test
const MOIU = MOI.Utilities

@testset "Test default attributes" begin
    # Without an optimizer attached (i.e., `MOI.state(model) == NO_OPTIMIZER`) we
    # need throw nice errors for attributes that are based on the optimizer. For
    # `AbstractModelAttribute`s that `is_set_by_optimize` returns `true` for, we
    # overload `TerminationStatus`, `PrimalStatus`, or `DualStatus` to return
    # sane default values. Otherwise we throw a nice error.
    model = MOIU.CachingOptimizer(MOIU.Model{Float64}(), MOIU.MANUAL)
    @test MOIU.state(model) == MOIU.NO_OPTIMIZER

    @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
    @test MOI.get(model, MOI.PrimalStatus()) == MOI.NO_SOLUTION
    @test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION
    x = MOI.add_variables(model, 2)
    attr = MOI.VariablePrimal()
    exception = ErrorException(
        "Cannot query $(attr) from caching optimizer because no optimizer" *
        " is attached.")
    @test_throws exception MOI.get(model, MOI.VariablePrimal(), x[1])
    @test_throws exception MOI.get(model, MOI.VariablePrimal(), x)

    attr = MOI.SolverName()
    exception = ErrorException(
        "Cannot query $(attr) from caching optimizer because no optimizer" *
        " is attached.")
    @test_throws exception MOI.get(model, attr)
    attr = MOI.Silent()
    exception = ErrorException(
        "Cannot query $(attr) from caching optimizer because no optimizer" *
        " is attached.")
    @test_throws exception MOI.get(model, attr)
    attr = MOI.TimeLimitSec()
    exception = ErrorException(
        "Cannot query $(attr) from caching optimizer because no optimizer" *
        " is attached.")
    @test_throws exception MOI.get(model, attr)
    attr = MOI.NumberOfThreads()
    exception = ErrorException(
        "Cannot query $(attr) from caching optimizer because no optimizer" *
        " is attached.")
    @test_throws exception MOI.get(model, attr)
    attr = MOI.ResultCount()
    exception = ErrorException(
        "Cannot query $(attr) from caching optimizer because no optimizer" *
        " is attached.")
    @test_throws exception MOI.get(model, attr)
end

@testset "Copyable solver attributes" begin
    cache = MOIU.UniversalFallback(MOIU.Model{Float64}())
    cached = MOIU.CachingOptimizer(cache, MOIU.MANUAL)
    MOI.set(cached, MOI.Silent(), true)
    MOI.set(cached, MOI.TimeLimitSec(), 0.0)
    MOI.set(cached, MOI.NumberOfThreads(), 1)
    mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
    MOIU.reset_optimizer(cached, mock)
    @test MOI.get(mock, MOI.Silent())
    @test MOI.get(cached, MOI.Silent())
    @test MOI.get(mock, MOI.TimeLimitSec()) == 0.0
    @test MOI.get(cached, MOI.TimeLimitSec()) == 0.0
    @test MOI.get(mock, MOI.NumberOfThreads()) == 1
    @test MOI.get(cached, MOI.NumberOfThreads()) == 1
    MOI.set(cached, MOI.Silent(), false)
    MOI.set(cached, MOI.TimeLimitSec(), 1.0)
    MOI.set(cached, MOI.NumberOfThreads(), 2)
    @test !MOI.get(mock, MOI.Silent())
    @test !MOI.get(cached, MOI.Silent())
    @test MOI.get(mock, MOI.TimeLimitSec()) ≈ 1.0
    @test MOI.get(cached, MOI.TimeLimitSec()) ≈ 1.0
    @test MOI.get(mock, MOI.NumberOfThreads()) == 2
    @test MOI.get(cached, MOI.NumberOfThreads()) == 2
    mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
    MOIU.reset_optimizer(cached, mock)
    @test !MOI.get(mock, MOI.Silent())
    @test !MOI.get(cached, MOI.Silent())
    @test MOI.get(mock, MOI.TimeLimitSec()) ≈ 1.0
    @test MOI.get(cached, MOI.TimeLimitSec()) ≈ 1.0
    @test MOI.get(mock, MOI.NumberOfThreads()) == 2
    @test MOI.get(cached, MOI.NumberOfThreads()) == 2
    MOI.set(cached, MOI.Silent(), true)
    MOI.set(cached, MOI.TimeLimitSec(), 0.0)
    MOI.set(cached, MOI.NumberOfThreads(), 1)
    @test MOI.get(mock, MOI.Silent())
    @test MOI.get(cached, MOI.Silent())
    @test MOI.get(mock, MOI.TimeLimitSec()) == 0.0
    @test MOI.get(cached, MOI.TimeLimitSec()) == 0.0
    @test MOI.get(mock, MOI.NumberOfThreads()) == 1
    @test MOI.get(cached, MOI.NumberOfThreads()) == 1
    mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
    MOIU.reset_optimizer(cached, mock)
    @test MOI.get(mock, MOI.Silent())
    @test MOI.get(cached, MOI.Silent())
    @test MOI.get(mock, MOI.TimeLimitSec()) == 0.0
    @test MOI.get(cached, MOI.TimeLimitSec()) == 0.0
    @test MOI.get(mock, MOI.NumberOfThreads()) == 1
    @test MOI.get(cached, MOI.NumberOfThreads()) == 1
end

struct DummyModelAttribute <: MOI.AbstractModelAttribute end
struct DummyEvaluator <: MOI.AbstractNLPEvaluator end
struct DummyVariableAttribute <: MOI.AbstractVariableAttribute end
struct DummyConstraintAttribute <: MOI.AbstractConstraintAttribute end

@testset "Mapping of variables" begin
    mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
    model = MOIU.CachingOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()), mock)
    x = MOI.add_variable(model)
    y = first(MOI.get(mock, MOI.ListOfVariableIndices()))
    @test x != y # Otherwise, these tests will trivially pass
    @test !MOI.is_valid(model, y)
    @test !MOI.is_valid(mock, x)
    fx = MOI.SingleVariable(x)
    fy = MOI.SingleVariable(y)

    cfx = MOI.add_constraint(model, fx, MOI.GreaterThan(1.0))
    cfy = first(MOI.get(mock, MOI.ListOfConstraintIndices{
        MOI.SingleVariable, MOI.GreaterThan{Float64}}()))
    @test !MOI.is_valid(model, cfy)
    @test !MOI.is_valid(mock, cfx)
    @test MOI.get(mock, MOI.ConstraintFunction(), cfy) == fy

    c2fx = MOI.add_constraint(model, 2.0fx, MOI.GreaterThan(1.0))
    c2fy = first(MOI.get(mock, MOI.ListOfConstraintIndices{
        MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()))
    @test !MOI.is_valid(model, c2fy)
    @test !MOI.is_valid(mock, c2fx)
    @test MOI.get(model, MOI.ConstraintFunction(), c2fx) ≈ 2.0fx
    @test MOI.get(model, MOI.ConstraintSet(), c2fx) == MOI.GreaterThan(1.0)
    @test MOI.get(mock, MOI.ConstraintFunction(), c2fy) ≈ 2.0fy
    @test MOI.get(mock, MOI.ConstraintSet(), c2fy) == MOI.GreaterThan(1.0)

    MOI.set(model, MOI.ConstraintSet(), c2fx, MOI.GreaterThan(2.0))
    @test MOI.get(model, MOI.ConstraintFunction(), c2fx) ≈ 2.0fx
    @test MOI.get(model, MOI.ConstraintSet(), c2fx) == MOI.GreaterThan(2.0)
    @test MOI.get(mock, MOI.ConstraintFunction(), c2fy) ≈ 2.0fy
    @test MOI.get(mock, MOI.ConstraintSet(), c2fy) == MOI.GreaterThan(2.0)

    MOI.set(model, MOI.ConstraintFunction(), c2fx, 3.0fx)
    @test MOI.get(model, MOI.ConstraintFunction(), c2fx) ≈ 3.0fx
    @test MOI.get(model, MOI.ConstraintSet(), c2fx) == MOI.GreaterThan(2.0)
    @test MOI.get(mock, MOI.ConstraintFunction(), c2fy) ≈ 3.0fy
    @test MOI.get(mock, MOI.ConstraintSet(), c2fy) == MOI.GreaterThan(2.0)

    MOI.set(model, MOI.ConstraintSet(), c2fx, MOI.GreaterThan(4.0))
    @test MOI.get(model, MOI.ConstraintFunction(), c2fx) ≈ 3.0fx
    @test MOI.get(model, MOI.ConstraintSet(), c2fx) == MOI.GreaterThan(4.0)
    @test MOI.get(mock, MOI.ConstraintFunction(), c2fy) ≈ 3.0fy
    @test MOI.get(mock, MOI.ConstraintSet(), c2fy) == MOI.GreaterThan(4.0)

    MOI.set(model, MOI.ObjectiveFunction{typeof(fx)}(), fx)
    @test MOI.get(mock, MOI.ObjectiveFunction{typeof(fy)}()) == fy

    MOI.set(model, MOI.ObjectiveFunction{typeof(2.0fx)}(), 2.0fx)
    @test MOI.get(mock, MOI.ObjectiveFunction{typeof(2.0fy)}()) ≈ 2.0fy

    MOI.set(model, DummyModelAttribute(), 2.0fx + 1.0)
    @test MOI.get(model, DummyModelAttribute()) ≈ 2.0fx + 1.0
    @test MOI.get(mock, DummyModelAttribute()) ≈ 2.0fy + 1.0

    for (attr, cache_index, optimizer_index) in [
        (DummyVariableAttribute(), x, y),
        (DummyConstraintAttribute(), cfx, cfy)]
        MOI.set(model, attr, cache_index, 1.0fx)
        @test MOI.get(model, attr, cache_index) ≈ 1.0fx
        @test MOI.get(mock, attr, optimizer_index) ≈ 1.0fy

        MOI.set(model, attr, [cache_index], [3.0fx])
        @test MOI.get(model, attr, [cache_index])[1] ≈ 3.0fx
        @test MOI.get(mock, attr, [optimizer_index])[1] ≈ 3.0fy
    end

    @testset "RawSolver" begin
        MOI.get(model, MOI.RawSolver()) === mock
    end

    @testset "HeuristicCallback" begin
        attr = MOI.HeuristicCallback()
        f(callback_data) = nothing
        MOI.set(model, attr, f)
        @test MOI.get(model, attr) === f
    end

    @testset "CallbackVariablePrimal" begin
        attr = MOI.CallbackVariablePrimal(nothing)
        err = ErrorException("No mock callback primal is set for variable `$y`.")
        @test_throws err MOI.get(model, attr, x)
        MOI.set(mock, attr, y, 1.0)
        @test_throws MOI.InvalidIndex(x) MOI.get(mock, attr, x)
        @test MOI.get(mock, attr, y) == 1.0
        @test MOI.get(model, attr, x) == 1.0
    end

    @testset "LazyConstraint" begin
        sub = MOI.LazyConstraint(nothing)
        @test MOI.supports(model, sub)
        MOI.submit(model, sub, 2.0fx, MOI.GreaterThan(1.0))
        @test mock.submitted[sub][1][1] ≈ 2.0fy
        @test mock.submitted[sub][1][2] == MOI.GreaterThan(1.0)
    end
    @testset "HeuristicSolution" begin
        sub = MOI.HeuristicSolution(nothing)
        @test MOI.supports(model, sub)
        MOI.submit(model, sub, [x], [1.0])
        @test mock.submitted[sub][1][1] == [y]
        @test mock.submitted[sub][1][2] == [1.0]
    end

    nlp_data = MOI.NLPBlockData(
        [MOI.NLPBoundsPair(1.0, 2.0)],
        DummyEvaluator(), false)
    MOI.set(model, MOI.NLPBlock(), nlp_data)
    for nlp_data in [MOI.get(model, MOI.NLPBlock()), MOI.get(mock, MOI.NLPBlock())]
        @test nlp_data.constraint_bounds == [MOI.NLPBoundsPair(1.0, 2.0)]
        @test nlp_data.evaluator isa DummyEvaluator
        @test nlp_data.has_objective == false
    end
end

@testset "CachingOptimizer MANUAL mode" begin
    m = MOIU.CachingOptimizer(MOIU.Model{Float64}(), MOIU.MANUAL)
    @test MOIU.state(m) == MOIU.NO_OPTIMIZER

    s = MOIU.MockOptimizer(MOIU.Model{Float64}(), supports_names=false)
    @test MOI.is_empty(s)
    MOIU.reset_optimizer(m, s)
    @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER

    v = MOI.add_variable(m)
    x = MOI.add_variables(m, 2)
    saf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0, 3.0], [v; x]), 0.0)
    @test MOI.supports(m, MOI.ObjectiveFunction{typeof(saf)}())
    MOI.set(m, MOI.ObjectiveFunction{typeof(saf)}(), saf)
    @test MOI.get(m, MOIU.AttributeFromModelCache(MOI.ObjectiveFunction{typeof(saf)}())) ≈ saf
    @test MOI.get(m, MOI.ObjectiveFunction{typeof(saf)}()) ≈ saf

    @test_throws AssertionError MOI.optimize!(m)

    MOIU.attach_optimizer(m)
    @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER
    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.ObjectiveFunction{typeof(saf)}())) ≈ saf

    @test MOI.supports(m, MOI.ObjectiveSense())
    MOI.set(m, MOI.ObjectiveSense(), MOI.MAX_SENSE)
    @test MOI.get(m, MOI.ObjectiveSense()) == MOI.MAX_SENSE
    @test MOI.get(m, MOIU.AttributeFromModelCache(MOI.ObjectiveSense())) == MOI.MAX_SENSE
    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.ObjectiveSense())) == MOI.MAX_SENSE

    @test MOI.supports(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute()))
    MOI.set(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute()), 10)
    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute())) == 10

    MOI.set(m, MOIU.AttributeFromOptimizer(MOI.ResultCount()), 1)
    @test MOI.supports(m, MOIU.AttributeFromOptimizer(MOI.VariablePrimal()), typeof(v))
    MOI.set(m, MOIU.AttributeFromOptimizer(MOI.VariablePrimal()), v, 3.0)

    MOI.optimize!(m)

    @test MOI.get(m, MOI.VariablePrimal(), v) == 3.0
    @test MOI.get(m, MOI.VariablePrimal(), [v]) == [3.0]
    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.VariablePrimal()), v) == 3.0

    @test MOI.supports_constraint(m.model_cache, MOI.SingleVariable, MOI.LessThan{Float64})
    @test MOI.supports_constraint(m.optimizer.inner_model, MOI.SingleVariable, MOI.LessThan{Float64})
    @test MOI.supports_constraint(m.optimizer, MOI.SingleVariable, MOI.LessThan{Float64})
    @test MOI.supports_constraint(m, MOI.SingleVariable, MOI.LessThan{Float64})
    lb = MOI.add_constraint(m, MOI.SingleVariable(v), MOI.LessThan(10.0))
    MOI.set(m, MOI.ConstraintSet(), lb, MOI.LessThan(11.0))
    @test MOI.get(m, MOI.ConstraintSet(), lb) == MOI.LessThan(11.0)
    @test MOI.get(m, MOI.ConstraintFunction(), lb) == MOI.SingleVariable(v)

    MOIU.drop_optimizer(m)
    @test MOIU.state(m) == MOIU.NO_OPTIMIZER

    MOI.set(m, MOI.ConstraintSet(), lb, MOI.LessThan(12.0))
    @test MOI.get(m, MOI.ConstraintSet(), lb) == MOI.LessThan(12.0)

    MOI.delete(m, x[2])
    @test_throws MOI.InvalidIndex{typeof(x[2])} MOI.delete(m, x[2])
    @test !MOI.is_valid(m, x[2])

    # TODO: test more constraint modifications

    @test sprint(show, m) == raw"""
    MOIU.CachingOptimizer{MOI.AbstractOptimizer,MOIU.Model{Float64}}
    in state NO_OPTIMIZER
    in mode MANUAL
    with model cache MOIU.Model{Float64}
    with optimizer nothing"""
end

@testset "CachingOptimizer AUTOMATIC mode" begin
    m = MOIU.CachingOptimizer(MOIU.Model{Float64}(), MOIU.AUTOMATIC)
    @test MOIU.state(m) == MOIU.NO_OPTIMIZER

    v = MOI.add_variable(m)
    @test MOI.supports(m, MOI.VariableName(), typeof(v))
    MOI.set(m, MOI.VariableName(), v, "v")
    @test MOI.get(m, MOI.VariableName(), v) == "v"

    s = MOIU.MockOptimizer(MOIU.Model{Float64}(), supports_names=false)
    @test MOI.is_empty(s)
    MOIU.reset_optimizer(m, s)
    @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER

    saf = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, v)], 0.0)
    @test MOI.supports(m, MOI.ObjectiveFunction{typeof(saf)}())
    MOI.set(m, MOI.ObjectiveFunction{typeof(saf)}(), saf)
    @test MOI.get(m, MOIU.AttributeFromModelCache(MOI.ObjectiveFunction{typeof(saf)}())) ≈ saf

    MOI.optimize!(m)
    @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER
    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.TerminationStatus())) == MOI.OPTIMIZE_NOT_CALLED

    @test MOI.get(m, MOI.VariableName(), v) == "v"
    @test MOI.get(m, MOIU.AttributeFromModelCache(MOI.VariableName()), v) == "v"
    # The caching optimizer does not copy names
    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.VariableName()), v) == ""

    @test MOI.supports(m, MOI.ObjectiveSense())
    MOI.set(m, MOI.ObjectiveSense(), MOI.MAX_SENSE)
    @test MOI.get(m, MOIU.AttributeFromModelCache(MOI.ObjectiveSense())) == MOI.MAX_SENSE
    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.ObjectiveSense())) == MOI.MAX_SENSE

    @test MOI.supports(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute()))
    MOI.set(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute()), 10)
    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute())) == 10

    MOI.set(m, MOIU.AttributeFromOptimizer(MOI.ResultCount()), 1)
    @test MOI.supports(m, MOIU.AttributeFromOptimizer(MOI.VariablePrimal()), typeof(v))
    MOI.set(m, MOIU.AttributeFromOptimizer(MOI.VariablePrimal()), v, 3.0)

    MOI.optimize!(m)

    @test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.VariablePrimal()), v) == 3.0

    @testset "Modify not allowed" begin
        s.modify_allowed = false
        MOI.modify(m, MOI.ObjectiveFunction{typeof(saf)}(),
                    MOI.ScalarConstantChange(1.0))
        s.modify_allowed = true
        @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER
        MOIU.attach_optimizer(m)
        @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER
        ci = MOI.add_constraint(m, saf, MOI.EqualTo(0.0))
        s.modify_allowed = false
        MOI.modify(m, ci, MOI.ScalarCoefficientChange(v, 1.0))
        s.modify_allowed = true
        @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER
        MOIU.attach_optimizer(m)
        @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER
    end

    # Simulate that constraints cannot be added
    @testset "Add constraint not allowed" begin
        s.add_con_allowed = false
        MOI.add_constraint(m, MOI.VectorOfVariables([v]), MOI.SecondOrderCone(1))
        s.add_con_allowed = true
        @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER
        MOI.empty!(m)
        @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER
    end

    @testset "Add variable not allowed" begin
        s.add_var_allowed = false # Simulate optimizer that cannot add variables incrementally
        MOI.add_variable(m)
        @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER
        @test_throws MOI.AddVariableNotAllowed MOIU.attach_optimizer(m)
        @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER

        s.add_var_allowed = true
        MOIU.attach_optimizer(m)
        @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER
        s.add_var_allowed = false
        MOI.add_variables(m, 2)
        s.add_var_allowed = true
        @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER
    end

    @testset "Delete not allowed" begin
        vi = MOI.add_variable(m)
        s.delete_allowed = false # Simulate optimizer that cannot delete variable
        MOI.delete(m, vi)
        s.delete_allowed = true
        @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER
        MOIU.attach_optimizer(m)
        @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER

        vi = MOI.add_variable(m)
        ci = MOI.add_constraint(m, MOI.SingleVariable(vi), MOI.EqualTo(0.0))
        s.delete_allowed = false # Simulate optimizer that cannot delete constraint
        MOI.delete(m, ci)
        s.delete_allowed = true
        @test MOIU.state(m) == MOIU.EMPTY_OPTIMIZER
        MOIU.attach_optimizer(m)
        @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER
    end

    @test sprint(show, m) == raw"""
    MOIU.CachingOptimizer{MOI.AbstractOptimizer,MOIU.Model{Float64}}
    in state ATTACHED_OPTIMIZER
    in mode AUTOMATIC
    with model cache MOIU.Model{Float64}
    with optimizer MOIU.MockOptimizer{MOIU.Model{Float64}}"""
end

@testset "Constructor with optimizer" begin
    @testset "Empty model and optimizer" begin
        s = MOIU.MockOptimizer(MOIU.Model{Float64}(), supports_names=false)
        model = MOIU.Model{Float64}()
        m = MOIU.CachingOptimizer(model, s)
        @test m isa MOIU.CachingOptimizer{typeof(s), typeof(model)}
        @test MOI.is_empty(m)
        @test MOIU.state(m) == MOIU.ATTACHED_OPTIMIZER
        @test MOIU.mode(m) == MOIU.AUTOMATIC
        @test MOI.get(m, MOI.SolverName()) == "Mock"
        @test sprint(show, m) == raw"""
        MOIU.CachingOptimizer{MOIU.MockOptimizer{MOIU.Model{Float64}},MOIU.Model{Float64}}
        in state ATTACHED_OPTIMIZER
        in mode AUTOMATIC
        with model cache MOIU.Model{Float64}
        with optimizer MOIU.MockOptimizer{MOIU.Model{Float64}}"""
    end
    @testset "Non-empty optimizer" begin
        s = MOIU.MockOptimizer(MOIU.Model{Float64}(), supports_names=false)
        MOI.add_variable(s)
        model = MOIU.Model{Float64}()
        @test MOI.is_empty(model)
        @test !MOI.is_empty(s)
        @test_throws AssertionError MOIU.CachingOptimizer(model, s)
    end
    @testset "Non-empty model" begin
        s = MOIU.MockOptimizer(MOIU.Model{Float64}(), supports_names=false)
        model = MOIU.Model{Float64}()
        MOI.add_variable(model)
        @test !MOI.is_empty(model)
        @test MOI.is_empty(s)
        @test_throws AssertionError MOIU.CachingOptimizer(model, s)
    end
end

for state in (MOIU.NO_OPTIMIZER, MOIU.EMPTY_OPTIMIZER, MOIU.ATTACHED_OPTIMIZER)
    @testset "Optimization tests in state $state and mode $mode" for mode in (MOIU.MANUAL, MOIU.AUTOMATIC)
        m = MOIU.CachingOptimizer(MOIU.Model{Float64}(), mode)
        if state != MOIU.NO_OPTIMIZER
            s = MOIU.MockOptimizer(MOIU.Model{Float64}(), supports_names=false)
            MOIU.reset_optimizer(m, s)
            if state == MOIU.ATTACHED_OPTIMIZER
                MOIU.attach_optimizer(m)
            end
        end
        @test MOIU.state(m) == state
        @test MOIU.mode(m) == mode

        @testset "Name test" begin
            MOIT.nametest(m)
        end

        @testset "Copy test" begin
            MOIT.failcopytestc(m)
            MOIT.failcopytestia(m)
            MOIT.failcopytestva(m)
            MOIT.failcopytestca(m)
            MOIT.copytest(m, MOIU.Model{Float64}())
        end

        config = MOIT.TestConfig(solve=false)
        @testset "Unit" begin
            MOIT.unittest(m, config)
        end
        @testset "Continuous Linear" begin
            exclude = ["partial_start"] # VariablePrimalStart not supported.
            MOIT.contlineartest(m, config, exclude)
        end
    end
end

mutable struct NoFreeVariables <: MOI.AbstractOptimizer
    inner::MOIU.Model{Float64}
    function NoFreeVariables()
        return new(MOIU.Model{Float64}())
    end
end
MOI.is_empty(model::NoFreeVariables) = MOI.is_empty(model.inner)
MOI.empty!(model::NoFreeVariables) = MOI.empty!(model.inner)
MOI.get(model::NoFreeVariables, attr::MOI.AnyAttribute, idx::Vector) = MOI.get(model.inner, attr, idx)
MOI.get(model::NoFreeVariables, attr::MOI.AnyAttribute, args...) = MOI.get(model.inner, attr, args...)
MOI.supports_add_constrained_variables(::NoFreeVariables, ::Type{MOI.Reals}) = false
MOI.supports_add_constrained_variable(::NoFreeVariables, ::Type{<:MOI.AbstractScalarSet}) = true
function MOI.add_constrained_variable(model::NoFreeVariables, set::MOI.AbstractScalarSet)
    return MOI.add_constrained_variable(model.inner, set)
end
MOI.supports_add_constrained_variables(::NoFreeVariables, ::Type{<:MOI.AbstractVectorSet}) = true
function MOI.add_constrained_variables(model::NoFreeVariables, set::MOI.AbstractVectorSet)
    return MOI.add_constrained_variables(model.inner, set)
end

MOI.Utilities.supports_default_copy_to(::NoFreeVariables, names::Bool) = !names
function MOI.copy_to(dest::NoFreeVariables, src::MOI.ModelLike; kwargs...)
    return MOI.Utilities.automatic_copy_to(dest, src; kwargs...)
end

function constrained_variables_test(model)
    @test !MOI.supports_add_constrained_variables(model, MOI.Reals)
    @test MOI.supports_add_constrained_variable(model, MOI.ZeroOne)
    @test !MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne)
    @test MOI.supports_add_constrained_variables(model, MOI.Nonnegatives)
    @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Nonnegatives)
    scalar_set = MOI.ZeroOne()
    x, cx = MOI.add_constrained_variable(model, scalar_set)
    vector_set = MOI.Nonnegatives(2)
    y, cy = MOI.add_constrained_variables(model, vector_set)
    constraint_types = Set([(MOI.SingleVariable, MOI.ZeroOne), (MOI.VectorOfVariables, MOI.Nonnegatives)])
    @test Set(MOI.get(model.model_cache, MOI.ListOfConstraints())) == constraint_types
    if MOIU.state(model) == MOIU.EMPTY_OPTIMIZER
        MOIU.attach_optimizer(model)
    end
    @test Set(MOI.get(model.optimizer, MOI.ListOfConstraints())) == constraint_types
end

@testset "Constrained Variables" begin
    cache = NoFreeVariables()
    optimizer = NoFreeVariables()
    model = MOIU.CachingOptimizer(cache, optimizer)
    constrained_variables_test(model)
    MOI.empty!(cache)
    MOI.empty!(optimizer)
    model = MOIU.CachingOptimizer(cache, MOIU.AUTOMATIC)
    MOIU.reset_optimizer(model, optimizer)
    constrained_variables_test(model)
end


text:
#=
Generate markdown files automatically, for generating
docs using `Documenter.jl`.

@author: Zejin Shi

=#

# path = abspath("..")
path = replace(pathof(GameTheory), "src/GameTheory.jl" => "")

# read the basic structures
order = SubString{String}[]
sections_names = SubString{String}[]
sections = Dict{SubString{String}, Vector{SubString{String}}}()
re = r"(.*): (.*)\n"
open(joinpath(path, "docs/Structure")) do f
    for match in eachmatch(re, String(read(f)))
        list = map(i -> strip(i), split(match.captures[2], ","))
        if list ==[""]
            continue
        end

        if match.captures[1] == "Order"
            order = list
            global order
        else
            section_name = match.captures[1]
            push!(sections_names, section_name)
            sections[section_name] = list
        end
    end
end

modules = String["GameTheory"]
# find all files used in GameTheory.jl
re = r"include\(\"(.*)\.jl\"\)"
files = String[]

open(joinpath(path, "src/GameTheory.jl")) do f
    for match in eachmatch(re, String(read(f)))
        # check if it is a submodule
        if occursin("/", match.captures[1])
            submodule_path = match.captures[1]
            submodule_dir, submodule_name = split(submodule_path, "/")
            # add submodule name to modules
            push!(modules, "GameTheory.$submodule_name")
            # find all files used in submodules
            open(joinpath(path, "src/$submodule_path.jl")) do f_submodule
                for match_sub in eachmatch(re, String(read(f_submodule)))
                    file = match_sub.captures[1]
                    push!(files, "$submodule_dir/$file")
                end
            end
        else
            push!(files, match.captures[1])
        end
    end
end

module_names = join(modules, ", ")

files_names = Dict{String, String}(
                file => replace(join(map(uppercasefirst, split(file, "_")), " "),
                                "Util" => "Utilities")
                for file in files
                )

# generate paths
if !ispath(joinpath(path, "docs/src/lib"))
    mkpath(joinpath(path, "docs/src/lib"))
end

# write .md for files in main modules not in section
for file in files
    if file in vcat(values(sections)...)
        continue
    end

    if !(file in order)
        push!(order, file)
    end
    file_name = files_names[file]
    file_page = """
# [$file_name](@id $file)

This is documentation for `$file.jl`.

## Exported

```@autodocs
Modules = [$module_names]
Pages   = ["$file.jl"]
Private = false
```

## Internal

```@autodocs
Modules = [$module_names]
Pages   = ["$file.jl"]
Public = false
```
"""
    open(joinpath(path, "docs/src/lib/$file.md"), "w") do f
        write(f, file_page)
    end
end

# write .md for sections
for section_name in sections_names
    if !(section_name in order)
        push!(order, section_name)
    end

    section_files = sections[section_name]
    section_name_lower = replace(lowercase(section_name), " " => "_")
    section_file_list = join(map(i -> string("\"", i, ".jl\""),
                             section_files), ", ")
    # include "GameTheory.jl" in "Base Types and Methods"
    if section_name == "Base Types and Methods"
        section_file_list = string("\"GameTheory.jl\", ", section_file_list)
    end
    section_page = """
# [$section_name](@id $section_name_lower)

## Exported
```@autodocs
Modules = [$module_names]
Pages   = [$section_file_list]
Private = false
```

## Internal
```@autodocs
Modules = [$module_names]
Pages   = [$section_file_list]
Public = false
```
"""
    open(joinpath(path, "docs/src/lib/$section_name_lower.md"), "w") do f
        write(f, section_page)
    end
end

# write index page for Liabrary
index = """
# Index

```@index
Modules = [$module_names]
```
"""

open(joinpath(path, "docs/src/lib/index.md"), "w") do f
    write(f, index)
end

# add Library Outline to Homepage index.md
open(joinpath(path, "docs/src/index.md"), "a") do f
    for page in order
        if page in keys(sections)
            section_name_lower = replace(lowercase(page), " " => "_")
            write(f, "* [$page](@ref $section_name_lower)\n\n")
        else
            file = page
            file_name = files_names[file]
            write(f, "* [$file_name](@ref $file)\n\n")
        end
    end
end

pages_names = map(i -> replace(lowercase(i), " " => "_"), order)

PAGES = ["Home" => "index.md",
         "Library" => push!(
                        ["lib/$page_name.md"
                         for page_name in pages_names
                        ],
                         "lib/index.md"
                    )
    ]


text:
#Tests steady state finders for minimal reaction network.
for rn in min_reaction_networks_standard
    addequi1!(rn)
    addequi2!(rn)
    p_vals = map(amp->amp*rand(length(rn.params)),[1.,10.,100.])
    for p in p_vals
        fps = steady_states(rn,p)
        stab = map(fp->stability(fp,rn,p), fps)
        for i=1:length(fps)
            stab[i] ? (tend = 10) : (tend = -10)
            end_point = OrdinaryDiffEq.solve(ODEProblem(rn,fps[i],(0.,tend),p),Rosenbrock23())[end]
            @test maximum(end_point-fps[i])<0.0001
        end
    end
end