function plot_lines!(vis::Visualizer, model::Quadruped, q::AbstractVector;
		r=0.0205, offset=0.05, size=10, name::Symbol=:Quadruped, col::Bool=true, α::Real=1.0)
	p_shift = [0.0, 0.0, r]
	orange_mat, blue_mat, black_mat = get_line_material(size, α=α)
	black_mat.color = RGBA(0.0,153/256,153/256,α)
	black_mat.color = RGBA(209/256,0/256,209/256,α)
	black_mat.color = RGBA(136/256,008/256,008/256,α)
	black_mat.color = RGBA(170/256,074/256,068/256,α)

	# Point Traj
	torso_point = Vector{Point{3,Float64}}()
	f1_point = Vector{Point{3,Float64}}()
	f2_point = Vector{Point{3,Float64}}()
	f3_point = Vector{Point{3,Float64}}()
	f4_point = Vector{Point{3,Float64}}()

	for qi in q
		k_torso = kinematics_1(model, qi, body = :torso, mode = :com)
		p_torso = [k_torso[1], -offset, k_torso[2]] + p_shift

		k_calf_1 = kinematics_2(model, qi, body = :calf_1, mode = :ee)
		p_calf_1 = [k_calf_1[1], -offset, k_calf_1[2]] + p_shift

		k_calf_2 = kinematics_2(model, qi, body = :calf_2, mode = :ee)
		p_calf_2 = [k_calf_2[1], -offset, k_calf_2[2]] + p_shift

		k_calf_3 = kinematics_3(model, qi, body = :calf_3, mode = :ee)
		p_calf_3 = [k_calf_3[1], -offset, k_calf_3[2]] + p_shift

		k_calf_4 = kinematics_3(model, qi, body = :calf_4, mode = :ee)
		p_calf_4 = [k_calf_4[1], -offset, k_calf_4[2]] + p_shift

		push!(torso_point, Point(p_torso...))
		push!(f1_point, Point(p_calf_1...))
		push!(f2_point, Point(p_calf_2...))
		push!(f3_point, Point(p_calf_3...))
		push!(f4_point, Point(p_calf_4...))
	end

	# Set lines
	if col
		setobject!(vis[name]["lines/torso"], MeshCat.Line(torso_point, orange_mat))
		setobject!(vis[name]["lines/foot1"], MeshCat.Line(f1_point, blue_mat))
		setobject!(vis[name]["lines/foot2"], MeshCat.Line(f2_point, blue_mat))
		setobject!(vis[name]["lines/foot3"], MeshCat.Line(f3_point, blue_mat))
		setobject!(vis[name]["lines/foot4"], MeshCat.Line(f4_point, blue_mat))
	else
		setobject!(vis[name]["lines/torso"], MeshCat.Line(torso_point, black_mat))
		setobject!(vis[name]["lines/foot1"], MeshCat.Line(f1_point, black_mat))
		setobject!(vis[name]["lines/foot2"], MeshCat.Line(f2_point, black_mat))
		setobject!(vis[name]["lines/foot3"], MeshCat.Line(f3_point, black_mat))
		setobject!(vis[name]["lines/foot4"], MeshCat.Line(f4_point, black_mat))
	end
	return nothing
end

function build_payload!(vis::Visualizer, model::Quadruped; name::Symbol=:Quadruped,
		object=:mesh, r=0.0205, rp=0.20, α=1.0)
	rp = convert(Float32, rp)
	pl_mat = MeshPhongMaterial(color = RGBA(0.4, 0.4, 0.4, α))
	thickness = 0.10
	quad_thickness = 0.267
	default_background!(vis)

	if object == :mesh
		obj = MeshFileObject(joinpath(@__DIR__, "payload_mesh", "Box.obj"))
		setobject!(vis[name][:payload][:object], obj)
		tr = Translation(0.058, quad_thickness/2, 0.18)
		rt = LinearMap(rp*0.5*I(3)*RotZ(π/2)*RotX(pi))
		tform = compose(tr, rt)
		settransform!(vis[name][:payload][:object], tform)
	elseif object == :rectangle
		obj = Rect(Vec(0.058, quad_thickness/2-thickness/2, -rp/2+0.18), Vec(rp, thickness, rp))
		setobject!(vis[name][:payload][:object], obj, pl_mat)
	end
	return nothing
end

function set_payload!(vis::Visualizer, model::Quadruped, q::AbstractVector;
		name::Symbol=:Quadruped, r=0.0205, rp=0.15, offset=0.00)

	r = convert(Float32, r)
	p_shift = [0.0; offset; r]

	p = [q[1]; 0.0; q[2]] + p_shift

	k_torso = kinematics_1(model, q, body = :torso, mode = :ee)
	p_torso = [k_torso[1], 0.0, k_torso[2]] + p_shift
	settransform!(vis[name][:payload], cable_transform(p, p_torso))
	return nothing
end

function animate_payload!(vis::Visualizer, anim::MeshCat.Animation, model::ContactModel,
		q::AbstractVector; name::Symbol=model_name(model))
	for t in 1:length(q)
		MeshCat.atframe(anim, t) do
			set_payload!(vis, model, q[t], name=name)
		end
	end
	setanimation!(vis, anim)
	return nothing
end

function visualize_payload!(vis::Visualizer, model::ContactModel, q::AbstractVector;
		h=0.01, α=1.0, object::Symbol=:mesh,
		anim::MeshCat.Animation=MeshCat.Animation(Int(floor(1/h))),
		name::Symbol=model_name(model))

	build_payload!(vis, model, name=name, object=object, α=α)
	animate_payload!(vis, anim, model, q, name=name)
	return anim
end

function visualize_payload!(vis::Visualizer, model::ContactModel, traj::ContactTraj;
		sample=max(1, Int(floor(traj.H / 100))), h=traj.h*sample,  α=1.0, object::Symbol=:mesh,
		anim::MeshCat.Animation=MeshCat.Animation(Int(floor(1/h))),
		name::Symbol=model_name(model))

	visualize_payload!(vis, model, traj.q[3:sample:end]; anim=anim, name=name, object=object, h=h, α=α)
	return anim
end

function build_robot!(vis::Visualizer, model::Quadruped; name::Symbol=:Quadruped, r=0.0205, α=1.0)
	r = convert(Float32, r)
	body_mat = MeshPhongMaterial(color = RGBA(0.0, 0.0, 0.0, α))
	contact_mat = MeshPhongMaterial(color = RGBA(1.0, 165.0 / 255.0, 0.0, α))

	default_background!(vis)

	torso = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_torso),
		convert(Float32, 0.035))
	setobject!(vis[name][:robot]["torso"], torso, body_mat)

	thigh_1 = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_thigh1),
		convert(Float32, 0.0175))
	setobject!(vis[name][:robot]["thigh1"], thigh_1, body_mat)

	calf_1 = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_calf1),
		convert(Float32, 0.0125))
	setobject!(vis[name][:robot]["leg1"], calf_1, body_mat)

	thigh_2 = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_thigh2),
		convert(Float32, 0.0175))
	setobject!(vis[name][:robot]["thigh2"], thigh_2, body_mat)

	calf_2 = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_calf2),
		convert(Float32, 0.0125))
	setobject!(vis[name][:robot]["leg2"], calf_2, body_mat)

	thigh_3 = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_thigh3),
		convert(Float32, 0.0175))
	setobject!(vis[name][:robot]["thigh3"], thigh_3, body_mat)

	calf_3 = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_calf3),
		convert(Float32, 0.0125))
	setobject!(vis[name][:robot]["leg3"], calf_3, body_mat)

	thigh_4 = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_thigh4),
		convert(Float32, 0.0175))
	setobject!(vis[name][:robot]["thigh4"], thigh_4, body_mat)

	calf_4 = GeometryBasics.Cylinder(Point3f0(0),
		Point3f0(0.0, 0.0, model.l_calf4),
		convert(Float32, 0.0125))
	setobject!(vis[name][:robot]["leg4"], calf_4, body_mat)

	hip1 = setobject!(vis[name][:robot]["hip1"],
		GeometryBasics.Sphere(Point3f0(0), convert(Float32, 0.035)), body_mat)
	hip2 = setobject!(vis[name][:robot]["hip2"],
		GeometryBasics.Sphere(Point3f0(0), convert(Float32, 0.035)), body_mat)
	knee1 = setobject!(vis[name][:robot]["knee1"],
		GeometryBasics.Sphere(Point3f0(0), r), body_mat)
	knee2 = setobject!(vis[name][:robot]["knee2"],
		GeometryBasics.Sphere(Point3f0(0), r), body_mat)
	knee3 = setobject!(vis[name][:robot]["knee3"],
		GeometryBasics.Sphere(Point3f0(0), r), body_mat)
	knee4 = setobject!(vis[name][:robot]["knee4"],
		GeometryBasics.Sphere(Point3f0(0), r), body_mat)

	feet1 = setobject!(vis[name][:robot]["feet1"],
		GeometryBasics.Sphere(Point3f0(0), r), contact_mat)
	feet2 = setobject!(vis[name][:robot]["feet2"],
		GeometryBasics.Sphere(Point3f0(0), r), contact_mat)
	feet3 = setobject!(vis[name][:robot]["feet3"],
		GeometryBasics.Sphere(Point3f0(0), r), contact_mat)
	feet4 = setobject!(vis[name][:robot]["feet4"],
		GeometryBasics.Sphere(Point3f0(0), r), contact_mat)
	return nothing
end

function set_robot!(vis::Visualizer, model::Quadruped, q::AbstractVector;
		name::Symbol=:Quadruped, r=0.0205, offset=0.00)

	r = convert(Float32, r)
	p_shift = [0.0; offset; r]

	p = [q[1]; 0.0; q[2]] + p_shift

	k_torso = kinematics_1(model, q, body = :torso, mode = :ee)
	p_torso = [k_torso[1], 0.0, k_torso[2]] + p_shift

	k_thigh_1 = kinematics_1(model, q, body = :thigh_1, mode = :ee)
	p_thigh_1 = [k_thigh_1[1], 0.0, k_thigh_1[2]] + p_shift

	k_calf_1 = kinematics_2(model, q, body = :calf_1, mode = :ee)
	p_calf_1 = [k_calf_1[1], 0.0, k_calf_1[2]] + p_shift

	k_thigh_2 = kinematics_1(model, q, body = :thigh_2, mode = :ee)
	p_thigh_2 = [k_thigh_2[1], 0.0, k_thigh_2[2]] + p_shift

	k_calf_2 = kinematics_2(model, q, body = :calf_2, mode = :ee)
	p_calf_2 = [k_calf_2[1], 0.0, k_calf_2[2]] + p_shift

	k_thigh_3 = kinematics_2(model, q, body = :thigh_3, mode = :ee)
	p_thigh_3 = [k_thigh_3[1], 0.0, k_thigh_3[2]] + p_shift

	k_calf_3 = kinematics_3(model, q, body = :calf_3, mode = :ee)
	p_calf_3 = [k_calf_3[1], 0.0, k_calf_3[2]] + p_shift

	k_thigh_4 = kinematics_2(model, q, body = :thigh_4, mode = :ee)
	p_thigh_4 = [k_thigh_4[1], 0.0, k_thigh_4[2]] + p_shift

	k_calf_4 = kinematics_3(model, q, body = :calf_4, mode = :ee)
	p_calf_4 = [k_calf_4[1], 0.0, k_calf_4[2]] + p_shift

	settransform!(vis[name][:robot]["thigh1"], cable_transform(p, p_thigh_1))
	settransform!(vis[name][:robot]["leg1"], cable_transform(p_thigh_1, p_calf_1))
	settransform!(vis[name][:robot]["thigh2"], cable_transform(p, p_thigh_2))
	settransform!(vis[name][:robot]["leg2"], cable_transform(p_thigh_2, p_calf_2))
	settransform!(vis[name][:robot]["thigh3"], cable_transform(p_torso, p_thigh_3))
	settransform!(vis[name][:robot]["leg3"], cable_transform(p_thigh_3, p_calf_3))
	settransform!(vis[name][:robot]["thigh4"], cable_transform(p_torso, p_thigh_4))
	settransform!(vis[name][:robot]["leg4"], cable_transform(p_thigh_4, p_calf_4))
	settransform!(vis[name][:robot]["torso"], cable_transform(p, p_torso))
	settransform!(vis[name][:robot]["hip1"], MeshCat.Translation(p))
	settransform!(vis[name][:robot]["hip2"], MeshCat.Translation(p_torso))
	settransform!(vis[name][:robot]["knee1"], MeshCat.Translation(p_thigh_1))
	settransform!(vis[name][:robot]["knee2"], MeshCat.Translation(p_thigh_2))
	settransform!(vis[name][:robot]["knee3"], MeshCat.Translation(p_thigh_3))
	settransform!(vis[name][:robot]["knee4"], MeshCat.Translation(p_thigh_4))
	settransform!(vis[name][:robot]["feet1"], MeshCat.Translation(p_calf_1))
	settransform!(vis[name][:robot]["feet2"], MeshCat.Translation(p_calf_2))
	settransform!(vis[name][:robot]["feet3"], MeshCat.Translation(p_calf_3))
	settransform!(vis[name][:robot]["feet4"], MeshCat.Translation(p_calf_4))

	return nothing
end

function contact_point(model::Quadruped, q::AbstractVector)
	k_calf_1 = kinematics_2(model, q, body = :calf_1, mode = :ee)
	p_calf_1 = [k_calf_1[1], 0.0, k_calf_1[2]]

	k_calf_2 = kinematics_2(model, q, body = :calf_2, mode = :ee)
	p_calf_2 = [k_calf_2[1], 0.0, k_calf_2[2]]

	k_calf_3 = kinematics_3(model, q, body = :calf_3, mode = :ee)
	p_calf_3 = [k_calf_3[1], 0.0, k_calf_3[2]]

	k_calf_4 = kinematics_3(model, q, body = :calf_4, mode = :ee)
	p_calf_4 = [k_calf_4[1], 0.0, k_calf_4[2]]

	pc = [p_calf_1, p_calf_2, p_calf_3, p_calf_4]
	return pc
end

function build_meshrobot!(vis::Visualizer, model::Quadruped; name::Symbol=:Quadruped, α=1.0)
	default_background!(vis)

	urdf = joinpath(@__DIR__, "mesh", "a1.urdf")
	package_path = @__DIR__
	build_meshrobot!(vis, model, urdf, package_path; name=name, α=α)
end

function convert_config(model::Quadruped, q::AbstractVector)
    # Quadruped configuration
    # 1.  position long axis +x
    # 2.  position long axis +z
    # 3.  trunk rotation along -y
    # 4.  back  left  shoulder rotation along -y
    # 5.  back  left  elbow    rotation along -y
    # 6.  back  right shoulder rotation along -y
    # 7.  back  right elbow    rotation along -y
    # 8.  front right shoulder rotation along -y
    # 9.  front right elbow    rotation along -y
	# 10. front left  shoulder rotation along -y
	# 11. front left  elbow    rotation along -y

    # URDF configuration
    # 1.  front right clavicle rotation along +x
    # 2.  front left  clavicle rotation along +x
    # 3.  back  right clavicle rotation along +x
    # 4.  back  left  clavicle rotation along +x
    # 5.  front right shoulder rotation along +y
    # 6.  front left  shoulder rotation along +y
    # 7.  back  right shoulder rotation along +y
    # 8.  back  left  shoulder rotation along +y
    # 9.  front right elbow    rotation relative to shoulder along +y
    # 10. front left  elbow    rotation relative to shoulder along +y
    # 11. back  right elbow    rotation relative to shoulder along +y
    # 12. back  left  elbow    rotation relative to shoulder along +y
    q_ = zeros(12)
    x, z, θ = q[1:3]
    q_[5]  = -q[10] + θ - π/2
    q_[6]  = -q[8] + θ - π/2
	q_[7]  = -q[4] + θ - π/2
    q_[8]  = -q[6] + θ - π/2
    q_[9]  = +q[10]-q[11]
    q_[10] = +q[8]-q[9]
	q_[11] = +q[4]-q[5]
    q_[12] = +q[6]-q[7]

	r_foot = 0.02
	trunk_length = 2*0.183
	trunk_width = 2*0.132
    tform = compose(
		Translation(x , trunk_width/2, z+r_foot),
		compose(
			LinearMap(AngleAxis(π/2-θ, 0, 1.0, 0)),
			Translation(trunk_length/2, 0,0)
			)
		)
    return q_, tform
end


#
# Graphs have a collapsable header and plotting area
# The header has fields for:
#   horizontal range, vertical range
#   cursor position,
#
# A graph renders data from a data collection.

module Graphs

using CImGui
using CImGui.CSyntax
using CImGui.CSyntax.CStatic
using CImGui.GLFWBackend
using CImGui.OpenGLBackend
using CImGui.GLFWBackend.GLFW
using CImGui.OpenGLBackend.ModernGL
using CImGui: ImVec2, ImVec4, IM_COL32, ImU32, ImColor
using Printf

using ..Gui

const GRAPH_WINDOW_WIDTH = Gui.GUI_WIDTH - 10
const GRAPH_WINDOW_HEIGHT = 200

const MAX_VERTICAL_BARS = 250
const LINE_THICKNESS = 1.0

const GRAY = 64
const YELLOW = IM_COL32(255, 255, 0, 255)
const GREEN = IM_COL32(0, 255, 0, 255)
const GREEN_TRAN = IM_COL32(0, 255, 0, 128)
const WHITE_TRAN = IM_COL32(255, 255, 255, 100)
const WHITE = IM_COL32(255, 255, 255, 255)
const LIME_GREEN = IM_COL32(166, 255, 77, 255)
const BLUE = IM_COL32(0, 0, 255, 255)
const LIGHT_BLUE = IM_COL32(0, 169, 224, 255) # Pantone light blue
const ORANGE = IM_COL32(255, 128, 0, 255)
const GREY = IM_COL32(100, 100, 100, 255)
const LIGHT_GREY = IM_COL32(200, 200, 200, 255)

abstract type AbstractGraph end

export linear, lerp

# linear returns 0->1 for a "value" between min and max.
# Generally used to map from view/data-space to unit-space
function linear(min::Float64, max::Float64, value::Float64)
   	if min < 0
        return 1.0 - ((value - max) / (min - max))
    end

   	(value - min) / (max - min)
end

# lerp returns a the value between min and max given t = 0->1
# Call linear() first to get "t".
# Generally used to map from unit-space to window-space.
# A graph will then map to local-space of the graph.
function lerp(min::Float64, max::Float64, t::Float64)
    min * (1.0 - t) + max * t
end

# Map from sample-space to unit-space where unit-space is 0->1
function map_sample_to_unit(v::Float64, min::Float64, max::Float64)
    linear(min, max, v)
end

# Map from unit-space to window-space
function map_unit_to_window(v::Float64, min::Float64, max::Float64)
    lerp(min, max, v)
end

# Local = graph-space
function map_window_to_local(x::Float64, y::Float64, offsets::CImGui.LibCImGui.ImVec2)
    (offsets.x + x, offsets.y + y)
end

function scroll_velocity(scroll::Float64)
    sign(scroll) * exp(sign(scroll) * scroll)
end

include("spike_scatter_graph.jl")
include("soma_ap_fast_graph.jl")
include("soma_ap_slow_graph.jl")
include("soma_psp_graph.jl")
include("synapse_weights_graph.jl")
include("synapse_surge_graph.jl")
include("synapse_psp_graph.jl")
# include("synapse_input_graph.jl")

spikes_graph = Graphs.SpikeScatterGraph()
soma_apFast_graph = Graphs.SomaAPFastGraph()
soma_apSlow_graph = Graphs.SomaAPSlowGraph()
soma_psp_graph = Graphs.SomaPSPGraph()
synapse_weights_graph = Graphs.SynapseWeightGraph()
synapse_surge_graph = Graphs.SynapseSurgeGraph()
synapse_psp_graph = Graphs.SynapsePSPGraph()
# synapse_input_graph = Graphs.SynapticInputGraph()

end # Module ---------------------------------------


module MotionPlanningProject

println("hi")
struct Position
    x::Float64
    y::Float64
end

checkpoints = [Position(0, 1), Position(0, 0), Position(2, 0)]

allocation = [2, 3]

frequency = 1000 # Hz

function doit(checkpoints, allocation, frequency)
    n_time_steps = frequency * sum(allocation)
    print(n_time_steps)
    trajectory = [Position(0, 0) for _ in 1:n_time_steps]

end

doit(checkpoints, allocation, frequency)

robot_position=Position(0, 0)
print(robot_position.x)
print("\n")
end


using Test

using MathOptInterface
const MOI = MathOptInterface
const MOIT = MathOptInterface.Test
const MOIU = MathOptInterface.Utilities
const MOIB = MathOptInterface.Bridges

struct DummyBridge <: MOIB.Constraint.AbstractBridge end

@testset "AbstractBridge" begin
    model = MOIU.Model{Float64}()
    bridge = DummyBridge()
    attr = MOI.ConstraintPrimalStart()
    @test !MOI.supports(model, attr, typeof(bridge))
    @test_throws MOI.UnsupportedAttribute(attr) MOI.set(model, attr, bridge, 1.0)
    attr = MOI.ConstraintFunction()
    err = MOI.SetAttributeNotAllowed(attr)
    @test_throws err MOI.set(model, attr, bridge, MOI.EqualTo(1.0))
end


# test_functions.jl
# File containing functions that reduce the suze of the test files

@doc """
###FreeFormDeformation.getnWallFaces

Get the number of element faces that exist on a geometric face of a mesh
despite the name of the function

**Inputs**

* `mesh` : Pumi DG mesh
* `bc_nums` : Array of boundary condition numbers that define the surface over
              which the number of element faces needs to be computed

**Output**

* `nwall_faces` : Number of element faces(edges in 2D) on each geometric face
"""->

function getnWallFaces(mesh::AbstractDGMesh, bc_nums::AbstractArray{Int,1})

  # Prepare the wall coordinates array for mesh warping
  nwall_faces = zeros(Int,length(bc_nums))
  vtx_per_face = mesh.dim # only true for simplex elements
  for (idx, itr) in enumerate(bc_nums)
    start_index = mesh.bndry_offsets[itr]
    end_index = mesh.bndry_offsets[itr+1]
    idx_range = start_index:(end_index-1)
    bndry_facenums = sview(mesh.bndryfaces, idx_range) # faces on geometric edge i
    nwall_faces[idx] = length(bndry_facenums)
  end      # End for itr = 1:length(geomfaces)

  return nwall_faces
end

@doc """
###FreeFormDeformation.getUniqueVertexArray

Get a 2D array of coordinates of unique vertices for the LOCAL portion of the
entire mesh.

**Input**

* `mesh` : Pumi DG mesh

**Output**

* `volNodes` : 2D array of unique vertices that exist on an MPI rank.
               size = [3, n_local_vertices]

"""->

function getUniqueVertexArray(mesh::AbstractMesh{Tmsh}) where Tmsh

  volNodes = zeros(Tmsh, 3, mesh.numVert)
  for i = 1:mesh.numEl
    for j = 1:size(mesh.vert_coords,2)
      # Get the vertex numbering on the portion of mesh owned by the processor
      local_vertnum = mesh.element_vertnums[j,i]
      for k = 1:mesh.dim
        volNodes[k, local_vertnum] = mesh.vert_coords[k,j,i] # mesh.element_vertnums
      end
    end
  end

  return volNodes
end

@doc """
###FreeFormDeformation.getUniqueWallCoordsArray

Get a 2D array of coordinates of unique vertices for the local portion of a
geometric face.

NOTE: Use this ONLY to compute the unique wall vertices on a given MPI rank.
      This function does not account for non-uniqueness that arises from the
      same vertex existing on 2 MPI ranks

**Inputs**

* `mesh` : Pumi DG mesh
* `bc_nums` : Array of boundary condition numbers that define hte surface over
              which the number of element faces needs to be computed

**Outputs**

* `wallCoords` : 2D array of coordinates of wall vertices. size = [3, total_vertices]

"""->

function getUniqueWallCoordsArray(mesh::AbstractMesh{Tmsh},
                            bc_nums::AbstractArray{Int,1}) where Tmsh

  vtx_arr = getUniqueVertexArray(mesh) # Get Unique vertex Array
  wallCoords = getUniqueWallCoordsArray(mesh, vtx_arr, bc_nums)

  return wallCoords
end

function getUniqueWallCoordsArray(mesh::AbstractMesh{Tmsh},
                            vtx_arr::AbstractArray{Tmsh,2},
                            bc_nums::AbstractArray{Int,1}) where Tmsh

  nface_verts = getLocalNumFaceVerts_unique(mesh, bc_nums)
  wallCoords = zeros(Tmsh, 3, nface_verts)

  local_vertnum_history = Int[]
  ctr = 1
  for (idx, itr) in enumerate(bc_nums)
    start_index = mesh.bndry_offsets[itr]
    end_index = mesh.bndry_offsets[itr+1]
    idx_range = start_index:(end_index-1)
    bndry_facenums = sview(mesh.bndryfaces, idx_range) # faces on geometric edge i
    nfaces = length(bndry_facenums)
    for i = 1:nfaces
      bndry_i = bndry_facenums[i]
      # get the local index of the vertices
      face_vtx_arr = mesh.topo.face_verts[:,bndry_i.face]
      for j = 1:length(face_vtx_arr)
        local_vertnum = mesh.element_vertnums[face_vtx_arr[j],bndry_i.element]
        if findfirst(local_vertnum_history, local_vertnum) == 0
          for k = 1:mesh.dim
            wallCoords[k,ctr] = vtx_arr[k, local_vertnum]
          end
          ctr += 1
        end
        push!(local_vertnum_history, local_vertnum)
      end     # for j = 1:length(face_vtx_arr)
    end       # for i = 1:nfaces
  end # End for itr = 1:length(bc_nums)

  return wallCoords
end

@doc """
###FreeFormDeformation.getGlobalUniqueWallCorrdsArray

Get a 2D array of coordinates of unique wall vertices that is owned by the MPI
rank. By default PUMI Mesh does not have ownership considering the unstructured
nature of the mesh where a vertex exists on several ranks. A sense of ownership
is created where the lowest rank containing the vertex owns it. The Array is
still distributed across the MPI ranks however the shared vertices appear on
only the lowest rank.

**Input**

* `mesh` : Pumi DG mesh
* `bc_nums` : Array of geometric faces (edges in 2D) over which the number
                 of element faces needs to be computed

**Output**

* `wallCoords` : 2D array of wall coordinates owned by the rank.
                 size = [3, n_owned_vertices]

"""->

function getGlobalUniqueWallCorrdsArray(mesh::AbstractMesh{Tmsh},
                                  bc_nums::AbstractArray{Int,1}) where Tmsh

  # Objective is to identify which MPI ranks have the boundary faces that lie
  # on a geometric face

  comm = MPI.COMM_WORLD
  comm_world = MPI.MPI_COMM_WORLD
  comm_self = MPI.COMM_SELF
  my_rank = MPI.Comm_rank(comm)
  comm_size = MPI.Comm_size(comm)

  wallCoords = zeros(Tmsh, 3, 0)
  vertex_coordinate = zeros(Tmsh,3)
  vtx_arr = getUniqueVertexArray(mesh) # Get Unique vertex Array

  # Gather nface_verts from all ranks
  nlocal_face_verts = getLocalNumFaceVerts_unique(mesh, bc_nums)
  nlocal_face_verts_arr = MPI.Allgather(nlocal_face_verts, comm)
  ranks_for_bndry_faces = Int[]
  for i = 1:length(nlocal_face_verts_arr)
    if nlocal_face_verts_arr[i] > 0
      push!(ranks_for_bndry_faces, i-1)
    end # End if nface_verts_arr[i] > 0
  end
  # println("rank = $my_rank, ranks_for_bndry_faces = $(ranks_for_bndry_faces)")
  if nlocal_face_verts != 0
    local_vertnum_history = Int[]
    for (idx, itr) in enumerate(bc_nums)
      start_index = mesh.bndry_offsets[itr]
      end_index = mesh.bndry_offsets[itr+1]
      idx_range = start_index:(end_index-1)
      bndry_facenums = sview(mesh.bndryfaces, idx_range)
      nfaces = length(bndry_facenums)
      for i = 1:nfaces
        bndry_i = bndry_facenums[i]
        face_vtx_arr = mesh.topo.face_verts[:,bndry_i.face]
        for j = 1:length(face_vtx_arr)
          local_vertnum = mesh.element_vertnums[face_vtx_arr[j],bndry_i.element]
          if findfirst(local_vertnum_history, local_vertnum) == 0
            # Now Check if this local vertex exists on multiple ranks
            if haskey(mesh.vert_sharing.rev_mapping, local_vertnum)

              # Check which all MPI ranks have this vertex and its position in
              # that rank's mesh.vert_sharing.
              rank_arr = first(mesh.vert_sharing.rev_mapping[local_vertnum])
              localIdx_arr = last(mesh.vert_sharing.rev_mapping[local_vertnum])
              # The Idea is that the lowest rank which has boundary face on the
              # geometric face always has ownership rights
              # if my_rank < minimum(rank_arr)
              #   for k = 1:mesh.dim
              #     vertex_coordinate[k] = vtx_arr[k, local_vertnum]
              #   end # End for k = 1:mesh.dim
              #   # Append a colum of coordinates
              #   wallCoords = hcat(wallCoords, vertex_coordinate)
              # else
              intersect_arr = intersect(rank_arr, ranks_for_bndry_faces)
              if intersect_arr == []
                for k = 1:mesh.dim
                  vertex_coordinate[k] = vtx_arr[k, local_vertnum]
                end # End for k = 1:mesh.dim
                # Append a colum of coordinates
                wallCoords = hcat(wallCoords, vertex_coordinate)
                # println("my_rank = $my_rank, rank_arr = $rank_arr, ranks_for_bndry_faces = $(ranks_for_bndry_faces)")
              elseif my_rank < minimum(intersect_arr)
                for k = 1:mesh.dim
                  vertex_coordinate[k] = vtx_arr[k, local_vertnum]
                end # End for k = 1:mesh.dim
                # Append a colum of coordinates
                wallCoords = hcat(wallCoords, vertex_coordinate)
              end # End if intersect_arr == []
              # end
            else # The vertex exist only on one MPI rank

              for k = 1:mesh.dim
                vertex_coordinate[k] = vtx_arr[k, local_vertnum]
              end
              # Append a colum of coordinates
              wallCoords = hcat(wallCoords, vertex_coordinate)

            end   # End if haskey(mesh.vert_sharing.rev_mapping, local_vertnum)
          end     # End if findfirst(local_vertnum_history, local_vertnum) == 0
          push!(local_vertnum_history, local_vertnum)
        end     # for j = 1:length(face_vtx_arr)
      end       # for i = 1:nfaces
    end # End for itr = 1:length(bc_nums)
  end

  MPI.Barrier(comm)

  return wallCoords
end
@doc """
### NOT NEEDED!!! ###
Keep this function around for future reference only

"""->
function getNeighborRank(mesh::AbstractDGMesh, bndry_face)

  npeers = length(mesh.peer_parts)
  mpi_neighbor_local_elem_no = zero(Int)
  ctr = 1
  for i = 1:npeers
    shared_interface_arr = mesh.shared_interfaces[i]
    for j = 1:length(shared_interface_arr)
      elementL = shared_interface_arr[j].elementL
      faceL = shared_interface_arr[j].faceL
      if elementL == bndry_face.element # && faceL == bndry_face.face
        mpi_neighbor_rank = mesh.peer_parts[i]
        return mpi_neighbor_rank
      end # End if elementL == local_elem_no
    end # End for j = 1:length(shared_interface_arr)
  end # End for i = 1:npeers

end

@doc """
###FreeFormDeformation.getLocalNumFaceVerts_unique

Obtain the number of unique face vertices that exist on one MPI rank. This
function DOES NOT take into account non-uniquness of vertices that exist across
multiple MPI ranks.

**Inputs**

* `mesh` : Pumi DG mesh
* `bc_nums : Array of boundary condition numbers that define the surface over
             which the number of element faces needs to be computed

**Output** :

* `ctr` : Number of unique vertices on a particular MPI rank
"""->
function getLocalNumFaceVerts_unique(mesh::AbstractDGMesh, bc_nums::AbstractArray{Int,1})

  local_vertnum_history = Int[]
  ctr = 0
  for (idx, itr) in enumerate(bc_nums)
    start_index = mesh.bndry_offsets[itr]
    end_index = mesh.bndry_offsets[itr+1]
    idx_range = start_index:(end_index-1)
    bndry_facenums = sview(mesh.bndryfaces, idx_range) # faces on geometric edge i
    nfaces = length(bndry_facenums)
    for i = 1:nfaces
      bndry_i = bndry_facenums[i]
      # get the local index of the vertices
      face_vtx_arr = mesh.topo.face_verts[:,bndry_i.face]
      for j = 1:length(face_vtx_arr)
        local_vertnum = mesh.element_vertnums[face_vtx_arr[j],bndry_i.element]
        if findfirst(local_vertnum_history, local_vertnum) == 0
          ctr += 1
        end
        push!(local_vertnum_history, local_vertnum)
      end # End for j = 1:length(face_vtx_arr)
    end   # End for i = 1:nfaces
  end

  return ctr
end

@doc """
###FreeFormDeformation.getWallCoords

Get a 2D array of coordinates that exist on a wall element face. This function
does not take into account uniqueness across adjacent element faces that share
a common vertex or across MPI ranks that share a vertex.

**Inputs**

* `mesh` : Pumi DG mesh
* `bc_nums` : Array of boundary condition numbers that define the surface over
              which the number of element faces needs to be computed

**Output**

* `wallCoords` : 2D array of coordinates of vertices that exist on a wall.
                 size = [3, sum(nwall_faces) \* vtx_per_face]

"""->

function getWallCoords(mesh::PumiMeshDG2, bc_nums::AbstractArray{Int, 1})

  nwall_faces = getnWallFaces(mesh, bc_nums)

  # Populate wallCoords
  vtx_per_face = mesh.dim # only true for simplex elements
  nWallCoords = sum(nwall_faces)*vtx_per_face
  wallCoords = zeros(3, nWallCoords)
  ctr = 1 # Counter for wall coordinates
  for (idx, itr) in enumerate(bc_nums)
    start_index = mesh.bndry_offsets[itr]
    end_index = mesh.bndry_offsets[itr+1]
    idx_range = start_index:(end_index-1)
    bndry_facenums = sview(mesh.bndryfaces, idx_range) # faces on geometric edge i
    nfaces = length(bndry_facenums)
    for i = 1:nfaces
      bndry_i = bndry_facenums[i]
      # get the local index of the vertices
      vtx_arr = mesh.topo.face_verts[:,bndry_i.face]
      for j = 1:length(vtx_arr)
        wallCoords[1:2, ctr] = mesh.vert_coords[:,vtx_arr[j],bndry_i.element]
        ctr += 1
      end  # End for j = 1:length(vtx_arr)
    end    # End for i = 1:nfaces
  end

  return wallCoords
end # End function getWallCoords


lattice = load_lattice(joinpath(@__DIR__, "GaAs20.json"))
atoms   = load_atoms(  joinpath(@__DIR__, "GaAs20.json"))
atoms   = attach_pseudos(atoms, Ga="hgh/pbe/ga-q3.hgh", As="hgh/pbe/as-q5.hgh")

cases = standard_cases()
cases["adaptive_03"] = (damping=DFTK.AdaptiveDamping(;α_trial_min=0.3, α_min=0.05), tol=1e-12)
cases["adaptive_04"] = (damping=DFTK.AdaptiveDamping(;α_trial_min=0.4, α_min=0.05), tol=1e-12)
cases["adaptive_05"] = (damping=DFTK.AdaptiveDamping(;α_trial_min=0.5, α_min=0.05), tol=1e-12)

function load_GaAs_20_potential(basis)
    guessfile = joinpath(@__DIR__, "GaAs20_initV.jld2")
    if !isfile(guessfile)
        ρ0 = guess_density(basis)
        _, ham = DFTK.energy_hamiltonian(basis, nothing, nothing; ρ=ρ0)
        V = DFTK.total_local_potential(ham)
        V .+= 3.0rand(Float64, size(V)...)

        jldopen(guessfile, "w") do file
            file["V"] = V
        end
    end

    jldopen(guessfile, "r") do file
        file["V"]
    end
end

# Load guess from file
config[@__DIR__] = (
    directory        = @__DIR__,
    lattice          = lattice,
    atoms            = atoms,
    magnetic_moments = [],
    #
    kwargs_model     = (smearing=Smearing.None(), temperature=0.0),
    Ecut             = 20,
    kgrid            = [4, 4, 1],
    supersampling    = 1.5,
    potential        = load_GaAs_20_potential,
    kwargs_scf       = (; ),
    mixing           = SimpleMixing(),
    cases            = cases,
)


function orderTransitions!( mpSim::MPsim )

    transitions = vcat( getindex.( Ref( mpSim.transitionsByName ),
        collect( keys( mpSim.transitionsByName ) ) )... )
    reflextrans = filter( trans -> trans.sourceNode == trans.targetNode,
        transitions )
    filter!( trans -> trans ∉ reflextrans, transitions )
    recruitment = vcat( getindex.( Ref( mpSim.recruitmentByName ),
        collect( keys( mpSim.recruitmentByName ) ) )... )

    transGraph = MetaDiGraph( length( transitions ) + length( recruitment ) )

    # Don't take any action if there are no transitions.
    nv( transGraph ) == 0 && return
    jj = 1

    for trans in transitions
        set_prop!( transGraph, jj, :name, trans.name )
        set_prop!( transGraph, jj, :source, trans.sourceNode )
        set_prop!( transGraph, jj, :target,
            trans.isOutTransition ? "OUT" : trans.targetNode )
        set_prop!( transGraph, jj, :trans, trans )
        jj += 1
    end  # for trans in transitions

    for rec in recruitment
        set_prop!( transGraph, jj, :name, rec.name )
        set_prop!( transGraph, jj, :source, "IN" )
        set_prop!( transGraph, jj, :target, rec.targetNode )
        set_prop!( transGraph, jj, :trans, rec )
        jj += 1
    end  # for rec in recruitment

    transitions = vcat( transitions, recruitment )

    # Out transitions have priority over in transitions.
    for node in keys( mpSim.baseNodeList )
        outTransitions = findall( trans -> ( trans isa Transition ) &&
            ( trans.sourceNode == node ), transitions )
        inTransitions = findall( trans -> trans.targetNode == node,
            transitions )

        for inTrans in inTransitions, outTrans in outTransitions
            add_edge!( transGraph, outTrans, inTrans )
        end  # for inTrans in inTransitions
    end  # for node in mpSim.baseNodeList

    # Prioritise transitions by transition type.
    names = get_prop.( Ref( transGraph ), vertices( transGraph ), :name )
    uniqueNames = unique( names )
    sources = get_prop.( Ref( transGraph ), vertices( transGraph ), :source )
    uniqueSources = unique( sources )
    targets = get_prop.( Ref( transGraph ), vertices( transGraph ), :target )
    uniqueTargets = unique( targets )
    transOrder = mpSim.transitionTypeOrder
    nodeOrder = mpSim.baseNodeOrder

    for source in uniqueSources
        transFromSource = findall( sources .== source )
        length(transFromSource) < 2 && continue

        for trans1 in transFromSource, trans2 in transFromSource
            if haskey( transOrder, names[trans1] ) &&
                haskey( transOrder, names[trans2] ) &&
                ( transOrder[names[trans1]] <
                    transOrder[names[trans2]] )
                add_edge!( transGraph, trans1, trans2 )
            end  # if haskey( transOrder, names[trans1] ) && ...
        end  # for trans1 in transFromSource, trans2 in transFromSource

        transNames = names[transFromSource]

        for name in uniqueNames
            transNameSource = transFromSource[transNames .== name]
            length(transNameSource) < 2 && continue

            for trans1 in transNameSource, trans2 in transNameSource
                if haskey( nodeOrder, targets[trans1] ) &&
                    haskey( nodeOrder, targets[trans2] ) &&
                    ( nodeOrder[targets[trans1]] <
                        nodeOrder[targets[trans2]] )
                    add_edge!( transGraph, trans1, trans2 )
                end  # if haskey( nodeOrder, targets[trans1] ) && ...
            end  # for trans1 in transNameSource, trans2 in transNameSource
        end  # for name in uniqueNames
    end  # for source in uniqueSources

    for target in uniqueTargets
        transToTarget = findall( targets .== target )
        length(transToTarget) < 2 && continue

        for trans1 in transToTarget, trans2 in transToTarget
            if haskey( transOrder, names[trans1] ) &&
                haskey( transOrder, names[trans2] ) &&
                ( transOrder[names[trans1]] <
                    transOrder[names[trans2]] )
                add_edge!( transGraph, trans1, trans2 )
            end  # if haskey( transOrder, names[trans1] ) && ...
        end  # for trans1 in transToTarget, trans2 in transToTarget

        transNames = names[transToTarget]

        for name in uniqueNames
            transNameTarget = transToTarget[transNames .== name]

            length( transNameTarget ) < 2 && continue

            for trans1 in transNameTarget, trans2 in transNameTarget
                if haskey( nodeOrder, sources[trans1] ) &&
                    haskey( nodeOrder, sources[trans2] ) &&
                    ( nodeOrder[sources[trans1]] <
                        nodeOrder[sources[trans2]] )
                    add_edge!( transGraph, trans1, trans2 )
                end  # if haskey( nodeOrder, sources[trans1] ) && ...
            end  # for trans1 in transNameTarget, trans2 in transNameTarget
        end  # for name in uniqueNames
    end  # for target in uniqueTargets

    # println( "Is the graph connected? ", is_connected( transGraph ) )
    # println( "Is the graph cyclic? ", is_cyclic( transGraph ) )

    subgraphNodes = connected_components( transGraph )
    graphRanks = zeros( vertices( transGraph ) )

    for nodeList in subgraphNodes
        subgraph = induced_subgraph( transGraph, nodeList )[1]
        generateGraphNodeRanks!( subgraph )
        graphRanks[nodeList] = get_prop.( Ref( subgraph ),
            vertices( subgraph ), :rank )
    end  # for nodeList in subgraphNodes

    setfield!.( transitions, :priority, -Int.( graphRanks ) )
    mpSim.nPriorities = maximum( graphRanks )

end  # orderTransitions!( mpSim )

@resumable function transitionProcess( sim::Simulation, transition::Transition,
    mpSim::MPsim )

    processTime = Dates.Millisecond( 0 )
    tStart = now()

    # Initialize the schedule.
    priority = transition.priority
    priorityShift = ( transition.hasPriority ? 0 : 1 ) * mpSim.nPriorities
    maxAttempts = transition.maxAttempts == 0 ?
        length( transition.probabilityList ) : transition.maxAttempts
    nAttempts = Dict{String,Int}()

    timeToWait = transition.offset

    # If an initial population snapshot is uploaded, and it contains zero-time
    #   events, don't execute zero-time transition.
    if ( timeToWait == 0 ) && !mpSim.isVirgin
        timeToWait += transition.freq
    end  # if ( timeToWait == 0 ) && ...

    while now( sim ) + timeToWait <= mpSim.simLength
        processTime += now() - tStart
        @yield( timeout( sim, timeToWait, priority = priority -
            ( transition.minFlux == 0 ? priorityShift : 0 ) ) )
        tStart = now()
        timeToWait = transition.freq

        # Identify all persons who're in the start state long enough and are not
        #   already going to transition to another state.
        # eligibleIDs = getEligibleIDs( transition, nAttempts, maxAttempts,
        #     mpSim )
        # checkedIDs = checkExtraConditions( transition, eligibleIDs, mpSim )
        checkedIDs = getEligibleIDs( transition, nAttempts, maxAttempts, mpSim )
        updateAttemptsAndIDs!( nAttempts, maxAttempts, checkedIDs )

        # Assign a modified probability to each person.
        transitionLevels = determineTransitionLevels( transition, checkedIDs,
            nAttempts )

        # If the transition has a minimum flux, transition those IDs first, and
        #   set the priority of the rest of the transition to the proper level.
        if transition.minFlux > 0
            mandatoryIDs = determineMandatoryIDs( transition, checkedIDs,
                transitionLevels )
            performTransitions( mpSim, transition, mandatoryIDs )
            @yield( timeout( sim, 0, priority = priority - priorityShift ) )
            sourceNode = mpSim.baseNodeList[transition.sourceNode]
            filter!( id -> haskey( sourceNode.inNodeSince, id ), checkedIDs )
        end  # if transition.minFlux > 0

        transitionIDs = determineAdditionalIDs( transition, checkedIDs,
            transitionLevels, mpSim )
        performTransitions( mpSim, transition, transitionIDs )
    end  # while now( sim ) + timeToWait <= mpSim.simLength

    processTime += now() - tStart

    if mpSim.showInfo
        println( "Transition process for '", transition.name, "' took ",
            processTime.value / 1000, " seconds." )
    end  # if mpSim.showOutput

end  # transitionProcess( sim, transition, mpSim )

function getEligibleIDs( transition::Transition, nAttempts::Dict{String,Int},
    maxAttempts::Int, mpSim::MPsim )
    sourceNode = mpSim.baseNodeList[transition.sourceNode]
    eligibleIDs = collect( keys( sourceNode.inNodeSince ) )

    # Check if attempts have been exhausted (important if transition doesn't
    #   fire after exhaustion of attempts).
    # The first condition means 0 attempts so far.
    maxAttempts > 0 && filter!( id -> !haskey( nAttempts, id ) ||
        ( nAttempts[id] < maxAttempts ), eligibleIDs )
    isempty(eligibleIDs) && return eligibleIDs

    # Check extra conditions.
    queryCmd = string( "SELECT *, ageAtRecruitment + ", now(mpSim),
        " - timeEntered age, ", now(mpSim), " - timeEntered tenure, ",
        now(mpSim), " - inNodeSince `time in node` FROM `",
        mpSim.persDBname, "` WHERE ",
        "\n    `", mpSim.idKey, "` IN ('", join( eligibleIDs, "', '" ), "')",
        "\n        AND `time in node` > 0",
        join(string.( "\n        AND ",
            conditionToSQLite.( transition.extraConditions, Ref(mpSim) ) )) )
    Vector{String}( DataFrame(DBInterface.execute( mpSim.simDB,
        queryCmd ))[:, Symbol(mpSim.idKey)] )
end  # getEligibleIDs( transition, nAttempts, maxAttempts, mpSim )
#=
function getEligibleIDs( transition::Transition, nAttempts::Dict{String,Int},
    maxAttempts::Int, mpSim::MPsim )::Vector{String}

    sourceNode = mpSim.baseNodeList[transition.sourceNode]
    eligibleIDs = collect( keys( sourceNode.inNodeSince ) )

    # Check if attempts have been exhausted (important if transition doesn't
    #   fire after exhaustion of attempts).
    if maxAttempts > 0
        # The first condition means 0 attempts so far.
        filter!( id -> !haskey( nAttempts, id ) ||
            ( nAttempts[id] < maxAttempts ), eligibleIDs )
    end  # if maxAttempts > 0

    # filter!( id -> now( mpSim ) > sourceNode.inNodeSince[id], eligibleIDs )  # Needed???

    for cond in filter( cond -> cond.attribute == "time in node",
        transition.extraConditions )
        filter!( id -> cond.operator( now( mpSim ) -
            sourceNode.inNodeSince[id], cond.value ), eligibleIDs )
    end  # for cond in filter( ...

    return eligibleIDs

end  # getEligibleIDs( transition, mpSim )

function checkExtraConditions( transition::Transition,
    eligibleIDs::Vector{String}, mpSim::MPsim )

    if isempty( eligibleIDs ) || isempty( transition.extraConditions )
        return eligibleIDs
    end  # if isempty( eligibleIDs ) || ...

    # Check extra conditions.
    queryCmd = string( "SELECT *, ageAtRecruitment + ", now( mpSim ),
        " - timeEntered age, ", now( mpSim ), " - timeEntered tenure FROM `",
        mpSim.persDBname, "` WHERE ",
        "\n    `", mpSim.idKey, "` IN ('", join( eligibleIDs, "', '" ), "')" )
    eligibleIDsState = DataFrame( DBInterface.execute( mpSim.simDB, queryCmd ) )
    checkedIDs = Vector{String}( eligibleIDsState[:, Symbol( mpSim.idKey )] )
    isIDokay = trues( length( checkedIDs ) )

    for cond in filter( cond -> cond.attribute != "time in node",
        transition.extraConditions )
        isIDokay = isIDokay .& map( ii -> cond.operator(
            eligibleIDsState[:, Symbol( cond.attribute )][ii], cond.value ),
            eachindex( isIDokay ) )
    end  # for cond in filter( ... )

    return checkedIDs[isIDokay]

end  # checkExtraConditions( transition, eligibleIDs, mpSim )
=#

function updateAttemptsAndIDs!( nAttempts::Dict{String,Int}, maxAttempts::Int,
    eligibleIDs::Vector{String} )

    # Increase number of attempts for all eligible IDs. 0 attempts means
    #   the personnel member has already executed the transition
    #   succesfully while stil being eligible for it.
    for id in eligibleIDs
        if haskey( nAttempts, id ) && ( ( maxAttempts == -1 ) ||
            ( nAttempts[id] <= maxAttempts ) ) && ( nAttempts[id] > 0 )
            nAttempts[id] += 1
        else
            nAttempts[id] = 1
        end  # if haskey( nAttempts, id ) && ...
    end  # for id in eligibleIDs

end  # updateAttemptsAndIDs!( nAttempts, eligibleIDs )

function determineTransitionLevels( transition::Transition,
    eligibleIDs::Vector{String}, nAttempts::Dict{String,Int} )

    probDict = Dict{String,Float64}()

    if isempty( eligibleIDs )
        return probDict
    end  # if isempty( eligibleIDs )

    # The probabilities of undergoing the transition for each person.
    tresholds =  transition.probabilityList[
        min.( length( transition.probabilityList ),
        getindex.( Ref( nAttempts ), eligibleIDs ) )]
    modProbs = rand( transition.probRNG, length( eligibleIDs ) ) ./ tresholds
    setindex!.( Ref( probDict ), modProbs, eligibleIDs )
    return probDict

end  # determineTransitionLevels( trans, eligibleIDs, nAttempts )

function determineMandatoryIDs( transition::Transition,
    eligibleIDs::Vector{String}, transitionLevels::Dict{String,Float64} )

    if length( eligibleIDs ) <= transition.minFlux
        return eligibleIDs
    end  # if length( eligibleIDs ) <= transition.minFlux

    # Get the min flux IDs which have the smallest value for their transLevel.
    modProbs = getindex.( Ref( transitionLevels ), eligibleIDs )
    return eligibleIDs[sortperm( modProbs )[1:(transition.minFlux)]]

end  # determineMandatoryIDs( transition, eligibleIDs, transLevels )

function determineAdditionalIDs( transition::Transition,
    eligibleIDs::Vector{String}, transitionLevels::Dict{String,Float64},
    mpSim::MPsim )

    if transition.maxFlux == transition.minFlux
        return Vector{String}()
    end  # if transition.maxFlux == transition.minFlux

    if isempty( eligibleIDs )
        return eligibleIDs
    end  # if isempty( eligibleIDs )

    # Determine the max number of personnel members left to transfer.
    maxToTransfer = transition.maxFlux == -1 ? length( eligibleIDs ) :
        transition.maxFlux - transition.minFlux
    vacancies = -1

    targetNode = transition.isOutTransition ? nothing :
        mpSim.baseNodeList[transition.targetNode]

    if !transition.isOutTransition && !transition.hasPriority &&
        ( transition.sourceNode != transition.targetNode ) &&
        ( targetNode.target >= 0 )
        vacancies = max( 0, targetNode.target -
            length( targetNode.inNodeSince ) )
    end  # if !( transition.isOutTransition ) && ...

    if vacancies >= 0
        maxToTransfer = min( maxToTransfer, vacancies )
    end  # if vacancies >= 0

    if maxToTransfer == 0
        return Vector{String}()
    end  # if maxToTransfer == 0

    modProbs = getindex.( Ref( transitionLevels ), eligibleIDs )
    hasPassed = modProbs .< 1

    # If the number of people who have passed the test is smaller than the
    #   number of people who can transfer, return the list of people who passed.
    if count( hasPassed ) <= maxToTransfer
        return eligibleIDs[hasPassed]
    end  # if sum( hasPassed ) <= maxToTransfer

    # Otherwise, only select people with highest scores.
    return eligibleIDs[sortperm( modProbs )[1:maxToTransfer]]

end  # determineAdditionalIDs( transition, eligibleIDs, transitionLevels,
     #   mpSim )

function performTransitions( mpSim::MPsim, transition::Transition,
    idList::Vector{String} )

    if isempty( idList )
        return
    end  # if isempty( idList )

    if transition.isOutTransition
        removePersons( idList, transition.sourceNode, transition.name, mpSim )
    else
        # Execute the transition for the selected personnel members and
        #   perform necessary updates.
        executeTransitions( transition, idList, mpSim )

        if transition.sourceNode !== transition.targetNode
            updateNodes( transition, idList, mpSim )
        end  # if transition.sourceNode !== transition.targetNode
    end  # if transition.isOutTrans

end  # performTransitions( mpSim, transition, idList )

function executeTransitions( transition::Transition, idList::Vector{String},
    mpSim::MPsim )

    # Get current state of the personnel members undergoing the
    #   transition.
    queryCmd = string( "SELECT * FROM `", mpSim.persDBname, "` WHERE",
        "\n    `", mpSim.idKey, "` IN ('", join( idList, "', '" ), "')" )
    idListState = DataFrame( DBInterface.execute( mpSim.simDB, queryCmd ) )
    tmpIDs = idListState[:, Symbol( mpSim.idKey )]

    # Make changes in personnel database.
    persChangesCmd = [string( "currentNode = '", transition.targetNode, "'" ),
        string( "inNodeSince = ", now(mpSim) )]
    changedAttrVals = Dict{String,String}()

    # Target node attributes.
    if !transition.isOutTransition &&
        ( transition.sourceNode !== transition.targetNode )
        targetNode = mpSim.baseNodeList[transition.targetNode]
        changedAttrVals = deepcopy( targetNode.requirements )
    end  # if !transition.isOutTransition && ...

    # Extra changes because of the transition.
    changedAttrs = collect( keys( transition.extraChanges ) )
    setindex!.( Ref( changedAttrVals ),
        getindex.( Ref( transition.extraChanges ), changedAttrs ),
        changedAttrs )

    # Change attributes in personnel database.
    changedAttrs = collect( keys( changedAttrVals ) )
    append!( persChangesCmd, string.( "`", changedAttrs, "` = '",
        getindex.( Ref( changedAttrVals ), changedAttrs ), "'" ) )
    persChangesCmd = string( "UPDATE `", mpSim.persDBname, "`",
        "\n    SET ", join( persChangesCmd, ", " ),
        "\n    WHERE `", mpSim.idKey, "` IN ('", join( tmpIDs, "', '" ), "')" )
    DBInterface.execute( mpSim.simDB, persChangesCmd )

    # Record attribute changes in history database.
    histChangesCmd = Vector{String}()

    for attr in changedAttrs
        oldAttrValues = idListState[:, Symbol( attr )]
        changedIDs = tmpIDs[oldAttrValues .!= changedAttrVals[attr]]

        if !isempty( changedIDs )
            push!( histChangesCmd, join( string.( "\n    ('", changedIDs,
                "', '", attr, "', ", now( mpSim ), ", '",
                changedAttrVals[attr], "')" ), ", " ) )
        end  # if !isempty( changedIDs )
    end  # for attr in changedAttrs

    if !isempty( histChangesCmd )
        histChangesCmd = string( "INSERT INTO `", mpSim.histDBname, "` (`",
            mpSim.idKey, "`, attribute, timeIndex, value) VALUES",
            join( histChangesCmd, "," ) )
        DBInterface.execute( mpSim.simDB, histChangesCmd )
    end  # if !isempty( histChangesCmd )

end  # executeTransitions( trans, idList, mpSim )

function updateNodes( transition::Transition, transIDs::Vector{String},
    mpSim::MPsim )

    # Update the inNodeSince fields of the source and target nodes.
    sourceNode = mpSim.baseNodeList[transition.sourceNode]
    tName = transition.isOutTransition ? "NULL" :
        string( "'", transition.targetNode, "'" )
    delete!.( Ref( sourceNode.inNodeSince ), transIDs )

    if !transition.isOutTransition
        targetNode = mpSim.baseNodeList[transition.targetNode]
        setindex!.( Ref( targetNode.inNodeSince ), now( mpSim ), transIDs )
    end  # if !transition.isOutTransition

    # Update the database of node transitions.
    transCmd = string.( "\n    ('", transIDs, "', ", now( mpSim ), ", '",
        transition.name, "', '", transition.sourceNode, "', ", tName, ")" )
    transCmd = string( "INSERT INTO `", mpSim.transDBname, "` (`", mpSim.idKey,
        "`, timeIndex, transition, sourceNode, targetNode) VALUES",
        join( transCmd, "," ) )
        DBInterface.execute( mpSim.simDB, transCmd )

    # Update the expected time of attrition.
    updateTimeOfAttrition( transIDs, transition.targetNode, mpSim )

end  # function updateNodes( transition, transIDs, mpSim )

function updateTimeOfAttrition( transIDs::Vector{String}, targetName::String,
    mpSim::MPsim  )

    # Update the expected times to attrition.
    targetNode = mpSim.baseNodeList[targetName]
    attrition = mpSim.attritionSchemes[targetNode.attrition]
    timeOfAttrition = now( mpSim ) .+ generateTimeToAttrition( attrition,
        length( transIDs ) )
    timeOfAttrition = map( toa -> toa == +Inf ? "NULL" : toa, timeOfAttrition )

    updateCmds = string.( "UPDATE `", mpSim.persDBname, "`",
        "\n    SET expectedAttritionTime = ", timeOfAttrition,
        "\n    WHERE `", mpSim.idKey, "` IS '", transIDs, "'" )
    SQLite.execute.( Ref( mpSim.simDB ), updateCmds )

    # Check if any of the new attrition times occur before the next attrition
    #   process check.
    # newProcessCheckTime = ceil( now( mpSim ) / mpSim.attritionTimeSkip ) *
    #     mpSim.attritionTimeSkip

    # for ii in eachindex( idList )
    #     if newAttrTimeList[ii] <= newProcessCheckTime
    #         executeAttritionProcess( mpSim.sim, idList[ii],
    #             newAttrTimeList[ii], mpSim )
    #     end  # if newAttrTimeList[ii] <= newProcessCheckTime
    # end  # for ii in eachindex( idList )

end  # updateTimeOfAttrition( transIDs, targetName, mpSim )

include(joinpath( simPrivPath, "graphops.jl" ))


using SeisDownload
using Test

using SeisIO, Dates #Please load SeisIO here to correctly define type of structure "SeisData"

@testset "SeisDownload.jl" begin
    # Write your own tests here.

    #==================================================#
    # Input Parameters
    #NP = 1 # number of processor
    MAX_MEM_PER_CPU = 2.0 # [GB] maximum allocated memory for one cpu
    DownloadType = "Noise" # Choise of "Noise" or "Earthquake"

    network     = ["BP"]
    station     = ["LCCB"]
    #station = ["CCRB","EADB","FROB","GHIB","JCNB","JCSB","LCCB","MMNB","SCYB","SMNB","VARB","VCAB"]

    location    = [""]
    channel     = ["BP1"]
    datacenter  = "FDSN" #Data center
    src         = "NCEDC"

    # Time info for Noise case
    starttime   = DateTime(2004,9,25,0,0,0)
    endtime     = DateTime(2004,9,25,2,0,0)

    IsLocationBox = false
    method  = "FDSN" # Method to download data.
    datasource = "NCEDC" # currently, only one src can be specified.

    DL_time_unit = 3600 * 1 #3600 * 24 # Download tiem unit [s] more than one day is better to avoid artifacts of response removal

    IsResponseRemove = false #whether instrumental response is removed or not
    pre_filt    = (0.001, 0.002, 10.0, 20.0) #prefilter tuple used obspy remove_response: taper between f1 and f2, f3 and f4 with obspy

    fodir       = "./dataset"
    foname      = "BPnetwork" # data is saved at ./dataset/$foname.jld2
    #==================================================#

    # store metadata in Dictionary
    # This can be customized by users

    stationlist       = String[]
    stationmethod    = String[]
    stationsrc        = String[]
    for i=1:length(network)
        for j=1:length(station)
            for k=1:length(location)
                for l=1:length(channel)
                    stationname = join([network[i], station[j], location[k], channel[l]], ".")
                    push!(stationlist, stationname)

                    #Here should be improved for multiple seismic network; we have to make
                    #proper conbination of request station and data server.
                    push!(stationmethod, method)
                    push!(stationsrc, datasource)
                end
            end
        end
    end

    stationinfo = Dict(["stationlist" => stationlist, "stationmethod" => stationmethod, "stationsrc" => stationsrc])

    mkpath(fodir)
    fopath=joinpath(fodir, foname*".jld2")

    #if lat-log box or not
    IsLocationBox ? reg=locationbox : reg=[]
    IsResponseRemove ? pre_filt = pre_filt : pre_filt = []

    InputDictionary = Dict([
            "DownloadType"=> DownloadType,
            "stationinfo" => stationinfo,
            "starttime"   => starttime,
            "endtime"     => endtime,
            "DL_time_unit"=> DL_time_unit,
            "IsLocationBox"   => IsLocationBox,
            "reg"             => reg,
            "IsResponseRemove"=> IsResponseRemove,
            "pre_filt"        => pre_filt,
            "fopath"          => fopath
        ])

    # mass request with input Dictionary
    @test 0 == seisdownload(InputDictionary, MAX_MEM_PER_CPU=float(MAX_MEM_PER_CPU))
end


@testset "gewekediag.jl" begin
    samples = randn(100)

    @testset "results" begin
        @test @inferred(gewekediag(samples)) isa
              NamedTuple{(:zscore, :pvalue),Tuple{Float64,Float64}}
    end

    @testset "exceptions" begin
        for x in (-0.3, 0, 1, 1.2)
            @test_throws ArgumentError gewekediag(samples; first=x)
            @test_throws ArgumentError gewekediag(samples; last=x)
        end
        @test_throws ArgumentError gewekediag(samples; first=0.6, last=0.5)
    end
end


"""
    const PointType = UInt8

Stores certain information about a grid point via bit-flags.

Right now, there are:

    const update_bit      = 0x01
    const undepleted_bit  = 0x02
    const pn_junction_bit = 0x04
    const bulk_bit     = 0x08

## Examples

How to get information out of a `PointType` variable `point_type`:
1. `point_type & update_bit == 0` -> do not update this point (for fixed points)
2. `point_type & update_bit >  0` -> do update this point
3. `point_type & undepleted_bit > 0` -> this point is undepleted
4. `point_type & pn_junction_bit > 0` -> this point belongs to the solid state detector, meaning that it is in the volume of the n-type or p-type material.
5. `point_type & bulk_bit > 0` -> this point is only surrounded by points marked as `pn_junction_bit`
"""
const PointType       = UInt8

# Point types for electric potential calculation
const update_bit      = 0x01 # parse(UInt8, "00000001", base=2) # 1 -> do update; 0 -> do not update
const undepleted_bit  = 0x02 # parse(UInt8, "00000010", base=2) # 0 -> depleted point; 1 -> undepleted point
const pn_junction_bit = 0x04 # parse(UInt8, "00000100", base=2) # 0 -> point is not part of pn-junction; 1 -> point is part of the pn-junction
const bulk_bit     = 0x08 # parse(UInt8, "00001000", base=2) # 0 -> point is surrounded by points that do not belong to the pn-junction; 1 -> point is only surrounded by points in the pn-junction

is_pn_junction_point_type(p::PointType) = p & pn_junction_bit > 0
is_undepleted_point_type(p::PointType) = p & undepleted_bit > 0
is_fixed_point_type(p::PointType) = p & update_bit == 0
"""
    struct PointTypes{T, N, S, AT} <: AbstractArray{T, N}

Information about the grid points used to calculate the [`ElectricPotential`](@ref)
stored via bit-flags. Data is stored as [`PointType`](@ref) which is an `UInt8`.

## Parametric types
* `T`: Element type of `grid.axes`.
* `N`: Dimension of the `grid` and `data` array.
* `S`: Coordinate system (`Cartesian` or `Cylindrical`).
* `AT`: Axes type.

## Fields
* `data::Array{PointType, N}`: Array containing the point type values at the discrete points of the `grid`.
* `grid::Grid{T, N, S, AT}`: [`Grid`](@ref) defining the discrete points for which the point types are determined.

See also [`PointType`](@ref).
"""
struct PointTypes{T, N, S, AT} <: AbstractArray{T, N}
    data::Array{PointType, N}
    grid::Grid{T, N, S, AT}
end

@inline size(point_types::PointTypes{T, N, S}) where {T, N, S} = size(point_types.data)
@inline length(point_types::PointTypes{T, N, S}) where {T, N, S} = length(point_types.data)
@inline getindex(point_types::PointTypes{T, N, S}, I::Vararg{Int, N}) where {T, N, S} = getindex(point_types.data, I...)
@inline getindex(point_types::PointTypes{T, N, S}, i::Int) where {T, N, S} = getindex(point_types.data, i)
@inline getindex(point_types::PointTypes{T, N, S}, s::Symbol) where {T, N, S} = getindex(point_types.grid, s)
@inline getindex(point_types::PointTypes{T, N, S}, pt::AbstractCoordinatePoint{T}) where {T, N, S} = point_types.data[find_closest_gridpoint(pt, point_types.grid)...]

function in(pt::AbstractCoordinatePoint{T}, point_types::PointTypes{T, 3, S})::Bool where {T <: SSDFloat, S}
    cpt = _convert_point(pt, S)
    point_type::PointType = point_types[cpt]
    return point_type & bulk_bit > 0
end

function mark_bulk_bits!(point_types::Array{PointType, 3})
    i1max, i2max, i3max = size(point_types)
    for i1 in Base.OneTo(i1max), i2 in Base.OneTo(i2max), i3 in Base.OneTo(i3max)
        (point_types[i1,i2,i3] & pn_junction_bit == 0) && continue
        point_types[i1,i2,i3] += bulk_bit * begin
            in_bulk = true
            for j1 in max(i1-1,1):min(i1+1,i1max), j2 in max(i2-1,1):min(i2+1,i2max), j3 in max(i3-1,1):min(i3+1,i3max)
                in_bulk &= point_types[j1,j2,j3] & pn_junction_bit > 0
                !in_bulk && break
            end
            in_bulk
        end
    end
    point_types
end

"""
    is_depleted(point_types::PointTypes)::Bool

Returns `true` if all [`PointType`](@ref) values of
the [`PointTypes`](@ref) of a [`Simulation`](@ref) are marked as depleted
and `false` if any point in the [`PointTypes`](@ref) is marked as undepleted.

It can be used to determine whether the [`SolidStateDetector`](@ref) is
depleted at the provided bias voltage.

## Arguments
* `point_types::PointTypes`: [`PointTypes`](@ref) of a [`Simulation`](@ref).

## Examples
```julia
is_depleted(sim.point_types)
```
"""
is_depleted(point_types::PointTypes)::Bool =
    !any(b -> undepleted_bit & b > 0, point_types.data)

"""
    get_active_volume(point_types::PointTypes{T}) where {T}

Returns an approximation of the active volume of the detector by summing up the cell volumes of
all cells marked as depleted.

## Arguments
* `point_types::PointTypes{T}`: Point types of a [`Simulation`](@ref).

## Examples
    get_active_volume(sim.point_types)

!!! note
    Only `φ`-symmetries are taken into account.
"""
function get_active_volume(point_types::PointTypes{T, 3, Cylindrical}) where {T}
    active_volume::T = 0

    only_2d::Bool = length(point_types.grid.axes[2]) == 1
    cyclic::T = point_types.grid.axes[2].interval.right

    r_ext::Vector{T} = get_extended_ticks(point_types.grid.axes[1])
    φ_ext::Vector{T} = get_extended_ticks(point_types.grid.axes[2])
    z_ext::Vector{T} = get_extended_ticks(point_types.grid.axes[3])

    mpr::Vector{T} = midpoints(r_ext)
    mpφ::Vector{T} = midpoints(φ_ext)
    mpz::Vector{T} = midpoints(z_ext)
    Δmpφ::Vector{T} = diff(mpφ)
    Δmpz::Vector{T} = diff(mpz)
    Δmpr_squared::Vector{T} = T(0.5) .* ((mpr[2:end].^2) .- (mpr[1:end-1].^2))
    if r_ext[2] == 0
        Δmpr_squared[1] = T(0.5) * (mpr[2]^2)
    end

    isclosed::Bool = typeof(point_types.grid.axes[2].interval).parameters[2] == :closed
    for iz in eachindex(point_types.grid.axes[3])
        if !isclosed || only_2d
            for iφ in eachindex(point_types.grid.axes[2])
                for ir in eachindex(point_types.grid.axes[1])
                    point_type::PointType = point_types[ir, iφ, iz]
                    if (point_type & pn_junction_bit > 0) && (point_type & undepleted_bit == 0) && (point_type & update_bit > 0)
                        dV::T = Δmpz[iz] * Δmpφ[iφ] * Δmpr_squared[ir]
                        active_volume += dV
                    end
                end
            end
        elseif isclosed && !only_2d
            for iφ in eachindex(point_types.grid.axes[2])
                for ir in eachindex(point_types.grid.axes[1])
                    point_type::PointType = point_types[ir, iφ, iz]
                    if (point_type & pn_junction_bit > 0) && (point_type & undepleted_bit == 0) && (point_type & update_bit > 0)
                        dV = Δmpz[iz] * Δmpφ[iφ] * Δmpr_squared[ir]
                        active_volume += if iφ == length(point_types.φ) || iφ == 1
                            dV / 2
                        else
                            dV
                        end
                    end
                end
            end
        end
    end
    if cyclic > 0
        active_volume *= 2π / cyclic
    end

    f::T = 10^6
    return active_volume * f * Unitful.cm * Unitful.cm * Unitful.cm
end

function get_active_volume(point_types::PointTypes{T, 3, Cartesian}) where {T}
    active_volume::T = 0

    x_ext::Vector{T} = get_extended_ticks(point_types.grid.axes[1])
    y_ext::Vector{T} = get_extended_ticks(point_types.grid.axes[2])
    z_ext::Vector{T} = get_extended_ticks(point_types.grid.axes[3])

    mpx::Vector{T} = midpoints(x_ext)
    mpy::Vector{T} = midpoints(y_ext)
    mpz::Vector{T} = midpoints(z_ext)
    Δmpx::Vector{T} = diff(mpx)
    Δmpy::Vector{T} = diff(mpy)
    Δmpz::Vector{T} = diff(mpz)

    for iz in eachindex(point_types.grid.axes[3])
        for iy in eachindex(point_types.grid.axes[2])
            for ix in eachindex(point_types.grid.axes[1])
                point_type::PointType = point_types[ix, iy, iz]
                if (point_type & pn_junction_bit > 0) && (point_type & undepleted_bit == 0) && (point_type & update_bit > 0)
                    dV = Δmpx[ix] * Δmpy[iy] * Δmpz[iz]
                    active_volume += dV
                end
            end
        end
    end

    f::T = 10^6
    return active_volume * f * Unitful.cm * Unitful.cm * Unitful.cm
end

function PointTypes(nt::NamedTuple)
    grid = Grid(nt.grid)
    T = typeof(grid.axes[1].ticks[1])
    S = get_coordinate_system(grid)
    N = get_number_of_dimensions(grid)
    PointTypes{T, N, S, typeof(grid.axes)}( nt.values, grid )
end

Base.convert(T::Type{PointTypes}, x::NamedTuple) = T(x)

function NamedTuple(point_types::PointTypes{T, 3}) where {T}
    return (
        grid = NamedTuple(point_types.grid),
        values = point_types.data,
    )
end

Base.convert(T::Type{NamedTuple}, x::PointTypes) = T(x)


# Only used for simulations of one cylinder in ga_binary.jl

using Random
using DataFrames
using CSV

# Generate position Array for one cylinder
# x,y: coordinate positions
function generatePosArray(x, y)
    p = Array{OneCylType,1}()
    for i in x
        for j in y
            push!(p, OneCylType(i,j))
        end
    end
    return p
end

# Get test data for one cylinder
function simDataOne(dname; xoffset=0, yoffset=0)
    x = DataFrame(CSV.read(dname*"/x.csv", header=false))
    x = collect(x[:,1]) .+ xoffset
    y = DataFrame(CSV.read(dname*"/y.csv", header=false))
    y = collect(y[:,1]) .+ yoffset
    data = DataFrame(CSV.read(dname*"/pressureData.csv", header=1))

    dict = Dict{OneCylType, Float64}()

    for i in 1:length(x)
        for k in 1:length(y)
            dict[OneCylType(x[i],y[k])] = data[i,k]
        end
    end

    return dict

end

# Generate randomic data for simulation (not working well)
# nc: number of cylinders
function simDataRandom(nc)
    if nc == 1 # OneCylType for size=[6,5]
        x = 0:1:63
        y = -15:1:16
        p = generatePosArray(x,y)
        n = length(p)

        x1 = 10:1:60
        y1 = -5:1:8
        p1 = generatePosArray(x1,y1)
        n1 = length(p1)

        x2 = 20:1:25
        y2 = 3:1:5
        p2 = generatePosArray(x2,y2)
        n2 = length(p2)

        d = Dict{OneCylType, Float64}()
        [d[p[k]] = 10 + rand(Random.seed!(k)) for k in 1:n]
        [d[p1[k]] = d[p1[k]] + 2 + rand(Random.seed!(k)) for k in 1:n1]
        [d[p2[k]] = d[p2[k]] + 2 + 2*rand(Random.seed!(k)) for k in 1:n2]
    else
        error("Dimension mismatch!")
    end
    return d
end


export kde

import ..KDEModel

using mlpack._Internal.io

import mlpack_jll
const kdeLibrary = mlpack_jll.libmlpack_julia_kde

# Call the C binding of the mlpack kde binding.
function kde_mlpackMain()
  success = ccall((:kde, kdeLibrary), Bool, ())
  if !success
    # Throw an exception---false means there was a C++ exception.
    throw(ErrorException("mlpack binding error; see output"))
  end
end

" Internal module to hold utility functions. "
module kde_internal
  import ..kdeLibrary

import ...KDEModel

# Get the value of a model pointer parameter of type KDEModel.
function IOGetParamKDEModel(paramName::String)::KDEModel
  KDEModel(ccall((:IO_GetParamKDEModelPtr, kdeLibrary), Ptr{Nothing}, (Cstring,), paramName))
end

# Set the value of a model pointer parameter of type KDEModel.
function IOSetParamKDEModel(paramName::String, model::KDEModel)
  ccall((:IO_SetParamKDEModelPtr, kdeLibrary), Nothing, (Cstring, Ptr{Nothing}), paramName, model.ptr)
end

# Serialize a model to the given stream.
function serializeKDEModel(stream::IO, model::KDEModel)
  buf_len = UInt[0]
  buf_ptr = ccall((:SerializeKDEModelPtr, kdeLibrary), Ptr{UInt8}, (Ptr{Nothing}, Ptr{UInt}), model.ptr, Base.pointer(buf_len))
  buf = Base.unsafe_wrap(Vector{UInt8}, buf_ptr, buf_len[1]; own=true)
  write(stream, buf)
end
# Deserialize a model from the given stream.
function deserializeKDEModel(stream::IO)::KDEModel
  buffer = read(stream)
  KDEModel(ccall((:DeserializeKDEModelPtr, kdeLibrary), Ptr{Nothing}, (Ptr{UInt8}, UInt), Base.pointer(buffer), length(buffer)))
end
end # module

"""
    kde(; [abs_error, algorithm, bandwidth, initial_sample_size, input_model, kernel, mc_break_coef, mc_entry_coef, mc_probability, monte_carlo, query, reference, rel_error, tree, verbose])

This program performs a Kernel Density Estimation. KDE is a non-parametric way
of estimating probability density function. For each query point the program
will estimate its probability density by applying a kernel function to each
reference point. The computational complexity of this is O(N^2) where there are
N query points and N reference points, but this implementation will typically
see better performance as it uses an approximate dual or single tree algorithm
for acceleration.

Dual or single tree optimization avoids many barely relevant calculations (as
kernel function values decrease with distance), so it is an approximate
computation. You can specify the maximum relative error tolerance for each query
value with `rel_error` as well as the maximum absolute error tolerance with the
parameter `abs_error`. This program runs using an Euclidean metric. Kernel
function can be selected using the `kernel` option. You can also choose what
which type of tree to use for the dual-tree algorithm with `tree`. It is also
possible to select whether to use dual-tree algorithm or single-tree algorithm
using the `algorithm` option.

Monte Carlo estimations can be used to accelerate the KDE estimate when the
Gaussian Kernel is used. This provides a probabilistic guarantee on the the
error of the resulting KDE instead of an absolute guarantee.To enable Monte
Carlo estimations, the `monte_carlo` flag can be used, and success probability
can be set with the `mc_probability` option. It is possible to set the initial
sample size for the Monte Carlo estimation using `initial_sample_size`. This
implementation will only consider a node, as a candidate for the Monte Carlo
estimation, if its number of descendant nodes is bigger than the initial sample
size. This can be controlled using a coefficient that will multiply the initial
sample size and can be set using `mc_entry_coef`. To avoid using the same amount
of computations an exact approach would take, this program recurses the tree
whenever a fraction of the amount of the node's descendant points have already
been computed. This fraction is set using `mc_break_coef`.

For example, the following will run KDE using the data in `ref_data` for
training and the data in `qu_data` as query data. It will apply an Epanechnikov
kernel with a 0.2 bandwidth to each reference point and use a KD-Tree for the
dual-tree optimization. The returned predictions will be within 5% of the real
KDE value for each query point.

```julia
julia> using CSV
julia> ref_data = CSV.read("ref_data.csv")
julia> qu_data = CSV.read("qu_data.csv")
julia> _, out_data = kde(bandwidth=0.2, kernel="epanechnikov",
            query=qu_data, reference=ref_data, rel_error=0.05, tree="kd-tree")
```

the predicted density estimations will be stored in `out_data`.
If no `query` is provided, then KDE will be computed on the `reference` dataset.
It is possible to select either a reference dataset or an input model but not
both at the same time. If an input model is selected and parameter values are
not set (e.g. `bandwidth`) then default parameter values will be used.

In addition to the last program call, it is also possible to activate Monte
Carlo estimations if a Gaussian kernel is used. This can provide faster results,
but the KDE will only have a probabilistic guarantee of meeting the desired
error bound (instead of an absolute guarantee). The following example will run
KDE using a Monte Carlo estimation when possible. The results will be within a
5% of the real KDE value with a 95% probability. Initial sample size for the
Monte Carlo estimation will be 200 points and a node will be a candidate for the
estimation only when it contains 700 (i.e. 3.5*200) points. If a node contains
700 points and 420 (i.e. 0.6*700) have already been sampled, then the algorithm
will recurse instead of keep sampling.

```julia
julia> using CSV
julia> ref_data = CSV.read("ref_data.csv")
julia> qu_data = CSV.read("qu_data.csv")
julia> _, out_data = kde(bandwidth=0.2, initial_sample_size=200,
            kernel="gaussian", mc_break_coef=0.6, mc_entry_coef=3.5,
            mc_probability=0.95, monte_carlo=, query=qu_data,
            reference=ref_data, rel_error=0.05, tree="kd-tree")
```

# Arguments

 - `abs_error::Float64`: Relative error tolerance for the prediction.
      Default value `0`.

 - `algorithm::String`: Algorithm to use for the prediction.('dual-tree',
      'single-tree').  Default value `dual-tree`.

 - `bandwidth::Float64`: Bandwidth of the kernel.  Default value `1`.

 - `initial_sample_size::Int`: Initial sample size for Monte Carlo
      estimations.  Default value `100`.

 - `input_model::KDEModel`: Contains pre-trained KDE model.
 - `kernel::String`: Kernel to use for the prediction.('gaussian',
      'epanechnikov', 'laplacian', 'spherical', 'triangular').  Default value
      `gaussian`.

 - `mc_break_coef::Float64`: Controls what fraction of the amount of
      node's descendants is the limit for the sample size before it recurses.
      Default value `0.4`.

 - `mc_entry_coef::Float64`: Controls how much larger does the amount of
      node descendants has to be compared to the initial sample size in order to
      be a candidate for Monte Carlo estimations.  Default value `3`.

 - `mc_probability::Float64`: Probability of the estimation being bounded
      by relative error when using Monte Carlo estimations.  Default value
      `0.95`.

 - `monte_carlo::Bool`: Whether to use Monte Carlo estimations when
      possible.  Default value `false`.

 - `query::Array{Float64, 2}`: Query dataset to KDE on.
 - `reference::Array{Float64, 2}`: Input reference dataset use for KDE.
 - `rel_error::Float64`: Relative error tolerance for the prediction.
      Default value `0.05`.

 - `tree::String`: Tree to use for the prediction.('kd-tree', 'ball-tree',
      'cover-tree', 'octree', 'r-tree').  Default value `kd-tree`.

 - `verbose::Bool`: Display informational messages and the full list of
      parameters and timers at the end of execution.  Default value `false`.

# Return values

 - `output_model::KDEModel`: If specified, the KDE model will be saved
      here.
 - `predictions::Array{Float64, 1}`: Vector to store density predictions.

"""
function kde(;
             abs_error::Union{Float64, Missing} = missing,
             algorithm::Union{String, Missing} = missing,
             bandwidth::Union{Float64, Missing} = missing,
             initial_sample_size::Union{Int, Missing} = missing,
             input_model::Union{KDEModel, Missing} = missing,
             kernel::Union{String, Missing} = missing,
             mc_break_coef::Union{Float64, Missing} = missing,
             mc_entry_coef::Union{Float64, Missing} = missing,
             mc_probability::Union{Float64, Missing} = missing,
             monte_carlo::Union{Bool, Missing} = missing,
             query = missing,
             reference = missing,
             rel_error::Union{Float64, Missing} = missing,
             tree::Union{String, Missing} = missing,
             verbose::Union{Bool, Missing} = missing,
             points_are_rows::Bool = true)
  # Force the symbols to load.
  ccall((:loadSymbols, kdeLibrary), Nothing, ());

  IORestoreSettings("Kernel Density Estimation")

  # Process each input argument before calling mlpackMain().
  if !ismissing(abs_error)
    IOSetParam("abs_error", convert(Float64, abs_error))
  end
  if !ismissing(algorithm)
    IOSetParam("algorithm", convert(String, algorithm))
  end
  if !ismissing(bandwidth)
    IOSetParam("bandwidth", convert(Float64, bandwidth))
  end
  if !ismissing(initial_sample_size)
    IOSetParam("initial_sample_size", convert(Int, initial_sample_size))
  end
  if !ismissing(input_model)
    kde_internal.IOSetParamKDEModel("input_model", convert(KDEModel, input_model))
  end
  if !ismissing(kernel)
    IOSetParam("kernel", convert(String, kernel))
  end
  if !ismissing(mc_break_coef)
    IOSetParam("mc_break_coef", convert(Float64, mc_break_coef))
  end
  if !ismissing(mc_entry_coef)
    IOSetParam("mc_entry_coef", convert(Float64, mc_entry_coef))
  end
  if !ismissing(mc_probability)
    IOSetParam("mc_probability", convert(Float64, mc_probability))
  end
  if !ismissing(monte_carlo)
    IOSetParam("monte_carlo", convert(Bool, monte_carlo))
  end
  if !ismissing(query)
    IOSetParamMat("query", query, points_are_rows)
  end
  if !ismissing(reference)
    IOSetParamMat("reference", reference, points_are_rows)
  end
  if !ismissing(rel_error)
    IOSetParam("rel_error", convert(Float64, rel_error))
  end
  if !ismissing(tree)
    IOSetParam("tree", convert(String, tree))
  end
  if verbose !== nothing && verbose === true
    IOEnableVerbose()
  else
    IODisableVerbose()
  end

  IOSetPassed("output_model")
  IOSetPassed("predictions")
  # Call the program.
  kde_mlpackMain()

  return kde_internal.IOGetParamKDEModel("output_model"),
         IOGetParamCol("predictions")
end


module OpenColor
  using ColorTypes

  export OCGRAY, OCRED, OCPINK, OCGRAPE,
    OCVIOLET, OCINDIGO, OCBLUE, OCCYAN,
    OCTEAL, OCGREEN, OCYELLOW, OCORANGE

  # Use Dict instead of NamedTuple because of zero-based index
  const OCGRAY = Base.Dict(0:9 .=> [
    RGB(248/255, 249/255, 250/255),
    RGB(241/255, 243/255, 245/255),
    RGB(233/255, 236/255, 239/255),
    RGB(222/255, 226/255, 230/255),
    RGB(206/255, 212/255, 218/255),
    RGB(173/255, 181/255, 189/255),
    RGB(134/255, 142/255, 150/255),
    RGB(73/255, 80/255, 87/255),
    RGB(52/255, 58/255, 64/255),
    RGB(33/255, 37/255, 41/255)])

  const OCRED = Base.Dict(0:9 .=> [
    RGB(255/255, 245/255, 245/255),
    RGB(255/255, 227/255, 227/255),
    RGB(255/255, 201/255, 201/255),
    RGB(255/255, 168/255, 168/255),
    RGB(255/255, 135/255, 135/255),
    RGB(255/255, 107/255, 107/255),
    RGB(250/255, 82/255, 82/255),
    RGB(240/255, 62/255, 62/255),
    RGB(224/255, 49/255, 49/255),
    RGB(201/255, 42/255, 42/255)])

  const OCPINK = Base.Dict(0:9 .=> [
    RGB(255/255, 240/255, 246/255),
    RGB(255/255, 222/255, 235/255),
    RGB(252/255, 194/255, 215/255),
    RGB(250/255, 162/255, 193/255),
    RGB(247/255, 131/255, 172/255),
    RGB(240/255, 101/255, 149/255),
    RGB(230/255, 73/255, 128/255),
    RGB(214/255, 51/255, 108/255),
    RGB(194/255, 37/255, 92/255),
    RGB(166/255, 30/255, 77/255)])

  const OCGRAPE = Base.Dict(0:9 .=> [
    RGB(248/255, 240/255, 252/255),
    RGB(243/255, 217/255, 250/255),
    RGB(238/255, 190/255, 250/255),
    RGB(229/255, 153/255, 247/255),
    RGB(218/255, 119/255, 242/255),
    RGB(204/255, 93/255, 232/255),
    RGB(190/255, 75/255, 219/255),
    RGB(174/255, 62/255, 201/255),
    RGB(156/255, 54/255, 181/255),
    RGB(134/255, 46/255, 156/255)])

  const OCVIOLET = Base.Dict(0:9 .=> [
    RGB(243/255, 240/255, 255/255),
    RGB(229/255, 219/255, 255/255),
    RGB(208/255, 191/255, 255/255),
    RGB(177/255, 151/255, 252/255),
    RGB(151/255, 117/255, 250/255),
    RGB(132/255, 94/255, 247/255),
    RGB(121/255, 80/255, 242/255),
    RGB(112/255, 72/255, 232/255),
    RGB(103/255, 65/255, 217/255),
    RGB(95/255, 61/255, 196/255)])

  const OCINDIGO = Base.Dict(0:9 .=> [
    RGB(237/255, 242/255, 255/255),
    RGB(219/255, 228/255, 255/255),
    RGB(186/255, 200/255, 255/255),
    RGB(145/255, 167/255, 255/255),
    RGB(116/255, 143/255, 252/255),
    RGB(92/255, 124/255, 250/255),
    RGB(76/255, 110/255, 245/255),
    RGB(66/255, 99/255, 235/255),
    RGB(59/255, 91/255, 219/255),
    RGB(54/255, 79/255, 199/255)])

  const OCBLUE = Base.Dict(0:9 .=> [
    RGB(231/255, 245/255, 255/255),
    RGB(208/255, 235/255, 255/255),
    RGB(165/255, 216/255, 255/255),
    RGB(116/255, 192/255, 252/255),
    RGB(77/255, 171/255, 247/255),
    RGB(51/255, 154/255, 240/255),
    RGB(34/255, 139/255, 230/255),
    RGB(28/255, 126/255, 214/255),
    RGB(25/255, 113/255, 194/255),
    RGB(24/255, 100/255, 171/255)])

  const OCCYAN = Base.Dict(0:9 .=> [
    RGB(227/255, 250/255, 252/255),
    RGB(197/255, 246/255, 250/255),
    RGB(153/255, 233/255, 242/255),
    RGB(102/255, 217/255, 232/255),
    RGB(59/255, 201/255, 219/255),
    RGB(34/255, 184/255, 207/255),
    RGB(21/255, 170/255, 191/255),
    RGB(16/255, 152/255, 173/255),
    RGB(12/255, 133/255, 153/255),
    RGB(11/255, 114/255, 133/255)])

  const OCTEAL = Base.Dict(0:9 .=> [
    RGB(230/255, 252/255, 245/255),
    RGB(195/255, 250/255, 232/255),
    RGB(150/255, 242/255, 215/255),
    RGB(99/255, 230/255, 190/255),
    RGB(56/255, 217/255, 169/255),
    RGB(32/255, 201/255, 151/255),
    RGB(18/255, 184/255, 134/255),
    RGB(12/255, 166/255, 120/255),
    RGB(9/255, 146/255, 104/255),
    RGB(8/255, 127/255, 91/255)])

  const OCGREEN = Base.Dict(0:9 .=> [
    RGB(235/255, 251/255, 238/255),
    RGB(211/255, 249/255, 216/255),
    RGB(178/255, 242/255, 187/255),
    RGB(140/255, 233/255, 154/255),
    RGB(105/255, 219/255, 124/255),
    RGB(81/255, 207/255, 102/255),
    RGB(64/255, 192/255, 87/255),
    RGB(55/255, 178/255, 77/255),
    RGB(47/255, 158/255, 68/255),
    RGB(43/255, 138/255, 62/255)])

  const OCLIME = Base.Dict(0:9 .=> [
    RGB(244/255, 252/255, 227/255),
    RGB(233/255, 250/255, 200/255),
    RGB(216/255, 245/255, 162/255),
    RGB(192/255, 235/255, 117/255),
    RGB(169/255, 227/255, 75/255),
    RGB(148/255, 216/255, 45/255),
    RGB(130/255, 201/255, 30/255),
    RGB(116/255, 184/255, 22/255),
    RGB(102/255, 168/255, 15/255),
    RGB(92/255, 148/255, 13/255)])

  const OCYELLOW = Base.Dict(0:9 .=> [
    RGB(255/255, 249/255, 219/255),
    RGB(255/255, 243/255, 191/255),
    RGB(255/255, 236/255, 153/255),
    RGB(255/255, 224/255, 102/255),
    RGB(255/255, 212/255, 59/255),
    RGB(252/255, 196/255, 25/255),
    RGB(250/255, 176/255, 5/255),
    RGB(245/255, 159/255, 0/255),
    RGB(240/255, 140/255, 0/255),
    RGB(230/255, 119/255, 0/255)])

  const OCORANGE = Base.Dict(0:9 .=> [
    RGB(255/255, 244/255, 230/255),
    RGB(255/255, 232/255, 204/255),
    RGB(255/255, 216/255, 168/255),
    RGB(255/255, 192/255, 120/255),
    RGB(255/255, 169/255, 77/255),
    RGB(255/255, 146/255, 43/255),
    RGB(253/255, 126/255, 20/255),
    RGB(247/255, 103/255, 7/255),
    RGB(232/255, 89/255, 12/255),
    RGB(217/255, 72/255, 15/255)])
end


@testset "Constraints" begin
    #TODO needs to be updated for the new constraint design and interaction with Core
    T = Float64
    N = 3

    # Test extreme constraints
    c1(x) = x[1] < 1
    c2(x) = x[1] + x[3] > 1
    c3(x) = x[2] > 0

    p1(x) = x[1] < 1 ? 0 : x[1] - 1
    p2(x) = x[1] + x[3] > 1 ? 0 : 1 - x[1] + x[3]
    p3(x) = x[2] > 0 ? 0 : -x[2]

    C = DS.Constraints{T}()
    @testset "Constraints" begin
        @test C.count == length(C.collections) == 2
        @test typeof(C.collections[1]) == DS.ConstraintCollection{T, DS.ExtremeConstraint}
        @test typeof(C.collections[2]) == DS.ConstraintCollection{T, DS.ProgressiveConstraint}
    end

    @testset "DefaultConstraintCollection" begin
        ex = C.collections[1]
        pr = C.collections[2]

        # Extreme barrier constraint default collection
        @test length(ex.constraints) == ex.count ==  0
        @test typeof(ex.constraints) == Vector{DS.ExtremeConstraint}
        @test ex.h_max == 0.0

        # Extreme barrier constraint default collection
        @test length(pr.constraints) == pr.count == 0
        @test typeof(pr.constraints) == Vector{DS.ProgressiveConstraint}
        @test pr.h_max == Inf

        # TODO: Test default update functions
    end

    C = DS.Constraints{T}()
    @testset "AddExtremeConstraint" begin
        c1_ref = DS.AddExtremeConstraint(C, c1)
        @test c1_ref.value == 1
        @test typeof(c1_ref) == DS.ConstraintIndex

        # Can't push an extreme constraint to a progressive collection
        @test_throws MethodError DS.AddExtremeConstraint(C, c1, index=DS.CollectionIndex(2))
        @test_throws ErrorException DS.AddExtremeConstraint(C, c1, index=DS.CollectionIndex(3))

        vec_ref = DS.AddExtremeConstraint(C, [c2, c3])
        @test length(vec_ref) == 2
        @test vec_ref[1].value == 2
        @test vec_ref[2].value == 3
    end

    @testset "AddProgressiveConstraint" begin
        p1_ref = DS.AddProgressiveConstraint(C, p1)
        @test p1_ref.value == 1
        @test typeof(p1_ref) == DS.ConstraintIndex

        # Can't push an extreme constraint to a progressive collection
        @test_throws MethodError DS.AddProgressiveConstraint(C, p1, index=DS.CollectionIndex(1))
        @test_throws ErrorException DS.AddProgressiveConstraint(C, p1, index=DS.CollectionIndex(3))

        vec_ref = DS.AddProgressiveConstraint(C, [c2, c3])
        @test length(vec_ref) == 2
        @test vec_ref[1].value == 2
        @test vec_ref[2].value == 3
    end

    C = DS.Constraints{T}()
    @testset "AddProgressiveCollection" begin
        c_ref = DS.AddProgressiveCollection(C)
        @test typeof(c_ref) == DS.CollectionIndex
        @test c_ref.value == 3

        @test typeof(C.collections[3]) == DS.ConstraintCollection{T, DS.ProgressiveConstraint}
        @test length(C.collections[3].constraints) == C.collections[3].count == 0

        p_ref = DS.AddProgressiveConstraint(C, p2, index=c_ref)
        @test length(C.collections[3].constraints) == C.collections[3].count == 1
    end

    #TODO constraint evaluation
end


module DerivableFunctions

using Reexport
using ReverseDiff, Zygote, FiniteDiff, FiniteDifferences

@reexport using DerivableFunctionsBase

# import such that they are available downstream as if defined here
import DerivableFunctionsBase: MaximalNumberOfArguments, KillAfter, Builder
import DerivableFunctionsBase: _GetArgLength, _GetArgLengthOutOfPlace, _GetArgLengthInPlace
import DerivableFunctionsBase: GetSymbolicDerivative, SymbolicPassthrough

import DerivableFunctionsBase: _GetDeriv, _GetGrad, _GetJac, _GetHess, _GetMatrixJac, _GetDoubleJac
import DerivableFunctionsBase: _GetDerivPass, _GetGradPass, _GetJacPass, _GetHessPass, _GetDoubleJacPass, _GetMatrixJacPass
import DerivableFunctionsBase: _GetGrad!, _GetJac!, _GetHess!, _GetMatrixJac!
import DerivableFunctionsBase: _GetGradPass!, _GetJacPass!, _GetHessPass!, _GetMatrixJacPass!

import DerivableFunctionsBase: suff
suff(x::ReverseDiff.TrackedReal) = typeof(x)

## Add new backends to the output of diff_backends()
import DerivableFunctionsBase: AddedBackEnds
AddedBackEnds(::Val{true}) = [:ReverseDiff, :Zygote, :FiniteDifferences, :FiniteDiff]

include("DifferentiationOperators.jl")

end


"""
The `UnmeasuredElementRule` mechanism provides a method to implement rules for adding unmeasured elements to
the fitting process.  Examples include element-by-stoichiometry or element-by-difference.

`UnmeasuredElementRule` implementations must include methods for:

    NeXLUncertainties.compute(uer::<:UnmeasuredElementRule, inp::Dict{Element,Float64})::Dict{Element,Float64}
    isunmeasured(uer::<:UnmeasuredElementRule, elm::Element)::Bool
"""
abstract type UnmeasuredElementRule end

"""
The NullUnmeasuredRule adds no additional elements in the iteration process.
"""
struct NullUnmeasuredRule <: UnmeasuredElementRule end

"""
    NeXLUncertainties.compute(::NullUnmeasuredRule, inp::Dict{Element,Float64})::Dict{Element,Float64}

A null UnmeasuredElementRule.  Just returns the inputs.
"""
NeXLUncertainties.compute(::NullUnmeasuredRule, inp::Dict{Element,<:AbstractFloat})::Dict{Element,Float64} = inp

isunmeasured(::NullUnmeasuredRule, elm::Element) = false

"""
The ElementByStoichiometry computes the oxygen mass fraction using the standard stoichiometric rules.
"""
struct ElementByStoichiometry <: UnmeasuredElementRule
    element::Element
    valences
    ElementByStoichiometry(elm::Element, val) = new(elm, val)
end

"""
    OByStoichiometry(valences = NeXLCore.valences)

Returns an ElementByStoichiometry for computing O using the valences provided as a tuple or Array indexed by z.
"""
OByStoichiometry(valences = NeXLCore.defaultValences) = ElementByStoichiometry(n"O", valences)

"""
    NeXLUncertainties.compute(stoic::ElementByStoichiometry, inp::Dict{Element,<:AbstractFloat})::Dict{Element,Float64}

Computes an element using stoichiometric rules.
"""
function NeXLUncertainties.compute(stoic::ElementByStoichiometry, inp::Dict{Element,<:AbstractFloat})::Dict{Element,Float64}
    inp[stoic.element] = obystoichiometry(inp, valences = stoic.valences)
    inp
end

function isunmeasured(stoic::ElementByStoichiometry, elm::Element)::Bool
    isequal(elm, stoic.element)
end

function Base.show(io::IO, stoic::ElementByStoichiometry)
    print(io,"ElementByStoichiometry[$(stoic.element)]")
end

struct ElementByFiat <: UnmeasuredElementRule
    element::Element
    massfraction::Float64
end

"""
    NeXLUncertainties.compute(stoic::ElementByFiat, inp::Dict{Element,<:AbstractFloat})::Dict{Element,Float64}

Simply assigns a quantity for an element.
"""
function NeXLUncertainties.compute(fiat::ElementByFiat, inp::Dict{Element,<:AbstractFloat})::Dict{Element,Float64}
    inp[fiat.element] = fiat.massfraction
    inp
end

function isunmeasured(fiat::ElementByFiat, elm::Element)::Bool
    isequal(elm, fiat.element)
end

function Base.show(io::IO, fiat::ElementByFiat)
    print(io,"ElementByFiat[$(fiat.element), $(fiat.massfraction)]")
end
"""
    MultiUnmeasuredElementRule

Apply multiple UnmeasuredElementRule items sequentially.
"""
struct MultiUnmeasuredElementRule <: UnmeasuredElementRule
    rules::Vector{<:UnmeasuredElementRule}
end

function NeXLUncertainties.compute(muer::MultiUnmeasuredElementRule, inp::Dict{Element,<:AbstractFloat})::Dict{Element,Float64}
    res = inp
    for rule in muer.rules
        res = compute(rule, res)
    end
    return res
end

function isunmeasured(muer::MultiUnmeasuredElementRule, elm::Element)::Bool
    any(rule->isunmeasured(rule, elm), muer.rules)
end

function Base.show(io::IO, muer::MultiUnmeasuredElementRule)
    print(io,"MultiUnmeasuredElementRule[$(join(muer.rules, ", "))]")
end


# Automatically generated by update_leap_seconds.jl - do not edit!

const leap_index_offset = 9 # For use with searchsortedlast()

const utc_offsets = [
    typemin(Date),  # For use with searchsortedlast()
    Date(1972, 7, 1),
    Date(1973, 1, 1),
    Date(1974, 1, 1),
    Date(1975, 1, 1),
    Date(1976, 1, 1),
    Date(1977, 1, 1),
    Date(1978, 1, 1),
    Date(1979, 1, 1),
    Date(1980, 1, 1),
    Date(1981, 7, 1),
    Date(1982, 7, 1),
    Date(1983, 7, 1),
    Date(1985, 7, 1),
    Date(1988, 1, 1),
    Date(1990, 1, 1),
    Date(1991, 1, 1),
    Date(1992, 7, 1),
    Date(1993, 7, 1),
    Date(1994, 7, 1),
    Date(1996, 1, 1),
    Date(1997, 7, 1),
    Date(1999, 1, 1),
    Date(2006, 1, 1),
    Date(2009, 1, 1),
    Date(2012, 7, 1),
    Date(2015, 7, 1),
    Date(2017, 1, 1)
]

const leap_secs_expiry_date = Date(2017, 12, 28)


################################################################################

module dummyMindReader

################################################################################

#  imports
begin
  using Plots           # plotting packages
  using CSV, DataFrames # file manipulation
  using ArgParse        # arg parser
end

################################################################################

export shArgParser
export mindReader

################################################################################

#  no documentation
function shArgParser(args)
  #  minimal argument parsing
  s = ArgParseSettings(description = "dummy mind reader")
  @add_arg_table! s begin
      "--file", "-f"
        arg_type = String
        required = true
        help = "csv file to read"
      "--output", "-o"
        arg_type = String
        required = false
        help = "output svg file"
  end
  parsed_args = parse_args(s)
  return parsed_args
end

function mindReader(file, output)

  #  read file
  df = CSV.File(file, header = false) |> DataFrame

  #  plot
  p = plot(df[:, 2] .^ 7)

  #  save plot
  savefig(p, output)

end

################################################################################

end

################################################################################