# TODO add bangs for functions that modify argument(s)
module SententialDecisionDiagrams

using Parameters

include("sddapi.jl")
include("fnf.jl")

using .SddLibrary

struct VTree
    vtree::Ptr{SddLibrary.VTree_c}
end

struct SddManager
    manager::Ptr{SddLibrary.SddManager_c}
end

struct SddNode
    node::Ptr{SddLibrary.SddNode_c}
    manager::Ptr{SddLibrary.SddManager_c}
end

struct PrimeSub
    prime::SddNode
    sub::SddNode
end

struct WmcManager
    manager::Ptr{SddLibrary.WmcManager_c}
end

function str_to_char(vtree_type::String)::Ptr{UInt8}
    return pointer(vtree_type)
end

# SDD MANAGER FUNCTIONS
function sdd_manager(vtree::VTree)::SddManager
    manager = SddLibrary.sdd_manager_new(vtree.vtree)
    return SddManager(manager)
end
function sdd_manager(var_count::Integer,  auto_gc_and_minimize::Bool)::SddManager
    manager = SddLibrary.sdd_manager_create(convert(SddLibrary.SddLiteral, var_count), auto_gc_and_minimize)
    return SddManager(manager)
end
# TODO function manager_new
function free(manager::SddManager)
    SddLibrary.sdd_manager_free(manager.manager)
end
function Base.print(manager::SddManager)
    SddLibrary.sdd_manager_print(manager.manager)
end
function auto_gc_and_minimize_on(manager::SddManager)
    SddLibrary.sdd_manager_auto_gc_and_minimize_on(manager.manager)
end
function sdd_manager_auto_gc_and_minimize_off(manager::SddManager)
    SddLibrary.sdd_manager_auto_gc_and_minimize_off(manager.manager)
end
function is_auto_gc_and_minimize_on(manager::SddManager)::Bool
    return convert(Bool, SddLibrary.sdd_manager_is_auto_gc_and_minimize_on(manager.manager))
end
# TODO void sdd_manager_set_minimize_function
function unset_minimize_function(manager::SddManager)
    SddLI.sdd_manager_unset_minimize_function(manager.manager)
end
function options(manager::SddManager)
    SddLI.sdd_manager_options(manager.manager)
end
# TODO void sdd_manager_set_options(void* options, SddManager* manager);
function is_var_used(var::Integer, manager::SddManager)::Bool
    return convert(Bool, SddLibrary.sdd_manager_is_var_used(convert(SddLibrary.SddLiteral, var), manager.manager))
end
function vtree_of_var(var::Integer, manager::SddManager)::VTree
    vtree = SddLibrary.sdd_manager_vtree_of_var(convert(SddLibrary.SddLiteral, var), manager.manager)
    return VTree(vtree)
end
# TODO Vtree* sdd_manager_lca_of_literals(int count, SddLiteral* literals, SddManager* manager);
function var_count(manager::SddManager)::SddLibrary.SddLiteral
    return SddLibrary.sdd_manager_var_count(manager.manager)
end
# TODO void sdd_manager_var_order(SddLiteral* var_order, SddManager *manager);
function add_var_before_first(manager::SddManager)
    SddLibrary.sdd_manager_add_var_before_first(manager.manager)
end
function add_var_after_last(manager::SddManager)
    SddLibrary.sdd_manager_add_var_after_last(manager.manager)
end
function add_var_before(manager::SddManager)
    SddLibrary.sdd_manager_add_var_before(manager.manager)
end
function add_var_after(manager::SddManager)
    SddLibrary.sdd_manager_add_var_after(manager.manager)
end

# TERMINAL SDDS
function literal(value::Bool, manager::SddManager)::SddNode
    if value
        node = SddLibrary.sdd_manager_true(manager.manager)
    else
        node = SddLibrary.sdd_manager_false(manager.manager)
    end
    return SddNode(node, manager.manager)
end
function literal(literal::Integer, manager::SddManager)::SddNode
    node = SddLibrary.sdd_manager_literal(convert(SddLibrary.SddLiteral, literal), manager.manager)
    return SddNode(node, manager.manager)
end

# SDD QUERIES AND TRANSFORMATIONS
function apply(node1::SddNode, node2::SddNode, op::Bool, manager::SddManager)::SddNode
    node = SddLibrary.sdd_apply(node1.node, node2.node, convert(SddLibrary.SddBoolOp, op), manager.manager)
    return SddNode(node, node1.manager)
end
function conjoin(node1::SddNode, node2::SddNode, manager::SddManager)::SddNode
    node = SddLibrary.sdd_conjoin(node1.node, node2.node, manager.manager)
    return SddNode(node, manager.manager)
end
function disjoin(node1::SddNode, node2::SddNode, manager::SddManager)::SddNode
    node = SddLibrary.sdd_disjoin(node1.node, node2.node, manager.manager)
    return SddNode(node, manager.manager)
end
function negate(node::SddNode, manager::SddManager)::SddNode
    node = SddLibrary.sdd_negate(node.node, node.manager)
    return SddNode(node, manager.manager)
end
function condition(lit::Integer, node::SddNode, manager::SddManager)::SddNode
    node = SddLibrary.sdd_condition(convert(SddLibrary.SddLiteral, lit), node.node, manager.manager)
    return SddNode(node, manager.manager)
end
function exists(lit::Integer, node::SddNode, manager::SddManager)::SddNode
    node = SddLibrary.sdd_exists(convert(SddLibrary.SddLiteral, lit), node.node, manager.manager)
    return SddNode(node, manager.manager)
end
function exists_multiple(exists_map::Array{Integer,1}, node::SddNode, manager::SddManager; static::Bool=false)::SddNode
    if !static
        node = SddLibrary.sdd_exists(convert(Array{Cint,1}, exists_map), node.node, manager.manager)
    else
        node = SddLibrary.sdd_exists_multiple_static(convert(Array{Cint,1}, exists_map), node.node, manager.manager)
    end
    return SddNode(node, manager.manager)
end
function for_all(lit::Integer, node::SddNode, manager::SddManager)::SddNode
    node = SddLibrary.sdd_forall(convert(SddLibrary.SddLiteral, lit), node.node, manager.manager)
    return SddNode(node, manager.manager)
end
function minimize_cardinality(node::SddNode, manager::SddManager; globally::Bool=false)::SddNode
    if !globally
        node = SddLibrary.sdd_minimize_cardinality(node.node, manager.manager)
    else
        node = SddLibrary.sdd_global_minimize_cardinality(node.node, manager.manager)
    end
    return SddNode(node, manager.manager)
end
function minimum_cardinality(node::SddNode)::SddLibrary.SddLiteral
    return SddLibrary.sdd_minimum_cardinality(node.node)
end
function model_count(node::SddNode, manager::SddManager; globally::Bool=false)::SddLibrary.SddModelCount
    if !globally
        return SddLibrary.sdd_model_count(node.node, manager.manager)
    else
        return SddLibrary.sdd_global_model_count(node.node, manager.manager)
    end
end

# SDD NAVIGATION
function is_true(node::SddNode)::Bool
    res = SddLibrary.sdd_node_is_true(node.node)
    return convert(Bool, res)
end
function is_false(node::SddNode)::Bool
    res = SddLibrary.sdd_node_is_false(node.node)
    return convert(Bool, res)
end
function is_literal(node::SddNode)::Bool
    res = SddLibrary.sdd_node_is_literal(node.node)
    return convert(Bool, res)
end
function is_decision(node::SddNode)::Bool
    res = SddLibrary.sdd_node_is_decision(node.node)
    return convert(Bool, res)
end
function node_size(node::SddNode)::SddLibrary.SddNodeSize
    return SddLibrary.sdd_node_size(node.node)
end
function literal(node::SddNode)::SddLibrary.SddLiteral
    return SddLibrary.sdd_node_literal(node.node)
end
function elements(node::SddNode)::Array{PrimeSub,1}
    # TODO make abstract array for primesubs and avoid copying
    elements_ptr = SddLibrary.sdd_node_elements(node.node)
    m = size(node)
    primesubs = PrimeSub[]
    for i in 0:m - 1
        e = unsafe_load(elements_ptr + i)
        p = SddNode(e, node.manager)
        s = SddNode(e + 1, node.manager)
        push!(primesubs, PrimeSub(p, s))
    end
    return primesubs
end
function set_bit(bit::Int32, node::SddNode)
    SddLibrary.sdd_node_set_bit(bit, node.node)
end
function bit(node::SddNode)::Int32
    return SddLibrary.sdd_node_bit(node.node)
end

#
# # SDD FUNCTIONS

# SDD FILE I/O
# TODO make this safer
function read_sdd(filename::String, manager::SddManager)::SddNode
    node = SddLibrary.sdd_read(str_to_char(filename), manager.manager)
    return SddNode(node, manager.manager)
end
function save(filename::String, node::SddNode)
    SddLibrary.sdd_save(str_to_char(filename), node.node)
end
function save_as_dot(filename::String, node::SddNode)
    SddLibrary.sdd_save_as_dot(str_to_char(filename), node.node)
end
function shared_save_as_dot(filename::String, manager::SddManager)
    SddLibrary.sdd_shared_save_as_dot(str_to_char(filename), manager.manager)
end

# SDD SIZE AND NODE COUNT
# SDD
function count(node::SddNode)::SddLibrary.SddSize
    return SddLibrary.sdd_count(node.node)
end
function size(node::SddNode)::SddLibrary.SddSize
    return SddLibrary.sdd_size(node.node)
end
# SDD OF MANAGER
manager_size_fnames_c = [
    "size", "live_size", "dead_size",
    "count", "live_count", "dead_count"
]
for (fnj, fnc) in zip(manager_size_fnames_c, SddLibrary.manager_size_fnames_c)
    @eval begin
        function $(Symbol(fnj))(manager::SddManager)::SddLibrary.SddSize
            return ((SddLibrary).$(Symbol(fnc)))(manager.manager)
        end
    end
end
# SDD SIZE OF VTREE
vtree_size_fnames_j = [
    "size", "live_size", "dead_size",
    "size_at", "live_size_at", "dead_size_at",
    "size_above", "live_size_above", "dead_size_above",
    "count", "live_count", "dead_count",
    "count_at", "live_count_at", "dead_count_at",
    "count_above", "live_count_above", "dead_count_above"
]
for (fnj, fnc) in zip(vtree_size_fnames_j, SddLibrary.vtree_size_fnames_c)
    @eval begin
        function $(Symbol(fnj))(vtree::VTree)::SddLibrary.SddSize
            return ((SddLibrary).$(Symbol(fnc)))(vtree.vtree)
        end
    end
end

# CREATING VTREES
function vtree(var_count::Integer, vtree_type::String)::VTree
    vtree = SddLibrary.sdd_vtree_new(convert(SddLibrary.SddLiteral, var_count), str_to_char(vtree_type))
    return VTree(vtree)
end
function vtree(var_count::Integer, order::Array{<:Integer,1}, vtree_type::String; order_type::String="var_order")::VTree
    @assert order_type in Set(["var_order", "is_X_var"]) "$order_type not in a valid order type (var_order, is_X_var]"
    if order_type == "var_order"
        vtree = SddLibrary.sdd_vtree_new_with_var_order(convert(SddLibrary.SddLiteral, var_count), convert(Array{SddLibrary.SddLiteral,1}, order), str_to_char(vtree_type))
    elseif order_type == "is_X_var"
        vtree = SddLibrary.sdd_vtree_new_X_constrained(convert(SddLibrary.SddLiteral, var_count), convert(Array{SddLibrary.SddLiteral,1}, order), str_to_char(vtree_type))
    end
    return VTree(vtree)
end
function free(vtree::VTree)
    SddLibrary.sdd_vtree_free(vtree.vtree)
end

# VTREE FILE I/O
# TODO make this safer
function read_vtree(filename::String)::VTree
    vtree = SddLibrary.sdd_vtree_read(str_to_char(filename))
    return VTree(vtree)
end
function save(filename::String, vtree::VTree)
    SddLibrary.sdd_vtree_save(str_to_char(filename), vtree.vtree)
end
function save_as_dot(filename::String, vtree::VTree)
    SddLibrary.sdd_vtree_save_as_dot(str_to_char(filename), vtree.vtree)
end

# SDD MANAGER VTREE
function vtree(manager::SddManager; copy::Bool=false)::VTree
    if !copy
        vtree = SddLibrary.sdd_manager_vtree(manager.manager)
        return VTree(vtree)
    else
        vtree = SddLibrary.sdd_manager_vtree_copy(manager.manager)
        return VTree(vtree)
    end
end

# VTREE NAVIGATION
function left(vtree::VTree)::VTree
    vtree = SddLibrary.sdd_vtree_left(vtree.vtree)
    return VTree(vtree)
end
function right(vtree::VTree)::VTree
    vtree = SddLibrary.sdd_vtree_right(vtree.vtree)
    return VTree(vtree)
end
function parent(vtree::VTree)::VTree
    vtree = SddLibrary.sdd_vtree_parent(vtree.vtree)
    return VTree(vtree)
end

# VTREE FUNCTIONS
function is_leaf(vtree::VTree)::Bool
    return convert(Bool, SddLibrary.sdd_vtree_is_leaf(vtree.vtree))
end
function is_sub(vtree1::VTree, vtree2::VTree)::Bool
    return convert(Bool, SddLibrary.sdd_vtree_is_sub(vtree1.vtree, vtree2.vtree))
end
function lca(vtree1::VTree, vtree2::VTree)::VTree
    vtree =  SddLibrary.sdd_vtree_lca(vtree1.vtree, vtree2.vtree)
    return VTree(vtree)
end
function var_count(vtree::VTree)::SddLibrary.SddLiteral
    return SddLibrary.sdd_vtree_var_count(vtree.vtree)
end
function var(vtree::VTree)::SddLibrary.SddLiteral
    return SddLibrary.sdd_vtree_var(vtree.vtree)
end
function position(vtree::VTree)::SddLibrary.SddLiteral
    return SddLibrary.sdd_vtree_position(vtree.vtree)
end
# Vtree** sdd_vtree_location(Vtree* vtree, SddManager* manager);

# VTREE/SDD EDIT OPERATIONS
function rotate_left(vtree::VTree, manager::SddManager, limited::Union{Bool,Integer})::Bool
    return convert(Bool, SddLibrary.sdd_vtree_rotate_left(vtree.vtree, manager.manager, convert(Cint, limited)))
end
function rotate_right(vtree::VTree, manager::SddManager, limited::Union{Bool,Integer})::Bool
    return convert(Bool, SddLibrary.sdd_vtree_rotate_right(vtree.vtree, manager.manager, convert(Cint, limited)))
end
function swap(vtree::VTree, manager::SddManager, limited::Union{Bool,Integer})::Bool
    return convert(Bool, SddLibrary.sdd_vtree_swap(vtree.vtree, manager.manager, convert(Cint, limited)))
end

# LIMITS FOR VTREE/SDD EDIT OPERATIONS
function init_vtree_size_limit(vtree::VTree, manager::SddManager)
    SddLibrary.sdd_manager_init_vtree_size_limit(vtree.vtree, manager.manager)
end
function update_vtree_size_limit(manager::SddManager)
    SddLibrary.sdd_manager_update_vtree_size_limit(manager.manager)
end

# # VTREE STATE

# GARBAGE COLLECTION
function ref_count(node::SddNode)::SddLibrary.SddRefCount
    return SddLibrary.sdd_ref_count(node.node)
end
function ref(node::SddNode, manager::SddManager)::SddNode
    ref_node = SddLibrary.sdd_ref(node.node, manager.manager)
    return SddNode(ref_node, manager.manager)
end
function deref(node::SddNode, manager::SddManager)::SddNode
    ref_node = SddLibrary.sdd_deref(node.node, manager.manager)
    return SddNode(ref_node, manager.manager)
end
function garbage_collect(manager::SddManager)
    SddLibrary.sdd_manager_garbage_collect(manager.manager)
end
function garbage_collect(vtree::VTree, manager::SddManager)
    SddLibrary.sdd_manager_garbage_collect(vtree.vtree, manager.manager)
end
function garbage_collect_if(dead_node_threshold::Real, manager::SddManager)::Int32
    return SddLibrary.sdd_manager_garbage_collect_if(convert(Float32, dead_node_threshold), manager.manager)
end
function garbage_collect_if(dead_node_threshold::Real, vtree::VTree, manager::SddManager)::Int32
    return SddLibrary.sdd_manager_garbage_collect_if(convert(Float32, dead_node_threshold), vtree.vtree, manager.manager)
end

# MINIMIZATION
function minimize(manager::SddManager; limited::Bool=false)
    if !limited
        SddLibrary.sdd_manager_minimize(manager.manager)
    else
        SddLibrary.sdd_manager_minimize_limited(manager.manager)
    end
end
function minimize(vtree::VTree, manager::SddManager; limited::Bool=false)::VTree
    if !limited
        vtree = SddLibrary.sdd_vtree_minimize(vtree.vtree, manager.manager)
    else
        vtree = SddLibrary.sdd_vtree_minimize_limited(vtree.vtree, manager.manager)
    end
    return VTree(vtree)
end

function set_vtree_search_convergence_threshold(threshold::Real, manager::SddManager)
    SddLibrary.sdd_manager_set_vtree_search_convergence_threshold(convert(Float32, threshold), manager.manager)
end

function set_vtree_search_time_limit(time_limit::Real, manager::SddManager)
    SddLibrary.sdd_manager_set_vtree_search_time_limit(convert(Float32, time_limit), manager.manager)
end
function set_vtree_fragment_time_limit(time_limit::Real, manager::SddManager)
    SddLibrary.sdd_manager_set_vtree_fragment_time_limit(convert(Float32, time_limit), manager.manager)
end
function set_vtree_operation_time_limit(time_limit::Real, manager::SddManager)
    SddLibrary.sdd_manager_set_vtree_operation_time_limit(convert(Float32, time_limit), manager.manager)
end
function set_vtree_apply_time_limit(time_limit::Real, manager::SddManager)
    SddLibrary.sdd_manager_set_vtree_apply_time_limit(convert(Float32, time_limit), manager.manager)
end
function set_vtree_operation_memory_limit(memory_limit::Real, manager::SddManager)
    SddLibrary.sdd_manager_set_vtree_operation_memory_limit(convert(Float32, memory_limit), manager.manager)
end
function set_vtree_operation_size_limit(size_limit::Real, manager::SddManager)
    SddLibrary.sdd_manager_set_vtree_operation_size_limit(convert(Float32, size_limit), manager.manager)
end
function set_vtree_cartesian_product_limit(size_limit::Real, manager::SddManager)
    SddLibrary.sdd_manager_set_vtree_cartesian_product_limit(convert(Float32, size_limit), manager.manager)
end

# WMC
function wmc_manager(node::SddNode, log_mode::Bool, manager::SddManager)::WmcManager
    wmc = SddLibrary.wmc_manager_new(node.node, convert(Cint, log_mode), manager.manager)
    return WmcManager(wmc)
end
function free(manager::WmcManager)
    SddLibrary.wmc_manager_free(manager.manager)
end
function set_literal_weight(node::SddNode, weight::Real, manager::WmcManager)
    literal = SddLibrary.sdd_node_literal(node.node)
    SddLibrary.wmc_set_literal_weight(literal, convert(SddLibrary.SddWmc, weight), manager.manager)
end
function propagate(manager::WmcManager)::SddLibrary.SddWmc
    return SddLibrary.wmc_propagate(manager.manager)
end
function zero(manager::WmcManager)::SddLibrary.SddWmc
    return SddLibrary.wmc_zero_weight(manager.manager)
end
function one(manager::WmcManager)::SddLibrary.SddWmc
    return SddLibrary.wmc_one_weight(manager.manager)
end
function weight(literal::Integer, manager::WmcManager)::SddLibrary.SddWmc
    return SddLibrary.wmc_literal_weight(convert(SddLibrary.SddLiteral, literal), manager.manager)
end
function derivative(literal::Integer, manager::WmcManager)::SddLibrary.SddWmc
    return SddLibrary.wmc_literal_derivative(convert(SddLibrary.SddLiteral, literal), manager.manager)
end
function probability(literal::Integer, manager::WmcManager)::SddLibrary.SddWmc
    return SddLibrary.wmc_literal_pr(convert(SddLibrary.SddLiteral, literal), manager.manager)
end

# CONVENIENCE METHODS
function conjoin(node1::SddNode, node2::SddNode)::SddNode
    node = SddLibrary.sdd_conjoin(node1.node, node2.node, node1.manager)
    return SddNode(node, node1.manager)
end
function disjoin(node1::SddNode, node2::SddNode)::SddNode
    node = SddLibrary.sdd_disjoin(node1.node, node2.node, node1.manager)
    return SddNode(node, node1.manager)
end
function negate(node::SddNode)::SddNode
    nodeptr = SddLibrary.sdd_negate(node.node, node.manager)
    return SddNode(nodeptr, node.manager)
end
function equiv(left::SddNode, right::SddNode)::SddNode
    return (!left | right) & (left | !right)
end
function model_count(node::SddNode; globally::Bool=false)::SddLibrary.SddModelCount
    if !globally
        return SddLibrary.sdd_model_count(node.node, node.manager)
    else
        return SddLibrary.sdd_global_model_count(node.node, node.manager)
    end
end
function ref(node::SddNode)::SddNode
    ref_node = SddLibrary.sdd_ref(node.node, node.manager)
    return SddNode(ref_node, node.manager)
end
function deref(node::SddNode)::SddNode
    ref_node = SddLibrary.sdd_deref(node.node, node.manager)
    return SddNode(ref_node, node.manager)
end

function dot(filename::String, structure::Union{VTree,SddNode})
    save_as_dot(filename, structure)
end
function wmc_manager(node::SddNode; log_mode::Bool=true)::WmcManager
    wmc = SddLibrary.wmc_manager_new(node.node, convert(Cint, log_mode), node.manager)
    return WmcManager(wmc)
end

Base.:&(node1::SddNode, node2::SddNode) = conjoin(node1, node2)
Base.:|(node1::SddNode, node2::SddNode) = disjoin(node1, node2)
Base.:~(node::SddNode) = negate(node)
↔(left::SddNode, right::SddNode) = equiv(left, right)

# FNF methods
@with_kw struct CompilerOptions
    vtree_search_mode::Int32 = -1
    post_search::Bool = false
    verbose::Bool = false
end

function read_cnf(filename::String, manager::SddManager; compiler_options=CompilerOptions())::SddNode
    cnf = read_cnf(filename)
    sdd_node = fnf_to_sdd(cnf, manager.manager, compiler_options)
    return SddNode(sdd_node, manager.manager)
end
function read_dnf(filename::String, manager::SddManager; compiler_options=CompilerOptions())::SddNode
    dnf = read_dnf(filename)
    sdd_node = fnf_to_sdd(dnf, manager.manager, compiler_options)
    return SddNode(sdd_node, manager.manager)
end

export ↔
end


__precompile__()

# HamNNNNclassifier reporting functions

"""
Print a report detailing the data file.
"""
function printdatafiledescription(dataFile)
	printheader()
	print_with_color(:blue, "Data File:\n"; bold=true)
	# print file path
	println(dataFile)
	# print file type
	format = getorangefileformat(dataFile)
	println("Orange file format: ", format, "\n")
	# get the data file contents
	codes, names, dt = readdatafile(dataFile)
	# @show codes names
	#for the class attribute, list each class and the number of cases, % of total, as well as the total
	printclassdescription(codes, names, dt)
	printattributedescriptions(codes, names, dt)
end

function printheader()
	# print date, time, software version, platform, file
	print("\n\n")
	f1 = @sprintf "%18s ; HamNNNNclassifier version %5s" Dates.format(now(), "yyyy-m-d HH:MM:SS") version
	print_with_color(:blue, f1, "\n"; bold=true)
	println("Julia version: ", VERSION)
	println("On a Mac ", Sys.cpu_info()[1].model, " '", ccall(:jl_get_cpu_name, Ref{String}, ()), "', \nRunning ", Sys.MACHINE)
	run(`sw_vers`) # shows os version
	println()
end

function printclassdescription(codes, names, table)
	#for the class attribute, list each class and the number of cases, % of total, as well as the total
	i, classesVector = extractclassattribute(table, codes)
	classAttributeName = names[i]
	println("Class Attribute: ", classAttributeName)
	casesNo = length(classesVector)
	# extract unique values
	# as it is easy to print out the strings for the levels of a categorical array, do the conversion.
	cv = categorical(classesVector)
	# levels is simply the array of uniques, for a categorical array
	levels = CategoricalArrays.levels(cv)
	# uniques works well as keys for getting values out of a countmap dict
	uniques = unique(cv)
	# create a dict of uniques and their counts
	cm = countmap(classesVector)
	# @show cm levels uniques
	print_with_color(:blue, "\nClass        Number of cases      Percent of total\n"; bold=true)
	foreach((x,y) -> print(rpad(y,20,),rpad(get(cm,x,""),20,),signif(get(cm,x,"")*100/casesNo,4),"\n"), uniques, levels)
	print_with_color(:bold, "total: ", casesNo, " cases\n")
end

function printattributedescriptions(codes, names, table)
	# for each attribute, print its name, type, number of uniques, number and % of missing values;
	# for continuous attributes, print the minimum, maximum, mean, and median
	# start with a header
	println()
	print_with_color(:blue, " Index   Label             Type   Missing      %   Uniques   Min   Max   Mean   Median    Unique Values\n"; bold=true)
	# initialize a counter to hold the total number of missing values
	missingTotal = 0
	# initialize an array for new attribute codes
	newCodes = Array{String}(length(names))
	for i in 1:length(names)
		# print the index and the attribute name
		name = rpad(names[i], 18, )
		print(lpad(i,6,),"   ", name)
		# take out null values to get type purity for type dispatching
		d = dropnull(table[i])
		# print the orange format code; if missing, print "C" for continuous (ie numeric)
		# and "D" for discrete (ie categorical) attributes
		typeCode = generateattributecode(d, codes[i])
		# convert "C" to "D" for integer attributes in range 0 to 9
		if typeCode == "C"
			typeCode = changeattributetype(d)
		end
		print(rpad(typeCode,7,))
		# add the new codes to the existing codes, in a newCodes array
		newCodes[i] = typeCode
		# print number and percent of missing values (nulls)
		missing = length(table[i]) - length(d)
		missingTotal += missing
		missingPerCent = roundpercentage(missing * 100 / length(table[i]),2)
		print(lpad(missing,7,), missingPerCent > 0.0 ? lpad(missingPerCent,7,) : "       ")
		# print number of unique values
		print(lpad(length(unique(d)),10,))
		# for continuous attributes, print the minimum, maximum, mean, and median; for discrete
		# attributes, print the number of unique values.
		if numericattribute(d) && typeCode == "C"
			((min, max),mean,median) = describecontinuousattribute(d)
			print(lpad(min,6,), lpad(max,6,), lpad(roundpercentage(mean,1),7,), lpad(roundpercentage(median,1),9,))
		else
			printuniquestring(d)
		end
		print("\n")
	end
	# print the total number of missing values and its percentage
	valuesTotal = prod(size(table))
	print_with_color(:bold, "Total missing values: ", missingTotal, " out of ", valuesTotal)
	print_with_color(:bold, " (", roundpercentage(missingTotal * 100 / valuesTotal,2), "%)\n\n")
	# print the counts for each attribute type and the total
	print_with_color(:blue, "Attribute Type   Count\n"; bold=true)
	# print(countmap(newCodes), "\n")
	for (key, value) in countmap(newCodes)
		println(rpad(key,14,), lpad(value,8,))
	end
	print_with_color(:bold, "Total count:        ", length(newCodes), "\n")
end

"""
Print a list of unique values for an attribute. If the list is too long, print just
its head and tail.
"""
function printuniquestring(d)
	uniquesNo, uniques = describediscreteattribute(d)
	print("                                ")
	# if uniquesNo is > 10, print just the first and last 5 values
	if uniquesNo > 10
		for i in 1:4
			print(uniques[i]," ")
		end
		print("... ")
		for i in uniquesNo - 3:uniquesNo
			print(uniques[i]," ")
		end
	else
		for i in 1:uniquesNo
			print(uniques[i]," ")
		end
	end
end

function printattributeranking(dataFile)
	labels, codes, table, D = generateattributeranking(dataFile)
	@show codes
	print_with_color(:blue, "Attributes in rank order of ability to separate classes:\n"; bold=true)
	print_with_color(:blue, "  Rank   Index   Label                   Type   RankValue   Optimized Slices\n"; bold=true)
	for i in indices(D,1)
		index = lpad(D[i,2], 7)
		label = rpad(labels[D[i,2]], 24)
		code = rpad(codes[D[i,2]], 4)
		rank = lpad(D[i,1], 12)
		println(lpad(i,7), index,"   ", label, code, rank)
	end
end


using EzXML
@testset "Variables" begin
    X = parsexml("""
                 <param name="x" type="real" d1="1" d2="1" local="false" dynamics="any" controlled="true" />
                 """)
    vars = Dict()
    SpaceExParser.add_variable!(vars, root(X))
    @test collect(keys(vars)) == [:x]
    @test vars[:x]["local"] == false
    @test vars[:x]["controlled"] == true
    @test vars[:x]["dynamics"] == "any"
    @test vars[:x]["id"] == 1
end


is_given_macro(::Any) = false
is_given_macro(e::Expr) = e.head == :macrocall && e.args[1] == Symbol("@given")

given_parameters(::Any) = []
function given_parameters(e::Expr)
    if e.head == :call && e.args[1] == :∈
        # The name and associated iterator generator.
        return [(e.args[2], e.args[3])]
    elseif e.head == :tuple || is_given_macro(e)
        return vcat(map(given_parameters, e.args)...)
    end

    error("Cannot parse given parameters from expression $(e)")
end

function settingexpr(blocks::Expr...)
    call = Expr(:call, Expr(:., :ParameterTests, :(:ParamTestSettings)))
    params = Expr(:parameters, [Expr(:kw, block.args...) for block in blocks]...)
    push!(call.args, params)
    return call
end

paramkeys(params::Vector) = vcat(map(paramkeys, params)...)
paramkeys(param::Symbol) = [param]
function paramkeys(param::Expr)
    param.head == :tuple || error("param keys must be a tuple")
    param.args
end

macro paramtest(name, blocks::Expr...)
    block = blocks[end]
    block.head == :block || error("paramtest requires a block expression")
    givenlines = filter(is_given_macro, block.args)
    length(givenlines) > 0 || error("paramtest must have a @given parameters")
    # Drop the given lines from our block, we don't need them.
    filter!((!)∘is_given_macro, block.args)
    givenparams = vcat(map(given_parameters, givenlines)...)
    setparams = paramkeys(map(first, givenparams))

    settings = settingexpr(blocks[1:end-1]...)
    testblock = quote
        @testset $name begin
            @testset ParameterTestSet begin
                ParameterTests.withsettings($settings) do
                    for $(Expr(:tuple, map(first, givenparams)...)) in $(Expr(:call, :zip, map(last, givenparams)...))
                        @testset SingleParameterTestSet begin
                            local ts = Test.get_testset()
                            $(map(p -> :(ts.parameters[$(QuoteNode(p))] = $p), setparams)...)
                            $block
                        end
                    end
                end
            end
        end
    end
    return esc(testblock)
end


"""
A Sweeps objects holds information
about the various parameters controlling
a density matrix renormalization group (DMRG)
or similar matrix product state (MPS) calculation.

For a Sweeps object `sw` the available
parameters are:
* `nsweep(sw)` -- the number of sweeps to do
* `maxdim(sw,n)` -- maximum MPS bond dimension for sweep n
* `mindim(sw,n)` -- minimum MPS bond dimension for sweep n
* `cutoff(sw,n)` -- truncation error cutoff for sweep n
* `noise(sw,n)` -- noise term coefficient for sweep n
"""
mutable struct Sweeps
  nsweep::Int
  maxdim::Vector{Int}
  cutoff::Vector{Float64}
  mindim::Vector{Int}
  noise::Vector{Float64}

  function Sweeps(nsw::Int)
    return new(nsw, fill(1, nsw), zeros(nsw), fill(1, nsw), zeros(nsw))
  end
end

"""
    Sweeps(d::AbstractMatrix)

    Sweeps(nsweep::Int, d::AbstractMatrix)

Make a sweeps object from a matrix of input values.
The first row should be strings that define which
variables are being set ("maxdim", "cutoff", "mindim",
and "noise").

If the number of sweeps are not specified, they
are determined from the size of the input matrix.

# Examples
```julia
julia> Sweeps([
         "maxdim" "mindim" "cutoff" "noise"
          50       10       1e-12    1E-7
          100      20       1e-12    1E-8
          200      20       1e-12    1E-10
          400      20       1e-12    0
          800      20       1e-12    1E-11
          800      20       1e-12    0
         ])
Sweeps
1 cutoff=1.0E-12, maxdim=50, mindim=10, noise=1.0E-07
2 cutoff=1.0E-12, maxdim=100, mindim=20, noise=1.0E-08
3 cutoff=1.0E-12, maxdim=200, mindim=20, noise=1.0E-10
4 cutoff=1.0E-12, maxdim=400, mindim=20, noise=0.0E+00
5 cutoff=1.0E-12, maxdim=800, mindim=20, noise=1.0E-11
6 cutoff=1.0E-12, maxdim=800, mindim=20, noise=0.0E+00
```
"""
function Sweeps(nsw::Int, d::AbstractMatrix)
  sw = Sweeps(nsw)
  vars = d[1, :]
  for (n, var) in enumerate(vars)
    inputs = d[2:end, n]
    if var == "maxdim"
      maxdim!(sw, inputs...)
    elseif var == "cutoff"
      cutoff!(sw, inputs...)
    elseif var == "mindim"
      mindim!(sw, inputs...)
    elseif var == "noise"
      noise!(sw, float.(inputs)...)
    else
      error("Sweeps object does not have the field $var")
    end
  end
  return sw
end

Sweeps(d::AbstractMatrix) = Sweeps(size(d, 1) - 1, d)

"""
    nsweep(sw::Sweeps)
    length(sw::Sweeps)

Obtain the number of sweeps parameterized
by this sweeps object.
"""
nsweep(sw::Sweeps)::Int = sw.nsweep

Base.length(sw::Sweeps)::Int = sw.nsweep

"""
    maxdim(sw::Sweeps,n::Int)

Maximum MPS bond dimension allowed by the
Sweeps object `sw` during sweep `n`
"""
maxdim(sw::Sweeps, n::Int)::Int = sw.maxdim[n]

"""
    mindim(sw::Sweeps,n::Int)

Minimum MPS bond dimension allowed by the
Sweeps object `sw` during sweep `n`
"""
mindim(sw::Sweeps, n::Int)::Int = sw.mindim[n]

"""
    cutoff(sw::Sweeps,n::Int)

Truncation error cutoff setting of the
Sweeps object `sw` during sweep `n`
"""
cutoff(sw::Sweeps, n::Int)::Float64 = sw.cutoff[n]

"""
    noise(sw::Sweeps,n::Int)

Noise term coefficient setting of the
Sweeps object `sw` during sweep `n`
"""
noise(sw::Sweeps, n::Int)::Float64 = sw.noise[n]

get_maxdims(sw::Sweeps) = sw.maxdim
get_mindims(sw::Sweeps) = sw.mindim
get_cutoffs(sw::Sweeps) = sw.cutoff
get_noises(sw::Sweeps) = sw.noise

"""
    maxdim!(sw::Sweeps,maxdims::Int...)

Set the maximum MPS bond dimension for each
sweep by providing up to `nsweep(sw)` values.
If fewer values are provided, the last value
is repeated for the remaining sweeps.
"""
function setmaxdim!(sw::Sweeps, maxdims::Int...)::Nothing
  mdims = collect(maxdims)
  for i in 1:nsweep(sw)
    sw.maxdim[i] = get(mdims, i, maxdims[end])
  end
end
maxdim!(sw::Sweeps, maxdims::Int...) = setmaxdim!(sw, maxdims...)

"""
    mindim!(sw::Sweeps,maxdims::Int...)

Set the minimum MPS bond dimension for each
sweep by providing up to `nsweep(sw)` values.
If fewer values are provided, the last value
is repeated for the remaining sweeps.
"""
function setmindim!(sw::Sweeps, mindims::Int...)::Nothing
  mdims = collect(mindims)
  for i in 1:nsweep(sw)
    sw.mindim[i] = get(mdims, i, mindims[end])
  end
end
mindim!(sw::Sweeps, mindims::Int...) = setmindim!(sw, mindims...)

"""
    cutoff!(sw::Sweeps,maxdims::Int...)

Set the MPS truncation error used for each
sweep by providing up to `nsweep(sw)` values.
If fewer values are provided, the last value
is repeated for the remaining sweeps.
"""
function setcutoff!(sw::Sweeps, cutoffs::Real...)::Nothing
  cuts = collect(cutoffs)
  for i in 1:nsweep(sw)
    sw.cutoff[i] = get(cuts, i, cutoffs[end])
  end
end
cutoff!(sw::Sweeps, cutoffs::Real...) = setcutoff!(sw, cutoffs...)

"""
    noise!(sw::Sweeps,maxdims::Int...)

Set the noise-term coefficient used for each
sweep by providing up to `nsweep(sw)` values.
If fewer values are provided, the last value
is repeated for the remaining sweeps.
"""
function setnoise!(sw::Sweeps, noises::Real...)::Nothing
  nvals = collect(noises)
  for i in 1:nsweep(sw)
    sw.noise[i] = get(nvals, i, noises[end])
  end
end
noise!(sw::Sweeps, noises::Real...) = setnoise!(sw, noises...)

function Base.show(io::IO, sw::Sweeps)
  println(io, "Sweeps")
  for n in 1:nsweep(sw)
    @printf(
      io,
      "%d cutoff=%.1E, maxdim=%d, mindim=%d, noise=%.1E\n",
      n,
      cutoff(sw, n),
      maxdim(sw, n),
      mindim(sw, n),
      noise(sw, n)
    )
  end
end

struct SweepNext
  N::Int
  ncenter::Int
end

"""
    sweepnext(N::Int; ncenter::Int=2)

Returns an iterable object that evaluates
to tuples of the form `(b,ha)` where `b`
is the bond number and `ha` is the half-sweep
number. Takes an optional named argument
`ncenter` for use with an n-site MPS or DMRG
algorithm, with a default of 2-site.
"""
function sweepnext(N::Int; ncenter::Int=2)::SweepNext
  if ncenter < 0
    error("ncenter must be non-negative")
  end
  return SweepNext(N, ncenter)
end

function Base.iterate(sn::SweepNext, state=(0, 1))
  b, ha = state
  if ha == 1
    inc = 1
    bstop = sn.N - sn.ncenter + 2
  else
    inc = -1
    bstop = 0
  end
  new_b = b + inc
  new_ha = ha
  done = false
  if new_b == bstop
    new_b -= inc
    new_ha += 1
    if ha == 2
      return nothing
    end
  end
  return ((new_b, new_ha), (new_b, new_ha))
end


RANDOM_SEED = 809123
rng = MersenneTwister(RANDOM_SEED)

@testset "Particle" begin
    @test_throws AssertionError Particle(zeros(3), zeros(2), zeros(1), 0.0, 1.0)

    # * Test type promotion
    @test let
        p = Particle(15, 20.0, 10, rng)
        eltype(p.x) ≡ eltype(p.v)
    end
end

@testset "Population" begin
    @test let
        pops = typeof(Population(15, 20, -10.0, 10.0, rng))
        actual_type = typeof(Vector{Particle}(undef, 15))
        pops ≡ actual_type
    end

    # * Test random seeds
    @test let
        pops = typeof(Population(15, 20, -10.0, 10.0, rng))
        actual_type = typeof(Vector{Particle}(undef, 15))
        pops ≡ actual_type
    end

    # * Test multiple ranges
    @test let
        range_a = SVector(-10.0, 10.0)
        range_b = SVector(-2.5, 2.0)
        pops = Population(20, [range_a, range_b], rng)
        type_pops = typeof(pops)
        actual_type = typeof(Vector{Particle}(undef, 2))
        type_pops ≡ actual_type
    end
    @test_throws AssertionError Population(15, -1, 1.0, 1.0, rng)
    @test_throws AssertionError Population(0, 5, 1.0, 1.0, rng)
    @test_throws AssertionError Population(-1, 5, 1.0, 1.0, rng)

    @test let
        range_a = SVector(-10.0, 10.0)
        range_b = SVector(-2.5, 2.0)
        pops = Population(20, [range_a, range_b], rng)
        type_pops = typeof(pops)
        actual_type = typeof(Vector{Particle}(undef, 2))
        type_pops ≡ actual_type
    end
end


module Trie1

export PrefixTree, NodePtr, make_prefix_tree, lookup

struct Key
    row_no::Int32
    offset::Int32
    ch::Char
end

struct NodePtr{T}
    row_no::Int32
    is_final::Bool
    payload::Union{Nothing,T}
end

struct PrefixTree{T}
    tab::Dict{Key,NodePtr{T}}
end

function make_prefix_tree(sorted_word_with_payload::Array{Tuple{String,T},1})::PrefixTree{T} where {T}
    tab = Dict{Key,NodePtr{T}}()
    for i in 1:length(sorted_word_with_payload)
        word, payload = sorted_word_with_payload[i]
        row_no = 1
        j = 1
        for word_idx in eachindex(word)
            ch = word[word_idx]
            key = Key(row_no, j, ch)
            is_final = length(word) == j
            if haskey(tab, key)
                child_id, _, _ = tab[key]
                row_no = child_id
            else
                tab[key] = NodePtr{T}(Int32(i), is_final, is_final ? payload : nothing)
                 row_no = i
            end
            j += 1
        end
    end
    return PrefixTree(tab)
end

function lookup(tree::PrefixTree, row_no, offset, ch::Char)::Union{Nothing,NodePtr}
    key = Key(Int32(row_no), Int32(offset), ch)
    if haskey(tree.tab, key)
        return tree.tab[key]
    else
        return nothing
    end
end

end


module CircuitComponentRounding

import ESeriesRounding: E3, E6, E12, E24, E48, E96, E192
import ESeriesRounding: determine_E

export E3, E6, E12, E24, E48, E96, E192
export determine_E

end


w = 500
h = 300
a = Image(w, h)

purple = Color(0xff, 0, 0xff)
green = Color(0, 0xff, 0)
white = Color(0xff, 0xff, 0xff)

fill!(a, green)
for i in 20:220, j in 10:100
    splat!(a, i, j, purple)
end
for i in 180:400, j in 80:200
    splat!(a, i, j, white)
end

fn = "bitmap_write.ppm"
writeppm(fn, a)


module RegionCovarianceDescriptor

using StaticArrays

include("descriptor.jl")

export RegionCovariance,
       covariance_matrix,
       covariance_matrix!

end # module


using GPUifyLoops
using Test
using InteractiveUtils

function kernel(A)
    @loop for i in (1:size(A,1);
                    threadIdx().x)
        A[i] = 2*A[i]
    end
    @synchronize
end

f2(x) = sin(x)
f3(x) = 1 + f2(x)
f4(x) = x^1.2
f5(x) = x^3

function kernel2!(A, B, h)
    @inbounds @loop for i in (1:size(A,1); threadIdx().x)
        A[i] = h(B[i])
    end
    nothing
end

@testset "Array" begin
    data = Array{Float32}(undef, 1024)
    kernel(data)
end

@static if Base.find_package("CuArrays") !== nothing
    using CuArrays
    using CUDAnative

    function kernel(A::CuArray)
        @launch CUDA() threads=length(A) kernel(A)
    end

    @testset "CuArray" begin
        data = CuArray{Float32}(undef, 1024)
        kernel(data)
    end

    @testset "contextualize" begin
        f(x) = 2*x
        g(x) = GPUifyLoops.contextualize(f)(x)
        @test g(3.0) == 6.0
        f(x) = 3*x

        if VERSION < v"1.3"
            @test_broken g(3.0) == 9.0
        else
            @test g(3.0) == 9.0
        end

        f1(x) = (sin(1.0 + x); return nothing)
        g1(x) = GPUifyLoops.contextualize(f1)(x)
        asm = sprint(io->CUDAnative.code_llvm(io, g1, Tuple{Float64}, kernel=true,
                                              optimize=false, dump_module=true))
        @test occursin(r"call .* double @__nv_sin", asm)
        @test occursin("fadd contract double", asm)

        @testset "don't overdub intrinsics" begin
            global simple_kernel, kernel
            simple_kernel(A, x) = (A[1] = 1 + x; return nothing)
            kernel(A, x) = GPUifyLoops.contextualize(simple_kernel)(A, x)
            CI, ret = CUDAnative.code_typed(kernel, Tuple{CUDAnative.CuDeviceArray{Int64,1, CUDAnative.AS.Global}, Int64}, debuginfo=:source)[1]

            intrinsics = findall(CI.code) do stmt
                if Base.Meta.isexpr(stmt, :call)
                    f = stmt.args[1]
                    if f isa GlobalRef
                        f = getfield(f.mod, f.name)
                        return f isa Core.IntrinsicFunction || f isa Core.Builtin
                    end
                end
                return false
            end

            for i in intrinsics
                lineinfo = CI.linetable[CI.codelocs[i]]
                @test !(lineinfo.method === :call ||
                        lineinfo.file === Symbol("context.jl"))
            end
        end

        begin
            global kernel2!
            data = rand(Float32, 1024)
            fdata = similar(data)

            kernel2!(fdata, data, f3)
            @test f3.(data) ≈ fdata

            kernel2!(fdata, data, f4)
            @test f4.(data) ≈ fdata

            kernel2!(fdata, data, f5)
            @test f5.(data) ≈ fdata

            function kernel2!(A::CuArray, B::CuArray, f)
                @launch CUDA() threads=length(A) kernel2!(A, B, f)
            end

            cudata = CuArray(data)
            cufdata = similar(cudata)

            kernel2!(cufdata, cudata, f3)
            @test f3.(data) ≈ cufdata

            kernel2!(cufdata, cudata, f4)
            @test f4.(data) ≈ cufdata

            kernel2!(cufdata, cudata, f5)
            @test f5.(data) ≈ cufdata
        end
    end

    @testset "read only load" begin
        global read_only_kernel, con_read_only_kernel
        read_only_kernel(A, B) = (B[1] = read_only_load(A, 1); return nothing)
        con_read_only_kernel(A, B) = GPUifyLoops.contextualize(read_only_kernel)(A, B)
        tt = Tuple{CUDAnative.CuDeviceArray{Float32, 1, CUDAnative.AS.Global},
                   CUDAnative.CuDeviceArray{Float32, 1, CUDAnative.AS.Global}}

        asm = sprint(io->CUDAnative.code_llvm(io, con_read_only_kernel, tt, kernel=true,
                                              optimize=false, dump_module=true))

        @test occursin("llvm.nvvm.ldg.global.f.f32", asm)

        A = rand(Float32, 1024)
        B = similar(A)
        @launch CPU() threads=length(A) read_only_kernel(A, B)
        @test A[1] == B[1]

        cuA = CuArray(A)
        cuB = similar(cuA)

        @launch CUDA() threads=length(cuA) read_only_kernel(cuA, cuB)
        @test A[1] == Array(cuB)[1]
    end
end

function kernel3!(A)
    s1 = @shmem eltype(A) (1024,)
    s2 = @shmem eltype(A) (1024,)

    @loop for i in (1:size(A,1); threadIdx().x)
        s1[i] = 2*A[i]
        s2[i] = 3*A[i]
    end
    @synchronize
    @loop for i in (1:size(A,1); threadIdx().x)
        A[i] = s1[i]
    end
    nothing
end

let
    function ker1!(::Val{Nq}) where Nq
      s_x = @shmem Float32 Nq
    end

    CI, rt = @code_typed ker1!(Val(10))
    @test Base.isconcretetype(rt)
end

@testset "shared memory" begin
    data = rand(Float32, 1024)
    cpudata = copy(data)

    @launch CPU() kernel3!(cpudata)
    @test cpudata ≈ 2 .* data

    @static if Base.find_package("CuArrays") !== nothing
        using CuArrays
        using CUDAnative

        cudata = CuArray(data)
        @launch CUDA() threads=length(cudata) kernel3!(cudata)
        @test Array(cudata) ≈ 2 .* data
    end
end

# Scratch arrays

function kernel_scratch(A, ::Val{N}) where N
    a = @scratch eltype(A) (N, N) 2
    b = @scratch eltype(A) (2, N, N) 2
    @loop for j in (1:size(A,2); threadIdx().y)
        @loop for i in (1:size(A,1); threadIdx().x)
            a[i, j] = A[i, j]
            b[1, i, j] = -A[i, j]
            b[2, i, j] = 2a[i, j]
        end
    end
end

function f1()
    A = @scratch Int64 (12, 3) 2
    @test A.data isa GPUifyLoops.MArray
    @test size(A.data) == (1,)
end

function f2()
    A = @scratch Int64 (12, 3) 1
    @test A.data isa GPUifyLoops.MArray
end

function f3()
    A = @scratch Int64 (12, 3) 1
    @test A isa GPUifyLoops.MArray
end

@testset "Scratch Arrays" begin
    contextualize(f1)()
    contextualize(f2)()
    f3()
    N = 10
    A = rand(N, N)
    @launch CPU() kernel_scratch(A, Val(N))

    @static if Base.find_package("CuArrays") !== nothing
        using CuArrays

        d_A = CuArray(A)
        @launch CUDA() kernel_scratch(d_A, Val(N))
    end
end

@testset "Loopinfo" begin
    # Right now test that we don't break things
    # Should probably test that codegen is correct.
    f(N) = @unroll 2 for i in 1:N
        @show i
    end
    f(10)

    f() = @unroll for i in 1:10
        @show i
    end
    f()
end

using StaticArrays
function kernel_MArray!(A)
  l_F = MArray{Tuple{3, 3}, eltype(A)}(undef)
  @inbounds for j = 1:3, i = 1:3
    l_F[i, j] = A[i, j]
  end
  nothing
end
function kernel_similar_MArray!(A)
  l_F = MArray{Tuple{3, 3}, eltype(A)}(undef)
  l_G = similar(l_F, Size(2,2))
  @inbounds for j = 1:2, i = 1:2
    l_G[i, j] = A[i, j]
  end
  nothing
end

@testset "StaticArrays" begin
  @static if Base.find_package("CuArrays") !== nothing
    using CuArrays
    using CUDAnative

    A = CuArray(rand(3,3))
    @launch CUDA() threads=(3,3) kernel_MArray!(A)
    @launch CUDA() threads=(3,3) kernel_similar_MArray!(A)
  end

  A = rand(3,3)
  @launch CPU() threads=(3,3) kernel_MArray!(A)
  @launch CPU() threads=(3,3) kernel_similar_MArray!(A)
end

# For the next three test we check that the _apply got inlined correctly
Base.@pure pure_f1(x, y) = x + y
let
    CI, rt = @code_typed GPUifyLoops.Cassette.overdub(GPUifyLoops.ctx, pure_f1, 1, 2)
    expr = CI.code[end-1]
    @test expr.head === :call || expr.head === :invoke
    @test expr.args[1] === GlobalRef(Base, :add_int)
end

Base.@pure pure_f2(x, ys...) = x + sum(ys)
let
    CI, rt = @code_typed GPUifyLoops.Cassette.overdub(GPUifyLoops.ctx, pure_f2, 1, 2, 3, 4)
    expr = CI.code[end-1]
    @test expr.head === :call || expr.head === :invoke
    @test expr.args[1] === GlobalRef(Base, :add_int)
end

Base.@pure pure_f3(ys...) = sum(ys)
let
    CI, rt = @code_typed GPUifyLoops.Cassette.overdub(GPUifyLoops.ctx, pure_f3, 1, 2, 3, 4)
    expr = CI.code[end-1]
    @test expr.head === :call || expr.head === :invoke
    @test expr.args[1] === GlobalRef(Base, :add_int)
end


module LatticeSites

using StaticArrays, Random

include("spins.jl")
include("space.jl")
include("flip.jl")
include("lattices.jl")

end


# ClimaCore hacks
Base.isless(zp1::CC.Geometry.ZPoint, zp2::CC.Geometry.ZPoint) = Base.isless(zp1.z, zp2.z)
Base.isless(zp::CC.Geometry.ZPoint, val::Number) = Base.isless(zp.z, val)
Base.isless(val::Number, zp::CC.Geometry.ZPoint) = Base.isless(val, zp.z)

Base.:+(zp::CC.Geometry.ZPoint, val) = Base.:+(zp.z, val)
Base.:+(val, zp::CC.Geometry.ZPoint) = Base.:+(val, zp.z)
Base.:/(val, zp::CC.Geometry.ZPoint) = Base.:/(val, zp.z)
Base.:+(zp1::CC.Geometry.ZPoint, zp2::CC.Geometry.ZPoint) = Base.:+(zp1.z, zp2.z)

Base.:*(zp1::CC.Geometry.ZPoint, zp2::CC.Geometry.ZPoint) = Base.:*(zp1.z, zp2.z)

Base.log(zp::CC.Geometry.ZPoint) = Base.log(zp.z)
Base.:-(zp::CC.Geometry.ZPoint) = Base.:-(zp.z)
Base.:-(zp::CC.Geometry.ZPoint, val) = Base.:-(zp.z, val)
Base.:-(val, zp::CC.Geometry.ZPoint) = Base.:-(val, zp.z)
Base.:-(zp1::CC.Geometry.ZPoint, zp2::CC.Geometry.ZPoint) = Base.:-(zp1.z, zp2.z)

Base.convert(::Type{Float64}, zp::CC.Geometry.ZPoint) = zp.z

struct Grid{FT, CS, FS, SC, SF}
    zmin::FT
    zmax::FT
    Δz::FT
    Δzi::FT
    nz::Int
    cs::CS
    fs::FS
    zc::SC
    zf::SF
    function Grid(Δz::FT, nz::Int) where {FT <: AbstractFloat}
        z₀, z₁ = FT(0), FT(nz * Δz)

        domain = CC.Domains.IntervalDomain(
            CC.Geometry.ZPoint{FT}(z₀),
            CC.Geometry.ZPoint{FT}(z₁),
            boundary_tags = (:bottom, :top),
        )

        mesh = CC.Meshes.IntervalMesh(domain, nelems = nz)

        cs = CC.Spaces.CenterFiniteDifferenceSpace(mesh)
        fs = CC.Spaces.FaceFiniteDifferenceSpace(cs)
        zc = CC.Fields.coordinate_field(cs)
        zf = CC.Fields.coordinate_field(fs)

        #Set the inverse grid spacing
        Δzi = 1 / Δz

        zmin = minimum(parent(zf))
        zmax = maximum(parent(zf))
        CS = typeof(cs)
        FS = typeof(fs)
        SC = typeof(zc)
        SF = typeof(zf)
        return new{FT, CS, FS, SC, SF}(zmin, zmax, Δz, Δzi, nz, cs, fs, zc, zf)
    end
end

# Index of the first interior cell above the surface
kc_surface(grid::Grid) = Cent(1)
kf_surface(grid::Grid) = CCO.PlusHalf(1)
kc_top_of_atmos(grid::Grid) = Cent(grid.nz)
kf_top_of_atmos(grid::Grid) = CCO.PlusHalf(grid.nz + 1)

is_surface_center(grid::Grid, k) = k == kc_surface(grid)
is_toa_center(grid::Grid, k) = k == kc_top_of_atmos(grid)
is_surface_face(grid::Grid, k) = k == kf_surface(grid)
is_toa_face(grid::Grid, k) = k == kf_top_of_atmos(grid)

zc_surface(grid::Grid) = grid.zc[kc_surface(grid)]
zf_surface(grid::Grid) = grid.zf[kf_surface(grid)]
zc_toa(grid::Grid) = grid.zc[kc_top_of_atmos(grid)]
zf_toa(grid::Grid) = grid.zf[kf_top_of_atmos(grid)]

real_center_indices(grid::Grid) = CenterIndices(grid)
real_face_indices(grid::Grid) = FaceIndices(grid)

struct FaceIndices{Nstart, Nstop, G}
    grid::G
    function FaceIndices(grid::G) where {G <: Grid}
        Nstart, Nstop = kf_surface(grid).i, kf_top_of_atmos(grid).i
        new{Nstart, Nstop, G}(grid)
    end
end

struct CenterIndices{Nstart, Nstop, G}
    grid::G
    function CenterIndices(grid::G) where {G <: Grid}
        Nstart, Nstop = kc_surface(grid).i, kc_top_of_atmos(grid).i
        new{Nstart, Nstop, G}(grid)
    end
end

Base.keys(ci::CenterIndices) = 1:length(ci)
Base.keys(fi::FaceIndices) = 1:length(fi)

n_start(::CenterIndices{Nstart}) where {Nstart} = Nstart
n_start(::FaceIndices{Nstart}) where {Nstart} = Nstart
n_stop(::CenterIndices{Nstart, Nstop}) where {Nstart, Nstop} = Nstop
n_stop(::FaceIndices{Nstart, Nstop}) where {Nstart, Nstop} = Nstop

Base.getindex(ci::CenterIndices, i::Int) = Cent(Base.getindex(n_start(ci):n_stop(ci), i))
Base.getindex(fi::FaceIndices, i::Int) = CCO.PlusHalf(Base.getindex(n_start(fi):n_stop(fi), i))

Base.length(::FaceIndices{Nstart, Nstop}) where {Nstart, Nstop} = Nstop - Nstart + 1
Base.length(::CenterIndices{Nstart, Nstop}) where {Nstart, Nstop} = Nstop - Nstart + 1

Base.iterate(fi::CenterIndices{Nstart, Nstop}, state = Nstart) where {Nstart, Nstop} =
    state > Nstop ? nothing : (Cent(state), state + 1)

Base.iterate(fi::FaceIndices{Nstart, Nstop}, state = Nstart) where {Nstart, Nstop} =
    state > Nstop ? nothing : (CCO.PlusHalf(state), state + 1)

Base.iterate(fi::Base.Iterators.Reverse{T}, state = Nstop) where {Nstart, Nstop, T <: CenterIndices{Nstart, Nstop}} =
    state < Nstart ? nothing : (Cent(state), state - 1)

Base.iterate(fi::Base.Iterators.Reverse{T}, state = Nstop) where {Nstart, Nstop, T <: FaceIndices{Nstart, Nstop}} =
    state < Nstart ? nothing : (CCO.PlusHalf(state), state - 1)

face_space(grid::Grid) = grid.fs
center_space(grid::Grid) = grid.cs

Base.eltype(::Grid{FT}) where {FT} = FT


function add_surface_fluxes!(ds)
    Qθ = ds.metadata["temperature_flux"]

    T = ds["T"]
    wT = ds["wT"]

    Nz = size(T, 3)
    wT[:, :, Nz+1, :] .= Qθ

    return ds
end


#specific stuff for su2+higgs (fundamental and adjoint)


using ArchGDAL
using Test
include("../src/hilbert.jl")

coarse_geo = [-180.0, 0.0083333333333333, 0.0, 89.99999999999929, 0.0, -0.0083333333333333]
coarse_width_height = (43200, 21600)
fine_geo = [29.5734769457, 0.00027777777999999997, 0.0, 4.22813426181, 0.0, -0.00027777778]
fine_width_height = (19536, 20540)

origin, transform = geo_to_transform(coarse_geo)

xy1 = geo_to_xy_corner(origin, transform, 1, 1)
@test xy1[1] ≈ -180
@test xy1[2] ≈ 90
xy2 = geo_to_xy_corner(origin, transform, coarse_width_height[1] + 1, coarse_width_height[2] + 1)
@test xy2[1] ≈ 180
@test xy2[2] ≈ -90

xy3 = geo_to_xy_corner(origin, transform, 24, 36)
xy4 = geo_to_xy_center(origin, transform, 24, 36)
@test xy4[1] ≈ xy3[1] + coarse_geo[2] / 2
@test xy4[2] ≈ xy3[2] + coarse_geo[6] / 2 # Note that the offset is negative, -0.008333.

coarse_pg = PixelGrid((1, 1), coarse_width_height, geo_to_transform(coarse_geo)...)
xyb1 = xy_bounds(coarse_pg)
@test xyb1[1][1] ≈ -180
@test xyb1[1][2] ≈ 90
@test xyb1[2][1] ≈ 180
@test xyb1[2][2] ≈ -90

xy5 = geo_to_xy_center(origin, transform, 24, 36)
ij1 = pixel_containing(origin, transform, xy5)
@test ij1[1] == 24
@test ij1[2] == 36

xyb2 = xy_bounds(coarse_pg)
# Nudge the corner just a hair so it isn't right on the boundary.
xyb3 = ([xyb2[1][1], xyb2[1][2]], [xyb2[2][1] - 1e-5, xyb2[2][2] + 1e-5])
ij_corners = ij_cover_rect(origin, transform, xyb3)
@test ij_corners[1][1] == 1
@test ij_corners[1][2] == 1
@test ij_corners[2][1] == coarse_width_height[1]
@test ij_corners[2][1] == coarse_width_height[1]

bci_trial = [
   (([1, 1], [10, 10]), (200, 200), ((1, 1), (1, 1))),
   (((1, 1), (200, 200)), (200, 200), ((1, 1), (1, 1))),
   (((1, 1), (201, 250)), (200, 300), ((1, 1), (2, 1))),
   (((1, 1), (200, 301)), (200, 300), ((1, 1), (1, 2))),
   (((204, 1), (250, 601)), (200, 300), ((2, 1), (2, 3))),
   (((5, 50), (5, 50)), (2, 10), ((3, 5), (3, 5))),
   (((4, 50), (5, 50)), (2, 10), ((2, 5), (3, 5))),
   (((5, 40), (5, 50)), (2, 10), ((3, 4), (3, 5))),
   (((5, 50), (6, 50)), (2, 10), ((3, 5), (3, 5))),
   (((5, 50), (7, 50)), (2, 10), ((3, 5), (4, 5))),
]
for (ij_rect, bs, result) in bci_trial[8:8]
   ij = (Int.(ij_rect[1]), Int.(ij_rect[2]))
   bl1 = block_cover_ij_rect(ij, Int.(bs))
   @test bl1 == result
end

rectish = [
   ((1, 1), ((0, 2), (2, 0)), true),
   ((3, 1), ((0, 4), (2, 0)), false),
   ((1, 3), ((0, 4), (2, 0)), true),
   ((-1, 1), ((0, 4), (2, 0)), false),
   ((1, -1), ((0, 4), (2, 0)), false),
   ((-1, -1), ((0, 4), (2, 0)), false),
   ((3, 5), ((0, 4), (2, 0)), false),
   ((1.5, 3.5), ((1, 4), (2, 3)), true),
   ((.5, 3.5), ((1, 4), (2, 3)), false),
   ((1.5, 2.5), ((1, 4), (2, 3)), false),
]
for (pt, rect, result) in rectish
   @test pointinrect(pt, rect) == result
end

boundsrect = ([-6.0, 12.0], [-5.0, 11.0])
pt = [-4.9, 11.9]
@test !pointinrect(pt, boundsrect)

# Here's our stub setup.
# Coarse grid from ul=(-10, 16) to lr=(10, 0) in xy
# Give it w=20, h=16.
# Fine grid from ul=(-5, 12) to lr=(5, 4) in xy
# Give it w=10*5=50, h=8*5=40, so 5x the resolution of the coarse.
function make_coarse(A)
   StubBand{Int32}(
      A,
      [6, 2],  # blocksize
      Float64[-10, 16] , # origin
      Float64[1 0.0; 0.0 -1],  # pixel size
   )
end
coarse_band = make_coarse(fill(Int32(200), 20, 16))
function make_fine(A)
   StubBand{Float64}(
      A,
      [5, 3],  # blocksize
      Float64[-5, 12] , # origin
      Float64[.2 0.0; 0.0 -.2],  # pixel size
   )
end
fine_band = make_fine(fill(2.5, 50, 40))

coarse_pg = pixelgrid(coarse_band)
fine_pg = pixelgrid(fine_band)
# This should be the center of the pixel.
pc1 = pixel_containing(coarse_pg.origin, coarse_pg.transform, [-4.5, 11.5])
@test pc1 == [6, 5]
pc2 = pixel_containing(coarse_pg.origin, coarse_pg.transform, [-4.001, 11.001])
@test pc2 == [6, 5]
r1 = ij_cover_rect(coarse_pg.origin, coarse_pg.transform, ([-5, 12], [-4, 11]))
@test r1[1] == [6, 5]
@test r1[2][1] <= 7  # The cover rect can bring in a neighboring pixel.
@test r1[2][2] <= 6
r2 = ij_cover_rect(coarse_pg.origin, coarse_pg.transform, ([-4.999, 11.99], [4.999, 4.01]))
@test r2 == ([6, 5], [15, 12])
large_crop = crop_to(coarse_pg, xy_bounds(fine_pg))
@test large_crop.ul == (6, 5)

sb1 = load_single_block(coarse_band, coarse_pg, (1, 1))
sb2 = load_single_block(coarse_band, coarse_pg, (3, 3))
@test sb2.A[13, 5] == 200
# This is past the last block in x. There are 4 blocks.
@test_throws AssertionError sb3 = load_single_block(coarse_band, coarse_pg, (5, 3))

# Create an array with unique values we can check.
AA = Int32[encode_hilbert(x[1], x[2]) for x in CartesianIndices((20,16))]
copya = copy(AA)
cb2 = make_coarse(AA)
dg1 = load_data_grid(cb2, coarse_pg)
@test size(dg1.A) == (6 * 4, 16)  # Must be integral blocks to cover the domain.
@test dg1.A == copya
tune_band(fine_band, coarse_band, typemax(Int64))
@test fine_band.A[10, 10] ≈ 200 / 25

# Now try some nonzero structure.
A2 = fill(-floatmax(Float64), 50, 40)
fb2 = make_fine(A2)
tune_band(fb2, coarse_band, typemax(Int64))
@test all(fb2.A .== -floatmax(Float64))

# Now try some nonzero structure.
A3 = fill(-floatmax(Float64), 50, 40)
A3[1:5, 1:5] .= 1  # first 1x1 all filled.
A3[3:3, 7:7] .= 1  # Only one nonzero in this 1x1.
A3[6:7, 6:7] .= 1  # Four nonzero here.
fb3 = make_fine(A3)
@test sum(fb3.A .> 0) == 25 + 1 + 4
tune_band(fb3, coarse_band, 2)

@test all(fb3.A[1:5, 1:5] .≈ 200 / 25)
@test all(fb3.A[3:3, 7:7] .≈ 100)  # This will hit the max.
@test all(fb3.A[6:7, 6:7].≈ 200 / 4)

## A live data test.
hrsl_path = "~/data/inputs/HRSL/uganda2018/hrsl_uga_pop.tif"
landscan_path = "~/data/inputs/landscan/LandScan Global 2018/lspop2018/w001001.adf"
hrsl_exists = isfile(expanduser(hrsl_path))
landscan_exists = isfile(expanduser(landscan_path))

if hrsl_exists && landscan_exists
   out_path = "hrsl_ls_adjusted.tif"
   if ispath(out_path)
      rm(out_path)
   end
   assignweights(hrsl_path, landscan_path, out_path; max_pixels = 1)
   @test ispath(out_path)
   @test stat(out_path).size > 1210320848
else
   println("Could not find HRSL or landscan")
   println("$(hrsl_path)=$(hrsl_exists)")
   println("$(landscan_path)=$(landscan_exists)")
end


"""
	Box3d(p1::Point3d,p2::Point3d)

Mutable `struct`, with two fields `p1::Point3d` and `p2::Point3d`.
Two constructors `Box3d()` and `Box3d(p1::Point3d,p2::Point3d)`
# Example
```

```
"""
mutable struct Box3d
	p1::Point3d
	p2::Point3d

	# constyructor
	function Box3d()
		new(Point3d(0,0,0),Point3d(0,0,0))
	end

	# constructor
	function Box3d(p1::Point3d,p2::Point3d)
		new(p1,p2)
	end

end

Base.:(==)(a::Box3d, b::Box3d) = a.p1 == b.p1 && a.p2 == b.p2

"""
	invalidBox()

Returns an object of type `ViewerGL.Box3d`.
# Example
```
julia> invalidBox().p1
3-element StaticArrays.MArray{Tuple{3},Float64,1,3}:
 Inf
 Inf
 Inf

 julia> invalidBox().p2
 3-element StaticArrays.MArray{Tuple{3},Float64,1,3}:
  -Inf
  -Inf
  -Inf
```
"""
function invalidBox()
	m,M=typemin(Float64),typemax(Float64)
	return Box3d(Point3d(M,M,M),Point3d(m,m,m))
end

"""
	addPoint(box::GL.Box3d,point::GL.Point3d)

Update a mutable `Box3d`.
# Example
```
julia> box = GL.addPoint(GL.invalidBox(), GL.Point3d(1.0,2.0,0.0))
ViewerGL.Box3d([1.0, 2.0, 0.0], [1.0, 2.0, 0.0])
```
"""
function addPoint(box::Box3d,point::Point3d)
	for i in 1:3
		box.p1[i]=min(box.p1[i],point[i])
		box.p2[i]=max(box.p2[i],point[i])
	end
	return box
end

"""
	getPoints(box::Box3d)

Return the 8 vertices of an object of `Box3d` type.
# Example
```
julia> box = GL.getPoints(GL.Box3d(GL.Point3d(0,0,0),GL.Point3d(1,1,1)))
8-element Array{StaticArrays.MArray{Tuple{3},Float64,1,3},1}:
 [0.0, 0.0, 0.0]
 [1.0, 0.0, 0.0]
 [1.0, 1.0, 0.0]
 [0.0, 1.0, 0.0]
 [0.0, 0.0, 1.0]
 [1.0, 0.0, 1.0]
 [1.0, 1.0, 1.0]
 [0.0, 1.0, 1.0]
```
"""
function getPoints(box::Box3d)
	return [
		Point3d(box.p1[1],box.p1[2],box.p1[3]),
		Point3d(box.p2[1],box.p1[2],box.p1[3]),
		Point3d(box.p2[1],box.p2[2],box.p1[3]),
		Point3d(box.p1[1],box.p2[2],box.p1[3]),
		Point3d(box.p1[1],box.p1[2],box.p2[3]),
		Point3d(box.p2[1],box.p1[2],box.p2[3]),
		Point3d(box.p2[1],box.p2[2],box.p2[3]),
		Point3d(box.p1[1],box.p2[2],box.p2[3])
	]
end


using Gadfly
using CSV
using DataFrames
using Random
using Dates
import Cairo, Fontconfig

filepath = ARGS[1]
data = CSV.read(filepath, DataFrame)

conns = unique(data[!, :connection])

inds = randperm(length(conns))[1:5]

@show someconns = conns[inds]

someconns = [6474, 2264, 2241, 458, 2372]

filter!(row -> row[:connection] in someconns, data)

p = plot(
	data,
	x=:time,
	y=:flow,
	color=:connection,
	Geom.line,
	Guide.xticks(ticks=[DateTime(2021), DateTime(2026), DateTime(2031)]),
	Guide.xlabel("Year"),
	Guide.ylabel("Power flow (MWh)")
)
img = PNG("conn_flow.png", 6inch, 4inch)
draw(img, p)


"""
    abstract type AbstractChainElement{S<:AbstractCell, F}

Abstract type for representing an element of a chain. Create subtypes of this if there is a
more efficient way to store a simplex and coefficient than as two fields.

An `AbstractChainElement{S, F}` behaves like a `S` when doing comparisons and like a `F`
when doing arithmetic.

Coefficient values must be in a field - they must support `+`, `-`, `*`, `/`, `inv`, `zero`,
`one` and `iszero`.

# Interface

* `AbstractChainElement{S, F}(sx::S)` - default to element with `coefficient` equal to
  `sign(sx)`.
* `AbstractChainElement{S, F}(::S, coefficient)` - convert `coefficient` to `F` if
  necessary.
* `simplex(::ChainElement)`
* `coefficient(::ChainElement)`
"""
abstract type AbstractChainElement{S<:AbstractCell,F<:Number} end

for op in (:+, :-)
    @eval function (Base.$op)(ce1::C, ce2::C) where {C<:AbstractChainElement}
        @boundscheck ce1 == ce2 || throw(ArgumentError("simplices don't match"))
        return C(simplex(ce1), $op(coefficient(ce1), coefficient(ce2)))
    end
end
for op in (:oneunit, :zero, :-, :+)
    @eval function (Base.$op)(elem::C) where {C<:AbstractChainElement}
        return C(simplex(elem), $op(coefficient(elem)))
    end
end
function Base.:*(elem::C, x::F) where {F<:Number,C<:AbstractChainElement{<:Any,F}}
    return C(simplex(elem), coefficient(elem) * x)
end
function Base.:*(x::F, elem::C) where {F<:Number,C<:AbstractChainElement{<:Any,F}}
    return C(simplex(elem), x * coefficient(elem))
end
function Base.:/(elem::C, x::F) where {F<:Number,C<:AbstractChainElement{<:Any,F}}
    return C(simplex(elem), coefficient(elem) / x)
end
Base.one(::Type{<:AbstractChainElement{<:Any,F}}) where {F} = one(F)
Base.one(::AbstractChainElement{<:Any,F}) where {F} = one(F)
Base.iszero(elem::AbstractChainElement) = iszero(coefficient(elem))

Base.hash(elem::AbstractChainElement, u::UInt64) = hash(simplex(elem), u)
function Base.:(==)(elem1::AbstractChainElement, elem2::AbstractChainElement)
    return simplex(elem1) == simplex(elem2)
end
function Base.isless(elem1::AbstractChainElement, elem2::AbstractChainElement)
    return isless(simplex(elem1), simplex(elem2))
end

# Make chain elements useful when they come out as representatives.
birth(elem::AbstractChainElement) = birth(simplex(elem))
index(elem::AbstractChainElement) = index(simplex(elem))
Base.sign(elem::AbstractChainElement) = sign(coefficient(elem))
vertices(elem::AbstractChainElement) = vertices(simplex(elem))

simplex_type(::Type{<:AbstractChainElement{S}}) where {S} = S
coefficient_type(::Type{<:AbstractChainElement{<:Any,F}}) where {F} = F
simplex_type(::C) where {C<:AbstractChainElement} = simplex_type(C)
coefficient_type(::C) where {C<:AbstractChainElement} = coefficient_type(C)

# Make chain elements kinda similar to `Pair`s
function Base.getindex(elem::AbstractChainElement, i)
    if i == 1
        return simplex(elem)
    elseif i == 2
        return coefficient(elem)
    else
        throw(BoundsError(elem, i))
    end
end

Base.eltype(::Type{<:AbstractChainElement{S,F}}) where {S,F} = Union{S,F}
Base.iterate(elem::AbstractChainElement, i=1) = i > 2 ? nothing : (elem[i], i + 1)
Base.firstindex(::AbstractChainElement) = 1
Base.lastindex(::AbstractChainElement) = 2
Base.length(::AbstractChainElement) = 2

function Base.show(io::IO, elem::AbstractChainElement)
    return print(io, simplex(elem), " => ", coefficient(elem))
end

function Base.convert(::Type{C}, simplex::S) where {S,C<:AbstractChainElement{S}}
    return C(simplex)
end
function Base.convert(::Type{S}, elem::AbstractChainElement{S}) where {S<:AbstractCell}
    return simplex(elem)
end
function Base.convert(
    ::Type{C}, elem::AbstractChainElement{S,F}
) where {S,F,C<:AbstractChainElement{S,F}}
    return C(simplex(elem), coefficient(elem))
end

"""
    chain_element_type(simplex, coefficient)

Get the type of `AbstractChainElement` that is to be used for packing `simplex` and
`coefficient`. Overload this function to specialize element types for (simplex, field) type
pairs.
"""
chain_element_type(::S, ::F) where {S,F} = chain_element_type(S, F)

"""
    ChainElement{S<:AbstractCell, F} <: AbstractChainElement{S, F}

The default, basic subtype of [`AbstractChainElement`](@ref).
"""
struct ChainElement{S<:AbstractCell,F} <: AbstractChainElement{S,F}
    simplex::S
    coefficient::F

    function ChainElement{S,F}(simplex::S, coefficient=one(F)) where {S,F}
        return new{S,F}(abs(simplex), sign(simplex) * F(coefficient))
    end
end

simplex(elem::ChainElement) = elem.simplex
coefficient(elem::ChainElement) = elem.coefficient
chain_element_type(::Type{S}, ::Type{F}) where {S,F} = ChainElement{S,F}

"""
    PackedElement{S<:Simplex, F<:Mod} <: AbstractChainElement{S, F}

Like `ChainElement` for `Simplex` and `Mod`. Packs the coefficient value into top `M` bits
of index. Used internally by `Chain`.
"""
struct PackedElement{S<:Simplex,F<:Mod,M,U,T} <: AbstractChainElement{S,F}
    index_coef::U
    birth::T

    function PackedElement{S,F,M,U,T}(
        simplex::S, coefficient=one(F)
    ) where {M,U,T,S<:Simplex{<:Any,T},F<:Mod{M}}
        idx = simplex.index
        coef = F(coefficient) * sign(idx)
        uidx = U(abs(idx))
        index_coef = U(Int(coef)) << (sizeof(U) * 8 - n_bits(Val(M))) | uidx

        return new{S,F,M,U,T}(index_coef, birth(simplex))
    end
end

"""
    n_bits(::Val{M})

Get numer of bits needed to represent number mod `M`.
"""
@inline n_bits(::Val{M}) where {M} = 8 * sizeof(Int) - leading_zeros(M)

function simplex(pe::PackedElement{S,<:Any,M,U}) where {S,M,U}
    mask = typemax(U) << n_bits(Val(M)) >> n_bits(Val(M))
    return S(pe.index_coef & mask, pe.birth)
end
function coefficient(elem::PackedElement{<:Any,F,M,U}) where {F,U,M}
    return F(elem.index_coef >> (sizeof(U) * 8 - n_bits(Val(M))), false)
end

function chain_element_type(
    ::Type{S}, ::Type{F}
) where {M,I,T,S<:Simplex{<:Any,T,I},F<:Mod{M}}
    if n_bits(Val(M)) ≤ 8
        return PackedElement{S,F,M,unsigned(I),T}
    else
        return ChainElement{S,F}
    end
end

function index_overflow_check(
    ::Type{S}, ::Type{F}, nv, message
) where {T,I,F<:Mod,S<:Simplex{<:Any,T,I}}
    vertices = ntuple(i -> I(nv - i + 1), length(S))
    element = chain_element_type(S, F)(S(index(I.(vertices)), zero(T)))

    index_I = index(element)
    index_big = index(BigInt.(vertices))

    if index_I ≠ index_big
        throw(OverflowError(message))
    end
end

function index_overflow_check(::Type{<:AbstractCell}, _, _, _)
    return nothing
end


struct OrbitalFieldMatrix <: AbstractEnvironmentFeatureDescriptor
    # stuff
end

# more stuff


using OffsetArrays
using Printf # for @sprintf, etc.
#import Base.close

#=========================================================#
function out1(meqn::Int, mbc::Int, mx::Int, xlower::Float64, dx::Float64,
              q::OffsetArray{Float64,2}, t::Float64, iframe::Int,
              aux::OffsetArray{Float64}, maux::Int, outaux::Bool)
#=========================================================#
#
#     # Output the results for a general system of conservation laws
#     # in 1 dimension
#
#     # Write the results to the file fort.q<iframe>
#     # Use format required by matlab script  plotclaw1.m
#     # The same format is used by the amrclaw package.
#     # Here it's adapted to output just the single grid.
#
#     # set outaux = .true. to also output the aux arrays to fort.a<iframe>
#
#
##       implicit double precision (a-h,o-z)
##       dimension q(meqn,1-mbc:mx+mbc)
##       dimension aux(maux,1-mbc:mx+mbc)
##       character*10 fname1, fname2, fname3
##       logical outaux

#
#     # Write the results to the file fort.q<iframe>
#     # Use format required by matlab script  plotclaw1.m
#     # The same format is used by the amrclaw package.
#     # Here it's adapted to output just the single grid.
#
#     # first create the file name and open file
#
##      fname1 = 'fort.qxxxx'
##      fname2 = 'fort.txxxx'
##      fname3 = 'fort.axxxx'
##      nstp = iframe
##      do 55 ipos = 10, 7, -1
##         idigit = mod(nstp,10)
##         fname1(ipos:ipos) = char(ichar('0') + idigit)
##         fname2(ipos:ipos) = char(ichar('0') + idigit)
##         fname3(ipos:ipos) = char(ichar('0') + idigit)
##         nstp = nstp / 10
## 55   continue
##

    fname1 = Printf.@sprintf("fort.q%04d", iframe)
    fname2 = Printf.@sprintf("fort.t%04d", iframe)
    fname3 = Printf.@sprintf("fort.a%04d", iframe)

##      open(unit=50,file=fname1,status='unknown',form='formatted')
##      open(unit=60,file=fname2,status='unknown',form='formatted')

    unit50 = open(fname1, "w")
    unit60 = open(fname2, "w")

    # the following parameters are used in amrclaw where there are
    # multiple grids.  Here they are all set to 1:
    ngrids = 1
    mptr = 1
    level = 1

    # write(50,1001) mptr,level,mx

## 1001 format(i5,'                 grid_number',/,
##     &     i5,'                 AMR_level',/,
##     &     i5,'                 mx')
##

    @printf(unit50,"%5d                 grid_number\n",mptr)
    @printf(unit50,"%5d                 AMR_level\n",level)
    @printf(unit50,"%5d                 mx\n",mx)

##      write(50,1002) xlower,dx
## 1002 format(e18.8,'    xlow', /,
##     &     e18.8,'    dx', /)
###

    @printf(unit50,"%18.8e    xlow\n",xlower)
    @printf(unit50,"%18.8e    dx\n",dx)

##      do 10 i=1,mx
##         do m=1,meqn
###     # exponents with more than 2 digits cause problems reading
###     # into matlab... reset tiny values to zero:
##            if (dabs(q(m,i)) .lt. 1d-99) q(m,i) = 0.d0
##         enddo
###
##         write(50,1005) (q(m,i), m=1,meqn)
## 1005    format(4e16.8)
###
## 10   continue
##      write(50,*) ' '
##      write(50,*) ' '
##

    for i=1:mx
        for m=1:meqn
            if abs(q[m,i]) < 1e-99
                q[m,i] = 0.0
            end
            @printf(unit50,"%16.8e",q[m,i])
            # FIXME
            ## if i % 4 == 1 && i > 1
            ##     @printf(unit50,"\n")
            ## end
        end
        @printf(unit50,"\n")
    end
    @printf(unit50," \n \n")

##
##      if (outaux) then
###     # also output the aux arrays:
##         open(unit=70,file=fname3,status='unknown',form='formatted')
##         write(70,1001) mptr,level,mx
##         write(70,1002) xlower,dx
##         do 110 i=1,mx
##            do m=1,maux
###     # exponents with more than 2 digits cause problems reading
###     # into matlab... reset tiny values to zero:
##               if (dabs(aux(m,i)) .lt. 1d-99) aux(m,i) = 0.d0
##            enddo
###
##            write(70,1005) (aux(m,i), m=1,maux)
###
## 110     continue
##         write(70,*) ' '
##         close(unit=70)
##      endif
##
##      write(60,1000) t,meqn,ngrids,maux,1
##
## 1000 format(e26.16,'    time', /,
##     &     i5,'                 meqn'/,
##     &     i5,'                 ngrids'/,
##     &     i5,'                 maux'/,
##     &     i5,'                 ndim'/,/)
###
##
    Base.close(unit50)
    Base.close(unit60)

end


__precompile__(true)

module Photon

using Printf
using Statistics
import Knet
import CUDA

include("core.jl")
include("layers/layers.jl")
include("metrics/metrics.jl")
include("losses/losses.jl")
include("optimisers/optimisers.jl")
include("train.jl")
include("callbacks/callbacks.jl")
include("data/data.jl")
include("zoo/zoo.jl")

@debug "Loaded Photon"

end


Idx{N}      = NTuple{N, Int}
Name{N}     = NTuple{N, Symbol}

struct TableIndex{N}
    idx::Idx{N}
    name::Name{N}
end
Index{N}    = Vector{TableIndex{N}}
Printable   = Union{String, Symbol}

function TableIndex(idx, name)
    return TableIndex(tuplefy(idx), tuplefy(Symbol.(name)))
end

TableIndex(idx::Integer, name::Printable) = begin
    TableIndex(tuple(idx), tuple(Symbol(name)))
end

TableIndex(name::Printable) = TableIndex(1, name)
function update_index(index::TableIndex{N}, new_idx::Idx{N}) where N
    return TableIndex(new_idx, index.name)
end

TableDict{N, T} = OrderedDict{TableIndex{N}, T} where T <: FormattedNumber

########################################################################
#################### Sorting the Index #################################
########################################################################

function isless(index1::TableIndex{N}, index2::TableIndex{N}) where N
    for i=1:N
        # First Check the numeric index
        if index1.idx[i] < index2.idx[i]
            return true
        elseif index1.idx[i] > index2.idx[i]
            return false
        # Then check the strings
        elseif index1.name[i] < index2.name[i]
            return true
        elseif index1.name[i] > index2.name[i]
            return false
        end
    end
    return false
end

function isless_group(index1::TableIndex{N}, index2::TableIndex{N},
                     level=N-1) where N
    for i = 1:N-1
        # First Check the numeric index
        if index1.idx[i] < index2.idx[i]
            return true
        elseif index1.idx[i] > index2.idx[i]
            return false
        # Then check the strings
        elseif index1.name[i] < index2.name[i]
            return true
        elseif index1.name[i] > index2.name[i]
            return false
        end
    end
    return false
end

########################################################################
#################### Columns ###########################################
########################################################################

mutable struct TableCol{N,M} <: TexTable
    header::TableIndex{M}
    data::TableDict{N, FormattedNumber}
end

TableCol(header::Printable) = TableCol(TableIndex(1, header),
                                    TableDict{1, FormattedNumber}())

TableCol(x::TableCol; kwargs...) = x

function TableCol(header::Printable, kv::TableDict{N,T}) where
    {N,T<:FormattedNumber}
    return TableCol(TableIndex(header),
                    convert(TableDict{N, FormattedNumber}, kv))
end

# Columns are equal if they are the same entry all the way down
==(t1::TableCol, t2::TableCol) = begin
    t1.header == t2.header || return false
    t1.data   == t2.data   || return false
    return true
end

########################################################################
#################### Constructors ######################################
########################################################################

function TableCol(header::Printable, kv::AbstractDict)
    pairs = collect(TableIndex(i, key)=>FormattedNumber(value)
                    for (i, (key, value)) in enumerate(kv))
    TableCol(header,
             OrderedDict{TableIndex{1}, FormattedNumber}(pairs))
end

function TableCol(header, kv::AbstractDict, kp::AbstractDict)
    TableCol(header,
             OrderedDict{TableIndex{1}, FormattedNumber}(
                TableIndex(i, key)=>(key in keys(kp)) ?
                                    FormattedNumber(val, kp[key]) :
                                    FormattedNumber(val)
                                    for (i, (key, val))
                                    in enumerate(kv)))
end

function TableCol(header, ks::Vector, vs::Vector)

    pairs = [TableIndex(i, key)=>FormattedNumber(val)
             for (i, (key, val)) in enumerate(zip(ks, vs))]
    TableCol(header,
             OrderedDict{TableIndex{1}, FormattedNumber}(pairs...))
end

function TableCol(header, keys::Vector, values::Vector,
                  precision::Vector)

    pairs  = [ TableIndex(i, key)=>FormattedNumber(val, se)
               for (i, (key, val, se))
               in enumerate(zip(keys, values, precision))]
    data = OrderedDict(pairs...)
    return TableCol(header, data)
end

convert(::Type{FormattedNumber}, x) = FormattedNumber(x)
convert(::Type{FormattedNumber}, x::FormattedNumber) = x

Entry = Pair{T, K} where {T<:Printable, K<:Union{Printable, Number,
                                                 NTuple{2,Number}}}
function TableCol(header::Printable, pairs::Vararg{Entry})
    return TableCol(header, OrderedDict(pairs))
end

########################################################################
#################### Indexing ##########################################
########################################################################

function get_vals(x::FormattedNumber)
    val     = value(x)
    seval   = se(x)
    star    = "*"^x.star
    return val, seval, star
end

function get_vals(col::TableCol, x::TableIndex, backup="")
    if  x in keys(col.data)
        return get_vals(col.data[x])
    else
        return backup, "", ""
    end
end

# This is an inefficient backup getindex method to maintain string
# indexing for users
function getindex(col::TableCol, key::Printable)

    x   = Symbol(key)
    loc = name_lookup(col, x)
    index = keys(col.data) |> collect

    if length(loc) > 1
        throw(KeyError("""
           The string keys you've provided are not unique.  Try indexing
           by TableIndex instead.
           """))
    else
        return col[index[loc[1]]]
    end
end

function name_lookup(col::TableCol{N,M}, x::Symbol) where {N,M}
    index = keys(col.data) |> collect
    names = get_name(index, N)
    return  findall(y->y==x, names)
end

function getindex(col::TableCol, x::TableIndex)
    if haskey(col.data, x)
        return col.data[x]
    else
        return FormattedNumber("")
    end
end

function setindex!(col::TableCol{1,N}, value, key::Printable) where N
    skey        = Symbol(key)
    loc         = name_lookup(col, skey)
    col_index   = keys(col.data) |> collect
    if length(loc) > 1
        throw(KeyError("""
           The string keys you've provided are not unique.  Try indexing
           by TableIndex instead.
           """))
    elseif length(loc) == 0
        # We need to insert it at a new position
        index   =   get_idx(col_index, 1)
        new_idx =   length(index) > 0  ?
                    maximum(index) + 1 :
                    1
        col[TableIndex(new_idx, skey)] = value
    else
        col[col_index[loc[1]]] = value
    end
end

# General Backup falls back to FormattedNumber constructor
function setindex!(col::TableCol, value, key::TableIndex)
    col.data[key] = FormattedNumber(value)
    return col
end

# Handle values passed with precision
function setindex!(col::TableCol, value::Tuple{T, T2}, key::TableIndex) where
    {T, T2<:AbstractFloat}
    col.data[key] = FormattedNumber(value)
    return col
end

# Handle optional stars
function setindex!(col::TableCol, value::Tuple{T, T2, Int},
                   key::TableIndex) where {T, T2<:AbstractFloat}
    col.data[key] = FormattedNumber(value[1:2])
    star!(col.data[key], value[3])
    return col
end

function size(t::TableCol)
    n = length(t.data)
    return n, 1
end


function puzzle08(path::String=joinpath(@__DIR__, "..", "data/08.txt"))
    instructions = readlines(path)
    instructions = [replace(i, "inc" => "+=") for i in instructions]
    instructions = [replace(i, "dec" => "-=") for i in instructions]
    registers = unique(first.(split.(instructions, " ")))

    for register in registers
        r = Symbol(register)
        @eval $r = 0
    end

    highest = 0
    for instruction in instructions
        consequence, condition = Meta.parse.(String.(split(instruction, "if")))
        @eval $(condition) ? $(consequence) : nothing
        highest = max(highest, maximum(eval.(Meta.parse.(registers))))
    end

    partone = maximum(eval.(Meta.parse.(registers)))

    return [partone, highest]
end


module RocketCountOperatorTest

using Test
using Rocket

include("../test_helpers.jl")

@testset "operator: count()" begin

    println("Testing: operator count()")

    run_proxyshowcheck("Count", count())

    run_testset([
        (
            source = from(1:42) |> count(),
            values = @ts([ 42, c ])
        ),
        (
            source = from(1:42) |> async(0) |> count(),
            values = @ts([ 42, c ])
        ),
        (
            source = completed(Int) |> count(),
            values = @ts([ 0, c ])
        ),
        (
            source = never(Int) |> count(),
            values = @ts()
        ),
        (
            source = faulted("e") |> count(),
            values = @ts(e("e"))
        )
    ])

end

end


using MultiScaleArrays
using OrdinaryDiffEq, DiffEqBase, Test, StochasticDiffEq

#=
struct Cell{B} <: AbstractMultiScaleArrayLeaf{B}
    values::Vector{B}
end
struct Population{T<:AbstractMultiScaleArray,B<:Number} <: AbstractMultiScaleArray{B}
    nodes::Vector{T}
    values::Vector{B}
    end_idxs::Vector{Int}
end
struct Tissue{T<:AbstractMultiScaleArray,B<:Number} <: AbstractMultiScaleArray{B}
    nodes::Vector{T}
    values::Vector{B}
    end_idxs::Vector{Int}
end
struct Embryo{T<:AbstractMultiScaleArray,B<:Number} <: AbstractMultiScaleArrayHead{B}
    nodes::Vector{T}
    values::Vector{B}
    end_idxs::Vector{Int}
end
=#

cell1 = Cell([1.0;2.0;3.0])
cell2 = Cell([4.0;5])
population = construct(Population,deepcopy([cell1,cell2])) # Make a Population from cells
cell3 = Cell([3.0;2.0;5.0])
cell4 = Cell([4.0;6])
population2 = construct(Population,deepcopy([cell3,cell4]))
tissue1 = construct(Tissue,deepcopy([population,population2])) # Make a Tissue from Populations
tissue2 = construct(Tissue,deepcopy([population2,population]))
embryo = construct(Embryo,deepcopy([tissue1,tissue2])) # Make an embryo from Tissues

@test getindices(embryo) == 1:20
@test getindices(embryo,1) == 1:10
@test getindices(embryo,2) == 11:20
@test getindices(embryo,1,1) == 1:5
@test getindices(embryo,1,2) == 6:10
@test getindices(embryo,1,1,1) == 1:3
@test getindices(embryo,1,1,2) == 4:5
@test getindices(embryo,1,2,1) == 6:8
@test getindices(embryo,1,2,2) == 9:10
@test getindices(embryo,1,2,2,1) == 9:9
@test getindices(embryo,1,2,2,2) == 10:10
@test getindices(embryo,2,1) == 11:15
@test getindices(embryo,2,2) == 16:20
@test getindices(embryo,2,1,1) == 11:13
@test getindices(embryo,2,1,2) == 14:15
@test getindices(embryo,2,2,1) == 16:18
@test getindices(embryo,2,2,2) == 19:20
@test getindices(embryo,2,2,2,1) == 19:19
@test getindices(embryo,2,2,2,2) == 20:20


# Functionality for the promotion of dictionaries.

export promote_domaintype,
    promote_coefficienttype

# Convert the domaintype of the dictionary to `S`
convert(::Type{Dictionary{S}}, dict::Dictionary{S}) where {S} = dict
convert(::Type{Dictionary{S}}, dict::Dictionary) where {S} = similar(dict, S)

promote(d1::Dictionary, d2::Dictionary) =
    promote_coefficienttype(promote_domaintype(d1, d2)...)

## Domain type

"Promote the domain type of the dictionaries to a common type."
promote_domaintype(dict::Dictionary) = (dict,)

function promote_domaintype(d1::Dictionary, d2::Dictionary, d3::Dictionary...)
    d1b, d2b = promote_domaintype(d1, d2)
    (d1b, promote_domaintype(d2b, d3...))
end

promote_domaintype(d1::Dictionary, d2::Dictionary) =
    _promote_domaintype(d1, d2, promote_type(domaintype(d1), domaintype(d2)))
_promote_domaintype(d1, d2, ::Type{S}) where {S} =
    convert(Dictionary{S}, d1), convert(Dictionary{S}, d2)

## Coefficient type

"Promote the coefficient type of the dictionaries to a common type."
promote_coefficienttype(dict::Dictionary) = (dict,)

function promote_coefficienttype(d1::Dictionary, d2::Dictionary, d3::Dictionary...)
    d1b, d2b = promote_coefficienttype(d1, d2)
    (d1b, promote_coefficienttype(d2b, d3...))
end

promote_coefficienttype(d1::Dictionary, d2::Dictionary) =
    _promote_coefficienttype(d1, d2, coefficienttype(d1), coefficienttype(d2))

# Types are the same
_promote_coefficienttype(d1, d2, ::Type{T}, ::Type{T}) where {T} = (d1,d2)
# Types differ: compute a joined supertype
_promote_coefficienttype(d1, d2, ::Type{S}, ::Type{T}) where {S,T} =
    _promote_coefficienttype(d1, d2, S, T, promote_type(S,T))

_promote_coefficienttype(d1, d2, ::Type{S}, ::Type{T}, ::Type{U}) where {S,T,U} =
    ensure_coefficienttype(U, d1), ensure_coefficienttype(U, d2)

"Ensure that the dictionary (or dictionaries) have coefficient type `T` or wider."
ensure_coefficienttype(::Type{C}, dict::Dictionary) where {C} =
    _ensure_coefficienttype(C, ensure_prectype(prectype(C),dict))
_ensure_coefficienttype(::Type{C}, dict::Dictionary) where {C} =
    _ensure_coefficienttype(dict, C, coefficienttype(dict))
_ensure_coefficienttype(dict::Dictionary, ::Type{T}, ::Type{T}) where {T} = dict
# No fallback is provided, in order to generate an error when a coefficient type
# is requested that can not be achieved. Complexification of a dictionary is
# achieved by ComplexifiedDict.

ensure_coefficienttype(::Type{T}, dicts::Dictionary...) where {T} =
    ensure_coefficienttype.(T, dicts)

## Precision type

"Ensure that the dictionary has precision type `T` or a wider type."
ensure_prectype(::Type{T}, dict::Dictionary) where {T} =
    _ensure_prectype(dict, T, prectype(dict))
_ensure_prectype(dict::Dictionary, ::Type{T}, ::Type{T}) where {T} = dict
_ensure_prectype(dict::Dictionary, ::Type{S}, ::Type{T}) where {S,T} =
    _ensure_prectype(dict, S, T, promote_type(S,T))

_ensure_prectype(dict::Dictionary, S, T, ::Type{U}) where {U} =
    similar(dict, widen_prectype(domaintype(dict), U))

ensure_prectype(::Type{T}, dicts::Dictionary...) where {T} =
    ensure_prectype.(T, dicts)

"Widen the type such that its precision type matches `U`."
widen_prectype(::Type{Complex{T}}, ::Type{U}) where {T,U} = Complex{U}
widen_prectype(::Type{<:Number}, ::Type{U}) where {U<:Number} = U
widen_prectype(::Type{SVector{T,N}}, ::Type{U}) where {T,N,U} = SVector{widen_prectype(T,U),N}
widen_prectype(::Type{Array{T,N}}, ::Type{U}) where {T,N,U} = Array{widen_prectype(T,U),N}
widen_prectype(::Type{NTuple{N,T}}, ::Type{U}) where {T,N,U} = NTuple{N,widen_prectype(T,U)}
widen_prectype(::Type{Tuple{A}}, ::Type{U}) where {A,U} = Tuple{widen_prectype(A,U)}
widen_prectype(::Type{Tuple{A,B}}, ::Type{U}) where {A,B,U} =
    Tuple{widen_prectype(A,U),widen_prectype(B,U)}
widen_prectype(::Type{Tuple{A,B,C}}, ::Type{U}) where {A,B,C,U} =
    Tuple{widen_prectype(A,U),widen_prectype(B,U),widen_prectype(C,U)}


using Reactive

function standard_push_test(non_input::Signal)
    m = map(x->2x, non_input)
    pval = number()
    push!(non_input, pval)
    step()

    @test (value(non_input)) == (pval)
    @test (value(m)) == (2pval)
end

@testset "Push to non-input nodes" begin

    a = Signal(number(); name="a")
    b = map(x -> x*x, a; name="b")

    @testset "push to map" begin
        m = map(x->2x, b)

        push!(b, 10.0)
        step()
        @test (value(b)) == (10.0)
        @test (value(m)) == (2value(b))

        push!(a, 2.0)
        step()
        @test (value(b)) == (4.0)
        @test (value(m)) == (2value(b))

        push!(b, 3.0)
        step()
        @test (value(b)) == (3.0)
        @test (value(m)) == (2value(b))
    end

    @testset "push to merge" begin
        ## Merge
        d = Signal(number(); name="d")
        e = merge(b, d, a; name="e")
        m = map(x->2x, e)
        # precedence to d
        @test (value(e)) == (value(d))
        @test (value(m)) == (2value(e))

        standard_push_test(e)
    end

    @testset "push to foldp" begin
        GC.gc()
        x = Signal(number())
        f = foldp(+, 0, x)

        standard_push_test(f)

        # Check for correct continuation
        pval = value(f)
        sval = number()
        push!(x, sval)
        step()

        @test (value(f)) == (pval + sval)
    end

    @testset "push to filter" begin
        g = Signal(0)
        pred = x -> x % 2 != 0
        h = filter(pred, 1, g)
        standard_push_test(h)
    end

    @testset "push to sampleon" begin
        # sampleon
        g = Signal(0)
        nv = number()
        push!(g, nv)
        step()
        i = Signal(true)
        j = sampleon(i, g)
        standard_push_test(j)
    end

    @testset "push to droprepeats" begin
        # droprepeats
        k = Signal(1)
        l = droprepeats(k)

        standard_push_test(l)
    end

    @testset "push to filterwhen" begin
        # filterwhen
        b = Signal(false)
        n = Signal(1)
        dw = filterwhen(b, 0, n)
        standard_push_test(dw)
    end

    @testset "push to previous" begin
        x = Signal(0)
        y = previous(x)
        standard_push_test(y)
    end

    @testset "push to delay" begin
        x = Signal(0)
        y = delay(x)
        standard_push_test(y)
    end

    @testset "bind non-input" begin
        s = Signal(1; name="sig 1")
        m = map(x->2x, s; name="m")
        s2 = Signal(3; name="sig 2")
        push!(m, 10)
        step()
        @test (value(m)) == (10)

        bind!(m, s2) #two-way bind
        @test (value(m)) == (3)
        @test (value(s2)) == (3)

        push!(m, 6)
        step()
        @test (value(m)) == (6)
        @test (value(s2)) == (6)

        push!(s2, 10)
        step()
        @test (value(m)) == (10)
        @test (value(s2)) == (10)
    end

end


# This file was generated, do not modify it. # hide
fig = figure(figsize=(15, 15))

subplot(221)
scatter(data.Wind_gen, data.Radiation_dir)
xlabel("Wind power (kW)", fontsize=14)
ylabel("Solar radiation - directional", fontsize=14)

subplot(222)
scatter(data.Wind_gen, data.Radiation_dif)
xlabel("Wind power (kW)", fontsize=14)
ylabel("Solar radiation - diffuse", fontsize=14)

subplot(223)
scatter(data.Wind_gen, data.Windspeed)
xlabel("Wind power (kW)", fontsize=14)
ylabel("Wind speed (m/s)", fontsize=14)

subplot(224)
scatter(data.Wind_gen, data.Temperature)
xlabel("Wind power (kW)", fontsize=14)
ylabel("Temperature (C)", fontsize=14)

savefig(joinpath(@OUTPUT, "wind_scatter.png"), bbox_inches="tight") # hide


Base.@kwdef struct Spec{T} <: AbstractElement
    analyses::Tuple=()
    pkeys::NamedTuple=NamedTuple()
    mapping::Mapping=Mapping()
    options::NamedTuple=NamedTuple()
end

Spec(ctx::AbstractContext) = Spec{Any}(mapping=Mapping(ctx))
Spec(mapping::Mapping) = Spec{Any}(mapping=mapping)
Spec(s::Spec) = s

visual(args...; kwargs...) = Spec{Any}(options=namedtuple(args...; kwargs...))
visual(T::Union{Type, Symbol}, args...; kwargs...) = Spec{T}(options=namedtuple(args...; kwargs...))

@deprecate spec(args...; kwargs...) visual(args...; kwargs...)

plottype(::Spec{T}) where {T} = T

function Base.merge(t1::Spec{T1}, t2::Spec{T2}) where {T1, T2}
    T = T2 === Any ? T1 : T2
    analyses = (t1.analyses..., t2.analyses...)
    pkeys = merge(t1.pkeys, t2.pkeys)
    mapping = merge(t1.mapping, t2.mapping)
    options = merge(t1.options, t2.options)
    return Spec{T}(analyses, pkeys, mapping, options)
end

Base.:*(a1::AbstractElement, a2::AbstractElement) = merge(Spec(a1), Spec(a2))

layers(g::AbstractElement) = AlgebraicList([Spec(g)])
layers(g::AlgebraicList) = g

Base.:*(s1::AbstractElement, s2::AlgebraicList) = layers(s1) * s2
Base.:*(s1::AlgebraicList, s2::AbstractElement) = s1 * layers(s2)

const ElementOrList = Union{AbstractElement, AlgebraicList}

Base.:+(s1::ElementOrList, s2::ElementOrList) = layers(s1) + layers(s2)

# pipeline

# Expand pairs and run the analyses
function expand(sp::Spec{T}) where {T}
    analyses, pkeys, mapping, options = sp.analyses, sp.pkeys, sp.mapping, sp.options
    @assert isempty(pkeys)
    v = [Spec{T}(mapping=val, pkeys=key, options=options) for (key, val) in pairs(mapping)]
    list = AlgebraicList(v)
    return foldl((ls, an) -> apply(an, ls), analyses, init=list)
end

global_options(f, d::AlgebraicList) = NamedTuple()

function compute(f, layer, precomputed_attributes)
    args, kwargs = split(layer.mapping.value)
    return f(args...; precomputed_attributes..., kwargs...)
end

# default fallback to apply a callable to a vector of key => value pairs
# if customized, it must return a vector of key => value pairs
function apply(f, d::AlgebraicList)
    precomputed_attributes = global_options(f, d)
    v = map(parent(d)) do layer
        analyses, pkeys, options = layer.analyses, layer.pkeys, layer.options
        T = plottype(layer)
        res = compute(f, layer, precomputed_attributes) * Spec{T}(analyses=analyses, options=options, pkeys=pkeys)
        return parent(layers(res))
    end
    return AlgebraicList(reduce(vcat, v))
end

# Expand mappings, apply analyses, compute scales, and return vector of traces
function run_pipeline(s::ElementOrList)
    nested = [parent(expand(layer)) for layer in layers(s)]
    computescales(reduce(vcat, nested))
end


abstract type AbstractResult end

abstract type SpectralResult <: AbstractResult end

const Projection{T <: Real} = AbstractMatrix{T}


using Random: GLOBAL_RNG

struct ELBO <: VariationalObjective end

function (elbo::ELBO)(alg, q, logπ, num_samples; kwargs...)
    return elbo(GLOBAL_RNG, alg, q, logπ, num_samples; kwargs...)
end

const elbo = ELBO()


import ChainRulesCore.Tangent
import Yota: update!, setfield_nested!

mutable struct StructA
    t::Array{Float64, 1}
    s::Float64
end

mutable struct StructB
    a::StructA
    s::Float64
end

@testset "update" begin

    # setfield_nested
    b = StructB(StructA([1, 2, 3], 4), 5)
    setfield_nested!(b, (:a, :t), [-1.0, -2.0, -3.0])
    setfield_nested!(b, (:a, :s), -4.0)
    setfield_nested!(b, (:s,), -5.0)
    @test b.a.t == [-1.0, -2.0, -3.0]
    @test b.a.s == -4.0
    @test b.s == -5.0

    # struct update
    g = Dict((:a, :t) => [1.0, 2.0, 3.0],
             (:a, :s) => 4.0,
             (:s,) => 5.0)
    g = Tangent{StructB}(s=5.0, a=Tangent{StructA}(t=[1.0, 2.0, 3.0], s=4.0))
    update!(b, g)
    @test b.a.t == [-2.0, -4.0, -6.0]
    @test b.a.s == -8.0
    @test b.s == -10.0

    # struct update with function
    update!(b, g, (x, g) -> x .+ 2g)
    @test b.a.t == [0, 0, 0]
    @test b.a.s == 0
    @test b.s == 0

    # array update
    x = rand(3,4)
    update!(x, x)
    @test x == zero(x)

    # array update with function
    x = rand(3,4)
    xo = copy(x)
    update!(x, x, (x, g) -> x - 2g)
    @test x == -xo

    # with actual grad update
    b = StructB(StructA(ones(4), 1.0), 1.0)
    _, g = grad(b -> sum(b.a.t) + (b.a.s + b.s), b)
    update!(b, g[2], (x, gx) -> x .- 0.5gx)
    @test b.a.t == [0.5, 0.5, 0.5, 0.5]
    @test b.a.s == 0.5
    @test b.s == 0.5
end