import CSV

# This file implements using Cullen's Evaluated Atomic Data Library 1997 for emission probabilities.
# The file relax.csv contains five columns, the atomic number, 3 sub-shell indices and a probability.
# The first two columns indicate the atom and the sub-shell ionized.  The third and fourth columns
# contain the inner and outer sub-shells involved in the X-ray transition and the fifth contains
# the probability of an ionization in column 2 producing an X-ray in columns 3 and 4.  This is what
# Perkins would call the Total yield - An ionization in column 2 will initiate a cascade of transitions.
# When the atom finally returns to the ground state, it could have emitted zero or more x-rays and
# zero or more Augers.  A K shell vacancy could relax via L3 which could relax via M5 and so on.

struct CullenEADL <: NeXLAlgorithm end

let transitions_data, xrayweights_data

    function loadAltWeights()
        @info "Loading EADL transition rate data."
        nn = (
            1, # Shell index
            (2 for _ in 1:3)...,
            (3 for _ in 1:5)...,
            (4 for _ in 1:7)...,
            (5 for _ in 1:9)...,
            (6 for _ in 1:11)...,
            (7 for _ in 1:13)...
        ) # = collect(Iterators.flatten(collect(i for _ in 1:(2i-1)) for i in 1:7 ))
        trans = Dict{Tuple{Int,Int},Int}()
        xrw = [ Dict{NTuple{3, Int}, Float64}() for _ in 1:99 ]
        for row in CSV.File(joinpath(dirname(pathof(@__MODULE__)), "..", "data", "relax.csv"))
            z, ionized, inn, out, weight = row.ZZ, row.II, row.NN, row.OO, row.PP
            if (z <= 99) && hasedge(z, inn, FFASTDB) && hasedge(z, out, FFASTDB) && (nn[inn] != nn[out])
                # There seems to be a problem with the L2-M1 and L3-M1 weights which I resolve with this ad-hoc fix.
                if (out == 5) && ((inn == 4) || (inn == 3))
                    if z >= 29
                        weight *= max(0.1, 0.1 + ((0.9 * (z - 29.0)) / (79.0 - 29.0)))
                    else
                        weight *= max(0.1, 0.2 - ((0.1 * (z - 22.0)) / (29.0 - 22.0)))
                    end
                end
                xrw[z][(ionized, inn, out)] = weight
                trans[(inn, out)] = get(trans, (inn, out), 0) + 1
            end
        end
        # Add these which aren't in Cullen (The weights are from the Practical Handbook of Spectroscopy pg 834)
        for x in ((3, 1, 1, 2, 1.06e-4), (4, 1, 1, 2, 3.2e-4), (5, 1, 1, 3, 7.1e-4))
            xrw[x[1]] = Dict( (x[2], x[3], x[4]) => x[5] )
        end
        # Now compute some of the normalized weights
        xrw2 = [ Dict{NTuple{3, Int}, NTuple{4,Float64}}() for _ in 1:99 ]
        for (z, id) in enumerate(xrw)
            sum_s = Dict{Int, Float64}() # Normalize over all in shell
            sum_ss = Dict{Int, Float64}() # Normalize over all in sub-shell
            max_s = Dict{Int, Float64}() # Normalize most intense in sub-shell to 1.0
            for ((ionized, inn, _), w) in id
                # Assume large overvoltage so ignore differences in ionization rates
                if inn==ionized
                    icx = ionizationcrosssection(z, inn, 4.0*edgeenergy(z, inn, FFASTDB), Bote2009)
                    sum_ss[inn] = get(sum_ss, inn, 0.0) + w
                    sum_s[nn[inn]] = get(sum_s, nn[inn], 0.0) + icx*w
                    max_s[nn[inn]] = max(icx*w, get(max_s, nn[inn], 0.0))
                end
            end
            for ((ionized, inn, out), w) in id
                icx = ionizationcrosssection(z, inn, 4.0*edgeenergy(z, inn, FFASTDB), Bote2009)
                if inn == ionized
                    # Yield, norm[sub_shell], norm[shell], norm[max=1]
                    xrw2[z][(ionized, inn, out)] = (w, w/sum_ss[inn], (icx*w)/sum_s[nn[inn]], (icx*w)/max_s[nn[inn]])
                else
                    xrw2[z][(ionized, inn, out)] = (w, 0.0, 0.0, 0.0 )
                end
            end
        end
        return ( trans, xrw2 )
    end
    transitions_data, xrayweights_data = loadAltWeights()
    global transitions()::Dict{Tuple{Int,Int},Int} = transitions_data
    global xrayweights()::Vector{Dict{NTuple{3, Int}, NTuple{4, Float64}}} = xrayweights_data
end

fluorescenceyield(z::Int, ionized::Int, inner::Int, outer::Int, ::Type{CullenEADL})::Float64 =
    get(xrayweights()[z], (ionized, inner, outer), (0.0, 0.0, 0.0, 0.0))[1]
fluorescenceyield(z::Int, inner::Int, outer::Int, ::Type{CullenEADL})::Float64 =
    get(xrayweights()[z], (inner, inner, outer), (0.0, 0.0, 0.0, 0.0))[1]
function fluorescenceyield(z::Int, inner::Int, ::Type{CullenEADL})
    sum=0.0
    for ((ion,inn,_), ws) in xrayweights()[z]
        if (ion==inn) && (inner==inn)
            sum+=ws[1]
        end
    end
    return sum
end

isAvailable(z::Int, inner::Int, outer::Int, ::Type{CullenEADL}) =
    fluorescenceyield(z, inner, inner, outer, CullenEADL) > 0.0


module InfiniteOpt

# Import the necessary packages.
import JuMP
import MathOptInterface
import Distributions
import Random
import MutableArithmetics
const _MA = MutableArithmetics
const MOI = MathOptInterface
const MOIU = MOI.Utilities
const JuMPC = JuMP.Containers

# Import all of the datatpyes, methods, macros, and definitions.
include("datatypes.jl")
include("infinite_sets.jl")
include("parameters.jl")
include("variables.jl")
include("reduced_variables.jl")
include("expressions.jl")
include("mutable_arithmetics.jl")
include("measures.jl")
include("operators.jl")
include("constraints.jl")
include("objective.jl")
include("measure_expansions.jl")
include("macros.jl")
include("optimize.jl")
include("results.jl")
include("show.jl")
include("utilities.jl")

include("TranscriptionOpt/TranscriptionOpt.jl")
using .TranscriptionOpt

include("MeasureEvalMethods/MeasureEvalMethods.jl")
using .MeasureEvalMethods: generate_measure_data, mc_sampling, gauss_legendre,
gauss_hermite, gauss_laguerre, infinite_transform, generate_supports_and_coeffs,
trapezoid

# Export the measure eval functions and constants
export generate_measure_data, mc_sampling, gauss_legendre, gauss_hermite,
gauss_laguerre, trapezoid, infinite_transform, measure_dispatch,
generate_supports_and_coeffs

# Export model object datatype
export InfiniteModel

# Export macros
export @infinite_variable, @point_variable, @hold_variable, @infinite_parameter,
@BDconstraint, @finite_parameter, @set_parameter_bounds, @add_parameter_bounds

# Export constants
export Infinite, Hold, Point, Parameter, sampling, quadrature

# Export infinite parameter datatypes
export InfOptParameter, ParameterRef

# Export infinite parameter set types
export AbstractInfiniteSet, IntervalSet, DistributionSet

# Export infinite parameter methods
export build_parameter, add_parameter, infinite_set, set_infinite_set,
num_parameters, parameter_by_name, all_parameters, num_supports, has_supports,
set_supports, add_supports, delete_supports, supports, used_by_constraint,
used_by_measure, used_by_variable, is_used, group_id, is_independent,
set_independent, unset_independent, is_finite_parameter, supports_in_set,
generate_and_add_supports!, generate_support_values

# Export variable datatypes
export InfOptVariable, InfiniteVariable, PointVariable, HoldVariable,
GeneralVariableRef, InfiniteVariableRef, MeasureFiniteVariableRef,
FiniteVariableRef, PointVariableRef, HoldVariableRef, InfOptVariableRef,
ReducedInfiniteVariableRef, AbstractReducedInfo, ReducedInfiniteInfo,
ParameterBounds

# Export variable methods
export used_by_objective, infinite_variable_ref, parameter_refs,
set_parameter_refs, add_parameter_ref, used_by_point_variable, parameter_values,
eval_supports, used_by_reduced_variable, has_parameter_bounds, parameter_bounds,
set_parameter_bounds, add_parameter_bound, delete_parameter_bound,
delete_parameter_bounds

# Export expression datatypes
export InfiniteExpr, ParameterExpr, MeasureExpr

# Export constraint datatypes
export GeneralConstraintRef, InfiniteConstraintRef, MeasureConstraintRef,
FiniteConstraintRef, BoundedScalarConstraint

# Export measure datatypes
export AbstractMeasureData, DiscreteMeasureData, MultiDiscreteMeasureData,
Measure, MeasureRef

# Export measure methods
export add_measure, measure, measure_function, measure_data, expand,
expand_all_measures!, expect, support_sum, measure_name, measure_data_in_hold_bounds,
make_point_variable_ref, make_reduced_variable_ref, expand_measure, integral,
set_integral_defaults, integral_defaults

# Export transcription datatypes
export TranscriptionData, TranscriptionModel

# Export transcription methods
export is_transcription_model, transcription_data, transcription_variable,
transcription_constraint, transcription_model

# Export optimize methods
export optimizer_model, set_optimizer_model, optimizer_model_ready,
set_optimizer_model_ready, build_optimizer_model!, optimizer_model_key,
optimizer_model_variable, optimizer_model_constraint, variable_supports,
constraint_supports, constraint_parameter_refs, add_infinite_model_optimizer,
clear_optimizer_model_build!

# Export result query methods
export map_value, map_optimizer_index, map_dual, map_shadow_price,
map_lp_rhs_perturbation_range, map_lp_objective_perturbation_range

# Export functions that will be included in future JuMP releases

# Export printing methods
export bound_string

# Export support generation and fill-in methods
export fill_in_supports!, generate_supports

end # module


synth_paletted_imgs = [
    ("RGB_paletted", IndirectArray(rand(1:100, 127, 257), rand(RGB{N0f8}, 100))),
    ("BRG_paletted", IndirectArray(rand(1:100, 127, 257), rand(BGR{N0f8}, 100))),
    ("ARGB_paletted", IndirectArray(rand(1:100, 127, 257), rand(ARGB{N0f8}, 100))),
    ("ABGR_paletted", IndirectArray(rand(1:100, 127, 257), rand(ABGR{N0f8}, 100))),
    ("RGBA_paletted", IndirectArray(rand(1:100, 127, 257), rand(RGBA{N0f8}, 100))),
    ("RGB_paletted_float", IndirectArray(rand(1:100, 127, 257), rand(RGB{Float64}, 100))),
    ("BRG_paletted_float", IndirectArray(rand(1:100, 127, 257), rand(BGR{Float64}, 100))),
    ("ARGB_paletted_float", IndirectArray(rand(1:100, 127, 257), rand(ARGB{Float64}, 100))),
    ("ABGR_paletted_float", IndirectArray(rand(1:100, 127, 257), rand(ABGR{Float64}, 100))),
    ("RGBA_paletted_float", IndirectArray(rand(1:100, 127, 257), rand(RGBA{Float64}, 100))),
]

expected_img(x::Matrix{<:TransparentColor}) = RGBA{N0f8}.(x)
expected_img(x::Matrix{<:AbstractRGB}) = RGB{N0f8}.(x)

@testset "synthetic paletted images" begin
    for expand_paletted in (true, false)
        @testset "expand_paletted = $(expand_paletted)" begin
            for (case, image) in synth_paletted_imgs
                @testset "$(case)" begin
                    expected = expected_img(collect(image))
                    fpath = joinpath(PNG_TEST_PATH, "test_img_$(case).png")
                    @testset "write" begin
                        open(io -> PNGFiles.save(io, image), fpath, "w")
                        @test PNGFiles.save(fpath, image) === nothing
                    end
                    @testset "read" begin
                        global read_in_pngf = PNGFiles.load(fpath, expand_paletted=expand_paletted)
                        @test typeof(read_in_pngf) <: AbstractMatrix
                    end
                    @testset "compare" begin
                        @test all(expected .≈ read_in_pngf)
                    end
                    global read_in_immag = _standardize_grayness(ImageMagick.load(fpath))
                    @testset "$(case): ImageMagick read type equality" begin
                        # The lena image is Grayscale saved as RGB...
                        @test eltype(_standardize_grayness(read_in_pngf)) == eltype(read_in_immag)
                    end
                    @testset "$(case): ImageMagick read values equality" begin
                        imdiff_val = imdiff(read_in_pngf, read_in_immag)
                        onfail(@test imdiff_val < 0.01) do
                            PNGFiles._inspect_png_read(fpath)
                            _add_debugging_entry(fpath, case, imdiff_val)
                        end
                    end
                    path, ext = splitext(fpath)
                    newpath = path * "_new" * ext
                    PNGFiles.save(newpath, read_in_pngf)
                    @testset "$(case): IO is idempotent" begin
                        @test all(read_in_pngf .≈ PNGFiles.load(newpath, expand_paletted=expand_paletted))
                        @test all(read_in_pngf .≈ open(io -> PNGFiles.load(io, expand_paletted=expand_paletted), newpath))
                    end
                end
            end
        end
    end
end

@testset "palleted image with fewer transparency values than color values" begin
    expected_palette = RGBA{N0f8}[
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.0),
        RGBA{N0f8}(0.282, 0.133, 0.169, 0.024),
        RGBA{N0f8}(0.502, 0.431, 0.459, 0.337),
        RGBA{N0f8}(0.49, 0.427, 0.478, 0.51),
        RGBA{N0f8}(0.282, 0.133, 0.169, 0.208),
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.004),
        RGBA{N0f8}(0.714, 0.518, 0.153, 0.722),
        RGBA{N0f8}(0.6, 0.49, 0.322, 0.953),
        RGBA{N0f8}(0.298, 0.18, 0.208, 0.718),
        RGBA{N0f8}(0.196, 0.11, 0.11, 0.408),
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.039),
        RGBA{N0f8}(0.655, 0.765, 0.18, 0.227),
        RGBA{N0f8}(0.592, 0.686, 0.18, 0.212),
        RGBA{N0f8}(0.961, 0.839, 0.153, 0.957),
        RGBA{N0f8}(0.58, 0.412, 0.11, 0.831),
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.345),
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.188),
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.02),
        RGBA{N0f8}(0.667, 0.757, 0.208, 0.749),
        RGBA{N0f8}(0.616, 0.69, 0.196, 0.988),
        RGBA{N0f8}(0.584, 0.365, 0.169, 0.996),
        RGBA{N0f8}(0.427, 0.439, 0.208, 0.573),
        RGBA{N0f8}(0.537, 0.533, 0.18, 0.341),
        RGBA{N0f8}(0.51, 0.482, 0.196, 0.122),
        RGBA{N0f8}(0.545, 0.678, 0.196, 0.804),
        RGBA{N0f8}(0.208, 0.133, 0.082, 0.694),
        RGBA{N0f8}(0.298, 0.239, 0.133, 0.569),
        RGBA{N0f8}(0.082, 0.0, 0.0, 0.275),
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.008),
        RGBA{N0f8}(0.518, 0.627, 0.196, 0.659),
        RGBA{N0f8}(0.427, 0.522, 0.153, 0.8),
        RGBA{N0f8}(0.412, 0.494, 0.133, 0.765),
        RGBA{N0f8}(0.239, 0.18, 0.082, 0.769),
        RGBA{N0f8}(0.196, 0.0, 0.082, 0.635),
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.027),
        RGBA{N0f8}(0.082, 0.0, 0.0, 0.035),
        RGBA{N0f8}(0.196, 0.22, 0.0, 0.082),
        RGBA{N0f8}(0.082, 0.11, 0.0, 0.239),
        RGBA{N0f8}(0.208, 0.231, 0.0, 0.204),
        RGBA{N0f8}(0.133, 0.0, 0.0, 0.098),
        RGBA{N0f8}(0.133, 0.0, 0.0, 0.4),
        RGBA{N0f8}(0.0, 0.0, 0.0, 0.035),
        RGBA{N0f8}(0.796, 0.659, 0.22, 1.0), # <- Implicitly opaque
        RGBA{N0f8}(0.553, 0.698, 0.208, 1.0),
        RGBA{N0f8}(0.478, 0.561, 0.251, 1.0),
        RGBA{N0f8}(0.376, 0.314, 0.169, 1.0),
        RGBA{N0f8}(1.0, 1.0, 1.0, 1.0),
    ]
    img = PNGFiles.load("test_images/paletted_implicit_transparency.png")
    @testset "palette equality" begin
        @test img.values.parent == expected_palette
    end

    @testset begin "idempotency"
        new_path = "test_images/paletted_implicit_transparency_new.png"
        PNGFiles.save(new_path, img)
        new_img = PNGFiles.load(new_path)
        @test img == new_img
        Base.rm(new_path)
    end
end


import Avalon: convNd

@testset "conv" begin
    kwargs = (padding=1, stride=2)
    for (x_sz, w_sz) in [
            ((8, 3, 10), (3, 3, 1)),              # 1D
            ((8, 8, 3, 10), (3, 3, 3, 1)),        # 2D
        ]
        x = rand(Float32, x_sz); w = rand(Float32, w_sz)
        test_rrule(convNd, x, w; rtol=1e-3, atol=1e-3, check_inferred=false)
        test_rrule(convNd, x, w; fkwargs=kwargs, rtol=1e-3, atol=1e-3, check_inferred=false)
    end

    # 3D is very noisy for numeric gradient, using larger atol & rtol for it
    x = rand(Float32, 8, 8, 8, 3, 1); w = rand(Float32, 3, 3, 3, 3, 1)
    test_rrule(convNd, x, w; rtol=1e-1, atol=1e-1, check_inferred=false)
    test_rrule(convNd, x, w; fkwargs=kwargs, rtol=1e-1, atol=1e-1, check_inferred=false)
end

@testset "pooling" begin
    x = rand(7, 7, 3, 10);
    test_rrule(maxpool2d, x, 2; check_inferred=false)
    test_rrule(maxpool2d, x, 2; fkwargs=(stride=2,), check_inferred=false)
end


# Definitions of physical unit types for dispatch. At the moment this is a
# "dummy" set of definitions that just sets SI units to 1, but this may be
# swapped out for actual units if I have time.

using BasicUnits

const Mass = Real
const Distance = Real
const Radius = Distance
const Temperature = Real
const MassLuminosity = Real
const Luminosity = Real
const Flux = Real
const Pressure = Real
const Opacity = Real
const Acceleration = Real
const Density = Real
const Gravity = Acceleration
const Dimensionless = Real
const OpticalDepth = Dimensionless

#= definitions unused for now
using SIUnits
using SIUnits.ShortUnits
import SIUnits.unit, SIUnits.SIQuantity

# const Unit{T} = SIQuantity{T,...}
const Mass{T} = SIQuantity{T,0,1,0,0,0,0,0,0,0}
const Distance{T} = SIQuantity{T,1,0,0,0,0,0,0,0,0}
const Radius = Distance
const Temperature{T} = SIQuantity{T,0,0,0,0,1,0,0,0,0}
const MassLuminosity{T} = SIQuantity{T,2,0,-3,0,0,0,0,0,0}
const Luminosity{T} = SIQuantity{T,2,1,-3,0,0,0,0,0,0}
const Flux{T} = SIQuantity{T,0,1,-3,0,0,0,0,0,0}
const Pressure{T} = SIQuantity{T,-1,1,-2,0,0,0,0,0,0}
const Opacity{T} = SIQuantity{T,2,-1,0,0,0,0,0,0,0}
const Acceleration{T} = SIQuantity{T,1,0,-2,0,0,0,0,0,0}
const Density{T} = SIQuantity{T,-3,1,0,0,0,0,0,0,0}
const Gravity = Acceleration
const Dimensionless = Real
const OpticalDepth = Dimensionless
=#


#!/usr/bin/env julia

using Luxor

using Test

using Random
Random.seed!(42)

function hipster(fname)
    Drawing(400, 350, fname) # hide
    origin() # hide
    rotate(pi/8)

    circle(O, 135, :clip)
    sethue("antiquewhite2")
    paint()

    sethue("gray20")
    setline(3)
    circle(O, 130, :stroke)
    circle(O, 135, :stroke)
    circle(O, 125, :fill)

    sethue("antiquewhite2")
    circle(O, 85, :fill)

    sethue("wheat")
    fontsize(24)
    fontface("Helvetica-Bold")
    # Try both synonyms (long live the Queen, and UK spelling!)
    textcurvecentered("• LUXOR •", (3pi)/2, 100, O, clockwise=true, baselineshift = -4)
    textcurvecentred("• VECTOR GRAPHICS •", pi/2, 100, O, clockwise=false, letter_spacing=2, baselineshift = -15)

    sethue("gray20")
    map(pt -> star(pt, 40, 3, 0.5, -pi/2, :fill), ngon(O, 40, 3, 0, vertices=true))
    circle(O.x + 30, O.y - 55, 15, :fill)

    sethue("antiquewhite2")
    setline(0.2)
    setdash("dotdotdashed")
    for i in 1:500
        line(randompoint(Point(-200, -350), Point(200, 350)),
             randompoint(Point(-200, -350), Point(200, 350)),
             :stroke)
    end
    @test finish() == true
end

fname = "text-curve-centered.png"
hipster(fname)
println("...finished test: output in $(fname)")


using ProximityEnrichment
using Test
using GenomicFeatures

intA = Interval("chr1", 10, 100, '.')
intB = Interval("chr1", 80, 200, '.')
intC = Interval("chr1", 110, 500, '.')
intD = Interval("chr2", 100, 200, '.')
intE = Interval("chr2", 400, 600, '.')
intF = Interval("chrX", 10, 600, '-')

ivs = [intA, intB, intC, intD, intE, intF]

@testset "ProximityEnrichment.jl" begin
    # Write your own tests here
    @test ProximityEnrichment.intervaldist(intA, intB) == 0
    @test ProximityEnrichment.intervaldist(intB, intA) == 0
    @test ProximityEnrichment.intervaldist(intA, intC) == 10
    @test ProximityEnrichment.intervaldist(intC, intA) == 10
    @test ProximityEnrichment.intervaldist(intB, intC) == 0

    @test ProximityEnrichment.intervaldist(Interval("chr1", 1:20, '.'), Interval("chr2", 1:20, '.')) == typemax(Int)
    @test ProximityEnrichment.intervaldist(Interval("chr1", 1:20,), Interval("chr1", 100:120)) == 80

    @test ProximityEnrichment.closestinterval(ivs, Interval("chr1", 1, 8, '.')) == 2
    @test ProximityEnrichment.closestinterval(ivs, Interval("chr1", 1, 9, '.')) == 1
    @test ProximityEnrichment.closestinterval(ivs, Interval("chr1", 5, 15, '.')) == 0
    @test ProximityEnrichment.closestinterval(ivs, Interval("chr1", 50, 150, '.')) == 0

    @test ProximityEnrichment.closestinterval(ivs, Interval("chr1", 600, 750, '.')) == 100
    @test ProximityEnrichment.closestinterval(ivs, Interval("chr2", 10, 15, '.')) == 85
    @test ProximityEnrichment.closestinterval(ivs, Interval("chr2", 300, 300, '.')) == 100
    @test ProximityEnrichment.closestinterval(ivs, Interval("chr2", 300, 300, '.')) == 100
    @test ProximityEnrichment.closestinterval(ivs, Interval("chr2", 1000, 2000, '+')) == 400
    @test ProximityEnrichment.closestinterval(ivs, Interval("chr3", 1000, 2000, '+')) == -1
    @test ProximityEnrichment.closestinterval(ivs, Interval("chrY", 1000, 2000, '+')) == -1

end

intA = Interval("chr1", 10, 100, '.')
intB = Interval("chr1", 110, 500, '.')
intC = Interval("chr1", 80, 200, '.')

@show ProximityEnrichment.intervaldist(intA, intB)


module BedsData

using CSV
using DataFrames

export n_beds

basepath = normpath(@__DIR__, "../../")

function n_beds(locations::Array; level::Symbol=:state, source::Symbol=:definitivehc, bed_type::Symbol=:all, pct_beds_available::Real=0.25)
	if     level == :state    && source == :definitivehc
		beds_data = load_definitivehc()
		beds_data = definitivehc_filter!(beds_data, locations, level=level)
		beds_data = definitivehc_select_type!(beds_data, bed_type=bed_type)
		beds = definitivehc_aggregate(beds_data)
	elseif level == :state    && source == :gov
		@assert bed_type == :all
		beds_data = load_gov()
		beds = compute_beds_gov(beds_data, locations)
	elseif level == :county   && source == :definitivehc
		beds_data = load_definitivehc()
		beds_data = definitivehc_filter!(beds_data, locations, level=level)
		beds_data = definitivehc_select_type!(beds_data, bed_type=bed_type)
		beds = definitivehc_aggregate(beds_data, locations=locations)
	elseif level == :hospital && source == :definitivehc
		beds_data = load_definitivehc()
		beds_data = definitivehc_filter!(beds_data, locations, level=level)
		beds_data = definitivehc_select_type!(beds_data, bed_type=bed_type)
		sort!(beds_data, :selected_location)
		beds = beds_data.selected_beds
	else
		error("Invalid parameters to compute_beds.")
	end

	return beds * Float32(pct_beds_available)
end

########################
###### data.gov ########
########################

function load_gov()
	beds_data = DataFrame(CSV.File(joinpath(basepath, "data/hospitals/gov.csv")), copycols=true)

	filter!(row -> row.BEDS > 0, beds_data)
	filter!(row -> row.TYPE == "GENERAL ACUTE CARE", beds_data)

	beds_data = combine(groupby(beds_data, :STATE), :BEDS => sum)
	sort!(beds_data, :STATE)

	return beds_data
end

function compute_beds_gov(beds_data::DataFrame, states::Array{String,1})
	@assert states == sort(states)
	beds_df = filter(row -> row.STATE in states, beds_data)
	return Float32.(beds_df.BEDS_sum)
end

###########################
## Definitive Healthcare ##
###########################

function load_definitivehc()
	_beds_data = DataFrame(CSV.File(joinpath(basepath, "data/hospitals/definitivehc.csv")))
	beds_data = filter(row -> !(row.HOSPITAL_TYPE in ["Psychiatric Hospital", "Rehabilitation Hospital"]), _beds_data)
	return beds_data
end

function definitivehc_filter!(beds_data::DataFrame, locations::Array; level::Symbol=:state)
	if level == :state
		filter!(row -> !ismissing(row.HQ_STATE), beds_data)
		beds_data.selected_location = convert(Array{Union{String,Missing}}, deepcopy(beds_data.HQ_STATE))
	elseif level == :county
		filter!(row -> !ismissing(row.FIPS), beds_data)
		beds_data.selected_location = convert(Array{Union{Int,Missing}}, deepcopy(beds_data.FIPS))
	elseif level == :hospital
		filter!(row -> !ismissing(row.FIPS), beds_data)
		beds_data.selected_location = convert(Array{Union{Int,Missing}}, deepcopy(beds_data.FIPS))
		return beds_data
	else
		error("Invalid parameters.")
	end
	filter!(row -> row.selected_location in locations, beds_data)
	return beds_data
end

function definitivehc_select_type!(beds_data::DataFrame; bed_type::Symbol=:all)
	bed_type_cvt = Dict(
		:all       => :NUM_STAFFED_BEDS,
		:staffed   => :NUM_STAFFED_BEDS,
		:licensed  => :NUM_LICENSED_BEDS,
		:icu       => :NUM_ICU_BEDS,
		:adult_icu => :ADULT_ICU_BEDS,
		:regular   => :NUM_NON_ICU_BEDS,
	)
	col = bed_type_cvt[bed_type]

	if bed_type == :regular
		beds_data.NUM_NON_ICU_BEDS = beds_data.NUM_STAFFED_BEDS - beds_data.NUM_ICU_BEDS
	end

	filter!(row -> !ismissing(row[col]), beds_data)
	filter!(row -> row[col] > 0, beds_data)
	beds_data.selected_beds = convert(Array{Union{Int,Missing}}, deepcopy(beds_data[:,col]))
	return beds_data
end

function definitivehc_aggregate(beds_data::DataFrame; locations::Array=[])
	beds_data_agg = combine(groupby(beds_data, :selected_location), :selected_beds => sum => :selected_beds_agg)
	if !isempty(locations)
		missing_locs = setdiff(locations, beds_data_agg.selected_location)
		for loc in missing_locs
			push!(beds_data_agg, Dict(:selected_location => loc, :selected_beds_agg => 0), cols=:subset)
		end
	end
	sort!(beds_data_agg, :selected_location)
	return Float32.(beds_data_agg.selected_beds_agg)
end

end;


#=
This is Julia implementation of MCTS by Tejan Karmali.
Reference: Python version of the implementation of MCTS by
(https://github.com/tensorflow/minigo)
=#

using DataStructures: DefaultDict
using Distributions: Dirichlet

# Exploration constant balancing priors vs. value net output
c_puct = 0.96
# How much to weight the priors vs. dirichlet noise when mixing
dirichlet_noise_weight = 0.25

struct MCTSRules
  max_game_length::Int
  # Concentrated-ness of the noise being injected into priors
  dirichlet_noise_alpha::Float32

  function MCTSRules(env)
    max_game_length = (env.N ^ 2 * 7) ÷ 5
    dirichlet_noise_alpha = 0.03 * env.max_action_space / env.action_space
    new(max_game_length, dirichlet_noise_alpha)
  end
end

mutable struct DummyNode
  #= A fake node of a MCTS search tree.

  This node is intended to be a placeholder for the root node, which would
  otherwise have no parent node. If all nodes have parents, code becomes
  simpler. =#
  parent::Void
  child_N::DefaultDict{Any, Float32, Float32}
  child_W::DefaultDict{Any, Float32, Float32}
  function DummyNode()
    new(nothing, DefaultDict{Any, Float32}(0.0f0), DefaultDict{Any, Float32}(0.0f0))
  end
end

mutable struct MCTSNode
  #= A node of a MCTS search tree.

  A node knows how to compute the action scores of all of its children,
  so that a decision can be made about which move to explore next. Upon
  selecting a move, the children dictionary is updated with a new node.

  position: A go.Position instance
  fmove: A move (coordinate) that led to this position, a flattened coord
          (raw number ∈ [1-N^2], with nothing a pass)
  parent: A parent MCTSNode.
  =#

  parent
  fmove
  position :: Position
  is_expanded::Bool
  losses_applied::Int
  child_N::Array{Float32, 1}
  child_W::Array{Float32, 1}
  original_prior::Array{Float32, 1}
  child_prior::Array{Float32, 1}
  children::Dict
  mcts_rules::MCTSRules

  function MCTSNode(position :: T, fmove = nothing, parent = nothing) where T <: Position
    if parent == nothing
      parent = DummyNode()
    end
    is_expanded = false
    losses_applied = 0  # number of virtual losses on this node
    child_N = zeros(Float32, position.env.action_space)
    child_W = zeros(Float32, position.env.action_space)
    # save a copy of the original prior before it gets mutated by d-noise.
    original_prior = zeros(Float32, position.env.action_space)
    child_prior = zeros(Float32, position.env.action_space)
    children = Dict()  # map of flattened moves to resulting MCTSNode
    mcts_rules = MCTSRules(position.env)
    new(parent, fmove, position, is_expanded, losses_applied,
    child_N, child_W, original_prior, child_prior, children, mcts_rules)
  end
end

legal_moves(x::MCTSNode) = all_legal_moves(x.position)

child_action_score(x::MCTSNode) = child_Q(x) * x.position.to_play .+
                                            child_U(x) - 1000 * (1 - legal_moves(x))

child_Q(x::MCTSNode) = x.child_W ./ (1 + x.child_N)

child_U(x::MCTSNode) = (c_puct * √(1 + N(x)) *
                x.child_prior ./ (1 + x.child_N))

Q(x::MCTSNode) = W(x) / (1 + N(x))

get_N(x::MCTSNode) = x.parent.child_N[x.fmove]
set_N!(x::MCTSNode, value) = x.parent.child_N[x.fmove] = value
N(x::MCTSNode) = get_N(x)

get_W(x::MCTSNode) = x.parent.child_W[x.fmove]
set_W!(x::MCTSNode, value) = x.parent.child_W[x.fmove] = value
W(x::MCTSNode) = get_W(x)

# Return value of position, from perspective of player to play
Q_perspective(x::MCTSNode) = Q(x) * x.position.to_play

function select_leaf(mcts_node::MCTSNode)
  current = mcts_node
  board_size = mcts_node.position.env.N
  pass_move = board_size * board_size + 1
  while true
    current_new_N = N(current) + 1
    set_N!(current, current_new_N)
    # if a node has never been evaluated, we have no basis to select a child.
    if !current.is_expanded
      break
    end
    # HACK: if last move was a pass, always investigate double-pass first
    # to avoid situations where we auto-lose by passing too early.
    if (length(current.position.recent) != 0
      && current.position.recent[end].move == nothing
      && current.child_N[pass_move] == 0)
      current = maybe_add_child!(current, pass_move)
      continue
    end
    cas = child_action_score(current)
    #max_score = maximum(cas)
    #possible_moves = find(x -> x == max_score, cas)
    #best_move = sample(possible_moves)
    best_move = findmax(cas)[2]
    current = maybe_add_child!(current, best_move)
  end
  return current
end

function maybe_add_child!(mcts_node::MCTSNode, fcoord)
  # Adds child node for fcoord if it doesn't already exist, and returns it
  if fcoord ∉ keys(mcts_node.children)
    new_position = play_move!(mcts_node.position, from_flat(fcoord, mcts_node.position))
    mcts_node.children[fcoord] = MCTSNode(new_position, fcoord, mcts_node)
  	end
  return mcts_node.children[fcoord]
end

function add_virtual_loss!(mcts_node::MCTSNode, up_to::MCTSNode)
  #= Propagate a virtual loss up to the root node.

    	Args:
        up_to: The node to propagate until. (Keep track of this! You'll
            need it to reverse the virtual loss later.)
  =#
  mcts_node.losses_applied += 1
  # This is a "win" for the current node; hence a loss for its parent node
  # who will be deciding whether to investigate this node again.
  loss = mcts_node.position.to_play
  set_W!(mcts_node, W(mcts_node) + loss)
  if (mcts_node.parent == nothing || mcts_node == up_to) return  end
  add_virtual_loss!(mcts_node.parent, up_to)
end

function revert_virtual_loss!(mcts_node::MCTSNode, up_to::MCTSNode)
  mcts_node.losses_applied -= 1
  revert = -mcts_node.position.to_play
	set_W!(mcts_node, W(mcts_node) + revert)
  if (mcts_node.parent == nothing || mcts_node == up_to) return end
  revert_virtual_loss!(mcts_node.parent, up_to)
end

function revert_visits!(mcts_node::MCTSNode, up_to::MCTSNode)
  #= Revert visit increments.

    Sometimes, repeated calls to select_leaf return the same node.
    This is rare and we're okay with the wasted computation to evaluate
    the position multiple times by the dual_net. But select_leaf has the
    side effect of incrementing visit counts. Since we want the value to
    only count once for the repeatedly selected node, we also have to
    revert the incremented visit counts.
  =#
  set_N!(mcts_node, N(mcts_node) - 1)
  if mcts_node.parent == nothing || mcts_node == up_to return end
  revert_visits!(mcts_node.parent, up_to)
end

function incorporate_results!(mcts_node::MCTSNode, move_probs, value, up_to)
  board_size = mcts_node.position.env.N
  @assert size(move_probs) == (board_size ^ 2 + 1,)
  # A finished game should not be going through this code path - should
  # directly call backup_value() on the result of the game.
	@assert !mcts_node.position.done
  if mcts_node.is_expanded
    revert_visits!(mcts_node, up_to)
    return
	end
  mcts_node.is_expanded = true
  mcts_node.original_prior .= mcts_node.child_prior .= move_probs
  # initialize child Q as current node's value, to prevent dynamics where
  # if B is winning, then B will only ever explore 1 move, because the Q
  # estimation will be so much larger than the 0 of the other moves.
  #
  # Conversely, if W is winning, then B will explore all go.N² + 1 moves before
  # continuing to explore the most favorable move. This is a waste of search.
  #
  # The value seeded here acts as a prior, and gets averaged into Q calculations
  mcts_node.child_W .= ones(Float32, mcts_node.position.env.action_space) * value
  backup_value!(mcts_node, value, up_to)
end

function backup_value!(mcts_node::MCTSNode, value, up_to::MCTSNode)
  #= Propagates a value estimation up to the root node.

  Args:
    value: the value to be propagated (1 = black wins, -1 = white wins)
    up_to: the node to propagate until.
	=#
  set_W!(mcts_node, W(mcts_node) + value)
  if mcts_node.parent == nothing || mcts_node == up_to return end
  backup_value!(mcts_node.parent, value, up_to)
end

#= true if the last two moves were Pass or if the position is at a move
  	greater than the max depth.
=#
is_done(mcts_node::MCTSNode) = mcts_node.position.done ||
mcts_node.position.n >= mcts_node.mcts_rules.max_game_length

function inject_noise!(mcts_node::MCTSNode)
  action_space = mcts_node.position.env.action_space
  dirch = rand(Dirichlet(mcts_node.mcts_rules.dirichlet_noise_alpha
                                                * ones(action_space)))
  mcts_node.child_prior .= mcts_node.child_prior * (1 - dirichlet_noise_weight) .+
                      dirch .* dirichlet_noise_weight
end

function children_as_π(mcts_node::MCTSNode, squash::Bool = false)
  #= Returns the child visit counts as a probability distribution, π
  If squash is true, exponentiate the probabilities by a temperature
  slightly larger than unity to encourage diversity in early play and
  hopefully to move away from 3-3s
  =#
  probs = mcts_node.child_N
  if squash
    probs = probs .^ 0.98
  end
  return probs ./ sum(probs)
end

#=
function most_visited_path_nodes(mcts_node::MCTSNode)
  node = mcts_node
  output = Array{MCTSNode, 1}()
  while node.children
    next_kid = findmax(node.child_N)[2]
    node = get(node.children, next_kid, nothing)
    if node == nothing break end
    push!(output, node)
  end
  return output
end

function most_visited_path(mcts_node::MCTSNode)
  node = mcts_node
  output = Array{String, 1}()
  while !isempty(node.children)
    next_kid = findmax(node.child_N)[2]
    node = get(node.children, next_kid, nothing)
    if node == nothing
      push!(output, "GAME END")
      break
    end
    push!(output, go.to_kgs(go.from_flat(node.fmove)) * " ($(N(node))) ==> ")
  end
  push!(output, @sprintf("Q: %.5f\n", Q(node)))
  return join(output)
end

function mvp_gg(mcts_node::MCTSNode)
  # Returns most visited path in go-gui VAR format e.g. 'b r3 w c17...
  node = mcts_node
  output = Array{String, 1}()
  while !isempty(node.children) && maximum(node.child_N) > 1
    next_kid = findmax(node.child_N)[2]
    node = node.children[next_kid]
    push!(output, go.to_kgs(go.from_flat(node.fmove)))
  end
  return join(output, " ")
end

function describe(mcts_node::MCTSNode)
  sort_order = collect(1:go.N * go.N + 1)
  sort!(sort_order, by = i -> (
      mcts_node.child_N[i], child_action_score(mcts_node)[i]), rev = true)
  soft_n = mcts_node.child_N / max(1, sum(mcts_node.child_N))
  prior = mcts_node.child_prior
  p_delta = soft_n - prior
  p_rel = zeros(p_delta)
  mask = prior .!= 0
  p_rel[mask] .= p_delta[mask] ./ prior[mask]

  # Dump out some statistics
  output = Array{String, 1}()
  push!(output, @sprintf("%.4f\n", Q(mcts_node)))
  push!(output, most_visited_path(mcts_node))
  push!(output,
      "move : action    Q     U     P   P-Dir    N  soft-N  p-delta  p-rel")
  for key in sort_order[1:15]
    if mcts_node.child_N[key] == 0 break end
    push!(output, @sprintf("\n%s   : % .3f % .3f %.3f %.3f %.3f %5d %.4f % .5f % .2f",
        go.to_kgs(go.from_flat(key)),
        child_action_score(mcts_node)[key],
        child_Q(mcts_node)[key],
        child_U(mcts_node)[key],
        mcts_node.child_prior[key],
        mcts_node.original_prior[key],
        trunc(Int, mcts_node.child_N[key]),
        soft_n[key],
        p_delta[key],
        p_rel[key]))
  end
  return join(output)
end
=#


module MATLABPlots

using MATLAB

export MATLAB, figure, plot, plot!, clf

function figure()
    mxcall(:figure, 0)
end

function figure(i::Int)
    mxcall(:figure, 0, i)
end

function clf()
    mxcall(:clf, 0)
end

function clf(i::Int)
    mxcall(:clf, 0, i)
end

function plot(i::Int, x::AbstractArray, y::AbstractArray, style="-")
    mxcall(:hold, 0, i, "off")
    mxcall(:plot, 0, i, x, y, style)
end

function plot(i::Int, x::AbstractArray, y::AbstractArray, z::AbstractArray, style="-")
    mxcall(:hold, 0, i, "off")
    mxcall(:plot3, 0, i, x, y, z, style)
end

function plot(x::AbstractArray, y::AbstractArray, style="-")
    mxcall(:hold, 0, "off")
    mxcall(:plot, 0, x, y, style)
end

function plot(x::AbstractArray, y::AbstractArray, z::AbstractArray, style="-")
    mxcall(:hold, 0, "off")
    mxcall(:plot3, 0, x, y, z, style)
end

function plot!(i::Int, x::AbstractArray, y::AbstractArray, style="-")
    mxcall(:hold, 0, i, "on")
    mxcall(:plot, 0, i, x, y, style)
end

function plot!(i::Int, x::AbstractArray, y::AbstractArray, z::AbstractArray, style="-")
    mxcall(:hold, 0, i, "on")
    mxcall(:plot3, 0, i, x, y, z, style)
end

function plot!(x::AbstractArray, y::AbstractArray, style="-")
    mxcall(:hold, 0, "on")
    mxcall(:plot, 0, x, y, style)
end

function plot!(x::AbstractArray, y::AbstractArray, z::AbstractArray, style="-")
    mxcall(:hold, 0, "on")
    mxcall(:plot3, 0, x, y, z, style)
end

end # module


# This adds mutators to the Vimes.jl library.
# It uses this as a guide:
# http://pitest.org/quickstart/mutators/
using InteractiveUtils
using SourceWalk
using Vimes

# Checks whether a library replacement works on a given expression.
function show_replacements(ex, fs)
    match_any = false
    Vimes.pathwalk(ex) do p, x
      for f in fs
        if Vimes.matches(f, x)
          println("starts as $(x)")
          println("matches to $(f(x))")
          match_any = true
        end
      end
      return x
    end
    match_any
end

"""
    check_replacement(example_file, mutation)

Check that a patch works on a file and runs when patched.
The `example_file` is the path to a file to mutate.
The mutation is a single mutation function. The example
file must contain a function called `eg_func` that has
a single argument. It will be called with `eg_func(3)`
and should return one value unmutated and a different
value when mutated.
"""
function check_replacement(example_file, mutation)
    include(example_file)
    original_result = Base.invokelatest(eg_func, 3)

    pf = Vimes.parsefile(example_file)
    fidx = Vimes.index(pf, [mutation])
    length(fidx) > 0 || return (false, original_result, nothing)
    mut_fn = "$(tempname()).jl"
    cp(example_file, mut_fn, force = true)
    path, patch = first(fidx)
    Vimes.apply!(mut_fn, path, patch[1])

    mutated_result = original_result
    try
        include(mut_fn)
        mutated_result = Base.invokelatest(eg_func, 3)
    catch e
        rethrow()
    finally
        rm(mut_fn)
    end
    (true, original_result, mutated_result)
end

# Turns > into >=, >= into >. The same for less-than.
function conditionals_boundary(x)
    if isexpr(x, :call)
        if x.args[1] == :>
            Expr(x.head, :>=, x.args[2:end]...)
        elseif x.args[1] == :>=
            Expr(x.head, :>, x.args[2:end]...)
        elseif x.args[1] == :<
            Expr(x.head, :<=, x.args[2:end]...)
        elseif x.args[1] == :<=
            Expr(x.head, :<, x.args[2:end]...)
        else
            nothing
        end
    else
        nothing
    end
end

# This takes the unary - to a positive.
function invert_negatives(x)
    isexpr(x) && x.args[1] == :- && length(x.args) == 2 || return
    x.args[2]
end

# Turns + -> -, - -> +, etc, for integer and float.
function math_mutator(x)
    isexpr(x) && length(x.args) == 3 || return
    a, b = x.args[2:3]
    if x.args[1] == :-
        :(isa($a, Real) && isa($b, Real) ? $a + $b : $x)
    elseif x.args[1] == :+
        :(isa($a, Real) && isa($b, Real) ? $a - $b : $x)
    elseif x.args[1] == :*
        :(isa($a, Real) && isa($b, Real) ? $a / $b : $x)
    elseif x.args[1] == :/
        :(isa($a, Real) && isa($b, Real) ? $a * $b : $x)
    elseif x.args[1] == :%
        :(isa($a, Real) && isa($b, Real) ? $a * $b : $x)
    elseif x.args[1] == :*
        :(isa($a, Real) && isa($b, Real) ? $a % $b : $x)
    elseif x.args[1] == :^
        :(isa($a, InteRealger) && isa($b, Real) ? $a * $b : $x)
    elseif x.args[1] == :&
        :(isa($a, Integer) && isa($b, Integer) ? $a | $b : $x)
    elseif x.args[1] == :|
        :(isa($a, Integer) && isa($b, Integer) ? $a & $b : $x)
    elseif x.args[1] == :<<
        :(isa($a, Integer) && isa($b, Integer) ? $a >> $b : $x)
    elseif x.args[1] == :>>
        :(isa($a, Integer) && isa($b, Integer) ? $a << $b : $x)
    elseif x.args[1] == :>>>
        :(isa($a, Integer) && isa($b, Integer) ? $a << $b : $x)
    else
        nothing
    end
end

function incode_math_mutator(a, b)
    isa(a, Real) && isa(b, Real) ? a - b : a + b
end

empty_return(x::AbstractString) = ""
empty_return(x::Real) = zero(x)
empty_return(x::Set{T}) where {T} = Set{T}()
empty_return(x::Array{T,1}) where {T} = Array{T,1}()
empty_return(x::Dict{K,V}) where {K,V} = Dict{K,V}()
empty_return(x::Char) = '\0'
empty_return(x) = x

function empty_returns(x)
    isexpr(x, :return) || return
    Expr(:return, Expr(:call, :empty_return, x.args[1]))
end

"""
Swap a returned bool for its negation. This won't do anything unless it's a bool.
It's a good example of the difficulty of doing mutation at the
syntax level when you don't know the types.
"""
function flip_returns(x)
    isexpr(x, :return) || return
    Expr(:return,
        Expr(:if,
            Expr(:call,
                :isa,
                x.args[1],
                :Bool
            ),
            Expr(:call,
                :!,
                x.args[1]
            ),
            x.args[1]
        )
    )
end

function return_nothing(x)
    isexpr(x, :return) && x.args[1] !== nothing || return
    Expr(:return, nothing)
end

function increment_integer(x)
    x isa Integer && !(x isa Bool) && return x + 1
    nothing
end

function decrement_integer(x)
    x isa Integer && !(x isa Bool) && return x - 1
    nothing
end

function scale_float(x)
    x isa AbstractFloat && return 0.9 * x
    nothing
end

function flip_and_or(x)
    isexpr(x, :&&) && return Expr(:||, x.args...)
    isexpr(x, :||) && return Expr(:&&, x.args...)
end


using openbuildnet

# Create the node
node = OBNNode("extnode", "testext", length(ARGS)>0?ASCIIString(ARGS[1]):"tcp://localhost:1883")

# Create the ports
udouble = create_input(node, "input_scalardouble", Float64)
ydouble = create_output(node, "output_scalardouble", Float64)

uvector = create_input(node, "input_vectordouble", Vector{Float64})
yvector = create_output(node, "output_vectordouble", Vector{Float64})

umatrix = create_input(node, "input_matrixdouble", Matrix{Float64})
ymatrix = create_output(node, "output_matrixdouble", Matrix{Float64})

# Create Callbacks
on_init(node) do
  println("INIT")
end

on_term(node) do
  println("TERMINATE")
end

const MAINBLOCK = 0
d = 1.0
v = [2.1, 0.9]
m = [1.0 2.0; 3.0 4.0]

on_block_output(node, MAINBLOCK) do
  println("Current time is ", sim_time(node), "s.")
  #println("Current time is ", wallclock_time(node))
  println("Scalar double: ", get(udouble))
  println("Vector double: ", get(uvector))
  println("Matrix double: ", get(umatrix))

  global d
  set(ydouble, d)
  d += 1.0

  global v
  set(yvector, v)
  v += 0.5

  global m
  set(ymatrix, m)
  m += eye(2)

  schedule(node, update_mask(MAINBLOCK), sim_time(node)+0.5, :s, 1.0)
end

# Test the optional parameters given to callbacks
type TheState
  x::Float64
end
mystate = TheState(0.0)

on_block_state(node, MAINBLOCK, mystate) do s
  s.x += get(udouble)
  println("My new state: ", s.x)
end

println("Ready to start simulation...")
status = run(node, 20)
println("Final status: ", status)


struct GNNExplainer <: GeometricInterpretor

end

function explain_node(method :: GNNInterpretation{GNNExplainer}, node :: Int) :: GNNInterpretationResults

end


# tutorial on conventional 2D SLAM example

# 0. Load required packages
using Caesar, RoME

# 1. Get the authentication and session information
include(joinpath(ENV["HOME"],"Documents","blandauthlocal.jl"))
backend_config, user_config = standardcloudgraphsetup(addrdict=addrdict)
user_config["session"] = "SLAM2D_TUTORIAL"
addrdict["robotId"] = "TestRobot"

# Start with an empty graph (local dictionary version) # fg = initfg(sessionname="SLAM2D_TUTORIAL")
## TODO -- ISSUE Julia 0.6.0-0.6.2 dives into some StackOverflow problem using the functions, but fine when called separately.
fg = Caesar.initfg(sessionname=user_config["session"], robotname=addrdict["robotId"], cloudgraph=backend_config)

# fg = RoME.initfg(sessionname=addrdict["session"])
# fg = IncrementalInference.emptyFactorGraph()
# user_config["session"]
# fg.sessionname
# fg.sessionname = user_config["session"]
# fg.cg = backend_config

# also add a PriorPose2 to pin the first pose at a fixed location
addNode!(fg, :x0, Pose2) # , labels=["POSE"]
addFactor!(fg, [:x0], PriorPose2(MvNormal(zeros(3), 0.01*eye(3))) )

# ls(fg, :x0)
# isInitialized(fg, :x0)
# # to debug the zero lengths issue
# ensureAllInitialized!(fg)
# getVal(fg, :x0)
# Prior(MvNormal([0.0;0.0;0], diagm([1.0;1.0;0.01].^2)))

# Drive around in a hexagon
for i in 0:5
  psym = Symbol("x$i")
  nsym = Symbol("x$(i+1)")
  addNode!(fg, nsym, Pose2) # , labels=["VARIABLE";"POSE"]
  addFactor!(fg, [psym;nsym], Pose2Pose2(reshape([10.0;0;pi/3],3,1), 0.01*eye(3), [1.0]), autoinit=true )
  # Pose2Pose2_NEW(MvNormal([10.0;0;pi/3], diagm([0.1;0.1;0.1].^2)))
end

# Graphs.plot(fg.g)
isInitialized(fg, :x5)
isInitialized(fg, :x6)

ensureAllInitialized!(fg)

using RoMEPlotting

drawPoses(fg)

getVal(fg, :x0)
v = getVert(fg, :x0)
getVal(v)

importall CloudGraphs

exvid = fg.IDs[:x0]
neoID = fg.cgIDs[exvid]
cvr = CloudGraphs.get_vertex(fg.cg, neoID, false)
exv = CloudGraphs.cloudVertex2ExVertex(cvr)
getVal(exv)
# getData(exv)

getData(fg.g.vertices[1])

tree = wipeBuildNewTree!(fg)
# inferOverTree!(fg, tree)
inferOverTreeR!(fg, tree)

using RoMEPlotting, Gadfly

pl = plotKDE(fg, [:x0; :x1; :x2; :x3; :x4; :x5; :x6]);

Gadfly.draw(PDF("tmpX0123456.pdf", 15cm, 15cm), pl)

@async run(`evince tmpX0123456.pdf`)

# pl = drawPoses(fg)
pl = drawPosesLandms(fg)
Gadfly.draw(PDF("tmpPosesFg.pdf", 15cm, 15cm), pl)
@async run(`evince tmpPosesFg.pdf`)

tree = wipeBuildNewTree!(fg)

@async Graphs.plot(tree.bt)

@time inferOverTree!(fg, tree)

# Graphs.plot(tree.bt)

@async Graphs.plot(fg.g)


# some views are in colorchannels.jl
using Colors, FixedPointNumbers, ImageCore, OffsetArrays, Test
using OffsetArrays: IdentityUnitRange
using PaddedViews: filltype

@testset "rawview" begin
    a = map(N0f8, rand(3,5))
    a[2,2] = N0f8(0.5)
    v = rawview(a)
    @test v[2,2] === a[2,2].i
    v[1,3] = 0xff
    @test a[1,3] === N0f8(1)
    v[1,3] = 0x01
    @test a[1,3] === N0f8(1/255)
    s = view(a, 1:2, 1:2)
    v = rawview(s)
    @test v[2,2] === a[2,2].i
    v[2,2] = 0x0f
    @test a[2,2].i == 0x0f
    @test rawview(v) === v
end

@testset "normedview" begin
    a = rand(UInt8, 3, 5)
    a[2,2] = 0x80
    v = normedview(a)
    @test v[2,2] === N0f8(0.5)
    v[1,3] = 1
    @test a[1,3] === 0xff
    v[1,3] = 1/255
    @test a[1,3] === 0x01
    s = view(a, 1:2, 1:2)
    v = normedview(s)
    @test v[2,2] === N0f8(0.5)
    v[2,2] = 15/255
    @test a[2,2] == 0x0f
    @test normedview(v) === v
    @test normedview(N0f8, v) === v
end

@testset "StackedView" begin
    for (A, B, T) = (([1 3;2 4], [-1 -5; -2 -3], Int),
                     ([1 3;2 4], [-1.0 -5.0; -2.0 -3.0], Float64))
        V = @inferred(StackedView(A, B))
        @test eltype(V) == T
        @test size(V) == (2, 2, 2)
        @test axes(V) === (Base.OneTo(2), Base.OneTo(2), Base.OneTo(2))
        @test @inferred(V[1,1,1]) === T(1)
        @test @inferred(V[2,1,1]) === T(-1)
        @test V[1,:,:] == A
        @test V[2,:,:] == B
        @test_throws BoundsError V[0,1,1]
        @test_throws BoundsError V[3,1,1]
        @test_throws BoundsError V[2,0,1]
        @test_throws BoundsError V[2,3,1]
        @test_throws BoundsError V[1,1,0]
        @test_throws BoundsError V[1,1,3]
        V32 = @inferred(StackedView{Float32}(A, B))
        @test eltype(V32) == Float32
        @test V32[1,1,2] == Float32(3)
        V[1,2,2] = 0
        @test A[2,2] == 0
        V[2,1,2] = 11
        @test B[1,2] == 11

        V = @inferred(StackedView(A, zeroarray, B))
        @test eltype(V) == T
        @test size(V) == (3, 2, 2)
        @test axes(V) === (Base.OneTo(3), Base.OneTo(2), Base.OneTo(2))
        @test V[1,:,:] == A
        @test all(iszero, V[2,:,:])
        @test V[3,:,:] == B
        @test_throws ErrorException V[2,1,1] = 7
        V32 = @inferred(StackedView{Float32}(A, zeroarray, B))
        @test eltype(V32) == Float32
        @test V32[1,1,2] == Float32(3)
    end

    # With mixed grayscale/real arrays
    a, b = Gray{N0f8}[0.1 0.2; 0.3 0.4], [0.5 0.6; 0.7 0.8]
    V = @inferred(StackedView(a, b))
    @test eltype(V) == Float64
    @test V[1,1,1] === Float64(N0f8(0.1))
    @test V[2,1,1] === 0.5
    V = @inferred(StackedView{N0f8}(a, b))
    @test eltype(V) == N0f8
    @test V[1,1,1] === N0f8(0.1)
    @test V[2,1,1] === N0f8(0.5)
    @test b[1,1] === 0.5
    V = @inferred(StackedView(a, zeroarray, b))
    @test eltype(V) == Float64
    V = @inferred(StackedView{N0f8}(a, zeroarray, b))
    @test eltype(V) == N0f8

    # With colorview
    a = [0.1 0.2; 0.3 0.4]
    b = [0.5 0.6; 0.7 0.8]
    z = zeros(2,2)  # because setindex! won't work with zeroarray
    # GrayA
    v = @inferred(colorview(GrayA, a, b))
    @test eltype(v) == GrayA{Float64}
    @test @inferred(v[2,1]) === GrayA(0.3,0.7)
    v = @inferred(colorview(GrayA{N0f8}, a, b))
    @test @inferred(v[2,1]) === GrayA{N0f8}(0.3,0.7)
    v[1,2] = GrayA(0.25, 0.25)
    @test @inferred(v[1,2]) === GrayA{N0f8}(0.25, 0.25)
    v = @inferred(colorview(GrayA{N0f8}, a, zeroarray))
    @test @inferred(v[2,1]) === GrayA{N0f8}(0.3,0)
    @test_throws ErrorException (v[1,2] = GrayA(0.25, 0.25))
    # RGB
    v = @inferred(colorview(RGB{N0f8}, a, zeroarray, b))
    @test @inferred(v[2,1]) === RGB{N0f8}(0.3,0,0.7)
    v = colorview(RGB{N0f8}, a, z, b)
    @test @inferred(v[2,1]) === RGB{N0f8}(0.3,0,0.7)
    v[2,1] = RGB(0,0.9,0)
    @test @inferred(v[2,1]) === RGB{N0f8}(0,0.9,0)
    @test a[2,1] == b[2,1] == 0
    @test z[2,1] == N0f8(0.9)
    # RGBA
    v = @inferred(colorview(RGBA{N0f8}, a, zeroarray, b, b))
    @test @inferred(v[2,2]) === RGBA{N0f8}(0.4,0,0.8,0.8)
    v = colorview(RGBA{N0f8}, a, copy(z), b, copy(z))
    v[2,2] = RGBA(0.75,0.8,0.75,0.8)
    @test @inferred(v[2,2]) === RGBA{N0f8}(0.75,0.8,0.75,0.8)
    @test a[2,2] == b[2,2] == N0f8(0.75)

    @test eltype(zeroarray) == Union{}
    @test_throws ErrorException colorview(RGB{N0f8}, zeroarray, zeroarray, zeroarray)
    @test_throws DimensionMismatch StackedView(rand(2,3), rand(2,5))

    # With padding
    r = reshape([0.1,0.2], 2, 1)
    g = [false true]
    cv = colorview(RGB, paddedviews(0, r, g, zeroarray)...)
    @test cv[1,1] === RGB(0.1,0,0)
    @test cv[2,1] === RGB(0.2,0,0)
    @test cv[1,2] === RGB(0,1.0,0)
    @test cv[2,2] === RGB(0,0.0,0)
    @test axes(cv) === (Base.OneTo(2), Base.OneTo(2))

    a = [0.1 0.2; 0.3 0.4]
    b = OffsetArray([0.1 0.2; 0.3 0.4], 0:1, 2:3)
    @test_throws DimensionMismatch colorview(RGB, a, b, zeroarray)
    cv = colorview(RGB, paddedviews(0, a, b, zeroarray)...)
    @test axes(cv) == (0:2, 1:3)
    @test red.(cv[axes(a)...]) == a
    @test green.(cv[axes(b)...]) == b
    @test parent(copy(cv)) == [RGB(0,0,0)   RGB(0,0.1,0)   RGB(0,0.2,0);
                               RGB(0.1,0,0) RGB(0.2,0.3,0) RGB(0,0.4,0);
                               RGB(0.3,0,0) RGB(0.4,0,0)   RGB(0,0,0)]
    chanv = channelview(cv)
    @test @inferred(axes(chanv)) == (IdentityUnitRange(1:3), IdentityUnitRange(0:2), IdentityUnitRange(1:3))
    @test chanv[1,1,1] == 0.1
    @test chanv[2,1,2] == 0.3

    @test_throws ErrorException paddedviews(0, zeroarray, zeroarray)

    # Just to boost coverage. These methods are necessary to
    # make inference happy.
    @test_throws ErrorException ImageCore._unsafe_getindex(1, (1,1))
    @test_throws ErrorException ImageCore._unsafe_setindex!(1, (1,1), 0)
end

@testset "Multi-component colorview" begin
    r, g, b = rand(Gray{N0f16},5,5), rand(Gray{N0f16},5,5), rand(Gray{N0f16},5,5)
    A = colorview(RGB, r, g, b)
    @test A[1,1] == RGB(r[1,1], g[1,1], b[1,1])
    o = ones(2, 2)
    A = colorview(RGB, o, o, zeroarray)
    @test A[1,1] == RGB(1,1,0)
    @test size(A) == (2,2)
    @test axes(A) == (Base.OneTo(2),Base.OneTo(2))
    A = colorview(RGB, o, zeroarray, o)
    @test A[1,1] == RGB(1,0,1)
    @test size(A) == (2,2)
    @test axes(A) == (Base.OneTo(2),Base.OneTo(2))
    A = colorview(RGB, zeroarray, o, o)
    @test A[1,1] == RGB(0,1,1)
    @test size(A) == (2,2)
    @test axes(A) == (Base.OneTo(2),Base.OneTo(2))
end

@testset "mosaicviews" begin
    # only test image cases
    @testset "2D inputs" begin
        A1 = fill(Gray(1.), 2, 2)
        A2 = fill(RGB(1., 0., 0.), 3, 3)
        A3 = fill(RGB(0., 1., 0.), 3, 3)
        out = mosaic(A1, A2, A3) |> collect
        @test_reference "references/mosaicviews/2d_opaque_1.png" out by=isequal
        out = mosaic(A1, A2, A3; npad=2, fillvalue=Gray(0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/2d_opaque_2.png" out by=isequal
        out = mosaic(A1, A2, A3; npad=2, fillvalue=Gray(0.), nrow=2, rowmajor=true) |> collect
        @test_reference "references/mosaicviews/2d_opaque_3.png" out by=isequal

        A1 = fill(GrayA(1.), 2, 2)
        A2 = fill(RGBA(1., 0., 0.), 3, 3)
        A3 = fill(RGBA(0., 1., 0.), 3, 3)
        out = mosaic(A1, A2, A3) |> collect
        @test_reference "references/mosaicviews/2d_transparent_1.png" out by=isequal
        out = mosaic(A1, A2, A3; npad=2, fillvalue=GrayA(0., 0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/2d_transparent_2.png" out by=isequal
        out = mosaic(A1, A2, A3; npad=2, fillvalue=GrayA(0., 0.), nrow=2, rowmajor=true) |> collect
        @test_reference "references/mosaicviews/2d_transparent_3.png" out by=isequal
    end

    @testset "3D inputs" begin
        A = fill(RGB(0., 0., 0.), 2, 2, 3)
        A[:, :, 1] .= RGB(1., 0., 0.)
        A[:, :, 2] .= RGB(0., 1., 0.)
        A[:, :, 3] .= RGB(0., 0., 1.)
        out = mosaic(A) |> collect
        @test_reference "references/mosaicviews/3d_opaque_1.png" out by=isequal
        out = mosaic(A; npad=2, fillvalue=Gray(0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/3d_opaque_2.png" out by=isequal
        out = mosaic(A; npad=2, fillvalue=Gray(0.), nrow=2, rowmajor=true) |> collect
        @test_reference "references/mosaicviews/3d_opaque_3.png" out by=isequal
        out = mosaic(A, A; npad=2, fillvalue=Gray(0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/3d_opaque_4.png" out by=isequal

        A = fill(RGBA(0., 0., 0.), 2, 2, 3)
        A[:, :, 1] .= RGBA(1., 0., 0.)
        A[:, :, 2] .= RGBA(0., 1., 0.)
        A[:, :, 3] .= RGBA(0., 0., 1.)
        out = mosaic(A) |> collect
        @test_reference "references/mosaicviews/3d_transparent_1.png" out by=isequal
        out = mosaic(A; npad=2, fillvalue=GrayA(0., 0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/3d_transparent_2.png" out by=isequal
        out = mosaic(A; npad=2, fillvalue=GrayA(0.), nrow=2, rowmajor=true) |> collect
        @test_reference "references/mosaicviews/3d_transparent_3.png" out by=isequal
        out = mosaic(A, A; npad=2, fillvalue=GrayA(0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/3d_transparent_4.png" out by=isequal
    end

    @testset "4D inputs" begin
        A = fill(RGB(0., 0., 0.), 2, 2, 2, 2)
        A[1, :, 1, 1] .= RGB(1., 0., 0.)
        A[:, :, 1, 2] .= RGB(0., 1., 0.)
        A[:, :, 2, 1] .= RGB(0., 0., 1.)
        out = mosaic(A) |> collect
        @test_reference "references/mosaicviews/4d_opaque_1.png" out by=isequal
        out = mosaic(A; npad=2, fillvalue=Gray(0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/4d_opaque_2.png" out by=isequal
        out = mosaic(A; npad=2, fillvalue=Gray(0.), nrow=2, rowmajor=true) |> collect
        @test_reference "references/mosaicviews/4d_opaque_3.png" out by=isequal
        out = mosaic(A, A; npad=2, fillvalue=Gray(0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/4d_opaque_4.png" out by=isequal

        A = fill(RGBA(0., 0., 0.), 2, 2, 2, 2)
        A[1, :, 1, 1] .= RGBA(1., 0., 0.)
        A[:, :, 1, 2] .= RGBA(0., 1., 0.)
        A[:, :, 2, 1] .= RGBA(0., 0., 1.)
        out = mosaic(A) |> collect
        @test_reference "references/mosaicviews/4d_transparent_1.png" out by=isequal
        out = mosaic(A; npad=2, fillvalue=GrayA(0., 0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/4d_transparent_2.png" out by=isequal
        out = mosaic(A; npad=2, fillvalue=GrayA(0., 0.), nrow=2, rowmajor=true) |> collect
        @test_reference "references/mosaicviews/4d_transparent_3.png" out by=isequal
        out = mosaic(A, A; npad=2, fillvalue=GrayA(0.), nrow=2) |> collect
        @test_reference "references/mosaicviews/4d_transparent_4.png" out by=isequal
    end
end

@testset "PaddedViews" begin
    # cases that don't promote array eltype:
    #   * (Number, Colorant)
    #   * (Gray, Gray)
    #   * (Gray, Color3)
    #   * (Color3, Color3)
    #   * (Color3, TransparentColor)
    #   * (TransparentColor, TransparentColor)
    @test @inferred(filltype(Float32, Gray{N0f8})) == Gray{N0f8}
    @test @inferred(filltype(Float32, RGB{N0f8})) == RGB{N0f8}
    @test @inferred(filltype(Gray{Float32}, Gray{N0f8})) === Gray{N0f8}
    @test @inferred(filltype(Gray{N0f8}, Gray{Float32})) === Gray{Float32}
    @test @inferred(filltype(Gray{Float32}, RGB{N0f8})) === RGB{N0f8}
    @test @inferred(filltype(Gray{N0f8}, RGB{Float32})) === RGB{Float32}
    @test @inferred(filltype(RGB{Float32}, RGB{N0f8})) === RGB{N0f8}
    @test @inferred(filltype(RGB{N0f8}, RGB{Float32})) === RGB{Float32}
    @test @inferred(filltype(Lab{Float32}, RGB{N0f8})) === RGB{N0f8}
    @test @inferred(filltype(RGB{N0f8}, Lab{Float32})) === Lab{Float32}

    @test @inferred(filltype(Gray{N0f8}, AGray{Float32})) === AGray{Float32}
    @test @inferred(filltype(Gray{Float32}, AGray{N0f8})) === AGray{N0f8}
    @test @inferred(filltype(Gray{N0f8}, ARGB{Float32})) === ARGB{Float32}
    @test @inferred(filltype(Gray{Float32}, ARGB{N0f8})) === ARGB{N0f8}
    @test @inferred(filltype(BGR{N0f8}, ARGB{Float32})) === ARGB{Float32}
    @test @inferred(filltype(BGR{Float32}, ARGB{N0f8})) === ARGB{N0f8}
    @test @inferred(filltype(AGray{N0f8}, ARGB{Float32})) === ARGB{Float32}
    @test @inferred(filltype(AGray{Float32}, ARGB{N0f8})) === ARGB{N0f8}

    # cases that promote both colorant type and storage type
    #   * (Colorant, Number) -- numeric arrays are treated like gray images
    #   * (Color3, Gray)
    #   * (TransparentColor, Colorant)
    @test @inferred(filltype(Gray{N0f8}, Float32)) === Gray{Float32}
    @test @inferred(filltype(Gray{Float64}, Float32)) === Gray{Float32}
    @test @inferred(filltype(RGB{N0f8}, Float32)) === RGB{Float32}
    @test @inferred(filltype(RGB{Float64}, Float32)) === RGB{Float32}
    for T in (Gray24, AGray32, RGB24, ARGB32)
        @test @inferred(filltype(T, Float32)) === @inferred(filltype(Float32, T)) === T
    end

    @test @inferred(filltype(RGB{N0f8}, Gray{Float32})) === RGB{Float32}
    @test @inferred(filltype(RGB{Float32}, Gray{N0f8})) === RGB{Float32}
    @test @inferred(filltype(Lab{Float32}, Gray{N0f8})) === Lab{Float32}
    @test @inferred(filltype(Lab{Float32}, Gray{Float64})) === Lab{Float64}

    @test @inferred(filltype(AGray{N0f8}, Gray{Float32})) === AGray{Float32}
    @test @inferred(filltype(AGray{Float32}, Gray{N0f8})) === AGray{Float32}
    @test @inferred(filltype(AGray{N0f8}, RGB{Float32})) === ARGB{Float32}
    @test @inferred(filltype(AGray{Float32}, RGB{N0f8})) === ARGB{Float32}
    @test @inferred(filltype(AGray{N0f8}, Lab{Float32})) === ALab{Float32}
    @test @inferred(filltype(AGray{Float64}, Lab{Float32})) === ALab{Float64}

    @test @inferred(filltype(ARGB{N0f8}, Gray{Float32})) === ARGB{Float32}
    @test @inferred(filltype(ARGB{Float32}, Gray{N0f8})) === ARGB{Float32}
    @test @inferred(filltype(ARGB{N0f8}, BGR{Float32})) === ABGR{Float32}
    @test @inferred(filltype(ARGB{Float32}, BGR{N0f8})) === ABGR{Float32}
    @test @inferred(filltype(ARGB{N0f8}, Lab{Float32})) === ALab{Float32}
    @test @inferred(filltype(ARGB{Float64}, Lab{Float32})) === ALab{Float64}

    A = Gray{N0f8}[Gray(0.) Gray(0.3); Gray(0.6) Gray(1.)]
    fv = Gray{N0f8}(0)
    Ap = PaddedView(fv, A, (-1:4, -1:4))
    @test eltype(Ap) == Gray{N0f8}
    @test @inferred(getindex(Ap, -1, -1)) === fv
    @test @inferred(getindex(Ap, 2, 2)) === Gray{N0f8}(1.)
    @test Ap[axes(A)...] == A

    fv = RGB{Float32}(1., 0., 0.)
    Ap = PaddedView(fv, A, (-1:4, -1:4))
    @test eltype(Ap) == RGB{Float32}
    @test @inferred(getindex(Ap, -1, -1)) === fv
    @test @inferred(getindex(Ap, 2, 2)) === RGB{Float32}(1., 1., 1.)
    @test Ap[axes(A)...] == RGB{Float32}.(A)

    fv = AGray{Float32}(0.5, 0)
    Ap = PaddedView(fv, A, (-1:4, -1:4))
    @test eltype(Ap) == AGray{Float32}
    @test @inferred(getindex(Ap, -1, -1)) === fv
    @test @inferred(getindex(Ap, 2, 2)) === AGray{Float32}(1., 1.)
    @test Ap[axes(A)...] == AGray{Float32}.(A)

    fv = ARGB{Float32}(0.5, 0., 0., 0.)
    Ap = PaddedView(fv, A, (-1:4, -1:4))
    @test eltype(Ap) == ARGB{Float32}
    @test @inferred(getindex(Ap, -1, -1)) === fv
    @test @inferred(getindex(Ap, 2, 2)) === ARGB{Float32}(1., 1., 1., 1.)
    @test Ap[axes(A)...] == ARGB{Float32}.(A)

    # order irrelevant
    A = mosaic(rand(Float32, 4, 4), rand(RGB{N0f8}, 4, 4))
    @test eltype(A) == RGB{Float32}
    A = mosaic(rand(RGB{N0f8}, 4, 4), rand(Float32, 4, 4))
    @test eltype(A) == RGB{Float32}

    A = mosaic(rand(Float32, 4, 4), rand(Gray{Float64}, 4, 4))
    @test eltype(A) == Gray{Float64}
    A = mosaic(rand(Gray{Float64}, 4, 4), rand(Float32, 4, 4))
    @test eltype(A) == Gray{Float64}

    A = mosaic(rand(Float32, 4, 4), rand(Gray{Float64}, 4, 4), rand(RGB{N0f8}, 4, 4))
    @test eltype(A) == RGB{Float64}
    A = mosaic(rand(Float32, 4, 4), rand(RGB{N0f8}, 4, 4), rand(Gray{Float64}, 4, 4))
    @test eltype(A) == RGB{Float64}

    A = mosaic(rand(Gray, 4, 4), rand(RGB, 4, 4))
    @test eltype(A) == RGB{Float64} # the filltype is always a concrete type
end

nothing


using Base: @_noinline_meta, @_inline_meta
using Core: is_top_bit_set
using Core.Intrinsics: bitcast, trunc_int, sext_int, zext_int, sle_int, eq_int, and_int

# onehot erorr
struct OneHotEncodeError <: Exception
    K
    val
    OneHotEncodeError(@nospecialize(K), @nospecialize(val)) = (@_noinline_meta; new(K, val))
end

function Base.showerror(io::IO, e::OneHotEncodeError)
    print(io, "OneHotEncodeError: cannot encode ")
    print(io, e.val)
    print(io, " with OneHot{")
    print(io, e.K)
    print(io, '}')
end

throw_onehotencode_error(K, val) = (@_noinline_meta; throw(OneHotEncodeError(K, val)))

# onehot encode
primitive type OneHot{K} <: AbstractOneHotArray{1} 32 end

OneHot(k) = OneHot{UInt32(k)}
OneHot{K}(x) where K = convert(OneHot(K), x)
OneHot(k, x) = OneHot{k}(x)

onehotsize(::OneHot{K}) where K = Int(K)

# array interface

Base.size(o::OneHot) = (onehotsize(o),)
Base.@propagate_inbounds function Base.getindex(o::OneHot, i::I) where {I<:Integer}
    @boundscheck checkbounds(o, i)
    return convert(I, o) == i
end

Base.@propagate_inbounds Base.getindex(o::OneHot, i::Colon) = o

Base.argmax(o::OneHot) = Int(o)
Base.argmin(o::OneHot) = isone(o) ? 2 : 1

# printing

function Base.showarg(io::IO, x::OneHot, toplevel)
    toplevel || print(io, "::")
    print(io, "OneHot{", onehotsize(x), '}')
end

# convert

Base.UInt32(o::OneHot) = bitcast(UInt32, o)
Base.UInt64(o::OneHot) = zext_int(UInt64, o)
Base.Int32(o::OneHot) = bitcast(Int32, o)
Base.Int64(o::OneHot) = zext_int(Int64, o)

Base.convert(::Type{Any}, o::OneHot) = o
Base.convert(::Type{OneHot{K}}, o::OneHot{K}) where {K} = o
Base.convert(::Type{OneHot{K}}, o::OneHot) where {K} = OneHot(K, UInt32(o))
Base.convert(::Type{UInt32},  o::OneHot) = UInt32(o)
Base.convert(::Type{To}, o::OneHot) where {To<:Number} = convert(To, UInt32(o))
Base.convert(::Type{T}, o::OneHot) where {T<:Array} = T(o)

Base.reinterpret(::Type{T}, o::OneHot) where {T} = reinterpret(T, UInt32(o))

# one

Base.one(o::O) where {O<:OneHot} = convert(O, 0x00000001)
Base.one(::Type{<:OneHot{K}}) where K = OneHot(K, 1)
Base.isone(o::OneHot) = isone(convert(UInt32, o))

# zero

Base.zero(o::O) where {O<:OneHot} = convert(O, 0x00000000)
Base.zero(::Type{<:OneHot{K}}) where K = OneHot(K, 0)
Base.iszero(o::OneHot) = iszero(convert(UInt32, o))

# number

Base.typemin(::Type{OneHot{K}}) where K = OneHot(K, 0)
Base.typemax(::Type{OneHot{K}}) where K = OneHot(K, K)

Base.isless(a::OneHot, b::OneHot) = isless(UInt32(a), UInt32(b))
Base.:(==)(a::OneHot, b::OneHot) = UInt32(a) == UInt32(b)

# bit-op

function check_onehot_top_bit(::Type{OneHot{K}}, x) where {K}
    @_inline_meta
    is_top_bit_set(x) && throw_onehotencode_error(K, x)
    x
end

function check_onehot_encode(ot::Type{OneHot{K}}, x) where {K}
    @_inline_meta
    sle_int(x, K) || throw_onehotencode_error(K, x)
    bitcast(ot, x)
end

function checked_onehot_trunc_sint(ot::Type{OneHot{K}}, x::From) where {K, From}
    @_inline_meta
    y = trunc_int(UInt32, x)
    back = sext_int(From, y)
    eq_int(x, back) || throw_onehotencode_error(K, x)
    check_onehot_encode(ot, y)
end

function checked_onehot_trunc_uint(ot::Type{OneHot{K}}, x::From) where {K, From}
    @_inline_meta
    y = trunc_int(UInt32, x)
    back = zext_int(From, y)
    eq_int(x, back) || throw_onehotencode_error(K, x)
    check_onehot_encode(ot, y)
end

Base.convert(ot::Type{OneHot{K}}, x::Int8) where {K} = check_onehot_encode(ot, sext_int(UInt32, check_onehot_top_bit(ot, x)))
Base.convert(ot::Type{OneHot{K}}, x::Int16) where {K} = check_onehot_encode(ot, sext_int(UInt32, check_onehot_top_bit(ot, x)))
Base.convert(ot::Type{OneHot{K}}, x::Int32) where {K} = check_onehot_encode(ot, bitcast(UInt32, check_onehot_top_bit(ot, x)))
Base.convert(ot::Type{OneHot{K}}, x::Int64) where {K} = checked_onehot_trunc_sint(ot, check_onehot_top_bit(ot, x))
Base.convert(ot::Type{OneHot{K}}, x::Int128) where {K} = checked_onehot_trunc_sint(ot, check_onehot_top_bit(ot, x))
Base.convert(ot::Type{OneHot{K}}, x::UInt8) where {K} = check_onehot_encode(ot, zext_int(UInt32, x))
Base.convert(ot::Type{OneHot{K}}, x::UInt16) where {K} = check_onehot_encode(ot, zext_int(UInt32, x))
Base.convert(ot::Type{OneHot{K}}, x::UInt32) where {K} = check_onehot_encode(ot, x)
Base.convert(ot::Type{OneHot{K}}, x::UInt64) where {K} = checked_onehot_trunc_uint(ot, x)
Base.convert(ot::Type{OneHot{K}}, x::UInt128) where {K} = checked_onehot_trunc_uint(ot, x)
Base.convert(ot::Type{OneHot{K}}, x::Bool) where {K} = and_int(zext_int(ot, x), Base.convert(ot, 0x1))


module BulletsMaggots
using Random, ProgressMeter

include("tools.jl")
include("solver.jl")
include("build_table.jl")
include("play.jl")
include("jonah_solver.jl")

end # module


module LarSurf
    using Logging
    using Distributed
	using SparseArrays
	using JLD2, FileIO
	# using Pkg
    # @everywhere using LinearAlgebraicRepresentation
    # Lar = LinearAlgebraicRepresentation
    @info "Loaded M on $(myid())"

    export version
    # export convert
    hello(who::String) = "Hello, $who"

    # @everywhere LarSurf.ahoj("ku")
    # include("ahoj.jl")
    # @everywhere include("ahoj.jl")

	include("lar.jl")
    include("convert.jl")
    include("print_function.jl")
    include("arr_fcn.jl")
	include("triangulation.jl")
    include("read.jl")
    include("plasm.jl") ;
    include("surface.jl") ;
    include("representation.jl") ;
    include("import3d.jl") ;
    include("block.jl") ;
    include("sampledata.jl") ;
    # include("io3d.jl") ;
    include("boundary_operator.jl") ;
    include("inner_boundary.jl") ;
    include("datasets.jl") ;
    include("surface_extraction.jl") ;
    # include("experiment_support.jl") ;
    include("brick_surf_extraction.jl") ;
    include("surface_extraction_parallel_low_comunication.jl") ;
    # include("surface_extraction_parallel.jl") ;
	include("surface_extraction_per_voxel.jl")
	include("smoothing.jl")
	# if "ExSup" in keys(Pkg.installed())
		include("larsurf_experiments.jl")
	# end
	# include("dicom_support.jl")

    function version()
        return "0.0.2"
    end

end


module GCoptimization

using Cxx

const libPath = joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib")
const includePath = joinpath(dirname(@__FILE__), "..", "deps", "usr", "include")

global const libgco = Libdl.find_library(["libGCoptimization", "libGCoptimization-x86", "libGCoptimization-x64"], [libPath])
if isempty(libgco)
    error("GCoptimization not properly installed. Please run Pkg.build(\"GCoptimization\")")
end

function __init__()
    addHeaderDir(libPath, kind=C_System)
    Libdl.dlopen(libgco, Libdl.RTLD_GLOBAL)
    cxxinclude(includePath*"/GCoptimization.h")
end

# types
export GCObool, GCOnode_id, GCOLabelID, GCOEnergyTermType, GCOVarID, GCOSiteID
export GCOSparseDataCost

# low-level APIs
export GCoptimizationGridGraph, setSmoothCostVH,
       GCoptimizationGeneralGraph, setNeighbors

export expansion, alpha_expansion, swap, alpha_beta_swap

export setDataCost, setSmoothCost, setLabelCost, setLabelSubsetCost, whatLabel,
       setLabel, setLabelOrder, compute_energy, giveDataEnergy, giveSmoothEnergy,
       giveLabelEnergy, numSites, numLabels, setVerbosity

# high-level APIs
export gco_αexpansion, gco_αβswap

export gco_create, gco_setneighbors, gco_setdatacost, gco_setsmoothcost,
       gco_setlabelcost, gco_expansion, gco_swap, gco_energy, gco_getlabeling,
       gco_setlabeling, gco_setlabelorder

# types
include("types.jl")

# low-level APIs
include("lowlevel.jl")

# core functionalities
include("core.jl")

# high-level APIs
include("highlevel.jl")

end # module


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

using NodeNumbering

@testset "Renumbering the RCM ordered nodes." begin

    neworder = Int[6, 7, 2, 5, 3, 8, 1, 4, 9]

    expected = Dict(
               7 => 2,
               9 => 9,
               4 => 8,
               2 => 3,
               3 => 5,
               5 => 4,
               8 => 6,
               6 => 1,
               1 => 7)
    result = NodeNumbering.renumbering(neworder)
    @test result == expected
end


# ------------------------------------------
#    ACE Fitting

import IPFitting, ACE1pack, ACE1
using Dates, Base

export fit_params, fit_ace, make_ace_db, db_params, fit_ace_db, save_fit

"""
`fit_ace(params::Dict)` : function to set up and fit the least-squares
problem of "atoms' positions" -> "energy, forces, (virials)". Takes in a
dictionary with all the parameters. see `?fit_params` for details.

Returns `IP`, `lsqinfo`
"""
function fit_ace(params::Dict)

    db = _decide_how_to_get_db(params)
    IP, lsqinfo = fit_ace_db(db, params)

    return IP, lsqinfo
end

"""
`fit_params(; kwargs...)` : returns a dictionary containing all of the
parameters needed for making an ACE potential. All parameters are passed
as keyword argumts.

### Parameters
* `data` : data parameters, see `?data_params` for details (mandatory)
* `basis` : dictionary containing dictionaries that specify the basis used
in fitting. Usually just `ace_params` and `pair_params` (mandatory).
For example
```
basis = Dict(
    "ace" => ace_params(; kwargs...),
    "pair" => pair_params(; kwargs...))
````
keys of `basis` must correspond to one of the "type" of `basis_params`, see
`?basis_params` and values are corresponding parameters' dictionaries.
* `solver` : dictionary containing parameters that specify the solver for
least squares problem (mandatory). See `?solver_params`.
* `e0` : Dict{String, Float} containing reference values for isolated atoms'
energies (mandatory).
* `weights` : dictionary of `Dict("config_type" => Dict("E" => Float, "F => Float))``
entries specifying fitting weights. "default" is set to `1.0` for all of "E", "F",
and "V" weights.
* `P` : regularizer parameters (optional), see `?regularizer_params`.
* `ACE_fname = "ACE_fit.json"` : filename to save ACE to. Potential & info
do not get saved if `ACE_fname` isnothing() or is set to `""`. Files already _parse_entry
are renamed and not overwritten.
* `LSQ_DB_fname_stem = ""` : stem to save LsqDB to. Doesn't get saved if set to an empty
string (""). If the file is already present, but `fit_from_LSQ_DB` is set to false,
the old database is renamed, a new one constructed and saved under the given name.
* `fit_from_LSQ_DB = false`: whether to fit from a least squares database specified with
`LSQ_DB_fname_stem`. If `LSQ_DB_fname_stem * "_kron.h5"` file is not present, LsqDB is
constructed from scratch and saved.

"""
function fit_params(;
    data = nothing,
    basis = nothing,
    solver = nothing,
    e0 = nothing,
    weights = nothing,
    P = nothing,
    ACE_fname = "ACE_fit.json",
    LSQ_DB_fname_stem = "",
    fit_from_LSQ_DB = false)

    @assert !isnothing(data) "`data` is mandatory"
    @assert !isnothing(basis) "`basis` is mandatory"
    @assert !isnothing(solver) "`solver` is mandatory"
    @assert !isnothing(e0) "`e0` is mandatory"

    return Dict(
            "data" => data,
            "basis" => basis,
            "solver" => solver,
            "e0" => e0,
            "weights" => weights,
            "P" => P,
            "ACE_fname" => ACE_fname,
            "LSQ_DB_fname_stem" => LSQ_DB_fname_stem,
            "fit_from_LSQ_DB" => fit_from_LSQ_DB)
end

"""
`save_fit(fname, IP, lsqinfo)` : saves Dict("IP" => IP, "info" => lsqinfo) to fname.
If `fname` is already present, it is renamed and dictionary saved to `fname`.
"""
function save_fit(fname, IP, lsqinfo)
    # ENH: save to yace option
    if fname == "" || isnothing(fname)
        return
    end
    if isfile(fname)
        stem = replace(fname, ".json" => "")
        fnew =  stem * "." * String(rand('a':'z', 5)) * ".json"
        @warn("The file $fname already exists. It will be renamed to $fnew to avoid overwriting.")
        mv(fname, fnew)
    end
    @info("Saving ace fit to $(fname)")
    save_dict(fname, Dict("IP" => write_dict(IP), "info" => lsqinfo))
end

"""
`fit_ace_db(params::Dict)` : fits LsqDB with `params["LSQ_DB_fname_stem"], which must be already present.
`params["fit_from_LSQ_DB"]` must be set to true. See `?fit_params` for `params` specification,
of which `data` and `basis` aren't needed (are ignored).
"""
function fit_ace_db(params::Dict)
    @assert params["fit_from_LSQ_DB"]
    db = LsqDB(params["LSQ_DB_fname_stem"])
    IP, lsqinfo = fit_ace_db(db, params)
    return IP, lsqinfo
end

"""
`fit_ace_db(db::IPFitting.LsqDB, params::Dict)` : fits the given LsqDB. See `?fit_params` for
`params` specification, of which `data` and `basis` aren't needed (are ignored).
"""
function fit_ace_db(db::IPFitting.LsqDB, params::Dict)
    solver = ACE1pack.generate_solver(params["solver"])

    if !isnothing(params["P"])
        solver["P"] = ACE1pack.generate_regularizer(db.basis, params["P"])
    end

    if typeof(params["e0"]) == Dict{Any, Any}
        # sometimes gets read in (from yaml?) as Dict{Any, Any}
        # which gives StackOverflowError somewhere in OneBody
        params["e0"] = convert(Dict{String, Any}, params["e0"])
    end

    Vref = OneBody(params["e0"])
    weights = params["weights"]

    vals, solve_time, bytes, _, _ = @timed IPFitting.Lsq.lsqfit(
        db,
        Vref=Vref,
        solver=solver,
        weights=weights,
        error_table=true
    );

    IP = vals[1]
    lsqinfo = vals[2]
    solve_time = canonicalize(Dates.CompoundPeriod(Second(trunc(Int, solve_time))))
    bytes = Base.format_bytes(bytes)
    lsqinfo["solve_time"] = solve_time
    lsqinfo["solve_mem"] = bytes
    lsqinfo["fit_params"] = params

    save_fit(params["ACE_fname"], IP, lsqinfo)

    @info("Fitting errors")
    rmse_table(lsqinfo["errors"])
    @info("LsqDB solve time: $(solve_time)")
    @info("LsqDB memory: $(bytes)")

    return IP, lsqinfo
end

"""
`make_ace_db(params::Dict)` : makes a LsqDB from given parameters' dictionary.
For `params` see `?db_params`; parameters from `fit_params` also work, except
unnecessary entries will be ignored.

"""
function make_ace_db(params::Dict)
    data =  ACE1pack.read_data(params["data"])
    basis = [ACE1pack.generate_basis(basis_params) for (basis_name, basis_params) in params["basis"]]
    basis = JuLIP.MLIPs.IPSuperBasis(basis);
    db = LsqDB(params["LSQ_DB_fname_stem"], basis, data)
    return db
end

"""
db_params(; kwargs...)` : returns a dictionary containing all of the
parameters needed for making a LsqDB. All parameters are passed
as keyword argumts.

### Parameters
* `data` : data parameters, see `?data_params` for details (mandatory)
* `basis` : dictionary containing dictionaries that specify the basis used
in fitting. Usually just `ace_params` and `pair_params` (mandatory).
For example
```
basis = Dict{
    "ace" => ace_params(; kwargs...),
    "pair" => pair_params(; kwargs...)}
````
keys of `basis` must correspond to one of the "type" of `basis_params`, see
`?basis_params` and values are corresponding parameters' dictionaries.
* `LSQ_DB_fname_stem = ""` : stem to save LsqDB to. Doesn't get saved if set to an empty
string (""). If `LSQ_DB_fname_stem * "_kron.h5"` file is not present it gets renamed,
a new LsqDB is constructed and saved.
"""
function db_params(;
    data = nothing,
    basis = nothing,
    LSQ_DB_fname_stem = "")

    @assert !isnothing(data) "`data` is mandatory"
    @assert !isnothing(basis) "`basis` is mandatory"

    return Dict(
            "data" => data,
            "basis" => basis,
            "LSQ_DB_fname_stem" => LSQ_DB_fname_stem)
end

function _decide_how_to_get_db(params::Dict)
    if params["LSQ_DB_fname_stem"] == ""
        return make_ace_db(params)
    else
        db_fname = params["LSQ_DB_fname_stem"] * "_kron.h5"
        if ~isfile(db_fname)
            return make_ace_db(params)
        else
            if params["fit_from_LSQ_DB"]
                return LsqDB(params["LSQ_DB_fname_stem"])
            else
                @warn("Found $(db_fname), but wasn't asked to fit it. Making a new one and saving to $db_fname).")
                return make_ace_db(params)
            end
        end
    end
end


#NOTE: We do the error handling of nnp_initialize while loading NNPACK
function nnp_initialize()
    ccall((:nnp_initialize, libnnpack), nnp_status, (),)
end

function nnp_deinitialize()
    @nnpack_check ccall((:nnp_deinitialize, libnnpack), nnp_status, (),)
end

function pthreadpool_create(n = 0)
    ccall((:pthreadpool_create, libnnpack), Ptr{Cvoid}, (Csize_t,), n)
end

function nnp_relu_output(batch_size, channels, input, output, negative_slope, threadpool)
    @nnpack_check ccall((:nnp_relu_output, libnnpack), nnp_status, (Csize_t, Csize_t, Ptr{Cfloat}, Ptr{Cfloat}, Cfloat, pthreadpool_t), batch_size, channels, input, output, negative_slope, threadpool)
end

function nnp_relu_output(x::Array{Float32,N}, y::Array{Float32,N}; negative_slope::AbstractFloat = 0.0, threadpool = C_NULL) where {N}
    # Investigate why the channel and batch dims need to specified like this
    nnp_relu_output(prod(size(x)[N-1:N]), prod(size(x)[1:N-2]), x, y, negative_slope, threadpool)
    y
end

function nnp_relu_input_gradient(batch_size, channels, grad_output, input, grad_input, negative_slope, threadpool)
    @nnpack_check ccall((:nnp_relu_input_gradient, libnnpack), nnp_status, (Csize_t, Csize_t, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cfloat}, Cfloat, pthreadpool_t), batch_size, channels, grad_output, input, grad_input, negative_slope, threadpool)
end

function nnp_relu_input_gradient(x::Array{Float32,N}, dy::Array{Float32,N}, dx::Array{Float32,N}; negative_slope::AbstractFloat = 0.0, threadpool = C_NULL) where {N}
    # Investigate why the channel and batch dims need to specified like this
    nnp_relu_input_gradient(Csize_t(prod(size(x)[N-1:N])), prod(size(x)[1:N-2]), dy, x, dx, negative_slope, threadpool)
    dx
end

function nnp_softmax_output(batch_size, channels, input, output, threadpool)
    @nnpack_check ccall((:nnp_softmax_output, libnnpack), nnp_status, (Csize_t, Csize_t, Ptr{Cfloat}, Ptr{Cfloat}, pthreadpool_t), batch_size, channels, input, output, threadpool)
end

function nnp_softmax_output(x::VecOrMat{Float32}, y::VecOrMat{Float32}; threadpool = C_NULL)
    nnp_softmax_output(ndims(x) == 2 ? size(x, 2) : 1, size(x, 1), x, y, threadpool)
    y
end

#FIXME: Output of fully connected not consistent with `kernel * input`
#NOTE: This most likely due to nnpack being row major. Investigate this.

function nnp_fully_connected_output(batch_size, input_channels, output_channels, input, kernel, output, threadpool, profile)
    @nnpack_check ccall((:nnp_fully_connected_output, libnnpack), nnp_status, (Csize_t, Csize_t, Csize_t, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cfloat}, pthreadpool_t, Ptr{Cvoid}), batch_size, input_channels, output_channels, input, kernel, output, threadpool, C_NULL)
end

function nnp_fully_connected_output(x::Array{Float32,2}, w::Array{Float32,2}, y::Array{Float32,2}; profile = nothing, threadpool = C_NULL)
    profile = profile == nothing ? nnp_profile() : profile
    nnp_fully_connected_output(size(x, 2), size(x, 1), size(w, 1), x, w, y, threadpool, profile)
    y
end

function nnp_fully_connected_inference_f16f32(input_channels, output_channels, input, kernel, output, threadpool)
    @nnpack_check ccall((:nnp_fully_connected_inference_f16f32, libnnpack), nnp_status, (Csize_t, Csize_t, Ptr{Cfloat}, Ptr{Cvoid}, Ptr{Cfloat}, pthreadpool_t), input_channels, output_channels, input, kernel, output, threadpool)
end

nnp_fully_connected_inference_f16f32(x::Array{Float32, 1}, w::Array{Float16,2}, y::Array{Float32, 1}; threadpool = C_NULL) =
    nnp_fully_connected_inference(reshape(x, size(x), 1), w, reshape(y, size(y), 1), threadpool = threadpool)

function nnp_fully_connected_inference_f16f32(x::Array{Float32, 2}, w::Array{Float16,2}, y::Array{Float32, 2}; threadpool = C_NULL)
    nnp_fully_connected_inference(size(x, 1), size(y, 1), x, w, y, threadpool)
    y
end

function nnp_fully_connected_inference(input_channels, output_channels, input, kernel, output, threadpool)
    @nnpack_check ccall((:nnp_fully_connected_inference, libnnpack), nnp_status, (Csize_t, Csize_t, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cfloat}, pthreadpool_t), input_channels, output_channels, input, kernel, output, threadpool)
end

nnp_fully_connected_inference(x::Array{Float32, 1}, w::Array{Float32,2}; threadpool = C_NULL) =
    nnp_fully_connected_inference(reshape(x, size(x), 1), w, threadpool = threadpool)

function nnp_fully_connected_inference(x::Array{Float32, 2}, w::Array{Float32, 2}, y::Array{Float32, 2}; threadpool = C_NULL)
    nnp_fully_connected_inference(size(x, 1), size(y, 1), x, w, y, threadpool)
    y
end

function nnp_max_pooling_output(batch_size, channels, input_size, input_padding, pooling_size, pooling_stride, input, output, threadpool)
    @nnpack_check ccall((:nnp_max_pooling_output, libnnpack), nnp_status, (Csize_t, Csize_t, nnp_size, nnp_padding, nnp_size, nnp_size, Ptr{Cfloat}, Ptr{Cfloat}, pthreadpool_t), batch_size, channels, input_size, input_padding, pooling_size, pooling_stride, input, output, threadpool)
end

function nnp_max_pooling_output(y::Array{Float32,4}, x::Array{Float32,4}, kernel::Tuple; padding = 0, stride = 1, threadpool = C_NULL)
    input_size = nnp_size(Csize_t.((size(x, 1), size(x, 2)))...)
    pooling_size = nnp_size(Csize_t.(kernel)...)
    input_padding = nnp_padding(Csize_t(padding[2]), Csize_t(padding[1]), Csize_t(padding[2]), Csize_t(padding[1]))
    pooling_stride = nnp_size(Csize_t.(stride)...)
    nnp_max_pooling_output(size(x, 4), size(x, 3), input_size, input_padding, pooling_size, pooling_stride, x, y, threadpool)
    y
end

#TODO: Add wrapper for convolution inference

function nnp_convolution_input_gradient(algorithm, batch_size, input_channels, output_channels, input_size, input_padding, kernel_size, grad_output, kernel, grad_input, workspace_buffer, workspace_size, activation, activation_parameters, threadpool, profile)
    @nnpack_check ccall((:nnp_convolution_input_gradient, libnnpack), nnp_status, (nnp_convolution_algorithm, Csize_t, Csize_t, Csize_t, nnp_size, nnp_padding, nnp_size, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cvoid}, Csize_t, nnp_activation, Ptr{Cvoid}, pthreadpool_t, Ptr{Cvoid}), algorithm, batch_size, input_channels, output_channels, input_size, input_padding, kernel_size, grad_output, kernel, grad_input, workspace_buffer, workspace_size, activation, activation_parameters, threadpool, C_NULL)
end

function nnp_convolution_input_gradient(dx::Array{Float32,4}, dy::Array{Float32,4}, w::Array{Float32,4}; algo::nnp_convolution_algorithm = UInt32(0), workspace_buffer = nothing, workspace_size = 0, padding = 0, stride = 1, threadpool = C_NULL, profile = nothing)
    input_size = nnp_size(Csize_t.((size(dx,1), size(dx,2)))...)
    kernel_size = nnp_size(Csize_t.((size(w,1),size(w,2)))...)
    input_padding = nnp_padding(Csize_t(padding[2]), Csize_t(padding[1]), Csize_t(padding[2]), Csize_t(padding[1]))
    profile = profile == nothing ? nnp_profile() : profile
    workspace_buffer = workspace_buffer === nothing ? C_NULL : workspace_buffer
    nnp_convolution_input_gradient(UInt32(algo), size(dx,4), size(dx,3), size(w,4), input_size, input_padding, kernel_size, dy, w, dx, workspace_buffer, workspace_size, UInt32(0), C_NULL, threadpool, profile)
    dx
end

function nnp_convolution_kernel_gradient(algorithm, batch_size, input_channels, output_channels, input_size, input_padding, kernel_size, input, grad_output, grad_kernel, workspace_buffer, workspace_size, activation, activation_parameters, threadpool, profile)
    @nnpack_check ccall((:nnp_convolution_kernel_gradient, libnnpack), nnp_status, (nnp_convolution_algorithm, Csize_t, Csize_t, Csize_t, nnp_size, nnp_padding, nnp_size, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cvoid}, Csize_t, nnp_activation, Ptr{Cvoid}, pthreadpool_t, Ptr{Cvoid}), algorithm, batch_size, input_channels, output_channels, input_size, input_padding, kernel_size, input, grad_output, grad_kernel, workspace_buffer, workspace_size, activation, activation_parameters, threadpool, C_NULL)
end

function nnp_convolution_kernel_gradient(dw::Array{Float32,4}, x::Array{Float32,4}, dy::Array{Float32,4}; algo::nnp_convolution_algorithm = UInt32(0), workspace_buffer = nothing, workspace_size = 0, padding = 0, stride = 1, threadpool = C_NULL, profile = nothing)
    input_size = nnp_size(Csize_t.((size(x,1), size(x,2)))...)
    kernel_size = nnp_size(Csize_t.((size(dw,1),size(dw,2)))...)
    input_padding = nnp_padding(Csize_t(padding[2]), Csize_t(padding[1]), Csize_t(padding[2]), Csize_t(padding[1]))
    profile = profile == nothing ? nnp_profile() : profile
    workspace_buffer = workspace_buffer === nothing ? C_NULL : workspace_buffer
    nnp_convolution_kernel_gradient(UInt32(algo), size(x,4), size(x,3), size(dw,4), input_size, input_padding, kernel_size, x, dy, dw, workspace_buffer, workspace_size, UInt32(0), C_NULL, threadpool, profile)
    dw
end

function nnp_convolution_output(algorithm, batch_size, input_channels, output_channels, input_size, input_padding, kernel_size, input, kernel, bias, output, workspace_buffer, workspace_size, activation, activation_parameters, threadpool, profile)
    @nnpack_check ccall((:nnp_convolution_output, libnnpack), nnp_status, (nnp_convolution_algorithm, Csize_t, Csize_t, Csize_t, nnp_size, nnp_padding, nnp_size, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cfloat}, Ptr{Cvoid}, Csize_t, nnp_activation, Ptr{Cvoid}, pthreadpool_t, Ptr{Cvoid}), algorithm, batch_size, input_channels, output_channels, input_size, input_padding, kernel_size, input, kernel, bias, output, workspace_buffer, workspace_size, activation, activation_parameters, threadpool, C_NULL)
end

function nnp_convolution_output(y::Array{Float32,4}, x::Array{Float32,4}, w::Array{Float32,4}, b::Array{Float32,1}; algo::nnp_convolution_algorithm = UInt32(0), workspace_buffer = nothing, workspace_size = 0, padding = 0, stride = 1, threadpool = C_NULL, profile = nothing)
    input_size = nnp_size(Csize_t.((size(x,1), size(x,2)))...)
    kernel_size = nnp_size(Csize_t.((size(w,1),size(w,2)))...)
    input_padding = nnp_padding(Csize_t(padding[2]), Csize_t(padding[1]), Csize_t(padding[2]), Csize_t(padding[1]))
    profile = profile == nothing ? nnp_profile() : profile
    workspace_buffer = workspace_buffer === nothing ? C_NULL : workspace_buffer
    nnp_convolution_output(UInt32(algo), size(x,4), size(x,3), size(w,4), input_size, input_padding, kernel_size, x, w, b, y, workspace_buffer, workspace_size, UInt32(0), C_NULL, threadpool, profile)
    y
end


using Test, Random, LinearAlgebra, SparseArrays
using Yao
using YaoExtensions
using QuAlgorithmZoo

@testset "PhaseEstimation" begin
    include("PhaseEstimation.jl")
end

@testset "hamiltonian solvers" begin
    include("hamiltonian_solvers.jl")
end

@testset "hadamard test" begin
    include("HadamardTest.jl")
end


# tests to check the parts of the whole optimization that includes
# optTopLevel (search in the same level of hybrids) and
# addHybridizationUpdate(net) that will add a new hybrid if needed, and deleteHybridizationUpdate()


module AlignmentAlgorithms

export nwscore,
       nwsetupmatrix,
       nwscorematrix,
       swsetupmatrix,
       swscorematrix
#export nwalign

include("needleman_wunsch.jl")
include("smith_waterman.jl")

end


function read_bgen(bgen_file::String)
    kwargs = Dict{Symbol, Any}(:sample_path => nothing, :idx_path => nothing)
    if bgen_file[end-3:end] == "bgen"
        base = bgen_file[1:end-4]

        samplefile = base * "sample"
        isfile(samplefile) ? kwargs[:sample_path] = samplefile : nothing

        bgifile = bgen_file * ".bgi"
        isfile(bgifile) ? kwargs[:idx_path] = bgifile : nothing
    end
    return Bgen(bgen_file; kwargs...)
end

function samples_genotype(probabilities, variant_genotypes, threshold=0.9)
    n = size(probabilities)[2]
    # The default value is missing
    t = Vector{Union{String, Missing}}(missing, n)
    for i in 1:n
        # If no allele has been annotated with sufficient confidence
        # the sample is declared as missing for this variant
        sample_gen_index = findfirst(x -> x >= threshold, probabilities[:, i])
        sample_gen_index isa Nothing || (t[i] = variant_genotypes[sample_gen_index])
    end
    return t
end

"""

A heterozygous genotype can be specified as (ALLELE₁, ALLELE₂) or (ALLELE₂, ALLELE₁).
Here we align this heterozygous specification on the query and default to
(ALLELE₁, ALLELE₂) provided in the BGEN file if nothing is specified in the query.
"""
function variant_genotypes(variant::Variant, queries)
    all₁, all₂ = alleles(variant)
    # Either (ALLELE₂, ALLELE₁) is provided in the query
    # and we return it as the heterozygous genotype.
    # Or the other specification will do in all other cases.
    queried_alleles = [q[Symbol(variant.rsid)] for (qn, q) in queries]
    if all₂*all₁ in collect(Iterators.flatten(queried_alleles))
        return [all₁*all₁, all₂*all₁, all₂*all₂]
    end
    return [all₁*all₁, all₁*all₂, all₂*all₂]
end

function UKBBGenotypes(queryfile, queries)
    config = TOML.parsefile(queryfile)
    snps = config["SNPS"]
    threshold = config["threshold"]
    # Let's load the variants by the files they are in
    bgen_groups = Dict()
    for (rsid, path) in snps
        haskey(bgen_groups, path) ? push!(bgen_groups[path], rsid) : bgen_groups[path] = [rsid]
    end

    genotypes = nothing
    for (path, rsids) in bgen_groups
        b = read_bgen(path)
        chr_genotypes = DataFrame(SAMPLE_ID=b.samples)

        # Iterate over variants in this chromosome
        for rsid in rsids
            v = variant_by_rsid(b, rsid)
            variant_gens = variant_genotypes(v, queries)
            probabilities = probabilities!(b, v)
            chr_genotypes[!, rsid] = samples_genotype(probabilities, variant_gens, threshold)
        end
        # I think concatenating should suffice but I still join as a safety
        genotypes isa Nothing ? genotypes = chr_genotypes :
            genotypes = innerjoin(genotypes, chr_genotypes, on=:SAMPLE_ID)
    end
    return genotypes
end

function preprocess(genotypes, confounders, phenotypes;
                    verbosity=1)

    # Make sure data SAMPLE_ID types coincide
    genotypes.SAMPLE_ID = string.(genotypes.SAMPLE_ID)
    confounders.SAMPLE_ID = string.(confounders.SAMPLE_ID)
    phenotypes.eid = string.(phenotypes.eid)

    # Join all elements together
    data = innerjoin(
            innerjoin(genotypes, confounders, on=:SAMPLE_ID),
            phenotypes,
            on = :SAMPLE_ID =>:eid
            )

    # Check no data has been lost at this stage
    any(nrows(array) != nrows(data) for array in (genotypes, confounders, phenotypes)) &&
        @warn """The number of matching samples is different in data sources: \n
            - Genotypes: $(nrows(genotypes)) \n
            - Confounders: $(nrows(confounders)) \n
            - Phenotypes: $(nrows(phenotypes)) \n
            - After Join: $(nrows(data)) \n
        """

    # Filter any line where an element is missing
    filtered_data = dropmissing(data)
    verbosity >= 1 && @info "Samples size after missing removed: $(nrows(filtered_data))"

    # Retrieve T and convert to categorical data
    T = filtered_data[!, filter(!=("SAMPLE_ID"), names(genotypes))]
    for name in names(T)
        T[!, name] = categorical(T[:, name])
    end

    # Retrieve W
    W = filtered_data[!, filter(!=("SAMPLE_ID"), names(confounders))]

    # Retrieve y which is assumed to be the first column after SAMPLE_ID
    # and convert to categorical array if needed
    y = filtered_data[!, filter(!=("eid"), names(phenotypes))][!, 1]

    # Only support binary and continuous traits for now
    is_binary(y) ? y = categorical(convert(Vector{Bool}, y)) : nothing

    return T, W, y
end

function PhenotypeTMLEEpistasis(tmles::Dict, T, W, y, queries; verbosity=1, phenotypename=nothing)
    # Build TMLE machine, can I update Y withoug altering the machine state
    # so that only the Q mach is fit again?
    tmle = is_binary(y) ? tmles["binary"] : tmles["continuous"]
    mach = machine(tmle, T, W, y)
    # Run TMLE
    fit!(mach; verbosity=verbosity-1)

    reports = briefreport(mach)
    results = DataFrame(
        PHENOTYPE=Symbol[],
        QUERYNAME=String[],
        QUERYSTRING=String[],
        ESTIMATE=Float64[],
        PVALUE=Float64[],
        LOWER_BOUND=Float64[],
        UPPER_BOUND=Float64[],
        STD_ERROR=Float64[],
        QSTACK_COEFS=String[],
        NROWS=Int[],
        TCOUNTS=String[]
        )

    tcounts = TreatmentCountsRepr(T)
    n = nrows(y)
    for (i, (queryname, query)) in enumerate(queries)
        querystring_ = querystring(query)

        queryreport = get_query_report(reports, i)
        pvalue = queryreport.pvalue
        lwb, upb = queryreport.confint
        estimate = queryreport.estimate
        stderror = queryreport.stderror
        qstack_coefs = repr_Qstack_coefs(mach)

        push!(results, (phenotypename, queryname, querystring_, estimate, pvalue, lwb, upb, stderror, qstack_coefs, n, tcounts))
    end
    return results
end

function PhenotypeCrossValidation(library::Dict, T, W, y, queries; verbosity=1, phenotypename=nothing)
    # Cross validate models for Q̅
    Qsettings = is_binary(y) ? library["Qcat"] : library["Qcont"]
    Qmeasure = is_binary(y) ? log_loss : l2
    hot_mach = machine(OneHotEncoder(drop_last=true), T)
    fit!(hot_mach, verbosity=verbosity)
    T_hot = transform(hot_mach, T)
    X = hcat(T_hot, W)
    Qresult_string = ""
    for (modelname, model) in Qsettings[2]
        mach = machine(model, X, y)
        result = evaluate!(mach, resampling=Qsettings[1], measure=Qmeasure, verbosity=verbosity)
        Qresult_string *= "$modelname: m=$(result.measurement[1]) std=$(std(result.per_fold[1])) | "
    end

    # Cross validate models for G
    Gsettings = library["G"]
    Gresult_string = ""
    t_target = encode(T)
    for (modelname, model) in Gsettings[2]
        mach = machine(model, W, t_target)
        result = evaluate!(mach, resampling=Gsettings[1], measure=log_loss, operation=predict, verbosity=verbosity)
        Gresult_string *= "$modelname: m=$(result.measurement[1]) std=$(std(result.per_fold[1])) | "
    end

    variants = join(keys(queries[1][2]), " && ")

    return DataFrame(
        PHENOTYPE=[phenotypename],
        VARIANTS=[variants],
        Q_RESULTSTRING=[Qresult_string[1:length(Qresult_string)-3]],
        G_RESULTSTRING=[Gresult_string[1:length(Gresult_string)-3]]
        )

end

function UKBBVariantRun(parsed_args; run_fn=PhenotypeTMLEEpistasis)
    v = parsed_args["verbosity"]

    # Parse queries
    queries = parse_queries(parsed_args["queries"])

    # Build estimators
    tmle_config = TOML.parsefile(parsed_args["estimator"])
    estimators = estimators_from_toml(tmle_config, queries, run_fn)

    v >= 1 && @info "Loading Genotypes and Confounders."
    # Build Genotypes
    genotypes = UKBBGenotypes(parsed_args["queries"], queries)

    # Read Confounders
    confounders = CSV.File(parsed_args["confounders"]) |> DataFrame

    # Generate phenotypes range
    all_phenotype_names = phenotypesnames(parsed_args["phenotypes"])
    done_phenotypes = init_or_retrieve_results(parsed_args["output"], run_fn)
    phenotypes_range = phenotypes_list(
        parsed_args["phenotypes-list"],
        done_phenotypes,
        all_phenotype_names
        )

    for phenotypename in phenotypes_range
        v >= 1 && @info "Running procedure with phenotype: $phenotypename."
        # Read Target
        phenotype = CSV.File(parsed_args["phenotypes"], select=[:eid, phenotypename]) |> DataFrame
        # Preprocess all data together
        T, W, y = preprocess(genotypes,
                            confounders,
                            phenotype;
                            verbosity=v)
        # Run the estimation procedure
        results = run_fn(estimators, T, W, y, queries; verbosity=v, phenotypename=phenotypename)
        # Update the results
        CSV.write(parsed_args["output"], results, append=true)

    end

    v >= 1 && @info "Done."
end