using BenchmarkTools, Plots, Plots.PlotMeasures, LaTeXStrings
using BenchmarkTools: minimum, median

SUITE = BenchmarkGroup()
SUITE["Build"] = BenchmarkGroup()

# ==============================================================================
# Decomposition-based approach
# ==============================================================================
include("building_GLGM06.jl")

# ==============================================================================
# Execute benchmarks and save benchmark results
# ==============================================================================

# # tune parameters
# tune!(SUITE)
#
# # run the benchmarks
# results = run(SUITE, verbose=false)
#
# # return the sample with the smallest time value in each test
# println("minimum: ", minimum(results))
#
# # return the median for each test
# println("median: ", median(results))

# ==============================================================================
# Create plots
# ==============================================================================

function plot_dashed_line!(v, time_horizon=20.)
    plot!(x->x, x->v, 0.0, time_horizon, line=2, color="red", linestyle=:dash,
          legend=nothing)
end

plot_vars = (0, 25)

res = solve(build_TV, alg=algo_dense, T=time_horizon)

plot(res,
     vars=plot_vars,
     tickfont=font(30, "Times"), guidefontsize=45,
     xlab=L"t",
     ylab=L"x_{25}",
     xtick=[0., 0.5, 1.], ytick=[-6e-3, -3e-3, 0., 3e-3, 6e-3],
     xlims=(0., 1.), ylims=(-9e-3, 6e-3),
     bottom_margin=0mm, left_margin=1mm, right_margin=6mm, top_margin=3mm,
     size=(1000, 1000))
plot_dashed_line!(4e-3)
plot_dashed_line!(5.1e-3)
savefig(@relpath "BLDF01_time_horizon_1.png")

# plot(res,
#      vars=plot_vars,
#      tickfont=font(30, "Times"), guidefontsize=45,
#      xlab=L"t",
#      ylab=L"x_{25}",
#      xtick=[0., 10., 20.], ytick=[-6e-3, -3e-3, 0., 3e-3, 6e-3],
#      bottom_margin=6mm, left_margin=2mm, top_margin=3mm,
#      ylims=(-Inf, 6e-3),
#      size=(1000, 1000))
# plot_dashed_line!(4e-3)
# plot_dashed_line!(5.1e-3)
# savefig(@relpath "BLDF01_time_horizon_20.png")
#
# res = solve(build_CONST, algo=algo_dense, T=time_horizon)
#
# plot(res,
#      vars=plot_vars,
#      tickfont=font(30, "Times"), guidefontsize=45,
#      xlab=L"t",
#      ylab=L"x_{25}",
#      xtick=[0., 0.5, 1.], ytick=[-6e-3, -3e-3, 0., 3e-3, 6e-3],
#      xlims=(0., 1.), ylims=(-Inf, 6e-3),
#      bottom_margin=0mm, left_margin=1mm, right_margin=6mm, top_margin=3mm,
#      size=(1000, 1000))
# plot_dashed_line!(4e-3)
# plot_dashed_line!(5.1e-3)
# savefig(@relpath "BLDC01_time_horizon_1.png")
#
# plot(res,
#      vars=plot_vars,
#      tickfont=font(30, "Times"), guidefontsize=45,
#      xlab=L"t",
#      ylab=L"x_{25}",
#      xtick=[0., 10., 20.], ytick=[-6e-3, -3e-3, 0., 3e-3, 6e-3],
#      bottom_margin=6mm, left_margin=2mm, top_margin=3mm,
#      ylims=(-Inf, 6e-3),
#      size=(1000, 1000))
# plot_dashed_line!(4e-3)
# plot_dashed_line!(5.1e-3)
# savefig(@relpath "BLDC01_time_horizon_20.png")


abstract IntersectionEvent
#####under developing
type AllWayStop <: IntersectionEvent
    connections::Array{Tuple{Int,Int}}
    entrances::Array{Int}
    exits::Array{Int}

    function AllWayStop()
        new(Tuple{Int,Int}[],Int[],Int[])
    end

    function AllWayStop(connections::Array{Tuple{Int,Int}})
        entrances = []
        exits = []
        for connection in connections
            if isempty(find(entrances .== connection[1]))
                push!(entrances,connection[1])
            end
            if isempty(find(exits .== connection[2]))
                push!(exits,connection[2])
            end
        end
        new(connections,entrances,exits)
    end
end

function updateAllWayStops!(driver::DriverModel,allwaystops::Array{AllWayStop})
    driver.allwaystops = allwaystops
    driver
end

function get_distance_to_stop(veh::Vehicle,allwaystop::AllWayStop,roadway::Roadway)
    vehSegid = veh.state.posF.roadind.tag.segment
    vehLaneid = veh.state.posF.roadind.tag.lane
    vehTag = veh.state.posF.roadind.tag
    distance_to_stop = -1.0
    if !isempty(find(allwaystop.entrances .== vehSegid))
        lane = roadway[vehTag]
        cindS = curveindex_end(lane.curve)
        endPoint = lane.curve[cindS]
        distance_to_stop = endPoint.s - veh.state.posF.s
    end
    return distance_to_stop
end

function stop_before(v::Float64,distance_to_stop::Float64,horizon::Float64=MAX_HORIZON,model::IDMDriver = IDMDriver())
    s_min = CAR_LENGTH/2

    s_gap = distance_to_stop
    acc = Inf
    if s_gap < horizon
        if s_gap > 0.0
            Δv = 0.0 - v
            s_des = s_min + v*model.T - v*Δv / (2*sqrt(model.a_max*model.d_cmf))
            v_ratio = model.v_des > 0.0 ? (v/model.v_des) : 1.0
            acc = model.a_max * (1.0 - v_ratio^model.δ - (s_des/s_gap)^2)
        elseif s_gap > -CAR_LENGTH
            acc = -model.d_max
        else
            Δv = model.v_des - v
            acc = Δv*model.k_spd
        end

        if isnan(acc)

            warn("IDM acceleration was NaN!")
            if s_gap > 0.0
                Δv = veh_target.state.v - v
                s_des = model.s_min + v*model.T - v*Δv / (2*sqrt(model.a_max*model.d_cmf))
                println("\tΔv: ", Δv)
                println("\ts_des: ", s_des)
                println("\tv_des: ", model.v_des)
                println("\tδ: ", model.δ)
                println("\ts_gap: ", s_gap)
            elseif s_gap > -veh_ego.def.length
                println("\td_max: ", model.d_max)
            end

            acc = 0.0
        end
    else
        # no lead vehicle, just drive to match desired speed
        Δv = model.v_des - v
        acc = Δv*model.k_spd # predicted accel to match target speed
    end
    acc = clamp(acc, -model.d_max, model.a_max)
    return acc
end

function update_waitlist!(driver, new::Bool,ego_distance::Float64,allwaystop::AllWayStop, scene::Scene, roadway::Roadway, egoid::Int)
    vehicle_index = findfirst(scene, egoid)
    ego_veh = scene[vehicle_index]
    if new #add veh
        empty!(driver.allwaystop_waitlist)
        for (i,veh) in enumerate(scene)
            dist = get_distance_to_stop(veh,allwaystop,roadway)
            if dist > 0.0 && dist < ego_distance
                push!(driver.allwaystop_waitlist,i)
            end
        end
    else #delete veh
        new_waitlist = []
        for veh_ind in driver.allwaystop_waitlist
            veh = scene.entities[veh_ind]
            vehSegid = veh.state.posF.roadind.tag.segment
            if isempty(find(allwaystop.exits .== vehSegid)) #this veh is still in waiting zone
                push!(new_waitlist,veh_ind)
            end
        end
        driver.allwaystop_waitlist = new_waitlist
    end
end

function reactAllWayStop!(driver, scene::Scene, roadway::Roadway, egoid::Int)
    vehicle_index = findfirst(scene, egoid)
    ego_veh = scene[vehicle_index]
    acc = Inf
    for i = 1:length(driver.allwaystops)
        distance_to_stop = get_distance_to_stop(ego_veh,driver.allwaystops[i],roadway)
        if distance_to_stop > 0.0 && distance_to_stop < driver.allwaystop_horizon
            allwaystop = driver.allwaystops[i]
            if distance_to_stop < CAR_LENGTH #in the waiting zone
                if i == driver.allwaystop_ind
                    update_waitlist!(driver, false,distance_to_stop,allwaystop, scene, roadway, egoid)
                    if isempty(driver.allwaystop_waitlist) && driver.allwaystop_stopcount > driver.allwaystop_waittime #time to go!
                        return acc
                    else #wait
                        if abs(ego_veh.state.v) < 0.1
                            driver.allwaystop_stopcount += 1
                        end
                        acc = stop_before(ego_veh.state.v,distance_to_stop,driver.allwaystop_horizon,driver.mlon)
                        return acc
                    end
                else # encounter new waiting zone
                    driver.allwaystop_stopcount = 0
                    acc = stop_before(ego_veh.state.v,distance_to_stop,driver.allwaystop_horizon,driver.mlon)
                    driver.allwaystop_ind = i
                    update_waitlist!(driver, true,distance_to_stop,allwaystop, scene, roadway, egoid)
                    return acc
                end
            else #outside the waiting zone
                acc = stop_before(ego_veh.state.v,distance_to_stop,driver.allwaystop_horizon,driver.mlon)
                break
            end
        end
    end
    #no allwaystop is encounterd
    driver.allwaystop_ind = 0
    empty!(driver.allwaystop_waitlist)
    return acc
end

##########################################################
type TrafficLight <: IntersectionEvent
    color::Int #0 green 1 red 2 yellow
    pos::VecSE2
end

function updateTrafficLights!(driver::DriverModel,trafficlights::Array{TrafficLight})
    driver.trafficlights = trafficlights
    driver
end

function get_yellowlight_decisison(distance)
    decision = 0
    if distance > MAX_HORIZON
        decision = 0
    else
        prob = distance*0.7/MAX_HORIZON
        #println("distance is : ",distance, " prob is : ",prob)
        if rand() < prob
            decision = -1
        end
    end
    return decision
end

function reac_yellowlight(driver,scene,roadway,foreResult)
    acc = Inf
    if driver.yellowlight_decision == -1
        acc = reac_redlight(driver,scene,roadway,foreResult)
    end
    return acc
end

function reac_redlight(driver,scene,roadway,foreResult)
    s_min_old = driver.mlon.s_min
    driver.mlon.s_min = 2.0
    AutomotiveDrivingModelsOld.track_longitudinal!(driver.mlon, scene, roadway, 1, foreResult.ind)
    driver.mlon.s_min = s_min_old
    acc = rand(driver.mlon)
    return acc
end

function reactTrafficLights(driver, scene::Scene, roadway::Roadway, egoid::Int)
    vehicle_index = findfirst(scene, egoid)
    ego_veh = scene[vehicle_index]
    scene= Scene(length(driver.trafficlights)+1)
    push!(scene,Vehicle(ego_veh.state,
                        VehicleDef(1, AgentClass.CAR, CAR_LENGTH, CAR_WIDTH)))
    for i=1:length(driver.trafficlights)
        push!(scene,Vehicle(VehicleState(driver.trafficlights[i].pos, roadway, 0.0),
            VehicleDef(AgentClass.CAR, 1.0, 3.0),i))
    end
    foreResult = get_neighbor_fore_along_lane(scene, 1, roadway)
    light_ind = 0
    distance = 0.0
    acc = Inf
    if foreResult.ind != 0 && foreResult.Δs < MAX_HORIZON
        light_ind = foreResult.ind - 1
        distance = foreResult.Δs
        if driver.trafficlights[light_ind].color == 2
            #println("light_id : ",light_ind," driver.yellowlight_id: ",driver.yellowlight_id)
            if light_ind == driver.yellowlight_id
                #println("continue previous decision ", driver.yellowlight_decision)
            else
                driver.yellowlight_id = light_ind
                driver.yellowlight_decision = get_yellowlight_decisison(distance)
            end
            acc = reac_yellowlight(driver,scene,roadway,foreResult)
        else
            driver.yellowlight_decision = 0
            driver.yellowlight_id = 0
        end
        if driver.trafficlights[light_ind].color == 1
            acc = reac_redlight(driver,scene,roadway,foreResult)
        end
    end
    return acc
end


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Algorithm test for SGP4. All tests are based on [1].
#
#   Notice that only the tests related to the SGP4 in [1] are considered,
#   because the SDP4 (orbit propagator for deep space) are not available yet in
#   SatelliteToolbox.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
#
#   [1] Vallado, D. A., Crawford, P., Hujsak, R., Kelso, T. S (2006). Revisiting
#       Spacetrack Report #3: Rev1. AIAA.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

################################################################################
#                                   Test 01
################################################################################

@testset "Tests from the paper AIAA 2006-6753" begin
    # Read all TLEs that will be used to test.
    tles = read_tle("./sgp4_tests/sgp4_tests.tle")

    st_sgp4_result = []
    SGP4_results = []

    for tle in tles
        filename = @sprintf("./sgp4_tests/aiaa-2006-6753/sgp4_tle_%d_result.txt",
                            tle.sat_num)
        SGP4_results = readdlm(filename; comments=true)

        # Initialize the orbit propagator.
        sgp4d = sgp4_init(tle, sgp4_gc_wgs72)

        t = SGP4_results[:,1]
        @inbounds for k = 1:length(t)

            # Propagate the orbit.
            r_TEME, v_TEME = sgp4!(sgp4d, t[k])

            # Assemble the result vector.
            st_sgp4_result = vcat(t[k], r_TEME, v_TEME)

            # Compare the values.
            @test st_sgp4_result[1] == SGP4_results[k,1]
            @test st_sgp4_result[2] ≈  SGP4_results[k,2] atol=1e-8
            @test st_sgp4_result[3] ≈  SGP4_results[k,3] atol=1e-8
            @test st_sgp4_result[4] ≈  SGP4_results[k,4] atol=1e-8
            @test st_sgp4_result[5] ≈  SGP4_results[k,5] atol=1e-9
            @test st_sgp4_result[6] ≈  SGP4_results[k,6] atol=1e-9
            @test st_sgp4_result[7] ≈  SGP4_results[k,7] atol=1e-9
        end
    end
end


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md

#=
- read meshes from different formats
- reorder connectivity, create element sets, node sets, ...
- create partitions for parallel runs
- renumber elements / nodes
- maybe precheck for bad elements
- check surface normal direction in boundary elements
- orientation of 2d elements
- etc only topology related stuff
=#

importall Base

using JuliaFEM

type Mesh
    nodes :: Dict{Int64, Vector{Float64}}
    node_sets :: Dict{Symbol, Set{Int64}}
    elements :: Dict{Int64, Vector{Int64}}
    element_types :: Dict{Int64, Symbol}
    element_codes :: Dict{Int64, Symbol}
    element_sets :: Dict{Symbol, Set{Int64}}
    surfaces :: Dict{Symbol, Vector{Tuple{Int64, Symbol}}}
    surface_types :: Dict{Symbol, Symbol}
end

function Mesh()
    return Mesh(Dict(), Dict(), Dict(), Dict(), Dict(), Dict(), Dict(), Dict())
end

function add_node!(mesh::Mesh, nid::Int, ncoords::Vector{Float64})
    mesh.nodes[nid] = ncoords
end

function add_nodes!(mesh::Mesh, nodes::Dict{Int64, Vector{Float64}})
    for (nid, ncoords) in nodes
        add_node!(mesh, nid, ncoords)
    end
end

function add_node_to_node_set!(mesh::Mesh, set_name, nids...)
    if !haskey(mesh.node_sets, set_name)
        mesh.node_sets[set_name] = Set{Int64}()
    end
    push!(mesh.node_sets[set_name], nids...)
end

function add_element!(mesh::Mesh, elid::Int, eltype::Symbol, connectivity::Vector{Int64})
    mesh.elements[elid] = connectivity
    mesh.element_types[elid] = eltype
end

function add_elements!(mesh::Mesh, elements::Dict{Int64, Tuple{Symbol, Vector{Int64}}})
    for (elid, (eltype, elcon)) in elements
        add_element!(mesh, elid, eltype, elcon)
    end
end

function add_element_to_element_set!(mesh::Mesh, set_name, elids...)
    if !haskey(mesh.element_sets, set_name)
        mesh.element_sets[set_name] = Set{Int64}()
    end
    push!(mesh.element_sets[set_name], elids...)
end

function copy(mesh::Mesh)
    mesh2 = Mesh()
    mesh2.nodes = copy(mesh.nodes)
    mesh2.node_sets = copy(mesh.node_sets)
    mesh2.elements = copy(mesh.elements)
    mesh2.element_types = copy(mesh.element_types)
    mesh2.element_sets = copy(mesh.element_sets)
    return mesh2
end

function filter_by_element_id(mesh::Mesh, element_ids::Vector{Int64})
    mesh2 = copy(mesh)
    mesh2.elements = Dict()
    for elid in element_ids
        if haskey(mesh.elements, elid)
            mesh2.elements[elid] = mesh.elements[elid]
        end
    end
    return mesh2
end

function filter_by_element_set(mesh::Mesh, set_name)
    filter_by_element_id(mesh::Mesh, collect(mesh.element_sets[set_name]))
end

function create_element(mesh::Mesh, id::Int)
    connectivity = mesh.elements[id]
    element_type = JuliaFEM.(mesh.element_types[id])
    element = Element(element_type, connectivity)
    update!(element, "geometry", mesh.nodes)
    element.id = id
    return element
end

function create_elements(mesh::Mesh; element_type=nothing)
    element_ids = collect(keys(mesh.elements))
    if element_type != nothing
        filter!(id -> mesh.element_types[id] == element_type, element_ids)
    end
    elements = [create_element(mesh, id) for id in element_ids]
    return elements
end

function create_elements(mesh::Mesh, element_sets::Symbol...; element_type=nothing)
    if isempty(element_sets)
        element_ids = collect(keys(mesh.elements))
    else
        element_ids = Set{Int64}()
        for set_name in element_sets
            element_ids = union(element_ids, mesh.element_sets[set_name])
        end
    end

    if element_type != nothing
        filter!(id -> mesh.element_types[id] == element_type, element_ids)
    end

    elements = [create_element(mesh, id) for id in element_ids]
    return elements
end

function create_elements(mesh::Mesh, element_sets::AbstractString...; element_type=nothing)
    element_sets = map(parse, element_sets)
    return create_elements(mesh, element_sets...; element_type=element_type)
end

""" find npts nearest nodes from mesh and return id numbers as list. """
function find_nearest_nodes(mesh::Mesh, coords::Vector, npts=1)
    dist = Dict{Int64, Float64}()
    for (nid, c) in mesh.nodes
        dist[nid] = norm(coords-c)
    end
    s = sort(collect(dist), by=x->x[2])
    nd = s[1:npts] # [(id1, dist1), (id2, dist2), ..., (id_npts, dist_npts)]
    node_ids = [n[1] for n in nd]
    return node_ids
end

"""
Apply new node ordering to elements. In JuliaFEM same node ordering is used
than in ABAQUS and if mesh is parsed from FEM format with other node ordering
this can be used to reorder nodes.

Parameters
----------
mapping :: Dict{Symbol, Vector{Int}}
    e.g. :Tet10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

"""
function reorder_element_connectivity!(mesh::Mesh, mapping::Dict{Symbol, Vector{Int}})
    for (elid, eltype) in mesh.element_types
        haskey(mapping, eltype) || continue
        new_order = mapping[eltype]
        element_connectivity = mesh.elements[elid]
        new_element_connectivity = element_connectivity[new_order]
        mesh.elements[elid] = new_element_connectivity
    end
end

function JuliaFEM.Problem{P<:FieldProblem}(mesh::Mesh, ::Type{P}, name::AbstractString, dimension::Int64)
    problem = Problem{P}(name, dimension, "none", [], Dict(), Assembly(), P())
    problem.elements = create_elements(mesh, name)
    return problem
end

function JuliaFEM.Problem{P<:BoundaryProblem}(mesh::Mesh, ::Type{P}, name, dimension, parent_field_name)
    problem = Problem{P}(name, dimension, parent_field_name, [], Dict(), Assembly(), P())
    problem.elements = create_elements(mesh, name)
    return problem
end

"""
Swap surface element connectivity s.t. normals point outward
"""
function check_orientation!
    # TODO
end

"""
Partition model using METIS
"""
function partition_model!
    # TODO
end


const primes = [53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741]

# Layout (for capacity C, value size V)
# -   C bytes for used hash bucket marker
# -   C bytes for used collition bucket marker
# - 16C bytes for 2C key hash values
# -  8C bytes for 2C next entry values
# -  8C bytes for 2C value sizes
# - 2VC bytes for 2C values, each of max V bytes
#
# Note:
# - actual dict capacity is greater than the requested capacity (actual capacity = 2 * next higher value from primes)
# - with this scheme, loadfactor is around 0.5
struct ShmDict
    capacity::UInt32            # requested capacity (actual capacity > 2 * requested capacity)
    maxvalsize::UInt32          # max bytes in value (or value + key if keeping keys)
    maxkeysize::UInt8           # max bytes in key
    keepkeys::Bool              # whether to keep keys along with values (used for exact comparison, take up space from value buffer)
    shmid::Cint                 # shm id
    shmptr::Ptr{Void}           # shm pointer
    lck::NamedSemaphore         # lock for dict operations
    used::Vector{Bool}          # bucket in use marker
    khash::Vector{UInt64}       # hash of key that occupies the bucket
    next::Vector{UInt32}        # the next collision entry for this bucket (0 if none)
    valsize::Vector{UInt32}     # size occupied in val
    val::Vector{UInt8}          # value bytes

    function ShmDict(path::Union{String,Tuple{String,Integer}}, capacity::Integer, maxvalsize::Integer; keepkeys::Bool=true, maxkeysize::Integer=32, create::Bool=false, create_exclusive::Bool=false, permission::Integer=0o660)
        if keepkeys
            maxvalsize = maxvalsize + maxkeysize + 1
        end
        capacity = primes[min(length(primes), searchsortedfirst(primes, capacity))]
        memsz, offsets = _shmsize(capacity, maxvalsize)
        id = 0
        if isa(path, Tuple)
            path,id = path
        end
        lck = NamedSemaphore("/$(hash(path))")
        tok = ftok(path, id)
        shmid = shmget(tok, memsz; create=create, create_exclusive=create_exclusive, permission=permission)
        shmptr = shmat(shmid)

        used = unsafe_wrap(Array, convert(Ptr{Bool}, shmptr), (2*capacity,))
        khash = unsafe_wrap(Array, convert(Ptr{UInt64}, shmptr+shift!(offsets)), (2*capacity,))
        next = unsafe_wrap(Array, convert(Ptr{UInt32}, shmptr+shift!(offsets)), (2*capacity,))
        valsize = unsafe_wrap(Array, convert(Ptr{UInt32}, shmptr+shift!(offsets)), (2*capacity,))
        val = unsafe_wrap(Array, convert(Ptr{UInt8}, shmptr+shift!(offsets)), (2*maxvalsize*capacity,))

        new(capacity, maxvalsize, maxkeysize, keepkeys, shmid, shmptr, lck, used, khash, next, valsize, val)
    end
end

function _shmsize(C, V)
    offsets = Vector{Int}()
    sz = 2*C
    sz = ceil(Int, sz/8) * 8
    push!(offsets, sz)
    sz += 16*C
    sz = ceil(Int, sz/8) * 8
    push!(offsets, sz)
    sz += 8*C
    sz = ceil(Int, sz/8) * 8
    push!(offsets, sz)
    sz += 8*C
    sz = ceil(Int, sz/8) * 8
    push!(offsets, sz)
    sz += 2*V*C
    sz, offsets
end

_byte_repr(val::Vector{UInt8}) = val
_byte_repr(val::String) = convert(Vector{UInt8}, val)
_byte_repr(val::Number) = reinterpret(UInt8, [val])
function _byte_repr(val)
    iob = IOBuffer()
    serialize(iob, val)
    take!(iob)
end

_from_byte_repr(::Type{Vector{UInt8}}, val::Vector{UInt8}) = val
_from_byte_repr(::Type{T}, val::Vector{UInt8}) where {T<:Number} = reinterpret(T, val)[1]
_from_byte_repr(::Type{String}, val::Vector{UInt8}) = String(val)
_from_byte_repr(::Type{T}, val::Vector{UInt8}) where {T} = deserialize(IOBuffer(val))

function _key_matches(D::ShmDict, key, bucket)
    if D.keepkeys
        keystart = Int((D.maxvalsize * (bucket-1)) + 1)
        keysz = D.val[keystart]
        keystart += 1
        keyend = Int(keystart + keysz - 1)
        key == SubArray(D.val, (keystart:keyend,))
    else
        true
    end
end

function _val_bytes(D::ShmDict, bucket)
    if D.keepkeys
        valstart = Int((D.maxvalsize * (bucket-1)) + 1)
        keysz = D.val[valstart]
        keyoffset = 1 + keysz
        valstart += keyoffset
        valend = Int(valstart + D.valsize[bucket] - keyoffset - 1)
    else
        valstart = Int((D.maxvalsize * (bucket-1)) + 1)
        valend = Int(valstart + D.valsize[bucket] - 1)
    end
    SubArray(D.val, (valstart:valend,))
end

haskey(D::ShmDict, key) = haskey(D, _byte_repr(key))
function haskey(D::ShmDict, key::Vector{UInt8})
    khash = hash(key)
    bucket = (khash % D.capacity) + 1
    if D.used[bucket]
        while bucket > 0
            (D.khash[bucket] === khash) && _key_matches(D, key, bucket) && (return true)
            bucket = D.next[bucket]
        end
    end
    return false
end

getindex(D::ShmDict, key) = getindex(D, _byte_repr(key))
function getindex(D::ShmDict, key::Vector{UInt8})
    khash = hash(key)
    bucket = (khash % D.capacity) + 1
    if D.used[bucket]
        while bucket > 0
            (D.khash[bucket] === khash) && _key_matches(D, key, bucket) && (return _val_bytes(D, bucket))
            bucket = D.next[bucket]
        end
    end
    error("key not found")
end

function _setbucket(D::ShmDict, bucket, khash::UInt64, key::Vector{UInt8}, val::Vector{UInt8}, next::UInt32)
    D.used[bucket] = true
    D.khash[bucket] = khash
    D.next[bucket] = next
    D.valsize[bucket] = length(val)
    valstart = (D.maxvalsize * (bucket-1)) + 1
    valend = valstart + D.valsize[bucket] - 1
    if D.keepkeys
        keysz = UInt8(length(key))
        D.valsize[bucket] += (1 + keysz)
        D.val[valstart] = keysz
        copy!(D.val, valstart+1, key, 1, keysz)
        valstart += (1 + keysz)
    end
    copy!(D.val, valstart, val, 1, length(val))
    nothing
end

function _setbucket(D::ShmDict, bucket, khash::UInt64, keyval::Vector{UInt8}, next::UInt32)
    D.used[bucket] = true
    D.khash[bucket] = khash
    D.next[bucket] = next
    D.valsize[bucket] = length(keyval)
    valstart = (D.maxvalsize * (bucket-1)) + 1
    valend = valstart + D.valsize[bucket] - 1
    copy!(D.keyval, valstart, keyval, 1, length(keyval))
    nothing
end

setindex!(D::ShmDict, val, key) = setindex!(D, _byte_repr(val), _byte_repr(key))
function setindex!(D::ShmDict, val::Vector{UInt8}, key::Vector{UInt8})
    lkey = length(key)
    lval = length(val)
    if D.keepkeys
        (lkey > D.maxkeysize) && error("key size $lkey greater than max allowed $(D.maxkeysize)")
        lval += (1 + lkey)
    end
    (lval > D.maxvalsize) && error("value size $(lval-lkey-1) greater than max allowed $(D.maxvalsize-D.maxkeysize-1)")
    khash = hash(key)
    bucket = (khash % D.capacity) + 1
    nextbucket = UInt32(0)
    if D.used[bucket]
        prevbucket = bucket
        while bucket > 0
            # navigate to matching entry or last entry
            (D.khash[bucket] === khash) && _key_matches(D, key, bucket) && break
            prevbucket = bucket
            bucket = D.next[bucket]
        end
        if bucket == 0
            # if no matching entry, find a free collision entry and chain it in
            bucket = findfirst(SubArray(D.used, (Int(D.capacity+1):Int(2*D.capacity),)), false)
            (bucket == 0) && error("no space left in dict")
            bucket += D.capacity
            D.next[prevbucket] = bucket
        else
            nextbucket = D.next[bucket]
        end
    end
    _setbucket(D, bucket, khash, key, val, nextbucket)
    val
end

delete!(D::ShmDict, key) = delete!(D, _byte_repr(key))
function delete!(D::ShmDict, key::Vector{UInt8})
    khash = hash(key)
    bucket = (khash % D.capacity) + 1
    if D.used[bucket]
        prevbucket = bucket
        while bucket > 0
            # navigate to matching entry
            (D.khash[bucket] === khash) && _key_matches(D, key, bucket) && break
            prevbucket = bucket
            bucket = D.next[bucket]
        end
        if bucket > 0
            nextbucket = D.next[bucket]
            if (prevbucket == bucket) && (nextbucket > 0)
                valstart = Int((D.maxvalsize * (nextbucket-1)) + 1)
                valend = Int(valstart + D.valsize[nextbucket] - 1)
                _setbucket(D, bucket, D.khash[nextbucket], SubArray(D.val, (valstart:valend,)), D.next[nextbucket])
                D.used[nextbucket] = false
            else
                D.next[prevbucket] = nextbucket
                D.used[bucket] = false
            end
            return D
        end
    end

    error("key not found")
end

function Base.empty!(D::ShmDict)
    memsz, offsets = _shmsize(D.capacity, D.maxvalsize)
    ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), D.shmptr, 0, memsz)
    D
end

function Base.delete!(D::ShmDict)
    shmrm(D.shmid)
    delete!(D.lck)
    nothing
end

function Base.close(D::ShmDict)
    shmdt(D.shmptr)
    close(D.lck)
    nothing
end


# interfacing with Random standard library

using Random

using GPUArrays

export rand_logn!, rand_poisson!

# the interface is split in two levels:
# - functions that extend the Random standard library, and take an RNG as first argument,
#   will only ever dispatch to CURAND and as a result are limited in the types they support.
# - functions that take an array will dispatch to either CURAND or GPUArrays
# - non-exported functions are provided for constructing GPU arrays from only an eltype

mutable struct RNG <: Random.AbstractRNG
    handle::curandGenerator_t
    ctx::CuContext
    typ::Int

    function RNG(typ=CURAND_RNG_PSEUDO_DEFAULT)
        handle_ref = Ref{curandGenerator_t}()
        @allocates curandCreateGenerator(handle_ref, typ)

        obj = new(handle_ref[], context(), typ)
        finalizer(unsafe_destroy!, obj)
        return obj
    end
end

function unsafe_destroy!(rng::RNG)
    if CUDAdrv.isvalid(rng.ctx)
        curandDestroyGenerator(rng)
    end
end

Base.unsafe_convert(::Type{curandGenerator_t}, rng::RNG) = rng.handle

## seeding

function Random.seed!(rng::RNG, seed=Base.rand(UInt64), offset=0)
    curandSetPseudoRandomGeneratorSeed(rng, seed)
    curandSetGeneratorOffset(rng, offset)
    @allocates curandGenerateSeeds(rng)
    return
end

# convenience function that seeds both generators
function seed!(seed=Base.rand(UInt64))
    Random.seed!(generator(), seed)
    Random.seed!(GPUArrays.global_rng(CuArray, device()), seed)
end

## in-place

# uniform
const UniformType = Union{Type{Float32},Type{Float64}}
const UniformArray = CuArray{<:Union{Float32,Float64}}
function Random.rand!(rng::RNG, A::CuArray{Float32})
    @allocates curandGenerateUniform(rng, A, length(A))
    return A
end
function Random.rand!(rng::RNG, A::CuArray{Float64})
    @allocates curandGenerateUniformDouble(rng, A, length(A))
    return A
end

# some functions need pow2 lengths: use a padded array and copy back to the original one
function inplace_pow2(A, f)
    len = length(A)
    if len > 1 && ispow2(len)
        f(A)
    else
        padlen = max(2, nextpow(2, len))
        B = similar(A, padlen)
        f(B)
        copyto!(A, 1, B, 1, len)
        CuArrays.unsafe_free!(B)
    end
    A
end

# normal
const NormalType = Union{Type{Float32},Type{Float64}}
const NormalArray = CuArray{<:Union{Float32,Float64}}
function Random.randn!(rng::RNG, A::CuArray{Float32}; mean=0, stddev=1)
    inplace_pow2(A, B->@allocates(curandGenerateNormal(rng, B, length(B), mean, stddev)))
    return A
end
function Random.randn!(rng::RNG, A::CuArray{Float64}; mean=0, stddev=1)
    inplace_pow2(A, B->@allocates(curandGenerateNormalDouble(rng, B, length(B), mean, stddev)))
    return A
end

# log-normal
const LognormalType = Union{Type{Float32},Type{Float64}}
const LognormalArray = CuArray{<:Union{Float32,Float64}}
function rand_logn!(rng::RNG, A::CuArray{Float32}; mean=0, stddev=1)
    inplace_pow2(A, B->@allocates(curandGenerateLogNormal(rng, B, length(B), mean, stddev)))
    return A
end
function rand_logn!(rng::RNG, A::CuArray{Float64}; mean=0, stddev=1)
    inplace_pow2(A, B->@allocates(curandGenerateLogNormalDouble(rng, B, length(B), mean, stddev)))
    return A
end

# poisson
const PoissonType = Union{Type{Cuint}}
const PoissonArray = CuArray{Cuint}
function rand_poisson!(rng::RNG, A::CuArray{Cuint}; lambda=1)
    @allocates curandGeneratePoisson(rng, A, length(A), lambda)
    return A
end

## out of place

# some functions need pow2 lengths: construct a compatible array and return part of it
function outofplace_pow2(shape, ctor, f)
    len = prod(shape)
    if ispow2(len)
        A = ctor(shape)
        f(A)
    else
        padlen = max(2, nextpow(2, len))
        A = ctor(padlen)
        f(A)
        B = reshape(A[1:len], shape)
        return B
    end
end

# arrays
Random.rand(rng::RNG, T::UniformType, dims::Dims) =
    Random.rand!(rng, CuArray{T}(undef, dims))
Random.randn(rng::RNG, T::NormalType, dims::Dims; kwargs...) =
    outofplace_pow2(dims, shape->CuArray{T}(undef, dims), A->randn!(rng, A; kwargs...))
rand_logn(rng::RNG, T::LognormalType, dims::Dims; kwargs...) =
    outofplace_pow2(dims, shape->CuArray{T}(undef, dims), A->rand_logn!(rng, A; kwargs...))
rand_poisson(rng::RNG, T::PoissonType, dims::Dims; kwargs...) =
    rand_poisson!(rng, CuArray{T}(undef, dims); kwargs...)

# specify default types
Random.rand(rng::RNG, dims::Dims; kwargs...) = Random.rand(rng, Float32, dims; kwargs...)
Random.randn(rng::RNG, dims::Dims; kwargs...) = Random.randn(rng, Float32, dims; kwargs...)
rand_logn(rng::RNG, dims::Dims; kwargs...) = rand_logn(rng, Float32, dims; kwargs...)
rand_poisson(rng::RNG, dims::Dims; kwargs...) = rand_poisson(rng, Cuint, dims; kwargs...)

# support all dimension specifications
Random.rand(rng::RNG, T::UniformType, dim1::Integer, dims::Integer...) =
    Random.rand(rng, T, Dims((dim1, dims...)))
Random.randn(rng::RNG, T::NormalType, dim1::Integer, dims::Integer...; kwargs...) =
    Random.randn(rng, T, Dims((dim1, dims...)); kwargs...)
rand_logn(rng::RNG, T::LognormalType, dim1::Integer, dims::Integer...; kwargs...) =
    rand_logn(rng, T, Dims((dim1, dims...)); kwargs...)
rand_poisson(rng::RNG, T::PoissonType, dim1::Integer, dims::Integer...; kwargs...) =
    rand_poisson(rng, T, Dims((dim1, dims...)); kwargs...)

## functions that dispatch to either CURAND or GPUArrays

# CURAND in-place
Random.rand!(A::UniformArray) = Random.rand!(generator(), A)
Random.randn!(A::NormalArray; kwargs...) = Random.randn!(generator(), A; kwargs...)
rand_logn!(A::LognormalArray; kwargs...) = rand_logn!(generator(), A; kwargs...)
rand_poisson!(A::PoissonArray; kwargs...) = rand_poisson!(generator(), A; kwargs...)

# CURAND out-of-place
rand(T::UniformType, dims::Dims) = Random.rand(generator(), T, dims)
randn(T::NormalType, dims::Dims; kwargs...) = Random.randn(generator(), T, dims; kwargs...)
rand_logn(T::LognormalType, dims::Dims; kwargs...) = rand_logn(generator(), T, dims; kwargs...)
rand_poisson(T::PoissonType, dims::Dims; kwargs...) = rand_poisson(generator(), T, dims; kwargs...)

# support all dimension specifications
rand(T::UniformType, dim1::Integer, dims::Integer...) =
    Random.rand(generator(), T, Dims((dim1, dims...)))
randn(T::NormalType, dim1::Integer, dims::Integer...; kwargs...) =
    Random.randn(generator(), T, Dims((dim1, dims...)); kwargs...)
rand_logn(T::LognormalType, dim1::Integer, dims::Integer...; kwargs...) =
    rand_logn(generator(), T, Dims((dim1, dims...)); kwargs...)
rand_poisson(T::PoissonType, dim1::Integer, dims::Integer...; kwargs...) =
    rand_poisson(generator(), T, Dims((dim1, dims...)); kwargs...)

# GPUArrays in-place
Random.rand!(A::CuArray) = Random.rand!(GPUArrays.global_rng(A), A)
Random.randn!(A::CuArray; kwargs...) =
    error("CuArrays does not support generating normally-distrubyted random numbers of type $(eltype(A))")
rand_logn!(A::CuArray; kwargs...) =
    error("CuArrays does not support generating lognormally-distributed random numbers of type $(eltype(A))")
rand_poisson!(A::CuArray; kwargs...) =
    error("CuArrays does not support generating Poisson-distributed random numbers of type $(eltype(A))")

# GPUArrays out-of-place
rand(T::Type, dims::Dims) = Random.rand!(CuArray{T}(undef, dims...))
randn(T::Type, dims::Dims; kwargs...) = Random.randn!(CuArray{T}(undef, dims...); kwargs...)
rand_logn(T::Type, dims::Dims; kwargs...) = rand_logn!(CuArray{T}(undef, dims...); kwargs...)
rand_poisson(T::Type, dims::Dims; kwargs...) = rand_poisson!(CuArray{T}(undef, dims...); kwargs...)

# support all dimension specifications
rand(T::Type, dim1::Integer, dims::Integer...) =
    Random.rand!(CuArray{T}(undef, dim1, dims...))
randn(T::Type, dim1::Integer, dims::Integer...; kwargs...) =
    Random.randn!(CuArray{T}(undef, dim1, dims...); kwargs...)
rand_logn(T::Type, dim1::Integer, dims::Integer...; kwargs...) =
    rand_logn!(CuArray{T}(undef, dim1, dims...); kwargs...)
rand_poisson(T::Type, dim1::Integer, dims::Integer...; kwargs...) =
    rand_poisson!(CuArray{T}(undef, dim1, dims...); kwargs...)

# untyped out-of-place
rand(dim1::Integer, dims::Integer...) =
    Random.rand(generator(), Dims((dim1, dims...)))
randn(dim1::Integer, dims::Integer...; kwargs...) =
    Random.randn(generator(), Dims((dim1, dims...)); kwargs...)
rand_logn(dim1::Integer, dims::Integer...; kwargs...) =
    rand_logn(generator(), Dims((dim1, dims...)); kwargs...)
rand_poisson(dim1::Integer, dims::Integer...; kwargs...) =
    rand_poisson(generator(), Dims((dim1, dims...)); kwargs...)


type Whisker
    pos::Array{GeometryTypes.Point2f0,1}
    ang::Float64
    phase::Float64
    amp::Float64
end

type analysis_gui
    win::GLWindow.Screen
    edit_screen::GLWindow.Screen
    viewscreen::GLWindow.Screen
    imgscreen::GLWindow.Screen
    datascreen::GLWindow.Screen
    timescreen::GLWindow.Screen
    sliderscreen::GLWindow.Screen
    gamma::Float32
    cov1::Array{GeometryTypes.Point{2,Float32},2}
    vid_path::String
    folder_path::String
    y_data::Array{Float32,2}
    max_time::Int64
    y_scales::Array{Float32,1}
    video_ts::Array{Int64,1}
    current_frame::Int64
    t::Int64
    start_time::Int64
    spikes_ts::Array{Int64,1}
    s::Sorting
    buf::Array{SpikeSorting.Spike,2}
    spike_nums::Array{Int64,1}
    show_spikes::Bool
    spikes::Array{GeometryTypes.Point{2,Float32},2}
    p_slider::Reactive.Signal{Int64}
    event_ts::Array{Array{Int64,1},1}
    myhist::Array{Int64,1}
    slider_values::StepRange{Int64,Int64}
    tif::Array{ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}},3}
    whiskers::Array{Whisker,2}
    pre_sorted::Bool
    pre_sorted_ts::Array{Int64,1}
end

type clip_times
    t1::Int64
    t2::Int64
    ts1::Int64
    ts2::Int64
    ind1::Int64
    ind2::Int64
    s1::Int64
    s2::Int64
end

Whisker()=Whisker(zeros(GeometryTypes.Point2f0,0),0.0,0.0,0.0)


function cos(obj::SinCos)
    return jcall(obj, "cos", jdouble, ())
end

function sin(obj::SinCos)
    return jcall(obj, "sin", jdouble, ())
end


if !m._obbt_performed_flag
    if m._nonlinear_evaluator_created
        set_node!(m._working_problem._relaxed_evaluator, m._current_node)
        set_node_flag!(m)
        set_reference_point!(m)
        fill!(m._working_problem._relaxed_evaluator.subexpressions_eval, false)
    end
    update_relaxed_problem_box!(m)
    relax_constraints!(m)
end
relax_objective!(m)

# Optimizes the object
MOI.set(m.relaxed_optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)

"""

"""
function intrepret_relaxed_solution!(m::Optimizer, d::T) where T
    valid_flag, feasible_flag = is_globally_optimal(m._lower_termination_status,
                                                    m._lower_result_status)
    if valid_flag && feasible_flag
        set_dual!(m)
        m._cut_add_flag = true
        m._lower_feasibility = true
        m._lower_objective_value = MOI.get(d, MOI.ObjectiveValue())
        for i = 1:_variable_num(_working_problem(m))
             m._lower_solution[i] = MOI.get(d, MOI.VariablePrimal(),
                                               m._relaxed_variable_index[i])
        end
    elseif valid_flag
        m._cut_add_flag = false
        m._lower_feasibility  = false
        m._lower_objective_value = -Inf
    end
    return valid_flag
end

function _modify_existing_relax(m)
    flag = MOIU.supports_default_copy_to(m)
    flag &=
    return flag
end

function MOIU.supports_default_copy_to(opt::T)
end

function lower_problem!(t::ExtensionType, m::Optimizer)
    if _modify_existing_relax(m)
        _modify_relax!(m)
    elseif _add_delete_existing_relax(m)
        _add_delete_relax!(m)
    else
        MOI.empty!(opt)
        if MOIU.supports_default_copy_to(m.relaxed_optimizer)
            _add_relax!(m, m.relaxed_optimizer)
        else
            MOI.empty!(m.cached_opt)
            _add_relax!(m, cached_opt)
            MOIU.copyto!(m.relaxed_optimizer, m.cached_opt)
        end
    end

    MOI.optimize!(m.relaxed_optimizer)
    if !intrepret_relaxed_solution(m, m.relaxed_optimizer)
        fallback_interval_lower_bound!(m, n)
    end
    return
end

if supports_constraint_mod(opt)
    # modify
else
    MOI.empty!(opt)
    if MOIU.supports_default_copy_to(opt::T)
        _relax!(opt)
    else
        _relax!(cached_opt)
        MOIU.copyto!(opt, cached_opt)
    end
end


function rejection(m)
    r = makeRand(m)
    function f(data)
        prop = r(;data...)
        while NamedTupleTools.select(prop, data) != data
            prop = r(;data...)
        end
        return prop
    end
end


export Chain
export popitems

"""
    Chain(blocks...)

Chain multiple blocks / functions together, so that they are called in sequence on a given input.
`Chain` also supports indexing and slicing, e.g. `c[2]` or `c[1:3]` or c[[1,3]].

# Examples
    julia> m = Chain(Dense(256,128), lstm(128,64), Dense(64,10));
"""
mutable struct Chain <: Block
    blocks::Vector
    function Chain(sequence::Vector)
        nblocks = length(sequence)
        blocks = Vector(undef,nblocks)
        for i = 1:nblocks
            blocks[i] = sequence[i]
        end
        new(blocks)
    end
    function Chain(sequence...)
        nblocks = length(sequence)
        blocks = Vector(undef,nblocks)
        for i = 1:nblocks
            blocks[i] = sequence[i]
        end
        new(blocks)
    end
    function Chain(nblocks::Int)
        new(Vector(undef,nblocks))
    end
end

Base.length(c::Chain)     = length(c.blocks)
Base.lastindex(c::Chain)  = length(c.blocks)
Base.firstindex(c::Chain) = 1
Base.getindex(c::Chain, k...)     =  c.blocks[k...]
Base.setindex!(c::Chain, v, k...) = (c.blocks[k...] = v)
Base.iterate(c::Chain, i=firstindex(c)) = i>length(c) ? nothing : (c[i], i+1)

function clone(this::Chain; type::Type=Array{Float32})
    nblocks = length(this.blocks)
    cloned  = Chain(nblocks)
    for i = 1:nblocks
        cloned[i] = clone(this[i], type=type)
    end
    return cloned
end

function Base.show(io::IO, c::Chain)
    print(io,  "Chain(\n")
    join(io,c.blocks, "\n")
    println(io, "\n)")
end

function popitems(blocks::Vector{T}, list) where T
    lenb = length(blocks)
    lenl = length(list)
    @assert(lenb>lenl)
    newblocks = Vector(undef,0)
    for i = 1:lenb
        if i ∉ list
            push!(newblocks,blocks[i])
        end
    end
    return newblocks
end

"""
    unbiasedof(model::Chain)

unbiased weights of Chain block
"""
function unbiasedof(model::Chain)
    weights = Vector(undef, 0)
    for m in model
        append!(weights, unbiasedof(m))
    end
    return weights
end

function paramsof(c::Chain)
    params = Vector{Variable}(undef,0)
    for i = 1:length(c)
        p = paramsof(c[i])
        if p ≠ nothing
            append!(params, p)
        end
    end
    return params
end

function xparamsof(c::Chain)
    xparams = Vector{XVariable}(undef,0)
    for i = 1:length(c)
        p = xparamsof(c[i])
        if p ≠ nothing
            append!(xparams, p)
        end
    end
    return xparams
end

function nparamsof(c::Chain)
    nparams = 0
    for i = 1:length(c)
        nparams += nparamsof(c[i])
    end
    return nparams
end

function bytesof(model::Chain, unit::String="MB")
    n = nparamsof(model) * elsizeof(model[1].w)
    return blocksize(n, uppercase(unit))
end

function resethidden(c::Chain)
    for i = 1:length(c)
        if typeof(c[i]) ∈ RNNLIST
            resethidden(c[i])
        end
    end
end

function forward(c::Chain, x::Variable)
    for i = 1:length(c)
        x = forward(c[i], x)
    end
    return x
end

function predict(c::Chain, x)
    for i = 1:length(c)
        x = predict(c[i], x)
    end
    return x
end

function to(type::Type, c::Chain)
    for m in c
        m = to(type, m)
    end
    return c
end

function to!(type::Type, c::Chain)
    for m in c
        m = to(type, m)
    end
    return nothing
end


# tests for src/event_handler.jl
using MonteCarloX
using StatsBase
using Random

function test_event_handler_rate(event_handler_type::String; verbose = false)
    pass = true

    # test construction
    N = 1000
    rate0 = 0.1
    list_rate    = ones(N) * rate0
    list_event = [i for i = 1:N]
    if event_handler_type == "ListEventRateSimple"
        event_handler =    ListEventRateSimple{Int}(list_event, list_rate, 0.0, 0)
    elseif event_handler_type == "ListEventRateActiveMask"
        event_handler = ListEventRateActiveMask{Int}(list_event, list_rate, 0.0, 0)
    else
        throw(UndefVarError(:event_handler_type))
    end
    if verbose
        println("... construction")
        println("... ... rate[1]: $(event_handler.list_rate[1]) == $(rate0)")
        println("... ... rate[N]: $(event_handler.list_rate[N]) == $(rate0)")
    end
    pass &= event_handler.list_rate[1] == rate0
    pass &= event_handler.list_rate[N] == rate0

    # test length
    if verbose
        println("... length: $(length(event_handler)) = $(N)")
    end
    pass &= length(event_handler) == N

    # test setindex! - e.g. sum_rates remains correct (random changes and keep sum of changes)
    for i = 1:N
        event_handler[i] = 0.0
    end
    if verbose
        println("... set all rates to 0.0")
        println("... ... sum(rates): $(sum(event_handler.list_rate)) !> 1e-10")
        println("... ... length: $(length(event_handler)) == 0")
    end
    pass &= !(sum(event_handler.list_rate) > 1e-10)
    pass &= length(event_handler) == 0

    # test setindex! and getindex
    rng = MersenneTwister(1000)
    sum_before = sum(event_handler.list_rate)
    sum_changes = 0.0
    for i = 1:N
        value = rand(rng)
        sum_changes += value - event_handler[i]
        event_handler[i] = value
    end
    sum_expected = sum_before + sum_changes
    if verbose
        println("... set all rates random")
        println("... ... expected sum(rates): $(sum(event_handler.list_rate)) == $(sum_expected)")
        println("... ... diff: $(abs(sum(event_handler.list_rate) - sum_expected))  < 1e-10")
    end
    pass &= abs(sum(event_handler.list_rate) - sum_expected) < 1e-10

    # test changes in rate effecting the automatic update of sum(rates)
    if verbose
        println("... automatic update of sum(rates)")
    end
    for i = 1:10
        valid_sum = true
        for j = 1:1e7
            event_handler[rand(rng, 1:N)] = rand(rng)
        end
        sum_rates = 0.0
        for n=1:N
            sum_rates += event_handler[n]
        end
        if abs(sum(event_handler.list_rate) - sum_rates) < 1e-11
            valid_sum = true
        else
            valid_sum = false
        end
        if verbose
            if valid_sum
                println("... ... correct sum(rates): $(sum(event_handler.list_rate)) == $(sum_rates)")
            else
                println("... ... incorrect sum(rates): $(sum(event_handler.list_rate)) == $(sum_rates)")
            end
        end
        pass &= valid_sum
    end

    return pass
end


function Distributions.minimum(d::Distributions.Gamma{T}) where {T<:Real}
    return 0.0
end
function Distributions.maximum(d::Distributions.Gamma{T}) where {T<:Real}
    return Inf
end


### This support BLAS style multiplication
#           α * A * B + β C
# but avoids the broadcast machinery

# Lazy representation of α*A*B + β*C
struct MulAdd{StyleA, StyleB, StyleC, T, AA, BB, CC}
    α::T
    A::AA
    B::BB
    β::T
    C::CC
end

@inline MulAdd{StyleA,StyleB,StyleC}(α::T, A::AA, B::BB, β::T, C::CC) where {StyleA,StyleB,StyleC,T,AA,BB,CC} =
    MulAdd{StyleA,StyleB,StyleC,T,AA,BB,CC}(α,A,B,β,C)

@inline function MulAdd{StyleA,StyleB,StyleC}(αT, A, B, βV, C) where {StyleA,StyleB,StyleC}
    α,β = promote(αT,βV)
    MulAdd{StyleA,StyleB,StyleC}(α, A, B, β, C)
end

@inline MulAdd(α, A::AA, B::BB, β, C::CC) where {AA,BB,CC} =
    MulAdd{typeof(MemoryLayout(AA)), typeof(MemoryLayout(BB)), typeof(MemoryLayout(CC))}(α, A, B, β, C)

@inline _mul_eltype(A) = A
@inline _mul_eltype(A, B) = Base.promote_op(*, A, B)
@inline _mul_eltype(A, B, C, D...) = _mul_eltype(Base.promote_op(*, A, B), C, D...)

@inline eltype(::MulAdd{StyleA,StyleB,StyleC,T,AA,BB,CC}) where {StyleA,StyleB,StyleC,T,AA,BB,CC} =
     promote_type(_mul_eltype(T, eltype(AA), eltype(BB)), _mul_eltype(T, eltype(CC)))

size(M::MulAdd, p::Int) = size(M)[p]
axes(M::MulAdd, p::Int) = axes(M)[p]
length(M::MulAdd) = prod(size(M))
size(M::MulAdd) = map(length,axes(M))
axes(M::MulAdd) = axes(M.C)

similar(M::MulAdd, ::Type{T}, axes) where {T,N} = similar(Array{T}, axes)
similar(M::MulAdd, ::Type{T}) where T = similar(M, T, axes(M))
similar(M::MulAdd) = similar(M, eltype(M))

check_mul_axes(A) = nothing
_check_mul_axes(::Number, ::Number) = nothing
_check_mul_axes(::Number, _) = nothing
_check_mul_axes(_, ::Number) = nothing
_check_mul_axes(A, B) = axes(A,2) == axes(B,1) || throw(DimensionMismatch("Second axis of A, $(axes(A,2)), and first axis of B, $(axes(B,1)) must match"))
function check_mul_axes(A, B, C...)
    _check_mul_axes(A, B)
    check_mul_axes(B, C...)
end

# we need to special case AbstractQ as it allows non-compatiple multiplication
function check_mul_axes(A::AbstractQ, B, C...)
    axes(A.factors, 1) == axes(B, 1) || axes(A.factors, 2) == axes(B, 1) ||
        throw(DimensionMismatch("First axis of B, $(axes(B,1)) must match either axes of A, $(axes(A))"))
    check_mul_axes(B, C...)
end

function instantiate(M::MulAdd)
    @boundscheck check_mul_axes(M.α, M.A, M.B)
    @boundscheck check_mul_axes(M.β, M.C)
    @boundscheck axes(M.A,1) == axes(M.C,1) || throw(DimensionMismatch("First axis of A, $(axes(M.A,1)), and first axis of C, $(axes(M.C,1)) must match"))
    @boundscheck axes(M.B,2) == axes(M.C,2) || throw(DimensionMismatch("Second axis of B, $(axes(M.B,2)), and second axis of C, $(axes(M.C,2)) must match"))
    M
end

const ArrayMulArrayAdd{StyleA,StyleB,StyleC} = MulAdd{StyleA,StyleB,StyleC,<:Any,<:AbstractArray,<:AbstractArray,<:AbstractArray}
const MatMulVecAdd{StyleA,StyleB,StyleC} = MulAdd{StyleA,StyleB,StyleC,<:Any,<:AbstractMatrix,<:AbstractVector,<:AbstractVector}
const MatMulMatAdd{StyleA,StyleB,StyleC} = MulAdd{StyleA,StyleB,StyleC,<:Any,<:AbstractMatrix,<:AbstractMatrix,<:AbstractMatrix}
const VecMulMatAdd{StyleA,StyleB,StyleC} = MulAdd{StyleA,StyleB,StyleC,<:Any,<:AbstractVector,<:AbstractMatrix,<:AbstractMatrix}

broadcastable(M::MulAdd) = M

const BlasMatMulVecAdd{StyleA,StyleB,StyleC,T<:BlasFloat} = MulAdd{StyleA,StyleB,StyleC,T,<:AbstractMatrix{T},<:AbstractVector{T},<:AbstractVector{T}}
const BlasMatMulMatAdd{StyleA,StyleB,StyleC,T<:BlasFloat} = MulAdd{StyleA,StyleB,StyleC,T,<:AbstractMatrix{T},<:AbstractMatrix{T},<:AbstractMatrix{T}}
const BlasVecMulMatAdd{StyleA,StyleB,StyleC,T<:BlasFloat} = MulAdd{StyleA,StyleB,StyleC,T,<:AbstractVector{T},<:AbstractMatrix{T},<:AbstractMatrix{T}}

muladd!(α, A, B, β, C) = materialize!(MulAdd(α, A, B, β, C))
materialize(M::MulAdd) = copy(instantiate(M))
copy(M::MulAdd) = copyto!(similar(M), M)

@inline function copyto!(dest::AbstractArray{T}, M::MulAdd) where T
    M.C === dest  || copyto!(dest, M.C)
    muladd!(M.α, M.A, M.B, M.β, dest)
end

@inline function copyto!(dest::AbstractArray{T}, M::MulAdd{<:Any,<:Any,ZerosLayout}) where T
    α,A,B,β,C = M.α, M.A, M.B, M.β, M.C
    if !isbitstype(T) # instantiate
        dest .= β .* view(A,:,1) .* Ref(B[1])  # get shape right
    end
    muladd!(α, A, B, β, dest)
end

# Modified from LinearAlgebra._generic_matmatmul!
function tile_size(T, S, R)
    tile_size = 0
    if isbitstype(R) && isbitstype(T) && isbitstype(S)
        tile_size = floor(Int, sqrt(tilebufsize / max(sizeof(R), sizeof(S), sizeof(T))))
    end
    tile_size
end

function tiled_blasmul!(tile_size, α, A::AbstractMatrix{T}, B::AbstractMatrix{S}, β, C::AbstractMatrix{R}) where {S,T,R}
    mA, nA = size(A)
    mB, nB = size(B)
    nA == mB || throw(DimensionMismatch("Dimensions must match"))
    size(C) == (mA, nB) || throw(DimensionMismatch("Dimensions must match"))

    @inbounds begin
        sz = (tile_size, tile_size)
        # FIXME: This code is completely invalid!!!
        Atile = unsafe_wrap(Array, convert(Ptr{T}, pointer(Abuf[Threads.threadid()])), sz)
        Btile = unsafe_wrap(Array, convert(Ptr{S}, pointer(Bbuf[Threads.threadid()])), sz)

        z1 = zero(A[1, 1]*B[1, 1] + A[1, 1]*B[1, 1])
        z = convert(promote_type(typeof(z1), R), z1)

        if mA < tile_size && nA < tile_size && nB < tile_size
            copy_transpose!(Atile, 1:nA, 1:mA, 'N', A, 1:mA, 1:nA)
            copyto!(Btile, 1:mB, 1:nB, 'N', B, 1:mB, 1:nB)
            for j = 1:nB
                boff = (j-1)*tile_size
                for i = 1:mA
                    aoff = (i-1)*tile_size
                    s = z
                    for k = 1:nA
                        s += Atile[aoff+k] * Btile[boff+k]
                    end
                    C[i,j] = α*s + β*C[i,j]
                end
            end
        else
            # FIXME: This code is completely invalid!!!
            Ctile = unsafe_wrap(Array, convert(Ptr{R}, pointer(Cbuf[Threads.threadid()])), sz)
            for jb = 1:tile_size:nB
                jlim = min(jb+tile_size-1,nB)
                jlen = jlim-jb+1
                for ib = 1:tile_size:mA
                    ilim = min(ib+tile_size-1,mA)
                    ilen = ilim-ib+1
                    copyto!(Ctile, 1:ilen, 1:jlen, C, ib:ilim, jb:jlim)
                    lmul!(β,Ctile)
                    for kb = 1:tile_size:nA
                        klim = min(kb+tile_size-1,mB)
                        klen = klim-kb+1
                        copy_transpose!(Atile, 1:klen, 1:ilen, 'N', A, ib:ilim, kb:klim)
                        copyto!(Btile, 1:klen, 1:jlen, 'N', B, kb:klim, jb:jlim)
                        for j=1:jlen
                            bcoff = (j-1)*tile_size
                            for i = 1:ilen
                                aoff = (i-1)*tile_size
                                s = z
                                for k = 1:klen
                                    s += Atile[aoff+k] * Btile[bcoff+k]
                                end
                                Ctile[bcoff+i] += α*s
                            end
                        end
                    end
                    copyto!(C, ib:ilim, jb:jlim, Ctile, 1:ilen, 1:jlen)
                end
            end
        end
    end

    C
end

function default_blasmul!(α, A::AbstractMatrix, B::AbstractMatrix, β, C::AbstractMatrix)
    mA, nA = size(A)
    mB, nB = size(B)
    nA == mB || throw(DimensionMismatch("Dimensions must match"))
    size(C) == (mA, nB) || throw(DimensionMismatch("Dimensions must match"))

    @inbounds for k in colsupport(A), j in rowsupport(B)
        z2 = zero(A[k, 1]*B[1, j] + A[k, 1]*B[1, j])
        Ctmp = convert(promote_type(eltype(C), typeof(z2)), z2)
        @simd for ν = rowsupport(A,k) ∩ colsupport(B,j)
            Ctmp = muladd(A[k, ν],B[ν, j],Ctmp)
        end
        C[k,j] = muladd(α,Ctmp, β*C[k,j])
    end
    C
end

function default_blasmul!(α, A::AbstractMatrix, B::AbstractVector, β, C::AbstractVector)
    mA, nA = size(A)
    mB = length(B)
    nA == mB || throw(DimensionMismatch("Dimensions must match"))
    length(C) == mA || throw(DimensionMismatch("Dimensions must match"))

    lmul!(β, C)
    (nA == 0 || mB == 0)  && return C

    z = zero(A[1]*B[1] + A[1]*B[1])
    Astride = size(A, 1) # use size, not stride, since its not pointer arithmetic

    @inbounds for k in colsupport(B,1)
        aoffs = (k-1)*Astride
        b = B[k]
        for i = 1:mA
            C[i] += α * A[aoffs + i] * b
        end
    end

    C
end

function materialize!(M::MatMulMatAdd)
    α, A, B, β, C = M.α, M.A, M.B, M.β, M.C
    if C ≡ B
        B = copy(B)
    end
    ts = tile_size(eltype(A), eltype(B), eltype(C))
    if iszero(β) # false is a "strong" zero to wipe out NaNs
        if ts == 0 || !(axes(A) isa NTuple{2,OneTo{Int}}) || !(axes(B) isa NTuple{2,OneTo{Int}}) || !(axes(C) isa NTuple{2,OneTo{Int}})
            default_blasmul!(α, A, B, false, C)
        else
            tiled_blasmul!(ts, α, A, B, false, C)
        end
    else
        if ts == 0 || !(axes(A) isa NTuple{2,OneTo{Int}}) || !(axes(B) isa NTuple{2,OneTo{Int}}) || !(axes(C) isa NTuple{2,OneTo{Int}})
            default_blasmul!(α, A, B, β, C)
        else
            tiled_blasmul!(ts, α, A, B, β, C)
        end
    end
end

function materialize!(M::MatMulVecAdd)
    α, A, B, β, C = M.α, M.A, M.B, M.β, M.C
    if C ≡ B
        B = copy(B)
    end
    default_blasmul!(α, A, B, iszero(β) ? false : β, C)
end

# make copy to make sure always works
@inline function _gemv!(tA, α, A, x, β, y)
    if x ≡ y
        BLAS.gemv!(tA, α, A, copy(x), β, y)
    else
        BLAS.gemv!(tA, α, A, x, β, y)
    end
end

# make copy to make sure always works
@inline function _gemm!(tA, tB, α, A, B, β, C)
    if B ≡ C
        BLAS.gemm!(tA, tB, α, A, copy(B), β, C)
    else
        BLAS.gemm!(tA, tB, α, A, B, β, C)
    end
end

@inline materialize!(M::BlasMatMulVecAdd{<:AbstractColumnMajor,<:AbstractStridedLayout,<:AbstractStridedLayout}) =
    _gemv!('N', M.α, M.A, M.B, M.β, M.C)
@inline materialize!(M::BlasMatMulVecAdd{<:AbstractRowMajor,<:AbstractStridedLayout,<:AbstractStridedLayout}) =
    _gemv!('T', M.α, transpose(M.A), M.B, M.β, M.C)
@inline materialize!(M::BlasMatMulVecAdd{<:ConjLayout{<:AbstractRowMajor},<:AbstractStridedLayout,<:AbstractStridedLayout,<:BlasComplex}) =
    _gemv!('C', M.α, M.A', M.B, M.β, M.C)

@inline materialize!(M::BlasVecMulMatAdd{<:AbstractColumnMajor,<:AbstractColumnMajor,<:AbstractColumnMajor}) =
    _gemm!('N', 'N', M.α, M.A, M.B, M.β, M.C)
@inline materialize!(M::BlasVecMulMatAdd{<:AbstractColumnMajor,<:AbstractRowMajor,<:AbstractColumnMajor}) =
    _gemm!('N', 'T', M.α, M.A, transpose(M.B), M.β, M.C)
@inline materialize!(M::BlasVecMulMatAdd{<:AbstractColumnMajor,<:ConjLayout{<:AbstractRowMajor},<:AbstractColumnMajor,<:BlasComplex}) =
    _gemm!('N', 'C', M.α, M.A, M.B', M.β, M.C)

@inline materialize!(M::BlasMatMulMatAdd{<:AbstractColumnMajor,<:AbstractColumnMajor,<:AbstractColumnMajor}) =
    _gemm!('N', 'N', M.α, M.A, M.B, M.β, M.C)
@inline materialize!(M::BlasMatMulMatAdd{<:AbstractColumnMajor,<:AbstractRowMajor,<:AbstractColumnMajor}) =
    _gemm!('N', 'T', M.α, M.A, transpose(M.B), M.β, M.C)
@inline materialize!(M::BlasMatMulMatAdd{<:AbstractColumnMajor,<:ConjLayout{<:AbstractRowMajor},<:AbstractColumnMajor,<:BlasComplex}) =
    _gemm!('N', 'C', M.α, M.A, M.B', M.β, M.C)

@inline materialize!(M::BlasMatMulMatAdd{<:AbstractRowMajor,<:AbstractColumnMajor,<:AbstractColumnMajor}) =
    _gemm!('T', 'N', M.α, transpose(M.A), M.B, M.β, M.C)
@inline materialize!(M::BlasMatMulMatAdd{<:ConjLayout{<:AbstractRowMajor},<:AbstractColumnMajor,<:AbstractColumnMajor,<:BlasComplex}) =
    _gemm!('C', 'N', M.α, M.A', M.B, M.β, M.C)

@inline materialize!(M::BlasMatMulMatAdd{<:AbstractRowMajor,<:AbstractRowMajor,<:AbstractColumnMajor}) =
    _gemm!('T', 'T', M.α, transpose(M.A), transpose(M.B), M.β, M.C)
@inline materialize!(M::BlasMatMulMatAdd{<:AbstractRowMajor,<:ConjLayout{<:AbstractRowMajor},<:AbstractColumnMajor,<:BlasComplex}) =
    _gemm!('T', 'C', M.α, transpose(M.A), M.B', M.β, M.C)

@inline materialize!(M::BlasMatMulMatAdd{<:ConjLayout{<:AbstractRowMajor},<:AbstractRowMajor,<:AbstractColumnMajor,<:BlasComplex}) =
    _gemm!('C', 'T', M.α, M.A', M.B', M.β, M.C)
@inline materialize!(M::BlasMatMulMatAdd{<:ConjLayout{<:AbstractRowMajor},<:ConjLayout{<:AbstractRowMajor},<:AbstractColumnMajor,<:BlasComplex}) =
    _gemm!('C', 'C', M.α, M.A', M.B', M.β, M.C)

@inline materialize!(M::BlasMatMulMatAdd{<:AbstractColumnMajor,<:AbstractColumnMajor,<:AbstractRowMajor}) =
    _gemm!('T', 'T', M.α, M.B, M.A, M.β, transpose(M.C))
@inline materialize!(M::BlasMatMulMatAdd{<:AbstractColumnMajor,<:AbstractColumnMajor,<:ConjLayout{<:AbstractRowMajor},<:BlasComplex}) =
    _gemm!('C', 'C', M.α, M.B, M.A, M.β, M.C')

@inline materialize!(M::BlasMatMulMatAdd{<:AbstractColumnMajor,<:AbstractRowMajor,<:AbstractRowMajor}) =
    _gemm!('N', 'T', M.α, transpose(M.B), M.A, M.β, transpose(M.C))
@inline materialize!(M::BlasMatMulMatAdd{<:AbstractColumnMajor,<:AbstractRowMajor,<:ConjLayout{<:AbstractRowMajor},<:BlasComplex}) =
    _gemm!('N', 'T', M.α, transpose(M.B), M.A, M.β, M.C')
@inline materialize!(M::BlasMatMulMatAdd{<:AbstractColumnMajor,<:ConjLayout{<:AbstractRowMajor},<:ConjLayout{<:AbstractRowMajor},<:BlasComplex}) =
    _gemm!('N', 'C', M.α, M.B', M.A, M.β, M.C')

@inline materialize!(M::BlasMatMulMatAdd{<:AbstractRowMajor,<:AbstractColumnMajor,<:AbstractRowMajor}) =
    _gemm!('T', 'N', M.α, M.B, transpose(M.A), M.β, transpose(M.C))
@inline materialize!(M::BlasMatMulMatAdd{<:ConjLayout{<:AbstractRowMajor},<:AbstractColumnMajor,<:ConjLayout{<:AbstractRowMajor},<:BlasComplex}) =
    _gemm!('C', 'N', M.α, M.B, M.A', M.β, M.C')

@inline materialize!(M::BlasMatMulMatAdd{<:AbstractRowMajor,<:AbstractRowMajor,<:AbstractRowMajor}) =
    _gemm!('N', 'N', M.α, transpose(M.B), transpose(M.A), M.β, transpose(M.C))
@inline materialize!(M::BlasMatMulMatAdd{<:ConjLayout{<:AbstractRowMajor},<:ConjLayout{<:AbstractRowMajor},<:ConjLayout{<:AbstractRowMajor},<:BlasComplex}) =
    _gemm!('N', 'N', M.α, M.B', M.A', M.β, M.C')

###
# Symmetric
###

# make copy to make sure always works
@inline function _symv!(tA, α, A, x, β, y)
    if x ≡ y
        BLAS.symv!(tA, α, A, copy(x), β, y)
    else
        BLAS.symv!(tA, α, A, x, β, y)
    end
end

@inline function _hemv!(tA, α, A, x, β, y)
    if x ≡ y
        BLAS.hemv!(tA, α, A, copy(x), β, y)
    else
        BLAS.hemv!(tA, α, A, x, β, y)
    end
end

materialize!(M::BlasMatMulVecAdd{<:SymmetricLayout{<:AbstractColumnMajor},<:AbstractStridedLayout,<:AbstractStridedLayout}) =
    _symv!(symmetricuplo(M.A), M.α, symmetricdata(M.A), M.B, M.β, M.C)

materialize!(M::BlasMatMulVecAdd{<:SymmetricLayout{<:AbstractRowMajor},<:AbstractStridedLayout,<:AbstractStridedLayout}) =
    _symv!(symmetricuplo(M.A) == 'L' ? 'U' : 'L', M.α, transpose(symmetricdata(M.A)), M.B, M.β, M.C)

materialize!(M::BlasMatMulVecAdd{<:HermitianLayout{<:AbstractColumnMajor},<:AbstractStridedLayout,<:AbstractStridedLayout,<:BlasComplex}) =
    _hemv!(symmetricuplo(M.A), M.α, hermitiandata(M.A), M.B, M.β, M.C)

materialize!(M::BlasMatMulVecAdd{<:HermitianLayout{<:AbstractRowMajor},<:AbstractStridedLayout,<:AbstractStridedLayout,<:BlasComplex}) =
    _hemv!(symmetricuplo(M.A) == 'L' ? 'U' : 'L', M.α, hermitiandata(M.A)', M.B, M.β, M.C)

####
# Diagonal
####

# Diagonal multiplication never changes structure
similar(M::MulAdd{<:DiagonalLayout,<:DiagonalLayout}, ::Type{T}, axes) where T = similar(M.B, T, axes)
similar(M::MulAdd{<:DiagonalLayout}, ::Type{T}, axes) where T = similar(M.B, T, axes)
similar(M::MulAdd{<:Any,<:DiagonalLayout}, ::Type{T}, axes) where T = similar(M.A, T, axes)
# equivalent to rescaling
function materialize!(M::MulAdd{<:DiagonalLayout{<:AbstractFillLayout}})
    M.C .= (M.α * getindex_value(M.A.diag)) .* M.B .+ M.β .* M.C
    M.C
end

function materialize!(M::MulAdd{<:Any,<:DiagonalLayout{<:AbstractFillLayout}})
    M.C .= M.α .* M.A .* getindex_value(M.B.diag) .+ M.β .* M.C
    M.C
end

copy(M::MulAdd{<:DiagonalLayout{<:AbstractFillLayout}}) = (M.α * getindex_value(M.A.diag)) .* M.B .+ M.β .* M.C
copy(M::MulAdd{<:DiagonalLayout{<:AbstractFillLayout},<:Any,ZerosLayout}) = (M.α * getindex_value(M.A.diag)) .* M.B
copy(M::MulAdd{<:AbstractFillLayout,<:AbstractFillLayout,<:AbstractFillLayout}) = M.α*M.A*M.B + M.β*M.C
copy(M::MulAdd{<:Any,<:DiagonalLayout{<:AbstractFillLayout}}) = (M.α * getindex_value(M.B.diag)) .* M.A .+ M.β .* M.C
copy(M::MulAdd{<:Any,<:DiagonalLayout{<:AbstractFillLayout},ZerosLayout}) = (M.α * getindex_value(M.B.diag)) .* M.A

BroadcastStyle(::Type{<:MulAdd}) = ApplyBroadcastStyle()

scalarone(::Type{T}) where T = one(T)
scalarone(::Type{<:AbstractArray{T}}) where T = scalarone(T)
scalarzero(::Type{T}) where T = zero(T)
scalarzero(::Type{<:AbstractArray{T}}) where T = scalarzero(T)

fillzeros(::Type{T}, ax) where T = Zeros{T}(ax)

function mul!(dest::AbstractArray{W}, A::AbstractArray{T}, b::AbstractArray{V}) where {T,V,W}
    TVW = promote_type(W, _mul_eltype(T,V))
    muladd!(scalarone(TVW), A, b, scalarzero(TVW), dest)
end

function MulAdd(A::AbstractArray{T}, B::AbstractVector{V}) where {T,V}
    TV = _mul_eltype(eltype(A), eltype(B))
    MulAdd(scalarone(TV), A, B, scalarzero(TV), fillzeros(TV,(axes(A,1))))
end

function MulAdd(A::AbstractArray{T}, B::AbstractMatrix{V}) where {T,V}
    TV = _mul_eltype(eltype(A), eltype(B))
    MulAdd(scalarone(TV), A, B, scalarzero(TV), fillzeros(TV,(axes(A,1),axes(B,2))))
end

mul(A::AbstractArray, B::AbstractArray) = materialize(MulAdd(A,B))

macro layoutmul(Typ)
    ret = quote
        LinearAlgebra.mul!(dest::AbstractVector, A::$Typ, b::AbstractVector) =
            ArrayLayouts.mul!(dest,A,b)

        LinearAlgebra.mul!(dest::AbstractMatrix, A::$Typ, b::AbstractMatrix) =
            ArrayLayouts.mul!(dest,A,b)
        LinearAlgebra.mul!(dest::AbstractMatrix, A::$Typ, b::$Typ) =
            ArrayLayouts.mul!(dest,A,b)

        Base.:*(A::$Typ, B::$Typ) = ArrayLayouts.mul(A,B)
        Base.:*(A::$Typ, B::AbstractMatrix) = ArrayLayouts.mul(A,B)
        Base.:*(A::$Typ, B::AbstractVector) = ArrayLayouts.mul(A,B)
        Base.:*(A::AbstractMatrix, B::$Typ) = ArrayLayouts.mul(A,B)
        Base.:*(A::LinearAlgebra.AdjointAbsVec, B::$Typ) = ArrayLayouts.mul(A,B)
        Base.:*(A::LinearAlgebra.TransposeAbsVec, B::$Typ) = ArrayLayouts.mul(A,B)

        Base.:*(A::LinearAlgebra.AbstractQ, B::$Typ) = ArrayLayouts.lmul(A,B)
        Base.:*(A::$Typ, B::LinearAlgebra.AbstractQ) = ArrayLayouts.rmul(A,B)
    end
    for Struc in (:AbstractTriangular, :Diagonal)
        ret = quote
            $ret

            Base.:*(A::LinearAlgebra.$Struc, B::$Typ) = ArrayLayouts.mul(A,B)
            Base.:*(A::$Typ, B::LinearAlgebra.$Struc) = ArrayLayouts.mul(A,B)
        end
    end
    for Mod in (:Adjoint, :Transpose, :Symmetric, :Hermitian)
        ret = quote
            $ret

            LinearAlgebra.mul!(dest::AbstractMatrix, A::$Typ, b::$Mod{<:Any,<:AbstractMatrix}) =
                ArrayLayouts.mul!(dest,A,b)

            LinearAlgebra.mul!(dest::AbstractVector, A::$Mod{<:Any,<:$Typ}, b::AbstractVector) =
                ArrayLayouts.mul!(dest,A,b)

            Base.:*(A::$Mod{<:Any,<:$Typ}, B::$Mod{<:Any,<:$Typ}) = ArrayLayouts.mul(A,B)
            Base.:*(A::$Mod{<:Any,<:$Typ}, B::AbstractMatrix) = ArrayLayouts.mul(A,B)
            Base.:*(A::AbstractMatrix, B::$Mod{<:Any,<:$Typ}) = ArrayLayouts.mul(A,B)
            Base.:*(A::$Mod{<:Any,<:$Typ}, B::AbstractVector) = ArrayLayouts.mul(A,B)

            Base.:*(A::$Mod{<:Any,<:$Typ}, B::$Typ) = ArrayLayouts.mul(A,B)
            Base.:*(A::$Typ, B::$Mod{<:Any,<:$Typ}) = ArrayLayouts.mul(A,B)

            Base.:*(A::$Mod{<:Any,<:$Typ}, B::Diagonal) = ArrayLayouts.mul(A,B)
            Base.:*(A::Diagonal, B::$Mod{<:Any,<:$Typ}) = ArrayLayouts.mul(A,B)

            Base.:*(A::LinearAlgebra.AbstractTriangular, B::$Mod{<:Any,<:$Typ}) = ArrayLayouts.mul(A,B)
            Base.:*(A::$Mod{<:Any,<:$Typ}, B::LinearAlgebra.AbstractTriangular) = ArrayLayouts.mul(A,B)
        end
    end

    esc(ret)
end


module OceanTurb

export # This file, core functionality:
    AbstractModel,
    AbstractSolution,
    AbstractParameters,
    Clock,
    Constants,
    time,
    iter,
    set!,
    reset!,
    @use_pyplot_utils,
    t_ldiv!,

    # utils.jl
    second,
    minute,
    hour,
    day,
    year,
    stellaryear,
    Ω,
    pressenter,
    @zeros,
    @solution,
    @prefixed_solution,
    @typed_solution,
    @pair_typed_solution,
    @add_standard_model_fields,
    @add_clock_grid_timestepper,
    run_until!,

    # grids.jl
    Grid,
    UniformGrid,
    height,
    length,
    size,

    # fields.jl
    FieldLocation,
    Cell,
    Face,
    AbstractField,
    Field,
    CellField,
    FaceField,
    arraytype,
    data,
    nodes,
    Δc,
    Δf,
    onface,
    oncell,
    ∂z,
    ∂²z,
    ∂z!,
    set!,
    interiorindices,
    top,
    bottom,
    integral,
    top_flux_div,
    bottom_flux_div,
    absolute_error,
    relative_error,

    # timesteppers.jl
    Equation,
    Timestepper,
    implicit,
    iterate!,
    ForwardEulerTimestepper,
    BackwardEulerTimestepper,

    # boundary_conditions.jl
    Flux,
    Gradient,
    Value,
    BoundaryCondition,
    BoundaryConditions,
    FluxBoundaryCondition,
    ValueBoundaryCondition,
    GradientBoundaryCondition,
    FieldBoundaryConditions,
    ZeroFluxBoundaryConditions,
    DefaultBoundaryConditions,
    set_bcs!,
    getbc,
    fill_top_ghost_cell!,
    fill_bottom_ghost_cell!,
    fill_ghost_cells!,

    # Ocean turbulence models
    Diffusion,
    KPP,
    ModularKPP,
    PacanowskiPhilander,
    KPP_TKE

using
    Statistics,
    StaticArrays,
    OffsetArrays,
    LinearAlgebra

import Base: time, setproperty!

#
# Preliminary abstract types for OceanTurb.jl
#

abstract type AbstractParameters end
abstract type AbstractEquation end
abstract type Grid{T, A} end
abstract type Timestepper end
abstract type AbstractField{A, G, T} end
abstract type AbstractSolution{N, T} <: FieldVector{N, T} end
abstract type AbstractModel{T, G, TS} end

#
# Core OceanTurb.jl functionality
#

include("utils.jl")
include("solvers.jl")
include("grids.jl")
include("boundary_conditions.jl")
include("fields.jl")
include("equations.jl")
include("timesteppers.jl")

mutable struct Clock{T}
  time :: T
  iter :: Int
end

Clock() = Clock(0.0, 0)

"Get the current simulation time of the model."
time(m::AbstractModel) = m.clock.time

"Get the current iteration of the model."
iter(m::AbstractModel) = m.clock.iter

function reset!(clock)
  clock.time = 0
  clock.iter = 0
  return nothing
end

#
# Sugary things for solutions and models
#

"""
    set!(solution, kwargs...)

Set the fields of a solution. For example, use

T0 = rand(4)
S0(z) = exp(-z^2/10)
set!(solution, T=T0, S=S0)

To set solution.T and solution.S to T0 and S0.
"""
function set!(solution::AbstractSolution; kwargs...)
  for (k, v) in kwargs
    setproperty!(solution, k, v)
  end
  return nothing
end

function setproperty!(sol::AbstractSolution, c::Symbol, data::Union{Number, AbstractArray, Function})
  set!(getproperty(sol, c), data)
  return nothing
end

#
# Physical oceanic constants

"""
    Constants(T=Float64; α=2.5e-4, β=8e-5, ρ₀=1035, cP=3992, f=0, g=9.81)

Construct `Constants` with
    * thermal expansion coefficient `α` [C⁻¹]
    * haline contraction coefficient `β` [psu⁻¹]
    * reference density `ρ₀` [kg m⁻³]
    * heat capacity `cP` [...]
    * Coriolis parameter `f` [s⁻¹]
    * gravitational acceleration `g` [m² s⁻¹]
"""
struct Constants{T}
    g  :: T # Gravitiational acceleration
    cP :: T # Heat capacity of water
    ρ₀ :: T # Reference density
    α  :: T # Thermal expansion coefficient
    β  :: T # Haline expansion coefficient
    f  :: T # Coriolis parameter
end

function Constants(T=Float64; α=2.5e-4, β=8e-5, ρ₀=1035, cP=3992, f=0, g=9.81)
    Constants{T}(g, cP, ρ₀, α, β, f)
end

#
# Ocean Turbulence Models
#

include("models/Diffusion.jl")
include("models/KPP/KPP.jl")
include("models/ModularKPP/ModularKPP.jl")
include("models/PacanowskiPhilander.jl")

# Convenient utilities for plotting
macro use_pyplot_utils()
    return esc(quote
        using PyPlot, PyCall
        include(joinpath(@__DIR__, "..", "plotting", "pyplot_utils.jl"))
        using Main.OceanTurbPyPlotUtils
    end
    )
end

end # module


module Example

if !isdefined(Core, :String)
    typealias String UTF8String
end

using FlatBuffers

@enum(Color, Red = 1, Green = 2, Blue = 8)
@default Color Red

@struct immutable Test
    a::Int16
    b::UInt8
    # _1::UInt8 # padding
end

type TestSimpleTableWithEnum
    color::Color
end

@default TestSimpleTableWithEnum color=Green

@struct immutable Vec3
    x::Float32
    y::Float32
    z::Float32
    # _::UInt32 # padding
    test1::Float64
    test2::Color
    # __::UInt8 # padding
    test3::Test
    # ___::UInt16 # padding
end

@align Vec3 16

type Stat
    id::String
    val::Int64
    count::UInt16
end

# Julia doesn't support forward referencing of types
# @union Any_ Union{Monster, TestSimpleTableWithEnum}

type Monster
    pos::Vec3
    mana::Int16
    hp::Int16
    name::String
    friendly::Bool # deprecated
    inventory::Vector{UInt8}
    color::Color
    # test_type::Any_
    # test::Vector{UInt8}
    test4::Vector{Test}
    testarrayofstring::Vector{String}
    testarrayoftables::Vector{Monster}
    # don't support nested circulr reference objects yet
    # enemy::Monster
    testnestedflatbuffer::Vector{UInt8}
    testempty::Stat
    testbool::Bool
    testhashs32_fnv1::Int32
    testhashu32_fnv1::UInt32
    testhashs64_fnv1::Int64
    testhashu64_fnv1::UInt64
    testhashs32_fnv1a::Int32
    testhashu32_fnv1a::UInt32
    testhashs64_fnv1a::Int64
    testhashu64_fnv1a::UInt64
    testarrayofbools::Vector{Bool}
    testf::Float32
    testf2::Float32
    testf3::Float32
end

@default Monster hp=100 mana=150 color=Blue friendly=false testf=Float32(3.14159) testf2=Float32(3)

end # module


"""
# Description

A logical node that represents an order by operation. It has two fields:

1. `source`: A node describing a relation that will be ordered.
2. `expressions`: A tuple of `Expression` objects that define the ordering.
"""
struct OrderBy <: LogicalNode
    source::Any
    expressions::Tuple
end

function Base.print(io::IO, node::OrderBy)
    println(io, "OrderBy LogicalNode")
    println(io, type(node.source))
    for e in node.expressions
        println(io, e)
    end
end


type BasicContMuvParameterNState{N<:Real} <: ParameterNState{Continuous, Multivariate}
  value::Matrix{N}
  loglikelihood::Vector{N}
  logprior::Vector{N}
  logtarget::Vector{N}
  gradloglikelihood::Matrix{N}
  gradlogprior::Matrix{N}
  gradlogtarget::Matrix{N}
  tensorloglikelihood::Array{N, 3}
  tensorlogprior::Array{N, 3}
  tensorlogtarget::Array{N, 3}
  dtensorloglikelihood::Array{N, 4}
  dtensorlogprior::Array{N, 4}
  dtensorlogtarget::Array{N, 4}
  diagnosticvalues::Matrix
  size::Integer
  n::Integer
  diagnostickeys::Vector{Symbol}
  copy::Function

  function BasicContMuvParameterNState(
    size::Integer,
    n::Integer,
    monitor::Vector{Bool}=[true; fill(false, 12)],
    diagnostickeys::Vector{Symbol}=Symbol[],
    ::Type{N}=Float64,
    diagnosticvalues::Matrix=Array(Any, length(diagnostickeys), isempty(diagnostickeys) ? 0 : n)
  )
    instance = new()

    fnames = fieldnames(BasicContMuvParameterNState)
    for i in 2:4
      l = (monitor[i] == false ? zero(Integer) : n)
      setfield!(instance, fnames[i], Array(N, l))
    end
    for i in (1, 5, 6, 7)
      s, l = (monitor[i] == false ? (zero(Integer), zero(Integer)) : (size, n))
      setfield!(instance, fnames[i], Array(N, s, l))
    end
    for i in 8:10
      s, l = (monitor[i] == false ? (zero(Integer), zero(Integer)) : (size, n))
      setfield!(instance, fnames[i], Array(N, s, s, l))
    end
    for i in 11:13
      s, l = (monitor[i] == false ? (zero(Integer), zero(Integer)) : (size, n))
      setfield!(instance, fnames[i], Array(N, s, s, s, l))
    end

    instance.diagnosticvalues = diagnosticvalues

    instance.size = size
    instance.n = n
    instance.diagnostickeys = diagnostickeys

    instance.copy = eval(codegen(:copy, instance, monitor))

    instance
  end
end

BasicContMuvParameterNState{N<:Real}(
  size::Integer,
  n::Integer,
  monitor::Vector{Bool}=[true; fill(false, 12)],
  diagnostickeys::Vector{Symbol}=Symbol[],
  ::Type{N}=Float64,
  diagnosticvalues::Matrix=Array(Any, length(diagnostickeys), isempty(diagnostickeys) ? 0 : n)
) =
  BasicContMuvParameterNState{N}(size, n, monitor, diagnostickeys, N, diagnosticvalues)

function BasicContMuvParameterNState{N<:Real}(
  size::Integer,
  n::Integer,
  monitor::Vector{Symbol},
  diagnostickeys::Vector{Symbol}=Symbol[],
  ::Type{N}=Float64,
  diagnosticvalues::Matrix=Array(Any, length(diagnostickeys), isempty(diagnostickeys) ? 0 : n)
)
  fnames = fieldnames(BasicContMuvParameterNState)
  BasicContMuvParameterNState(
    size, n, [fnames[i] in monitor ? true : false for i in 1:13], diagnostickeys, N, diagnosticvalues
  )
end

typealias ContMuvMarkovChain BasicContMuvParameterNState

codegen(f::Symbol, nstate::BasicContMuvParameterNState, monitor::Vector{Bool}) = codegen(Val{f}, nstate, monitor)

function codegen(::Type{Val{:copy}}, nstate::BasicContMuvParameterNState, monitor::Vector{Bool})
  body = []
  fnames = fieldnames(BasicContMuvParameterNState)
  local f::Symbol # f must be local to avoid compiler errors. Alternatively, this variable declaration can be omitted
  statelen::Integer

  for j in 2:4
    if monitor[j]
      f = fnames[j]
      push!(body, :(getfield(_nstate, $(QuoteNode(f)))[_i] = getfield(_state, $(QuoteNode(f)))))
    end
  end

  for j in (1, 5, 6, 7)
    if monitor[j]
      f = fnames[j]
      push!(
        body,
        :(getfield(_nstate, $(QuoteNode(f)))[1+(_i-1)*_state.size:_i*_state.size] = getfield(_state, $(QuoteNode(f))))
      )
    end
  end

  if monitor[8] || monitor[9] || monitor[10]
    statelen = (nstate.size)^2
  end
  for j in 8:10
    if monitor[j]
      f = fnames[j]
      push!(
        body,
        :(getfield(_nstate, $(QuoteNode(f)))[1+(_i-1)*$(statelen):_i*$(statelen)] = getfield(_state, $(QuoteNode(f))))
      )
    end
  end

  if monitor[11] || monitor[12] || monitor[13]
    statelen = (nstate.size)^3
  end
  for j in 11:13
    if monitor[j]
      f = fnames[j]
      push!(
        body,
        :(getfield(_nstate, $(QuoteNode(f)))[1+(_i-1)*$(statelen):_i*$(statelen)] = getfield(_state, $(QuoteNode(f))))
      )
    end
  end

  if !isempty(nstate.diagnosticvalues)
    push!(body, :(_nstate.diagnosticvalues[:, _i] = _state.diagnosticvalues))
  end

  @gensym _copy

  quote
    function $_copy(_nstate::BasicContMuvParameterNState, _state::BasicContMuvParameterState, _i::Integer)
      $(body...)
    end
  end
end

Base.eltype{N<:Real}(::Type{BasicContMuvParameterNState{N}}) = N
Base.eltype{N<:Real}(::BasicContMuvParameterNState{N}) = N

Base.(:(==)){S<:BasicContMuvParameterNState}(z::S, w::S) =
  reduce(&, [getfield(z, n) == getfield(w, n) for n in fieldnames(S)[1:17]])

Base.isequal{S<:BasicContMuvParameterNState}(z::S, w::S) =
  reduce(&, [isequal(getfield(z, n), getfield(w, n)) for n in fieldnames(S)[1:17]])

function Base.show{N<:Real}(io::IO, nstate::BasicContMuvParameterNState{N})
  fnames = fieldnames(BasicContMuvParameterNState)
  fbool = map(n -> !isempty(getfield(nstate, n)), fnames[1:13])
  indentation = "  "

  println(io, "BasicContMuvParameterNState:")

  println(io, indentation*"eltype: $(eltype(nstate))")
  println(io, indentation*"state size = $(nstate.size)")
  println(io, indentation*"number of states = $(nstate.n)")

  print(io, indentation*"monitored components:")
  if !any(fbool)
    println(io, " none")
  else
    print(io, "\n")
    for i in 1:13
      if fbool[i]
        println(io, string(indentation^2, fnames[i]))
      end
    end
  end

  print(io, indentation*"diagnostics:")
  if isempty(nstate.diagnostickeys)
    print(io, " none")
  else
    for k in nstate.diagnostickeys
      print(io, "\n")
      print(io, string(indentation^2, k))
    end
  end
end


# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip

@testset "slice" begin
    instance = UnitCommitment.read_benchmark("test/case14")
    modified = UnitCommitment.slice(instance, 1:2)

    # Should update all time-dependent fields
    @test modified.time == 2
    @test length(modified.power_balance_penalty) == 2
    @test length(modified.reserves.spinning) == 2
    for u in modified.units
        @test length(u.max_power) == 2
        @test length(u.min_power) == 2
        @test length(u.must_run) == 2
        @test length(u.min_power_cost) == 2
        @test length(u.provides_spinning_reserves) == 2
        for s in u.cost_segments
            @test length(s.mw) == 2
            @test length(s.cost) == 2
        end
    end
    for b in modified.buses
        @test length(b.load) == 2
    end
    for l in modified.lines
        @test length(l.normal_flow_limit) == 2
        @test length(l.emergency_flow_limit) == 2
        @test length(l.flow_limit_penalty) == 2
    end
    for ps in modified.price_sensitive_loads
        @test length(ps.demand) == 2
        @test length(ps.revenue) == 2
    end

    # Should be able to build model without errors
    optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    model = UnitCommitment.build_model(
        instance = modified,
        optimizer = optimizer,
        variable_names = true,
    )
end


#= extract_object_from_radar.jl
extract the object_list from raw-data of radar =#

using DataFrames, XLSX;
include("visualize_radar_points.jl")
using .visualize

prj_path = dirname(dirname(@__FILE__));
xls_file_path = prj_path * raw"\data\radar_process\radar_points.xlsx";

#=
for id_sample in 1:38
    sh_name = "$id_sample.pcd"
    df=DataFrame(XLSX.readtable(
        xls_file_path,
        sh_name));
    println(df)
end =#

data, col_names = XLSX.readtable(xls_file_path, "1.pcd", header=true);

df = DataFrame(data, col_names);

x = convert(Array{Float64,1}, df.x);
y = convert(Array{Float64,1}, df.y);
vx_comp = convert(Array{Float64,1}, df.vx_comp);
vy_comp = convert(Array{Float64,1}, df.vy_comp);
v_comp = @. abs(vx_comp + im * vy_comp)

# set the v_comp-threshold(10km/s)
v_comp_threshold = 10 / 3.6;
id_v_comp = findall(x -> x >= v_comp_threshold, v_comp);

println(names(df))


export prob_calculation

function prob_calculation(pdb::String, cos_name::String, cos_selection::String, cutoff, trajectory; solute="protein", water=true, water_sel="name OW")

  # solute (protein) selection
  atoms   = PDBTools.readPDB(pdb)
  protein = PDBTools.select(atoms,solute)
  solute  = Selection(protein,nmols=1) # next step, create a function to calculate these things for electrolyte solutions w/out proteins

  # component of the cosolvent
  cosolvent      = PDBTools.select(atoms,cos_selection)
  solvent        = Selection(dca,natomspermol=1)
  trajectory     = Trajectory(trajectory,solute,solvent,format="XTC")
  time, prob_cos, dist, domain, evals, sp  = correlation(trajectory, cutoff)

  if water==true
    water          = PDBTools.select(atoms,water_sel)
    solvent        = Selection(water,natomspermol=1)
    trajectory     = Trajectory(trajectory,solute,solvent,format="XTC")
    time, prob_wat = correlation(trajectory, cutoff)
  end

  #writting the file
  if water==true
    writefile(il,c,time, prob_cos, prob_wat)
  else
    writefile(il,c,time, prob_cos)
  end

end


using ThermProps
using Base.Test

# write your own tests here
LiquidDensity("1,1,1-Trichloroethane",298.15)
LiquidDensity("1,1,1-Trichloroethane1",298.15)
LiquidDensity("1,1,1-Trichloroethane",600.00)
LiquidDensity()

GasViscosity("1,3-Butadiene",298.15)
GasViscosity("1,3-Butadiene1",298.15)
GasViscosity("1,3-Butadiene",800.0)
GasViscosity()

LiquidViscosity("Carbon monoxide",71.00)
LiquidViscosity("Carbon monoxide1",298.15)
LiquidViscosity("Carbon monoxide",600.00)
LiquidViscosity()


files = Dict("EARTH" => "VSOP87E.ear",
             "JUPITER" => "VSOP87E.jup",
             "MARS" => "VSOP87E.mar",
             "MERCURY" => "VSOP87E.mer",
             "NEPTUNE" => "VSOP87E.nep",
             "SATURN" => "VSOP87E.sat",
             "SUN" => "VSOP87E.sun",
             "URANUS" => "VSOP87E.ura",
             "VENUS" => "VSOP87E.ven")

coeffs = (x=[], y=[], z=[])

indent = repeat(" ", 4)

for (name, f) in files
    lines = open(readlines, f)
    for line in lines
        startswith(line, " VSOP") && continue

        idx = parse(Int, line[4:4])
        order = parse(Int, line[5:5]) + 1
        if length(coeffs[idx]) < order
            push!(coeffs[idx], (amplitude=[], phase=[], frequency=[]))
        end
        push!(coeffs[idx][order][:amplitude], parse(Float64, line[80:97]))
        push!(coeffs[idx][order][:phase], parse(Float64, line[98:111]))
        push!(coeffs[idx][order][:frequency], parse(Float64, line[112:131]))
    end

    open("vsop87_$name.jl", "w") do f
        for (idx, coord) in enumerate(["X", "Y", "Z"])
            write(f, "const $(name)_$(coord)_NUM = (\n")
            for (order, co) in enumerate(coeffs[idx])
                write(f, "$indent$(length(co[1])),\n")
            end
            write(f, ")\n\n")
            for (i, co) in enumerate(coeffs[idx])
                order = i - 1
                write(f, "const $(name)_$(coord)_$(order)_AMP = [\n")
                for c in co[1]
                    write(f, "$indent$c,\n")
                end
                write(f, "]\n\n")
                write(f, "const $(name)_$(coord)_$(order)_PHS = [\n")
                for c in co[2]
                    write(f, "$indent$c,\n")
                end
                write(f, "]\n\n")
                write(f, "const $(name)_$(coord)_$(order)_FRQ = [\n")
                for c in co[3]
                    write(f, "$indent$c,\n")
                end
                write(f, "]\n\n")
            end
        end

        write(f, "const VSOP_$(name) = ((\n")
        for (idx, coord) in enumerate(["X", "Y", "Z"])
            for i in 0:length(coeffs[idx]) - 1
                write(f, "$indent($(name)_$(coord)_$(i)_AMP, $(name)_$(coord)_$(i)_PHS, $(name)_$(coord)_$(i)_FRQ),\n")
            end
            if idx in (1, 2)
                write(f, "), (\n")
            end
        end
        write(f, "))\n\n")
        write(f, "const VSOP_$(name)_NUM = ($(name)_X_NUM, $(name)_Y_NUM, $(name)_Z_NUM)\n\n")
    end
    empty!(coeffs[1])
    empty!(coeffs[2])
    empty!(coeffs[3])
end


# Dynamics model flags
abstract type DynamicsFlag end
struct CR3BP <: DynamicsFlag end

# Homotopy flags
abstract type HomotopyFlag end
struct MEMF <: HomotopyFlag end     # Min. Energy -> Min. Fuel
struct HypTanMF <: HomotopyFlag end # Hyperbolic Tangent Minimum Fuel

# Desired integration and returned solution
# Flag indicating integration to be performed for initialization cost function
abstract type IntegrationFlag end
# Returns full state vector and time to final desired time (for early termination)
# as first and second return values respectively.
abstract type InitializationFlag <: IntegrationFlag end
struct Initialization <: InitializationFlag end
struct InitializationWithIntegralCost <: InitializationFlag end

# Flag indicating integration to be performed for solving BVP via
# shooting.
#
# Returns final full state vector
abstract type SolvingFlag <: IntegrationFlag end
struct Solving <: SolvingFlag end
struct SolvingWithSTM <: SolvingFlag end

# Flag indicating full state history is disired
#
# Returns DifferentialEquations.jl Solution object
abstract type SolutionHistoryFlag <: IntegrationFlag end
struct FullSolutionHistory <: SolutionHistoryFlag end
struct FullSolutionHistoryWithSTM <: SolutionHistoryFlag end

# Flag indicating full state history without
# control is desired
struct FullSolutionHistoryNoControl <: SolutionHistoryFlag end
# struct FullSolutionHistoryNoControlWithSTM end # Do not have this implemented!


function FieldCircularOrbit(arg0::FieldOrbit)
    return FieldCircularOrbit((FieldOrbit,), arg0)
end

function FieldCircularOrbit(arg0::FieldPVCoordinates, arg1::Frame, arg2::FieldAbsoluteDate, arg3::RealFieldElement)
    return FieldCircularOrbit((FieldPVCoordinates, Frame, FieldAbsoluteDate, RealFieldElement), arg0, arg1, arg2, arg3)
end

function FieldCircularOrbit(arg0::RealFieldElement, arg1::RealFieldElement, arg10::RealFieldElement, arg11::RealFieldElement, arg12::PositionAngle, arg13::Frame, arg14::FieldAbsoluteDate, arg15::RealFieldElement, arg2::RealFieldElement, arg3::RealFieldElement, arg4::RealFieldElement, arg5::RealFieldElement, arg6::RealFieldElement, arg7::RealFieldElement, arg8::RealFieldElement, arg9::RealFieldElement)
    return FieldCircularOrbit((RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, PositionAngle, Frame, FieldAbsoluteDate, RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement), arg0, arg1, arg10, arg11, arg12, arg13, arg14, arg15, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
end

function FieldCircularOrbit(arg0::RealFieldElement, arg1::RealFieldElement, arg2::RealFieldElement, arg3::RealFieldElement, arg4::RealFieldElement, arg5::RealFieldElement, arg6::PositionAngle, arg7::Frame, arg8::FieldAbsoluteDate, arg9::RealFieldElement)
    return FieldCircularOrbit((RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement, PositionAngle, Frame, FieldAbsoluteDate, RealFieldElement), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
end

function FieldCircularOrbit(arg0::TimeStampedFieldPVCoordinates, arg1::Frame, arg2::RealFieldElement)
    return FieldCircularOrbit((TimeStampedFieldPVCoordinates, Frame, RealFieldElement), arg0, arg1, arg2)
end

function add_kepler_contribution(obj::FieldCircularOrbit, arg0::PositionAngle, arg1::RealFieldElement, arg2::Vector{RealFieldElement})
    return jcall(obj, "addKeplerContribution", void, (PositionAngle, RealFieldElement, Vector{RealFieldElement}), arg0, arg1, arg2)
end

function eccentric_to_mean(::Type{FieldCircularOrbit}, arg0::RealFieldElement, arg1::RealFieldElement, arg2::RealFieldElement)
    return jcall(FieldCircularOrbit, "eccentricToMean", RealFieldElement, (RealFieldElement, RealFieldElement, RealFieldElement), arg0, arg1, arg2)
end

function eccentric_to_true(::Type{FieldCircularOrbit}, arg0::RealFieldElement, arg1::RealFieldElement, arg2::RealFieldElement)
    return jcall(FieldCircularOrbit, "eccentricToTrue", RealFieldElement, (RealFieldElement, RealFieldElement, RealFieldElement), arg0, arg1, arg2)
end

function get_a(obj::FieldCircularOrbit)
    return jcall(obj, "getA", RealFieldElement, ())
end

function get_a_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getADot", RealFieldElement, ())
end

function get_alpha(obj::FieldCircularOrbit, arg0::PositionAngle)
    return jcall(obj, "getAlpha", RealFieldElement, (PositionAngle,), arg0)
end

function get_alpha_dot(obj::FieldCircularOrbit, arg0::PositionAngle)
    return jcall(obj, "getAlphaDot", RealFieldElement, (PositionAngle,), arg0)
end

function get_alpha_e(obj::FieldCircularOrbit)
    return jcall(obj, "getAlphaE", RealFieldElement, ())
end

function get_alpha_e_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getAlphaEDot", RealFieldElement, ())
end

function get_alpha_m(obj::FieldCircularOrbit)
    return jcall(obj, "getAlphaM", RealFieldElement, ())
end

function get_alpha_m_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getAlphaMDot", RealFieldElement, ())
end

function get_alpha_v(obj::FieldCircularOrbit)
    return jcall(obj, "getAlphaV", RealFieldElement, ())
end

function get_alpha_v_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getAlphaVDot", RealFieldElement, ())
end

function get_circular_ex(obj::FieldCircularOrbit)
    return jcall(obj, "getCircularEx", RealFieldElement, ())
end

function get_circular_ex_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getCircularExDot", RealFieldElement, ())
end

function get_circular_ey(obj::FieldCircularOrbit)
    return jcall(obj, "getCircularEy", RealFieldElement, ())
end

function get_circular_ey_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getCircularEyDot", RealFieldElement, ())
end

function get_e(obj::FieldCircularOrbit)
    return jcall(obj, "getE", RealFieldElement, ())
end

function get_e_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getEDot", RealFieldElement, ())
end

function get_equinoctial_ex(obj::FieldCircularOrbit)
    return jcall(obj, "getEquinoctialEx", RealFieldElement, ())
end

function get_equinoctial_ex_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getEquinoctialExDot", RealFieldElement, ())
end

function get_equinoctial_ey(obj::FieldCircularOrbit)
    return jcall(obj, "getEquinoctialEy", RealFieldElement, ())
end

function get_equinoctial_ey_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getEquinoctialEyDot", RealFieldElement, ())
end

function get_hx(obj::FieldCircularOrbit)
    return jcall(obj, "getHx", RealFieldElement, ())
end

function get_hx_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getHxDot", RealFieldElement, ())
end

function get_hy(obj::FieldCircularOrbit)
    return jcall(obj, "getHy", RealFieldElement, ())
end

function get_hy_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getHyDot", RealFieldElement, ())
end

function get_i(obj::FieldCircularOrbit)
    return jcall(obj, "getI", RealFieldElement, ())
end

function get_i_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getIDot", RealFieldElement, ())
end

function get_le(obj::FieldCircularOrbit)
    return jcall(obj, "getLE", RealFieldElement, ())
end

function get_le_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getLEDot", RealFieldElement, ())
end

function get_lm(obj::FieldCircularOrbit)
    return jcall(obj, "getLM", RealFieldElement, ())
end

function get_lm_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getLMDot", RealFieldElement, ())
end

function get_lv(obj::FieldCircularOrbit)
    return jcall(obj, "getLv", RealFieldElement, ())
end

function get_lv_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getLvDot", RealFieldElement, ())
end

function get_right_ascension_of_ascending_node(obj::FieldCircularOrbit)
    return jcall(obj, "getRightAscensionOfAscendingNode", RealFieldElement, ())
end

function get_right_ascension_of_ascending_node_dot(obj::FieldCircularOrbit)
    return jcall(obj, "getRightAscensionOfAscendingNodeDot", RealFieldElement, ())
end

function get_type(obj::FieldCircularOrbit)
    return jcall(obj, "getType", OrbitType, ())
end

function has_derivatives(obj::FieldCircularOrbit)
    return jcall(obj, "hasDerivatives", jboolean, ())
end

function interpolate(obj::FieldCircularOrbit, arg0::FieldAbsoluteDate, arg1::Stream)
    return jcall(obj, "interpolate", FieldCircularOrbit, (FieldAbsoluteDate, Stream), arg0, arg1)
end

function mean_to_eccentric(::Type{FieldCircularOrbit}, arg0::RealFieldElement, arg1::RealFieldElement, arg2::RealFieldElement)
    return jcall(FieldCircularOrbit, "meanToEccentric", RealFieldElement, (RealFieldElement, RealFieldElement, RealFieldElement), arg0, arg1, arg2)
end

function shifted_by(obj::FieldCircularOrbit, arg0::RealFieldElement)
    return jcall(obj, "shiftedBy", FieldCircularOrbit, (RealFieldElement,), arg0)
end

function shifted_by(obj::FieldCircularOrbit, arg0::jdouble)
    return jcall(obj, "shiftedBy", FieldCircularOrbit, (jdouble,), arg0)
end

function to_orbit(obj::FieldCircularOrbit)
    return jcall(obj, "toOrbit", CircularOrbit, ())
end

function to_string(obj::FieldCircularOrbit)
    return jcall(obj, "toString", JString, ())
end

function true_to_eccentric(::Type{FieldCircularOrbit}, arg0::RealFieldElement, arg1::RealFieldElement, arg2::RealFieldElement)
    return jcall(FieldCircularOrbit, "trueToEccentric", RealFieldElement, (RealFieldElement, RealFieldElement, RealFieldElement), arg0, arg1, arg2)
end


module AES
	include("aes-modes.jl")
	export AESEncrypt, AESDecrypt, AESParameters
	export AESECB, AESCBC, AESCFB, AESOFB, AESCTR
end


# N.B. This file is maintained in TensorFlowBuilder source
using PyCall
import Base: convert
import PyCall: PyObject

export SessionRunnable, AbstractTensor, DimsType, NoneDimsType, NegDimsType, PyVectorType

macro pywrapper(typename, supertype)
  quote
    immutable $typename <: $supertype
      x::PyCall.PyObject
    end
    PyCall.PyObject(o::$typename) = o.x
    isequal(x::$typename, y::$typename) = x.x[__eq__](y.x)
    export $typename
  end
end

abstract SessionRunnable
abstract AbstractTensor <: SessionRunnable

@pywrapper Dtype Any

@pywrapper Tensor AbstractTensor
@pywrapper Variable AbstractTensor
@pywrapper SparseTensor AbstractTensor
@pywrapper Placeholder AbstractTensor

@pywrapper Operation SessionRunnable

@pywrapper Session Any

@pywrapper Optimizer Any

convert(::Type{Tensor}, a::Array) = Tensor(a)
convert(::Type{AbstractTensor}, a::Array) = Tensor(a)

## DimsType ##
# TensorFlow shape arguments sometimes want None to mean "wild" (e.g. tf.placeholder)
# and sometimes use negatives to mean wildcard (e.g.reshape)

abstract DimsType

"A literal used for TensorFlow shape arguments.
  Negative indices are passed as Python `None` values."
type NoneDimsType <: DimsType
  arr::Vector{Int}
end
NoneDimsType(a::Tuple{Vararg{Int}}) = NoneDimsType(collect(a))
PyCall.PyObject(o::NoneDimsType) = PyCall.PyObject([a < 0 ? nothing : a for a in o.arr])

"A literal used for TensorFlow shape arguments.
  Negative indices are passed through to Python."
type NegDimsType <: DimsType
  arr::Vector{Int}
end
NegDimsType(a::Tuple{Vararg{Int}}) = NegDimsType(collect(a))
PyCall.PyObject(o::NegDimsType) = PyCall.PyObject(o.arr)

"A wrapper type for things that TensorFlow insists must be Python
  lists, like conv2d's strides argument."
type PyVectorType <: DimsType
  pyvec::PyVector
end
PyVectorType(a::Vector) = PyVectorType(PyVector(a))
PyVectorType(a::Tuple) = PyVectorType(collect(a))
PyCall.PyObject(o::PyVectorType) = PyCall.PyObject(o.pyvec)