mutable struct Course
    name::AbstractString            # Name of the course
    id::Int                         # Unique id for course
    term::Int                       # The term the course belongs to

    credits::Int                    # Course credit hours
    delay::Int                      # The course's delay factor - the value of the longest path the course is on
    blocking::Int                   # The course's blocking factor - the number of courses blocked by this course
    cruciality::Int                 # The sum of the delay and blocking factor
    centrality::Int                 # Course centrality
    reachability::Int               # Course reachability

    prereqs::Array{Course}          # Array of courses that are a prerequisite to this course
    coreqs::Array{Course}           # Array of courses that must be taken in the same term or before
    postreqs::Array{Course}         # Array of courses that has this course as a prerequisite
    termReq::Int                    # Number of terms that must be completed before enrolling

    students::Array{Student}        # Array of students enrolled in this course in a given term

    model::Dict                     # Dictionary to hold information for grade predictions
    passrate::Float64               # Percentage of students that pass the course
    failures::Int                   # Total number of students who do not pass the course
    grades::Array{Float64}          # Array of all grades made in the course
    enrolled::Int                   # Total number of students that are enrolled in the course
    termenrollment::Array{Int}      # An array of enrollment by term
    termpassed::Array{Int}          # An array of the number of students who pass each term

    # Minimum Information Required
    function Course(name::AbstractString, credits::Number, prereqs::Array{Course}, coreqs::Array{Course})
        this = new()

        this.name = name
        this.credits = credits
        this.prereqs = prereqs
        this.postreqs = Course[]
        this.coreqs = Course[]
        this.termReq = 0

        # Assign current course as postreq for prereqs
        for c in this.prereqs
            push!(c.postreqs, this)
        end

        this.passrate = 0
        this.failures = 0
        this.enrolled = 0
        this.grades = Float64[]
        this.students = Student[]

        return this
    end

    # With Passrate
    function Course(name::AbstractString, credits::Number, passrate::Number, prereqs::Array{Course}, coreqs::Array{Course})
        this = Course(name, credits, prereqs, coreqs)
        this.passrate = passrate
        return this
    end

    # With termReq
    function Course(name::AbstractString, credits::Number, prereqs::Array{Course}, coreqs::Array{Course}, termReq::Number)
        this = Course(name, credits, prereqs, coreqs)
        this.termReq = termReq
        return this
    end

    # With termReq and Passrate
    function Course(name::AbstractString, credits::Number, termReq::Number, passrate::Number, prereqs::Array{Course}, coreqs::Array{Course})
        this = Course(name, credits, prereqs, coreqs)
        this.termReq = termReq
        this.passrate = passrate
        return this
    end
end

# Helper Functions

# Compute the cruciality, delay, and blocking factor of a course
function cruciality(course::Course)
    b = blocking(course)
    d = delay(course)

    course.blocking = b
    course.delay = d
    course.cruciality = d + b
end

# Compute the blocking factor of a course
function blocking(course, b=-1, courses=[])
    for c in course.postreqs
        b += blocking(c, 0, courses)
    end

    if !in(course.name, courses)
        b += 1
        push!(courses, course.name)
    end

    return b
end

# Compute the delay factor of a course
function delay(course)
    # Traverse forward from course
    f = []
    forward(course, f)

    # Traverse backwards from course
    b = []
    backward(course, b)

    return maximum(f) + maximum(b) + 1
end

# Determines all path length (l) from course and stores them in path
function forward(course, paths, l=0)
    # Keep traversing forward
    for c in course.postreqs
        forward(c, paths, l+1)
    end

    # If no postreqs, record path length
    if length(course.postreqs) == 0
        push!(paths, l)
    end
end

# Determines all path length (l) behind course and stores them in path
function backward(course, paths, l=0)
    # Keep traversing backward
    for c in [course.prereqs; course.coreqs]
        backward(c, paths, l+1)
    end

    # If no prereqs, record path length
    if length(course.prereqs) == 0
        push!(paths, l)
    end
end


println("==============flag! BnBTree.jl======================================")

include("table_log.jl")
include("bb_inits_and_defaults.jl")
include("bb_strategies.jl")
include("bb_user_limits.jl")
include("bb_type_correct.jl")
include("bb_integral_or_branch.jl")
include("bb_gains.jl")

"""
    get_branching_disc_idx!(m, step_obj, opts, disc2var_idx, gains, counter::Int64=1)

Get the index of a variable to branch on.
"""
function get_branching_disc_idx!(m, step_obj, opts, disc2var_idx, gains, counter::Int64=1)
    start = time()
    node = step_obj.node
    idx = 0
    strong_restarts = 0
    branch_strat = opts.branch_strategy
    status = :Normal
    if branch_strat == :MostInfeasible
        idx = branch_mostinfeasible(m, node, disc2var_idx)
        step_obj.branch_strategy = :MostInfeasible
    elseif branch_strat == :PseudoCost || branch_strat == :StrongPseudoCost
        if counter == 1 && branch_strat == :PseudoCost
            idx = branch_mostinfeasible(m, node, disc2var_idx)
            step_obj.branch_strategy = :MostInfeasible
        elseif counter <= opts.strong_branching_nsteps && branch_strat == :StrongPseudoCost
            status, idx, strong_restarts = branch_strong!(m, opts, disc2var_idx, step_obj, counter)
            step_obj.branch_strategy = :Strong
        else
            idx = branch_pseudo(m, node, disc2var_idx, gains, opts.gain_mu, opts.atol)
            step_obj.branch_strategy = :Pseudo
        end
    elseif branch_strat == :Reliability
        status, idx, strong_restarts = branch_reliable!(m,opts,step_obj,disc2var_idx,gains,counter)
        step_obj.branch_strategy = :Reliability
    end
    step_obj.state = status
    step_obj.var_idx = idx
    step_obj.nrestarts = strong_restarts
    step_obj.idx_time = time()-start
    return
end

"""
    process_node!(m, step_obj, cnode, disc2var_idx, temp)

Solve a child node `cnode` by relaxation.
Set the state and best_bound property.
Push integrals and new branch nodes to the step object
Return state
"""
function process_node!(m, step_obj, cnode, disc2var_idx, temp; restarts=0)
     # set bounds
    for i=1:m.num_var
        JuMP.set_lower_bound(m.x[i], cnode.l_var[i])
        JuMP.set_upper_bound(m.x[i], cnode.u_var[i])
    end
    if restarts > 0
        println("Doing one restart")
        restart_values = generate_random_restart(m)
        JuMP.set_start_value.(m.x[1:m.num_var], restart_values)
    else
        JuMP.set_start_value.(m.x[1:m.num_var], step_obj.node.solution)
    end

    old_mu_init = set_subsolver_option!(m, m.model, "nl", "Ipopt", "mu_init", 0.1 => 1e-5)

    status, backend = optimize_get_status_backend(m.model)

    # reset mu_init
    reset_subsolver_option!(m, "nl", "Ipopt", "mu_init", old_mu_init)

    objval = NaN
    cnode.solution = fill(NaN, m.num_var)
    if state_is_optimal(status; allow_almost=m.options.allow_almost_solved)
        objval = JuMP.objective_value(m.model)
        cnode.solution = JuMP.value.(m.x)
    end

    cnode.relaxation_state = status
    if !state_is_optimal(status; allow_almost=m.options.allow_almost_solved) && !state_is_infeasible(status)
        cnode.state = :Error
    elseif state_is_optimal(status; allow_almost=m.options.allow_almost_solved)
        cnode.best_bound = objval
        set_cnode_state!(cnode, m, step_obj, disc2var_idx)
        if only_almost_solved(status) && (!(cnode.state == :Integral) || m.options.allow_almost_solved_integral)
            @warn "Only almost solved"
        end

        # if almost_solved_integral is not allowed but it is integral => restart and hope for the best
        if cnode.state == :Integral && only_almost_solved(status) && !m.options.allow_almost_solved_integral
            if restarts >= 1
                cnode.state = :Almost_Solved
                Base.finalize(backend)
                return cnode.state
            else
                return process_node!(m, step_obj, cnode, disc2var_idx, temp; restarts=restarts+1)
            end
        end
        if !temp
            push_integral_or_branch!(step_obj, cnode)
        end
    else
        cnode.state = :Infeasible
    end
    Base.finalize(backend)
    return cnode.state
end

"""
    branch!(m, opts, step_obj, counter, disc2var_idx; temp=false)

Branch a node by using x[idx] <= floor(x[idx]) and x[idx] >= ceil(x[idx])
Solve both nodes and set current node state to done.
"""
function branch!(m, opts, step_obj, counter, disc2var_idx; temp=false)
    ps = opts.log_levels
    node = step_obj.node
    vidx = step_obj.var_idx

    start = time()
    # it might be already branched on
    if !temp && node.state != :Branch
        for cnode in [step_obj.l_nd,step_obj.r_nd]
            if cnode.state == :Branch || cnode.state == :Integral
                push_integral_or_branch!(step_obj, cnode)
            end
        end
        return step_obj.l_nd,step_obj.r_nd
    end

    l_nd_u_var = copy(node.u_var)
    r_nd_l_var = copy(node.l_var)
    l_nd_u_var[vidx] = floor(node.solution[vidx])
    r_nd_l_var[vidx] = ceil(node.solution[vidx])

    if opts.debug
        path_l = copy(node.path)
        path_r = copy(node.path)
        push!(path_l,node.hash)
        push!(path_r,node.hash)
    else
        path_l = []
        path_r = []
    end
    l_nd = new_left_node(node, l_nd_u_var; path=path_l)
    r_nd = new_right_node(node, r_nd_l_var; path=path_r)

    # save that this node branches on this particular variable
    node.var_idx = vidx

    check_print(ps,[:All,:FuncCall]) && println("branch")

    if !temp
        step_obj.l_nd = l_nd
        step_obj.r_nd = r_nd
        node.state = :Done
    end

    start_process = time()
    l_state = process_node!(m, step_obj, l_nd, disc2var_idx, temp)
    r_state = process_node!(m, step_obj, r_nd, disc2var_idx, temp)
    node_time = time() - start_process

    if !temp
        step_obj.node_branch_time += node_time
    end

    branch_strat = opts.branch_strategy

    if check_print(ps,[:All])
        println("State of left node: ", l_state)
        println("State of right node: ", r_state)
        println("l sol: ", l_nd.solution)
        println("r sol: ", r_nd.solution)
    end

    if !temp
        step_obj.branch_time += time()-start
    end

    return l_nd,r_nd
end

"""
    update_incumbent!(tree::BnBTreeObj, node::BnBNode)

Gets called if new integral solution was found.
Check whether it's a new incumbent and update if necessary
"""
function update_incumbent!(tree::BnBTreeObj, node::BnBNode)
    ps = tree.options.log_levels
    check_print(ps,[:All,:FuncCall]) && println("update_incumbent")

    factor = tree.obj_fac
    if !isdefined(tree,:incumbent) || factor*node.best_bound > factor*tree.incumbent.objval
        objval = node.best_bound
        solution = copy(node.solution)
        status = node.relaxation_state
        @assert(state_is_optimal(status; allow_almost=false) || (only_almost_solved(status) && tree.options.allow_almost_solved_integral))
        tree.incumbent = Incumbent(objval, solution, only_almost_solved(status))
        if !tree.options.all_solutions
            bound!(tree)
        end
        return true
    end

    return false
end

"""
    bound!(tree::BnBTreeObj)
"""
function bound!(tree::BnBTreeObj)
    function isbetter(n)
        return f*n.best_bound > f*incumbent_val
    end
    incumbent_val = tree.incumbent.objval
    f = tree.obj_fac
    filter!(isbetter, tree.branch_nodes)
end

"""
    add_incumbent_constr(m, incumbent)

Add a constraint >=/<= incumbent
"""
function add_incumbent_constr(m, incumbent)
    add_obj_constraint(m, incumbent.objval)
end

"""
    add_obj_epsilon_constr(tree)

Add a constraint obj ≦ (1+ϵ)*LB or obj ≧ (1-ϵ)*UB
"""
function add_obj_epsilon_constr(tree)
    # add constr for objval
    if tree.options.obj_epsilon > 0
        ϵ = tree.options.obj_epsilon
        if tree.m.obj_sense == :Min
            rhs = (1+ϵ)*tree.m.relaxation_objval
        else
            rhs = (1-ϵ)*tree.m.relaxation_objval
        end
        add_obj_constraint(tree.m, rhs)
        tree.m.ncuts += 1
    end
end

"""
    get_next_branch_node!(tree)

Get the next branch node (sorted by best bound)
Return true,step_obj if there is a branch node and
false, nothing otherwise
"""
function get_next_branch_node!(tree)
    if length(tree.branch_nodes) == 0
        return false, false, nothing
    end
    bvalue, nidx = findmax([tree.obj_fac*n.best_bound for n in tree.branch_nodes])

    trav_strat = tree.options.traverse_strategy
    if trav_strat == :BFS
        # use the one with highest depth
        _value, nidx = findmax([if isapprox(tree.obj_fac*n.best_bound, bvalue) n.level else -Inf end for n in tree.branch_nodes])
    end

    if trav_strat == :DFS || (trav_strat == :DBFS && !isdefined(tree,:incumbent))
        _value, nidx = findmax([n.level for n in tree.branch_nodes])
    end

    tree.best_bound = tree.obj_fac*bvalue
    bbreak = isbreak_mip_gap(tree)
    if bbreak
        return false, true, nothing
    end

    node = tree.branch_nodes[nidx]
    deleteat!(tree.branch_nodes, nidx)

    return true, false, new_default_step_obj(tree.m,node)
end

"""
    one_branch_step!(m1, incumbent, opts, step_obj, disc2var_idx, gains, counter)

Get a branch variable using the specified strategy and branch on the node in step_obj
using that variable. Return the new updated step_obj
"""
function one_branch_step!(m1, incumbent, opts, step_obj, disc2var_idx, gains, counter)
    if m1 === nothing
        global m
        global is_newincumbent
        if opts.incumbent_constr && incumbent !== nothing && is_newincumbent
            is_newincumbent = false
            add_incumbent_constr(m, incumbent)
        end
    else
        m = m1
    end

    node = step_obj.node
    step_obj.counter = counter

# get branch variable
    node_idx_start = time()
    get_branching_disc_idx!(m, step_obj, opts, disc2var_idx, gains, counter)
    step_obj.node_idx_time = time()-node_idx_start
    # if no variable got selected might be true that all variables are already type correct
    # node.solution can be updated if one child is infeasible and the other optimal (can result in discrete)
    if step_obj.var_idx == 0 && are_type_correct(step_obj.node.solution, m.var_type, disc2var_idx, opts.atol)
        push!(step_obj.integral, node)
    else
        if step_obj.state != :Infeasible && step_obj.state != :PartlyInfeasible
            @assert step_obj.var_idx != 0
            branch!(m, opts, step_obj, counter, disc2var_idx)
        end
    end
    return step_obj
end

"""
    upd_time_obj!(time_obj, step_obj)

Add step_obj times to time_obj
"""
function upd_time_obj!(time_obj, step_obj)
    time_obj.solve_leaves_get_idx += step_obj.node_idx_time
    time_obj.solve_leaves_branch += step_obj.node_branch_time
    time_obj.branch += step_obj.branch_time
    time_obj.get_idx += step_obj.idx_time
    time_obj.upd_gains += step_obj.upd_gains_time
end

"""
    upd_tree_obj!(tree, step_obj, time_obj)

Update the tree obj like new incumbent or new branch nodes using the step_obj
Return true if it's the end of the algorithm (checking different break rules)
"""
function upd_tree_obj!(tree, step_obj, time_obj)
    node = step_obj.node
    still_running = true

    if step_obj.node.level+1 > tree.m.nlevels
        tree.m.nlevels = step_obj.node.level+1
    end

    if step_obj.state == :Infeasible
        # if there is no incumbent yet
        if !isdefined(tree,:incumbent)
            # this can be locally infeasible or globally infeasible depending on the solver
            tree.limit = :Infeasible
        end # it will terminate and use the current solution as optimal (might want to rerun as an option)
        still_running = false
    end

    if still_running
        upd_gains_step!(tree, step_obj)
    end

    bbreak = upd_integral_branch!(tree, step_obj)
    if bbreak
        still_running = false
    end

    upd_time_obj!(time_obj, step_obj)
    return !still_running
end

"""
    solve_sequential(tree,
        last_table_arr,
        time_bnb_solve_start,
        fields,
        field_chars,
        time_obj)

Run branch and bound on a single processor
"""
function solve_sequential(tree,
    last_table_arr,
    time_bnb_solve_start,
    fields,
    field_chars,
    time_obj)

    m = tree.m
    opts = tree.options
    disc2var_idx = tree.disc2var_idx
    counter = 0
    ps = tree.options.log_levels

    dictTree = Dict{Any,Any}()
    while true
        # the _ is only needed for parallel
        exists, _, step_obj = get_next_branch_node!(tree)
        !exists && break
        isbreak_after_step!(tree) && break
        counter += 1
        if isdefined(tree,:incumbent)
            step_obj = one_branch_step!(m, tree.incumbent, opts, step_obj, disc2var_idx, tree.obj_gain, counter)
        else
            step_obj = one_branch_step!(m, nothing, opts, step_obj, disc2var_idx, tree.obj_gain, counter)
        end
        m.nnodes += 2 # two nodes explored per branch
        node = step_obj.node

        bbreak = upd_tree_obj!(tree,step_obj,time_obj)
        tree.options.debug && (dictTree = push_step2treeDict!(dictTree,step_obj))

        if check_print(ps,[:Table])
            last_table_arr = print_table(1,tree,node,step_obj,time_bnb_solve_start,fields,field_chars;last_arr=last_table_arr)
        end

        if bbreak
            break
        end
    end

    tree.options.debug && (tree.m.debugDict[:tree] = dictTree)
    return counter
end

function sendto(p::Int; args...)
    for (nm, val) in args
        remotecall_fetch(Core.eval, p, Juniper, Expr(:(=), nm, val))
    end
end

function dummysolve()
    global m
    optimize!(m.model)
end

"""
    pmap(f, tree, last_table_arr, time_bnb_solve_start,
        fields, field_chars, time_obj)

Run the solving steps on several processors
"""
function pmap(f, tree, last_table_arr, time_bnb_solve_start,
    fields, field_chars, time_obj)
    np = nprocs()  # determine the number of processes available
    if tree.options.processors+1 < np
        np = tree.options.processors+1
    end

    # function to produce the next work item from the queue.
    # in this case it's just an index.
    ps = tree.options.log_levels
    still_running = true
    run_counter = 0
    counter = 0

    for p=2:np
        seed = Random.seed!
        remotecall_fetch(seed, p, 1)
    end
    @sync begin
        for p=2:np
            @async begin
                sendto(p, m=tree.m)
                sendto(p, is_newincumbent=false)
            end
        end
    end

    for p=3:np
        remotecall(dummysolve, p)
    end

    proc_counter = zeros(np)

    dictTree = Dict{Any,Any}()

    branch_strat = tree.options.branch_strategy
    opts = tree.options
    @sync begin
        for p=1:np
            if p != myid() || np == 1
                @async begin
                    while true
                        exists, bbreak, step_obj = get_next_branch_node!(tree)
                        if bbreak
                            still_running = false
                            break
                        end

                        while !exists && still_running
                            sleep(0.1)
                            exists, bbreak, step_obj = get_next_branch_node!(tree)
                            exists && break
                            if bbreak
                                still_running = false
                                break
                            end
                        end
                        !still_running && break

                        if isbreak_after_step!(tree)
                            still_running = false
                            break
                        end

                        run_counter += 1
                        counter += 1
                        if isdefined(tree,:incumbent)
                            step_obj = remotecall_fetch(f, p, nothing, tree.incumbent, tree.options, step_obj,
                                                        tree.disc2var_idx, tree.obj_gain, counter)
                        else
                            step_obj = remotecall_fetch(f, p, nothing, nothing, tree.options, step_obj,
                            tree.disc2var_idx, tree.obj_gain, counter)
                        end
                        tree.m.nnodes += 2 # two nodes explored per branch
                        run_counter -= 1

                        !still_running && break

                        bbreak = upd_tree_obj!(tree,step_obj,time_obj)
                        tree.options.debug && (dictTree = push_step2treeDict!(dictTree,step_obj))

                        if run_counter == 0 && length(tree.branch_nodes) == 0
                            still_running = false
                        end

                        bbreak && (still_running = false)

                        if check_print(ps,[:Table])
                            if length(tree.branch_nodes) > 0
                                bvalue, nidx = findmax([tree.obj_fac*n.best_bound for n in tree.branch_nodes])
                                tree.best_bound = tree.obj_fac*bvalue
                            end
                            last_table_arr = print_table(p,tree,step_obj.node,step_obj,time_bnb_solve_start,fields,field_chars;last_arr=last_table_arr)
                        end

                        !still_running && break
                    end
                end
            end
        end
    end
    tree.options.debug && (tree.m.debugDict[:tree] = dictTree)
    return counter
end

"""
    solvemip(tree::BnBTreeObj)

Solve the MIP part of a problem given by BnBTreeObj using branch and bound.
 - Identify the node to branch on
 - Get variable to branch on
 - Solve subproblems
"""
function solvemip(tree::BnBTreeObj)
    time_obj = init_time_obj()
    time_bnb_solve_start = time()

    ps = tree.options.log_levels

    # check if already integral
    if are_type_correct(tree.m.relaxation_solution,tree.m.var_type,tree.disc2var_idx, tree.options.atol)
        tree.nsolutions = 1
        objval = JuMP.objective_value(tree.m.model)
        sol = JuMP.value.(tree.m.x)
        bbound = try
            JuMP.objective_bound(tree.m.model)
        catch
            JuMP.objective_value(tree.m.model)
        end
        tree.incumbent = Incumbent(objval,sol,only_almost_solved(tree.m.status))
        return tree.incumbent
    end

    # check if incumbent and if mip gap already fulfilled (i.e found by fp)
    if isbreak_mip_gap(tree)
        return tree.incumbent
    end

    last_table_arr = []
    fields = []
    field_chars = []
    # Print table init
    if check_print(ps,[:Table])
        fields, field_chars = get_table_config(tree.options)
        print_table_header(fields,field_chars)
    end

    check_print(ps,[:All,:FuncCall]) && println("Solve Tree")

    branch_strat = tree.options.branch_strategy

    add_obj_epsilon_constr(tree)

    # use pmap if more then one processor
    if tree.options.processors > 1 || tree.options.force_parallel
        counter = pmap(Juniper.one_branch_step!,tree,
            last_table_arr,
            time_bnb_solve_start,
            fields,
            field_chars,
            time_obj)
    else
        counter = solve_sequential(tree,
            last_table_arr,
            time_bnb_solve_start,
            fields,
            field_chars,
            time_obj)
    end

    if !isdefined(tree,:incumbent) && tree.limit == :None
        # infeasible
        tree.limit = :Infeasible
    end

    if tree.options.obj_epsilon != 0 && tree.limit == :Infeasible
        @warn "Maybe only infeasible because of obj_epsilon."
    end

    if !isnan(tree.options.best_obj_stop) && isdefined(tree, :incumbent)
        inc_val = tree.incumbent.objval
        bos = tree.options.best_obj_stop
        sense = tree.m.obj_sense
        if (sense == :Min && inc_val > bos) || (sense == :Max && inc_val < bos)
            @warn "best_obj_gap couldn't be reached."
        end
    end

    if length(tree.branch_nodes) > 0
        bvalue, nidx = findmax([tree.obj_fac*n.best_bound for n in tree.branch_nodes])
        tree.best_bound = tree.obj_fac*bvalue
    else
        if isdefined(tree, :incumbent) && !isnan(tree.incumbent.objval)
            tree.best_bound = tree.incumbent.objval
        end
    end

    tree.m.nbranches = counter

    time_bnb_solve = time()-time_bnb_solve_start
    check_print(ps,[:Table]) && println("")
    check_print(ps,[:All,:Info]) && println("#branches: ", counter)

    tree.options.debug && debug_set_tree_obj_gain!(tree)
    check_print(ps,[:All,:Timing]) && print_final_timing(time_bnb_solve, time_obj)
end


# License for this file: MIT (expat)
# Copyright 2017-2018, DLR Institute of System Dynamics and Control
#
# This file is part of module
#   Modia3D.ForceElements (Modia3D/ForceElements/_module.jl)
#

using Base.Meta:quot, isexpr

"""
    @forceElement forceName(arguments) begin ... end - Generate a function that instantiates a new forceElement

`forceName(arguments) begin ... end` is treated as the constructor function of a new
(mutable) struct that consists of all left-hand-side (scalar or vector) symbols
present between `begin ... end`.

# Examples

```
julia> include("$(Modia3D.path)/examples/dynamics/Simulate_PendulumWithDamper.jl");
```

```julia
using ModiaMath
using Modia3D

@forceElement Damper(; d=1.0) begin
    w   = ModiaMath.RealScalar("w",   causality=ModiaMath.Input,  numericType=ModiaMath.WR)
    tau = ModiaMath.RealScalar("tau", causality=ModiaMath.Output, numericType=ModiaMath.WR)
end;
function Modia3D.computeTorque(damper::Damper, sim::ModiaMath.SimulationState)
    damper.tau.value = -damper.d*damper.w.value
end;
@assembly PendulumWithDamper(;Lx = 1.0, Ly=0.2*Lx, Lz=0.2*Lx, m=1.0, g=9.81) begin
   world  = Modia3D.Object3D(Modia3D.CoordinateSystem(0.5*Lx))
   body   = Modia3D.Object3D(Modia3D.Solid(Modia3D.SolidBeam(Lx,Ly,Lz), Modia3D.MassProperties(m=m), vmat1))
   frame1 = Modia3D.Object3D(body; r=[-Lx/2, 0.0, 0.0])
   cyl    = Modia3D.Object3D(frame1,Modia3D.Cylinder(Ly/2,1.2*Ly; material=vmat2))

   # Connect pendulum to world with a damper in the joint
   rev    = Modia3D.Revolute(world, frame1)
   d      = Damper(d=0.2)
   damper = Modia3D.AdaptorForceElementToFlange(w=d.w, tau=d.tau)
   Modia3D.connect(damper, rev)
end
pendulum = PendulumWithDamper()
model = Modia3D.SimulationModel( pendulum )
result = ModiaMath.simulate!(model, stopTime=5.0, interval=0.1, tolerance=1e-4, log=true)
ModiaMath.plot(result, ["rev.phi", "rev.w", "rev.a", "rev.tau"])
```
"""
macro forceElement(head, top_ex)
   # @forceElement Name(arguments) begin ... end
   #    is mapped to:
   # mutable struct Name <: ModiaMath.AbstractComponentWithVariables
   #    < declarations >
   #    function Name(<arguments>)
   #       < equations top_ex >
   #       _internal  = ModiaMath.ComponentInternal(:Name, nothing, computeVariables!)
   #       _force = new(_internal, <declarations>)
   #       ModiaMath.initComponent(_internal, name1, :name1)
   #       ModiaMath.initComponent(_internal, name2, :name2)
   #         ...
   #       return _force
   #    end
   # end

   if typeof(head) == Symbol
      force_symbol = head
      fc = Expr(:call, head)
   else
      @assert isexpr(head, :call)
      force_symbol = head.args[1]::Symbol
      fc = head
   end

   # Build:  internalComponentName = ModiaMath.Component(component_name)
   # internalComponentName = gensym()
   # push!(eq, :( a = ModiaMath.Component($component_name)) )

   # Inspect top_ex find
   #   symbol = < statements >
   # and store "symbol" in "names"
   this = gensym("this")
   eqInternal = []
   eq      = []
   push!(eqInternal,:( $this = new( ModiaMath.ComponentInternal($(quot(force_symbol))) ) ))
   namesSet = DataStructures.OrderedSet{Symbol}() # Set of left-hand side symbols
   helpArray = []

   # Add keyword arguments to namesSet
   if length(fc.args) <= 1
      # No keyword arguments
   elseif typeof(fc.args[2]) == Symbol
      # Positional arguments present, but no keyword arguments
   elseif fc.args[2].head == :parameters
      # Keyword arguments present
      kwargs = fc.args[2].args
      # println("... keyword arguments = ", kwargs)
      for kw in kwargs
         # println("... kw = ", kw, ", kw.head = ", kw.head, ", kw.args = ", kw.args, ", name = ", kw.args[1])
         symbol = kw.args[1]
         if in(symbol, namesSet)
            error("\nfrom @forceElement ", force_symbol, ": keyword argument",
                  "\n   ", kw,
                  "\nis defined twice.")
         end
         push!(namesSet, symbol)
         push!(helpArray, :($this.$symbol = $symbol))
      end
      # push!(fcp.args[2].args, Expr(:kw, :sceneOptions, :nothing))
   else
      error("Unknown error in forces.jl: typeof(fc.args[2]) = ", typeof(fc.args[2]))
   end

   for ex in top_ex.args
      # dump(ex)
      push!(eq, ex)
      if isexpr(ex, :(=), 2) && typeof(ex.args[1]) == Symbol
         symbol = ex.args[1]
         if in(symbol, namesSet)
            error("\nfrom @forceElement ", force_symbol, ": Left hand side symbol in",
                  "\n   ", ex,
                  "\nis defined twice.")
         end
         push!(namesSet, symbol)
         push!(eq, :( ModiaMath.initComponent!($this,$symbol,$(quot(symbol)) ) ) )
     end
   end
   names = collect(namesSet)

   # Build:
   #   mutable struct force_symbol <: ModiaMath.AbstractComponentWithVariables
   #      _internal::ModiaMath.ComponentInternal
   #      names[1]
   #      names[2]
   #       ...
   #      function force_symbol(...)
   #         $(eq...)
   #         ModiaMath.initComponentInternal!( new(...) )
   #      end
   #   end
   code = :(mutable struct $force_symbol <: Modia3D.AbstractForceTorque; end)
   # println("code.args = ", code.args)
   code.args[3].args = vcat( :( _internal::ModiaMath.ComponentInternal ), names )

   code2 = code.args[3].args
   push!(code2, :(function fc(); end) )
   i = length(code2)
   code2[i].args[1] = fc

   # Generate constructor function
   code2[i].args[2] = quote
      $(eqInternal...)
      $(helpArray...)
      $(eq...)
      return $this
   end

   # Print generated function (without "#....\n")
   # println( replace(sprint(showcompact,code) , r"# .*\n", "\n") )
   # println( sprint(showcompact,bcode) )

   return esc(code)
end


using NeXLCore
using Test

@testset "Staging" begin
    stg_coords = Dict(:X=>10.0, :Y=>5.0)
    img_coords = Dict(:X=>0.027777, :Y=>-0.016666 )
    rot = deg2rad(50.0)
    i2s = image2stage(NeXLCore.DefaultStageMapping, stg_coords, img_coords, rot)
    s2i = stage2image(NeXLCore.DefaultStageMapping, stg_coords, i2s, rot)
    @test isapprox(s2i[:X], img_coords[:X], atol=1.0e-6)
    @test isapprox(s2i[:Y], img_coords[:Y], atol=1.0e-6)
end


__precompile__()

module MendelImpute

    using LinearAlgebra
    using GeneticVariation
    using VCFTools
    using SnpArrays
    using GroupSlices
    using Random
    using ProgressMeter
    using JLD2, FileIO, JLSO
    using Distances
    using LazyArrays

    # main functions that users are exposed to
    export phase               # main function for imputation and phasing
    export compress_haplotypes # for compressing haplotype panels
    export paint, composition, unique_populations # chromosome painting and admix proportions
    export convert_compressed  # for ultra-compression outputs

    include("compress.jl")
    include("data_structures.jl")
    include("phasing.jl")
    include("haplotype_pair.jl")
    include("haplotype_rescreen.jl")
    include("haplotype_thinning.jl")
    include("haplotype_stepscreen.jl")
    include("dynamic_programming.jl")
    include("impute.jl")
    include("breakpoints.jl")
    include("intersect.jl")
    include("painting.jl")
    include("ultra_compress.jl")

end # module


#!/usr/bin/julia

#cont-addition.jl
#continuous testing of julia addition directives
include("../../unum.jl")
using Unums

function erand()
  u = convert(Unum{4,6}, exp(100 * randn()) * (rand() < 0.5 ? -1 : 1))
  #flip the ubit randomly
  unum_unsafe(u, u.flags | (rand() < 0.5 ? Unums.UNUM_UBIT_MASK : zero(UInt16)))
end

function to_big(u)
  typeof(u) <: Ubound && return calculate(u.lowbound)
  calculate(u)
end

sumsums = 0
sumcrash = 0
sumerror = 0

while true

  u1 = erand()
  f3 = to_big(u1)

  for idx = 1:100

    f1 = f3
    u2 = erand()
    f2 = to_big(u2)
    u3 = u1

    try
      u3 = u1 + u2

      f3 = to_big(u3)

      if (abs(f3 - (f1 + f2)) > (abs(f3) * 0.000000001))
        println("$f3 != $(f1 + f2) = $f1 + $f2, diff: $(f3 - f1 - f2)")
        println("x1:  $(u1)")
        println("x2:  $(u2)")
        println("x2:  $(u3)")
        println("----")
        sumerror += 1
      end
    catch
      println("failure in addition")
      println(u1)
      println(u2)
      bt = catch_backtrace()
      s = sprint(io->Base.show_backtrace(io, bt))
      println("$s")
      println("----")
      sumcrash += 1
    end

    sumsums += 1

    u1 = u3
  end
  println("$(100 - 100*(sumerror + sumcrash)/sumsums)% correct, $(100 * sumerror/sumsums)% error, $(100 * sumcrash/sumsums)% fatal")
end


export
    set_func_callback,
    set_grad_callback,
    set_hess_callback,
    # set_newpt_callback,
    # set_ms_process_callback,
    set_mip_node_callback #,
    # set_ms_initpt_callback,
    # set_puts_callback,

const callback_params = (Cint, Cint, Cint, Cint, Cint, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Nothing})

"""
Set the callback function that evaluates `obj` and `c` at `x`.

It may also evaluate `objGrad` and `jac` if EVALFC and EVALGA are
combined into a single call.
"""
function set_func_callback(kp::KnitroProblem, f::Function)
    cb = quote
        @cfunction($f, Cint, (Cint, Cint, Cint, Cint, Cint, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Nothing}))
    end
    return_code = @ktr_ccall(set_func_callback, Int32, (Ptr{Nothing},
                                                        Ptr{Nothing}), kp.env, eval(cb))
    if return_code != 0
        error("KNITRO: Error setting function callback")
    end
end

# /**
#  *  Do not modify "hessian" or "hessVector".
#  */
# int  KNITRO_API KTR_set_grad_callback (KTR_context_ptr       kc,
#                                        KTR_callback * const  fnPtr);

"""
Set the callback function that evaluates `objGrad` and `jac` at `x`.

It may do nothing if EVALFC and EVALGA are combined into a single call.
"""
function set_grad_callback(kp::KnitroProblem, f::Function)
    cb = quote
        @cfunction($f, Cint, (Cint, Cint, Cint, Cint, Cint, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Nothing}))
    end
    return_code = @ktr_ccall(set_grad_callback, Int32,(Ptr{Nothing},
                                                       Ptr{Nothing}), kp.env, eval(cb))
    if return_code != 0
        error("KNITRO: Error setting gradient callback")
    end
end

"""
Set the callback function that evaluates second derivatives at (x, lambda).

If `evalRequestCode` equals KTR_RC_EVALH, then the function must
return nonzeroes in `hess`.  If it equals KTR_RC_EVALHV, then the
function multiplies second derivatives by `hessVector` and returns the
product in `hessVector`.
"""
function set_hess_callback(kp::KnitroProblem, f::Function)
    cb = quote
        @cfunction($f, Cint, (Cint, Cint, Cint, Cint, Cint, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Nothing}))
    end
    return_code = @ktr_ccall(set_hess_callback, Int32, (Ptr{Nothing},
                                                        Ptr{Nothing}), kp.env, eval(cb))
    if return_code != 0
        error("KNITRO: Error setting hessian callback")
    end
end

"""
Set the callback function that is invoked after KNITRO computes a
new estimate of the solution point (i.e., after every major iteration).
"""
function set_newpt_callback(kp::KnitroProblem, f::Function)
    cb = quote
        @cfunction($f, Cint, (Ptr{Nothing}, Cint, Cint, Cint, Ptr{Cdouble},
                   Ptr{Cdouble}, Cdouble, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Nothing}))
    end
    return_code = @ktr_ccall(set_newpt_callback, Int32, (Ptr{Nothing},
                                                         Ptr{Nothing}), kp.env, eval(cb))
    if return_code != 0
        error("KNITRO: Error setting newpoint callback")
    end
end

"""
This callback function is for multistart (MS) problems only.

Set the callback function that is invoked after KNITRO finishes
processing a multistart solve.  The function should not modify any
KNITRO arguments.  Arguments `x` and `lambda` contain the solution from
the last solve. Arguments `obj` and `c` contain objective and constraint
values at `x`.  First and second derivative arguments are not currently
defined and should not be examined.
"""
function set_ms_process_callback(kp::KnitroProblem, f::Function)
    cb = quote
        @cfunction($f, Cint, (Cint, Cint, Cint, Cint, Cint, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Nothing}))
    end
    return_code = @ktr_ccall(set_ms_process_callback, Int32, (Ptr{Nothing},
                             Ptr{Nothing}), kp.env, eval(cb))
    if return_code != 0
        error("KNITRO: Error setting multi-start callback")
    end
end

"""
This callback function is for mixed integer (MIP) problems only.

Set the callback function that is invoked after KNITRO finishes
processing a node on the branch-and-bound tree.
"""
function set_mip_node_callback(kp::KnitroProblem, f::Function)
    cb = quote
        @cfunction($f, Cint, (Cint, Cint, Cint, Cint, Cint, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Nothing}))
    end
    return_code = @ktr_ccall(set_mip_node_callback, Int32, (Ptr{Nothing},
                                                            Ptr{Nothing}), kp.env, eval(cb))
    if return_code != 0
        error("KNITRO: Error setting MIP node callback")
    end
end

"""
This callback function is for multistart (MS) problems only.

Set the callback that allows applications to specify an initial point
before each local solve in the multistart procedure.
"""
function set_ms_initpt_callback(kp::KnitroProblem, f::Function)
    cb = quote
        @cfunction($f, Cint, (Cint, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble},
                   Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Nothing}))
    end
    return_code = @ktr_ccall(set_ms_initpt_callback, Int32, (Ptr{Nothing},
                                                             Ptr{Nothing}), kp.env, eval(cb))
    if return_code < 0
        error("KNITRO: Error setting multi-start initial-point callback")
    end
end

"""
Set a put-string callback function to handle output generated by the KNITRO solver.

The callback function should return the number of characters that were printed.

By default KNITRO prints to stdout or a file named `knitro.log`,
as determined by KTR_PARAM_OUTMODE.
"""
function set_puts_callback(kp::KnitroProblem, f::Function)
    cb = quote
        @cfunction($f, Cint, (Ptr{Cchar}, Ptr{Nothing}))
    end
    return_code = @ktr_ccall(set_puts_callback, Int32, (Ptr{Nothing},
                             Ptr{Nothing}), kp.env, eval(cb))
    if return_code < 0
        error("KNITRO: Error setting put-string callback")
    end
end


export
    WindowCreator,
    create_window,
    SyncWindow,
    AsyncWindow,
    CoherentWindow

"""
    abstract type WindowCreator end

A type that identifies how to create windows of instances for filtering algorithms.

T represents the time type.

Must implement `create_window`.
"""

abstract type WindowCreator{T} end

struct SyncWindow <: WindowCreator{Int} end

"""
    create_window(::SyncWindow, runtime::Runtime, variables::Vector{<:Variable}, time::Int)::Vector{Instance}

Creates a window by instantiating all variables at all intermediate time steps from the earliest parent to the given time.
The `variables` argument is ignored.
"""
function create_window(::SyncWindow, runtime::Runtime, variables_to_sample::Vector{<:Variable}, time::Int)::Vector{Instance}
    prevtime = time
    net = get_network(runtime)
    ord = topsort(get_transition_graph(net))
    for v in variables_to_sample
        pars = get_transition_parents(net, v)
        for p in pars
            t = get_time(latest_instance_before(runtime, p, time, p != v))
            prevtime = min(prevtime, t)
        end
    end

    insts = Instance[]
    for n in ord
        ph = Placeholder{output_type(n)}(n.name)
        push!(insts, PlaceholderInstance(ph, prevtime))
    end
    for t in prevtime+1:time
        for n in ord
            push!(insts, ensure_instance!(runtime, n, t))
        end
    end
    return insts
end

struct AsyncWindow{T <: Number} <: WindowCreator{T} end

"""
    create_window(::AsyncWindow, runtime::Runtime, variables::Vector{<:Variable}, time::Int)::Vector{Instance}

Creates a window by instantiating only the given variables at the given time.
"""

function create_window(::AsyncWindow{T}, runtime::Runtime, variables::Vector{<:Variable}, time::T)::Vector{Instance} where T
    insts = Instance[]
    done = Set{Variable}()
    for v in variables
        for p in get_transition_parents(get_network(runtime), v)
            if !(p in done)
                parinst = latest_instance_before(runtime, p, time, true) # changed false to true!!!
                partime = get_time(parinst)
                ph = Placeholder{output_type(p)}(p.name)
                phinst = PlaceholderInstance(ph, partime)
                push!(insts, phinst)
                push!(done, p)
            end
        end
        push!(insts, ensure_instance!(runtime, v, time))
        push!(done, v)
    end
    return insts
end

"""
    struct CoherentWindow <: WindowCreator end

A variant of AsyncWindow that ensures that parent values are never stale for any variable that
gets updated in a filter step. In other words, if any parent of a direct parent has been updated more recently than a variable
to be updated, the direct parent is added to the variables to be updated. This condition then recurses for the direct parents.
"""
struct CoherentWindow{T <: Number} <: WindowCreator{T} end

function create_window(::CoherentWindow{T}, runtime::Runtime, variables::Vector{<:Variable}, time::T)::Vector{Instance} where T
    # Note: This method does not allow placeholder parents of dynamic variables
    net = get_network(runtime)
    parents = get_transition_graph(net)
    fullvars = Set{Variable}()
    order = topsort(parents)
    times = Dict([(n, get_time(current_instance(runtime, n))) for n in order])

    function ensure(v)
        if !(v in fullvars)
            push!(fullvars, v)
            for anc in ancestors(parents, v, Set{Node}())
                for grandanc in get(parents, anc, [])
                    if times[grandanc] > times[anc]
                        ensure(anc)
                        break
                    end
                end
            end
            times[v] = time # Need to do this to ensure instantiation through a chain of dependencies
        end
    end

    for var in variables
        ensure(var)
    end
    # We must make sure variables get sampled in the correct order to maintain coherence
    orderedvars = Variable[]
    for v in order
        if v in fullvars
            push!(orderedvars, v)
        end
    end

    create_window(AsyncWindow{T}(), runtime, orderedvars, time)
end


using Blink
using Base.Test

cleanup = !AtomShell.isinstalled()

cleanup && AtomShell.install()

# open window and wait for it to initialize
w = Window(Blink.@d(:show => false)); sleep(5.0)

# make sure the window is really active
@test_approx_eq @js(w, Math.log(10)) log(10)

@test sprint(Blink.jsexpr, :(Dict("a" => 1, :b => 10))) == "{\"a\":1,b:10}"

cleanup && AtomShell.remove()


for (T, funcpairs) in (
    (Mag, (
        (:isfinite, :_is_finite),
        (:isinf, :_is_inf),
        (:isspecial, :_is_special),
        (:iszero, :_is_zero),)),
    (Arf, (
        (:isfinite, :_is_finite),
        (:isinf, :_is_inf),
        (:isinteger, :_is_int),
        (:isnan, :_is_nan),
        (:isneginf, :_is_neg_inf),
        (:isnormal, :_is_normal),
        (:isone, :_is_one),
        (:isposinf, :_is_pos_inf),
        (:isspecial, :_is_special),
        (:iszero, :_is_zero),
        )),
    (Arb, (
        (:isexact, :_is_exact),
        (:isfinite, :_is_finite),
        (:isinteger, :_is_int),
        (:isnegative, :_is_negative),
        (:isnonnegative, :_is_nonnegative),
        (:isnonpositive, :_is_nonpositive),
        (:isnonzero, :_is_nonzero),
        (:isone, :_is_one),
        (:ispositive, :_is_positive),
        (:iszero, :_is_zero),
    )),
    (Acb, (
        (:isexact, :_is_exact),
        (:isfinite, :_is_finite),
        (:isinteger, :_is_int),
        (:isone, :_is_one),
        (:isreal, :_is_real),
        (:iszero, :_is_zero),
    ))
    )
    for (jlf, arbsuffix) in funcpairs
        arbf = Symbol(cprefix(T), arbsuffix)
        @eval begin
            function $jlf(x::$T)
                return !iszero(ccall(@libarb($arbf), Cint, (Ref{$T},), x))
            end
        end
    end
end

for ArbT in (Arf, Arb, Acb, Mag)
    arbf = Symbol(cprefix(ArbT), :_equal)
    @eval begin
        function Base.isequal(x::$ArbT, y::$ArbT)
            res = ccall(@libarb($arbf), Cint, (Ref{$ArbT}, Ref{$ArbT}), x, y)
            return !iszero(res)
        end
    end

    ArbT == Mag && continue

    arbf = Symbol(cprefix(ArbT), :_equal_si)
    @eval begin
        function Base.isequal(x::$ArbT, y::Int)
            res = ccall(@libarb($arbf), Cint, (Ref{$ArbT}, Clong), x, y)
            return !iszero(res)
        end
        Base.isequal(y::Int, x::$ArbT) = isequal(x,y)
    end
end

Base.:(==)(x::Arf, y::Arf) = isequal(x, y)
Base.:(==)(x::Arf, y::Int) = isequal(x, y)
Base.:(==)(y::Int, x::Arf) = isequal(x, y)

for (suffix, jltype, ctype) in (
        ("",   Arf, Ref{Arf}),
        (:_si, Int, Clong),
        (:_ui, UInt, Culong),
        (:_d,  Float64, Cdouble)
        )
    arbf = Symbol(:arf_cmp, suffix)
    @eval begin
        function Base.isless(x::Arf, y::$jltype)
            res = ccall(@libarb($arbf), Cint, (Ref{Arf}, $ctype), x, y)
            return res < 0
        end
        Base.:(<)(x::Arf, y::$jltype) = isless(x, y)
        Base.:(<=)(x::Arf, y::$jltype) = isequal(x,y) | isless(x,y)
    end
end

for (ArbT, args) in (
    (Arb, (
        (:(==), :_eq), (:(!=), :_ne),
        (:(<),  :_lt), (:(<=), :_le),
        (:(>),  :_gt), (:(>=), :_ge),
        )),
    (Acb, (
        (:(==), :_eq), (:(!=), :_ne),
        )),
    )
    for (jlf, suffix) in args
        arbf = Symbol(cprefix(ArbT), suffix)
        @eval begin
            function Base.$jlf(x::$ArbT, y::$ArbT)
                res = ccall(@libarb($arbf), Cint, (Ref{$ArbT}, Ref{$ArbT}), x, y)
                return !iszero(res)
            end
        end
    end
end


## Functions to plot SimpleSDMLayer elements more easily

# Plot layer as a heatmap with worldmap background
"""
    plot_layer(layer::SimpleSDMLayer; shape=true, kw...)

Plots the layer over a grey background based on a WorldClim layer. Note that the
warning about multiple series sharing a color bar is normal. If `shape=true`, it
will also draw a shape countour around the land based on the shapefile return by
`worldmap` (this is slower). Normal plot options can be passed afterwards.
"""
function plot_layer(layer::SimpleSDMLayer; shape=true, kw...)
    ## Arguments
    # layer: SimpleSDMLayer to plot
    # scatter: add observations as points in scatter plot, requires to define occ
    # occ: observations to represent if scatter=true
    # kw: optional plotting arguments

    # Create background layer
    coords = boundingbox(layer)
    baselayer = similar(SimpleSDMPredictor(WorldClim, BioClim, 1; coords...))

    # Create empty plot
    sdm_plot = heatmap(baselayer; c=:lightgrey, lab="", msw=0.0, ms=0.0, frame=:box)

    # Add SDM output as heatmap
    heatmap!(
        sdm_plot,
        layer;
        legend=true,
        clim=extrema(layer), # colorbar limits
        kw..., # additional keyword arguments
    )

    # Add shape countour around land
    if shape
        # Load & clip worldmap background to SimpleSDMLayer (from shp in /assets folder)
        worldmap = clip(worldshape(50), layer)
        # Redraw polygons' outer lines over heatmap values
        for p in worldmap # loop for each polygon
            # Get outer lines coordinates
            xy = map(x -> (x.x, x.y), p.points)
            # Add outer lines to plot
            plot!(sdm_plot, xy; c=:grey, lab="")
        end
    end

    return sdm_plot
end

# Custom plot recipe for layers
@recipe function plot(layer::T) where {T<:SimpleSDMLayer}
    seriestype --> :heatmap
    if get(plotattributes, :seriestype, :heatmap) in [:heatmap, :contour]
        aspect_ratio --> 92.60 / 60.75
        xlims --> (layer.left, layer.right)
        ylims --> (layer.bottom, layer.top)
        xguide --> "Longitude"
        yguide --> "Latitude"
        lg = copy(layer.grid)
        lg[lg .== nothing] .= NaN
        longitudes(layer), latitudes(layer), lg
    elseif get(plotattributes, :seriestype, :histogram) in [:histogram, :density]
        filter(!isnothing, layer.grid)
    end
end


using GAFramework, GAFramework.MagnaGA
using LightGraphs, SparseArrays
using Random
using Statistics
using DelimitedFiles
#GAFramework.setthreads(false)

"""
Return line after skipping if pred(line) is true
Default pred: after skipping empty lines and commented lines
"""
function readskipping(fd::IO,
    pred=line -> length(line)==0 || line[1]=='#' || line[1]=='%')
    while !eof(fd)
        line = strip(readline(fd))
        if pred(line); continue; end
        return line
    end
    return ""
end

"""
    readgw(fd::IO)
    readgw(file::AbstractString) -> SparseMatrixCSC, node list
Reads LEDA format file describing a network. Outputs an undirected network.
An example of a LEDA file is in the examples/ directory.
"""
function readgw(fd::IO)
    line = readskipping(fd)
    strip(line)=="LEDA.GRAPH" || error("Error in line: $line")
    for i = 1:3; readskipping(fd); end
    nverts = parse(Int,readskipping(fd))
    vertices = Array{String}(undef, nverts)
    for i = 1:nverts
        line = readskipping(fd)
        vertices[i] = match(r"\|{(.*?)}\|",line).captures[1]
    end
    nedges = parse(Int,readskipping(fd))
    I = Vector{Int}(undef, nedges)
    J = Vector{Int}(undef, nedges)
    for i = 1:nedges
        line = readskipping(fd)
        caps = match(r"(\d+) (\d+) 0 \|{.*}\|",line).captures
        n1 = parse(Int,caps[1])
        n2 = parse(Int,caps[2])
        n1==n2 && continue
        I[i] = n1
        J[i] = n2
    end
    G = sparse(vcat(I,J), vcat(J,I), 1, nverts, nverts, max)
    return G, vertices
end
readgw(file::AbstractString, args...) = open(fd -> readgw(fd,args...), file, "r")

function main()
    n1 = ARGS[1]
    n2 = ARGS[2]
    sim = ARGS[3]
    alpha = parse(Float64, ARGS[4])
    npop = parse(Int, ARGS[5])
    ngen = parse(Int, ARGS[6])
    elite_fraction = parse(Float64, ARGS[7])
    out = ARGS[8]

    G1,verts1 = readgw(n1)
    G2,verts2 = readgw(n2)

    S,_ = readdlm(sim, header=true)

    model = MagnaModel(G1,G2,S,alpha)
    st = GAState(model, ngen=2, npop=2, elite_fraction=0.5)
    ga!(st)

    model = MagnaModel(G1,G2,S,alpha)
    st = GAState(model, ngen=ngen, npop=npop, elite_fraction=elite_fraction)
    @time ga!(st)

    r = st.pop[1]
    verts2map = verts2[r.f]
    correctness = mean(verts1 .== verts2map[1:length(verts1)])
    open("$(out)_final_alignment.txt", "w") do fd
        for i in 1:length(verts1)
            println(fd, "$(verts1[i]) $(verts2map[i])")
        end
    end
    function print_stats(fd)
        println(fd, "generation final")
        println(fd, "score $(r.score)")
        println(fd, "node_score $(r.node_score)")
        println(fd, "S3_score $(r.edge_score)")
        println(fd, "node_correctness $(correctness)")
    end
    print_stats(stdout)
    open(print_stats, "$(out)_final_stats.txt", "w")
end

main()


"""Functions to run firing and hebbian plasticity updates
on the `AssemblyCalculus` types defined in `types.jl`

Author: DJ Passey
Email: djpasseyjr@gmail.com
"""

## SIMULATION WRAPPERS

function simulate!(
    stims::Array{Stimulus{T}, 1},
    assemblies::Array{Assembly{T}, 1},
    stop_criteria::StopCriteria;
    random_initial::Bool = true
) where T
    # TODO Validate stims and assemblies
    active_areas = [inp.area for inp in stims]
    # Consolidate stim current with current from assemblies
    currents, assem_attrib = simulation_currents(stims, assemblies)
    # Save initial stim currents
    init_currents = deepcopy(currents)
    # Assign random neurons to fire initially
    random_initial && map(random_firing!, active_areas)
    # Allocate firing and stim current arrays for each area
    firing_and_currents = deepcopy(currents)
    # stop_criteria checks for convergence
    while ! stop_criteria(active_areas, currents)
        # All neurons fire and modulate firing_and_currents
        attrib = fire!(active_areas, firing_and_currents)
        # Compute k winners based on firing and stimulus current vectors
        map(winners!, active_areas, firing_and_currents)
        # Hebbian update for synapses
        map(area -> hebb_update!(area, attrib), active_areas)
        # Hebbian update for currents
        map(hebb_update!, active_areas, currents)
        # Reset firing_and_currents to exclude firing data
        firing_and_currents = deepcopy(currents)
    end
    # Hebbian update to assembly synapses
    map(x -> hebb_update!(assem_attrib, x...), zip(stims, currents, init_currents))
    final_assemblies = map(x -> Assembly(x, stims, assemblies), active_areas)
    return final_assemblies
end

function simulate!(
    stims::Array{Stimulus{T}, 1},
    assemblies::Array{Assembly{T}, 1},
    timesteps::Int;
    random_initial::Bool = true
) where T
    stop_criteria = MaxIters(max_iters=timesteps, record=true)
    new_assemblies = simulate!(stims, assemblies, stop_criteria, random_initial=random_initial)
    return new_assemblies, stop_criteria.spikes
end

function simulate!(
    stims::Array{Stimulus{T}, 1},
    timesteps::Int;
    random_initial::Bool = true
) where T
    return simulate!(stims, Assembly{T}[], timesteps; random_initial=random_initial)
end

function simulate!(
    stims::Array{Stimulus{T}, 1},
    assemblies::Array{Assembly{T}, 1};
    record = true,
    tol::Int = 2,
    fire_past_convergence::Int = 0,
    max_iters::Int = 50,
    random_initial::Bool = true
) where T
    stop_criteria = Converge(
        tol=tol,
        fire_past_convergence=fire_past_convergence,
        max_iters=max_iters,
        record=record
    )
    new_assemblies = simulate!(stims, assemblies, stop_criteria, random_initial=random_initial)
    if record
        return new_assemblies, stop_criteria.spikes, stop_criteria.distances
    else
        return new_assemblies
    end
end

function simulate!(
    stims::Array{Stimulus{T}, 1};
    record = true,
    tol::Int = 2,
    fire_past_convergence::Int = 0,
    max_iters::Int = 50,
    random_initial::Bool = true
) where T
    return simulate!(
                stims,
                Assembly{T}[],
                record=record,
                tol=tol,
                fire_past_convergence=fire_past_convergence,
                max_iters=max_iters,
                random_initial=random_initial
            )
end
"""
    simulation_currents(
        stims::Array{Stimulus{T}, 1},
        assemblies::Array{Assembly{T}, 1}
    ) where T -> currents, attribution

Computes the ionic current flow into each area and creates an attribution
graph to be used for ion current and assembly plasticity updates.

**Parameters**

1. `stims` The list of IonCurrents to be used in the simulation
2. `assemblies` The list of active Assemblies for the simulation

"""
function simulation_currents(
    stims::Array{Stimulus{T}, 1},
    assemblies::Array{Assembly{T}, 1} = Assembly{T}[]
) where T
    # Allocate
    currents = [zeros(T, size(inp.currents)) for inp in stims]
    attrib = neuron_attrib_graph(T, typeof(stims[1].area))
    # Compute currents and attributions
    for (i, inp) in enumerate(stims)
        currents[i] += inp.currents
        target_area = inp.area
        # Fire each assembly into the target area
        for assem in assemblies
            fire!(assem, target_area, currents[i], attrib)
        end
    end
    return currents, attrib
end

## FIRE! FUNCTIONS

"""
    fire!(
        assembly::Assembly{T},
        area::BrainArea{T},
        currents::Array{T, 1},
        attrib::AttributionGraph{Neuron{T, U}}
    ) where {T, U}

Fires each neuron in `assembly` into the target area. Adds resulting ion
currents to corresponding neuron indexes in `current` array.
"""
function fire!(
    assembly::Assembly{T},
    area::BrainArea{T},
    currents::Array{T, 1},
    attrib::AttributionGraph{Neuron{T, U}}
) where {T, U}
    # Fire each neuron in the assembly into the target area
    for idx in assembly.neurons
        neuron = assembly.area.neurons[idx]
        fire!(neuron, area, currents, attrib)
    end
end

"""
    fire!(
        source_area::BrainArea{T},
        target_area::BrainArea{T},
        target_currents::Array{T, 1},
        attrib::AttributionGraph{Neuron{T, U}}
    ) where {T, U}

Fires each neuron in `source_area` into `target_area`. Adds resulting ion
currents to corresponding neuron indexes in `currents` array. Collects
neurons into the attribution graph.
"""
function fire!(
    source_area::BrainArea{T},
    target_area::BrainArea{T},
    target_currents::Array{T, 1},
    attrib::AttributionGraph{Neuron{T, U}}
) where {T, U}
    # Fire each neuron in the assembly into the target area
    for idx in source_area.firing
        neuron = source_area.neurons[idx]
        fire!(neuron, target_area, target_currents, attrib)
    end
end

"""
    fire!(
        neuron::Neuron{T, U},
        target_area::BrainArea{T},
        target_currents::Array{T, 1},
        attrib::AttributionGraph{Neuron{T, U}}
    ) where {T, U}

Fires neuron into the target area. Adds resulting ion
currents to corresponding neuron indexes in `currents` array. Adds neruon
to the `AttributionGraph`.
"""
function fire!(
    neuron::Neuron{T, U},
    target_area::BrainArea{T},
    target_currents::Array{T, 1},
    attrib::AttributionGraph{Neuron{T, U}}
) where {T, U}
    # For each synapse into the target area
    for (target_neuron, w) in get(neuron, target_area, Dict{Neuron{T}, T}())
        # Add ion current to the current array
        target_currents[target_neuron.idx] += w
        # Update attribution graph entry for target neuron
        contributors = get!(attrib, target_neuron)
        push!(contributors, neuron)
    end
end

"""
    fire!(
        areas::Array{<:BrainArea{T}, 1},
        area_currents::Array{Array{T, 1}}
    ) where T -> attrib

Fires all active neurons in each area. Collects ion currents in `currents`.
Returns an `AttributionGraph` of the firing.
"""

function fire!(areas::Array{U, 1}, area_currents::Array{Array{T, 1}}) where {T, U <:BrainArea{T}}
    attrib = neuron_attrib_graph(T, U)
    for source_area in areas
        for (target_area, target_currents) in zip(areas, area_currents)
            fire!(source_area, target_area, target_currents, attrib)
        end
    end
    return attrib
end

## HEBB_UPDATE! FUNCTIONS

"""
    hebb_update!(
        neuron::Neuron{T, U},
        attrib::AttributionGraph{Neuron{T, U}};
        pow::Int=1
    ) where {T, U}

Uses the attribution graph to find which synapses fired on to `neuron`
in the previous timestep. Increases these synapses multiplicatively according
to the plasticity of their parent neuron's area. The parameter `pow` controls
how many multiplicative updates to apply.
"""
function hebb_update!(
    neuron::Neuron{T, U},
    attrib::AttributionGraph{Neuron{T, U}};
    pow::T = T(1)
) where {T, U}
    source_neurons = get(attrib, neuron, Neuron{T, U}[])
    if length(source_neurons) != 0
        for source_neuron in source_neurons
            β = source_neuron.area.plasticity
            w = source_neuron[neuron.area][neuron]
            source_neuron[neuron.area][neuron] = w * (1 + β)^pow
        end
    end
end

"""
    hebb_update!(area::BrainArea{T}, attrib::AttributionGraph{Neuron{T, U}})

Uses the attribution graph to update the synapses pointing to all currently
firing neurons in `area`.
"""
function hebb_update!(area::BrainArea{T}, attrib::AttributionGraph{Neuron{T, U}}) where {T, U}
    map(n -> hebb_update!(n, attrib), area.neurons[area.firing])
end

"""
    hebb_update!(area::BrainArea{T}, current::Array{T, 1}) where T

Multiplicatively increases the current into currently firing neurons in `area`
by a factor of `(1 + area.plasticity)`.
"""
function hebb_update!(area::BrainArea{T}, current::Array{T, 1}) where T
    current[area.firing] .*= (1 + area.plasticity)
end

"""
    hebb_update!(
        attrib::AttributionGraph{Neuron{T, U}},
        stim::Stimulus{T},
        currents::Array{T, 1},
        init_currents::Array{T, 1}
    ) where {T, U}

Updates the assembly synapse weights all at once after the simulation is
finished.

Since the contribution of these synapses is included in the simulation current,
we can update the synapses once the simulation is finished based on changes in
the current array due to plasticity. This is faster than updating them each
timestep.
"""
function hebb_update!(
    attrib::AttributionGraph{Neuron{T, U}},
    ic::Stimulus{T},
    currents::Array{T, 1},
    init_currents::Array{T, 1},
) where {T, U}
    times_grown = _find_growth(currents, init_currents, ic.area.plasticity)
    # Update assembly synapses
    for (i, pow) in enumerate(times_grown)
        if pow > 0
            ic.currents[i] *= (1 + ic.area.plasticity) ^ pow
            neuron = ic.area.neurons[i]
            hebb_update!(neuron, attrib, pow=pow)
        end
    end
end

"""Computes the number of multiplicative updates applied to the ion
currents during the simulation.
"""
function _find_growth(
    currents::Array{T, 1},
    init_currents::Array{T, 1},
    plasticity::T
) where T
    # Find nonzero ion currents
    nonzero = findall(currents .!= T(0))
    nonzero == nothing && return zeros(T, size(currents))
    # Compute multiplicative change from the initial current
    change = currents[nonzero] ./ init_currents[nonzero]
    # Compute how many multiplicative updates occured given the plasticity
    nz_times_grown = log.(change) ./ log(1 + plasticity)
    times_grown = zeros(T, size(currents))
    times_grown[nonzero] .= nz_times_grown
    return times_grown
end


using ACEbonds
using Test

@testset "ACEbonds.jl" begin
    # Write your tests here.
    @testset "Bonds basics" begin include("test_bonds.jl"); end
    @testset "Invariant Cylindrical" begin include("test_invcyl.jl"); end
    @testset "Calculator" begin include("test_bondpot.jl"); end
end


function FieldContinueOnEvent()
    return FieldContinueOnEvent(())
end

function event_occurred(obj::FieldContinueOnEvent, arg0::FieldSpacecraftState, arg1::FieldEventDetector, arg2::jboolean)
    return jcall(obj, "eventOccurred", Action, (FieldSpacecraftState, FieldEventDetector, jboolean), arg0, arg1, arg2)
end

function init(obj::FieldEventHandler, arg0::FieldSpacecraftState, arg1::FieldAbsoluteDate)
    return jcall(obj, "init", void, (FieldSpacecraftState, FieldAbsoluteDate), arg0, arg1)
end

function reset_state(obj::FieldEventHandler, arg0::FieldEventDetector, arg1::FieldSpacecraftState)
    return jcall(obj, "resetState", FieldSpacecraftState, (FieldEventDetector, FieldSpacecraftState), arg0, arg1)
end