using Caesar, RoME, IncrementalInference
using RoMEPlotting, Gadfly, KernelDensityEstimatePlotting
using KernelDensityEstimate
using DocStringExtensions
using DelimitedFiles, JLD

include(joinpath(@__DIR__,"slamUtils.jl"))
include(joinpath(@__DIR__,"plotSASUtils.jl"))

expnum = 2;
if expnum == 1
    topdir = joinpath(ENV["HOME"],"data","kayaks","07_18")
    trialstr = "07_18_parsed_set1";
    datadir = joinpath(topdir,trialstr);
    ijldname = datadir * "/exp1" * ".jld"
elseif expnum==2
    topdir = joinpath(ENV["HOME"],"data","kayaks","07_18")
    trialstr = "07_18_parsed_set2";
    datadir = joinpath(topdir,trialstr);
    ijldname = datadir * "/exp1" * ".jld"
end

cfgFile = joinpath(ENV["HOME"],"data","sas","SAS2D.yaml");
chirpFile = joinpath(ENV["HOME"],"data","sas","chirp250.txt");
cfgd=loadConfigFile(cfgFile)
cfgd["range_snr_floor"]=0.9
iload = load(ijldname)
nrange = iload["nrange"]
irange = iload["irange"];
itemp = readdlm(datadir*"/inav.csv",',',Float64,'\n', skipstart=1);

wstart = 190; wlen = 25;
dataframes = collect(nrange[wstart]:nrange[wstart]+wlen);

saswindow = 5;
sasstart = 1;
nsasfac = 5;
sasdataframes = [zeros(Int,saswindow) for _ in 1:nsasfac];
sasposes = [];
irangenew = irange[wstart] : 5 : irange[wstart+nsasfac]
igt = [itemp[irangenew,2] itemp[irangenew,4]];

poses = [Symbol("x$i") for i in 1:wlen]
posData = importdata_nav(dataframes,datadir=datadir);
navchecked, errorind = sanitycheck_nav(posData)

# dposData = deepcopy(posData)
# cumulativeDrift!(dposData,[0.0;0],[0.1,0.1])

# Check the trajectory
posPl = Gadfly.plot(layer(x=posData[:,1],y=posData[:,2], Geom.point, Geom.path, Theme(default_color=colorant"green",highlight_width = 0pt)),layer(x=igt[:,1],y=igt[:,2], Geom.point, Geom.path, Theme(default_color=colorant"red" ,highlight_width = 0pt))); posPl |> PDF("/tmp/test.pdf");

for i=1:nsasfac
    framestart = nrange[wstart]+sasstart-1+(i-1)*saswindow;
    sasdataframes[i] = collect(framestart:framestart+saswindow-1)
    push!(sasposes,[Symbol("x$j") for j in sasstart+(i-1)*saswindow:sasstart+saswindow-1+(i-1)*saswindow])
end

fg = initfg();
rtkCov= Matrix(Diagonal([0.1;0.1].^2));
for i in 1:wlen
  addVariable!(fg, poses[i], Point2)
  pp = PriorPoint2(MvNormal(posData[i,:], rtkCov))
  addFactor!(fg, [poses[i];], pp, autoinit=true)
end

beacons = [Symbol("l$j") for j in 1:nsasfac]

for i=1:nsasfac
    addVariable!(fg, beacons[i], DynPoint2(ut=1_000_000+(i-1)*saswindow) )

    if i>1
        dpμ = [0.0;0;0;0];
        dpσ = Matrix(Diagonal([1.0;1;0.1;0.1].^2))
        vp = VelPoint2VelPoint2(MvNormal(dpμ,dpσ))
        addFactor!(fg, [beacons[i-1];beacons[i]], vp, autoinit=false)
    end

    waveformData = importdata_waveforms(sasdataframes[i],2, datadir=datadir);
    sas2d = prepareSAS2DFactor(saswindow, waveformData, rangemodel=:Correlator,
                               cfgd=cfgd, chirpFile=chirpFile)
    addFactor!(fg, [beacons[i];sasposes[i]], sas2d, autoinit=false)
    @show currfac = ls(fg,beacons[i])[1]

    tmpInit = approxConv(fg,currfac,beacons[i]);
    XXkde = manikde!(tmpInit,getManifolds(fg,beacons[i]));
    setValKDE!(fg,beacons[i],XXkde);
end

# visualization tools for debugging
# writeGraphPdf(fg,viewerapp="", engine="neato", filepath = "/tmp/test.pdf")

# getSolverParams(fg).drawtree = true
getSolverParams(fg).limititers=500
#getSolverParams(fg).showtree = true

## solve the factor graph
tree, smt, hist = solveTree!(fg)

# Some Debuggging
# @time L1 = approxConv(fg,:l1x1x2x3x4x5x6x7x8f1,:l1)
# @time L1 = approxConv(fg,:l2x9x10x11x12x13x14x15x16f1,:l2)

# plk = plotBeaconSolve(fg)
plk=[];
for sym in ls(fg) #plotting all syms labeled
    X1 = getKDEMean(getVertKDE(fg,sym))
    push!(plk, layer(x=[X1[1];],y=[X1[2];],label=String["$sym";], Geom.point,Geom.label), Theme(default_color=colorant"red",point_size = 1.5pt,highlight_width = 0pt))
end
# push!(plk,layer(x=L1[1,:],y=L1[2,:],Geom.histogram2d(xbincount=300, ybincount=300)))

K1 = plotKDEContour(kde!(getVal(fg,:l1)[1:2,:]),xlbl="X (m)", ylbl="Y (m)",levels=5,layers=true);
push!(plk,K1...)
push!(plk,Gadfly.Theme(key_position = :none));

push!(plk,layer(x=posData[:,1],y=posData[:,2], Geom.point, Geom.path, Theme(default_color=colorant"blue")), layer(x=igt[:,1],y=igt[:,2], Geom.point, Geom.path, Theme(default_color=colorant"green" )))
push!(plk, Coord.cartesian(xmin=0, xmax=100, ymin=-50, ymax=20,fixed=true))
plkplot = Gadfly.plot(plk...); plkplot |> PDF("/tmp/test.pdf")

plk= [];

push!(plk,layer(x=posData[:,1],y=posData[:,2], Geom.path, Theme(default_color=colorant"green")), layer(x=igt[:,1],y=igt[:,2], Geom.point, Geom.path, Theme(default_color=colorant"red" )))

L1 = getVal(getVariable(fg, :l1))
push!(plk,layer(x=L1[1,:],y=L1[2,:],Geom.histogram2d(xbincount=300, ybincount=300)))

# push!(plk, Coord.cartesian(xmin=40, xmax=100, ymin=-120, ymax=-50,fixed=true))
plkplot = Gadfly.plot(plk...); plkplot |> PDF("/tmp/test.pdf");

## Plot the SAS factor pairs
fsym = :l1x1x2x3x4x5x6x7x8f1
pl = plotSASPair(fg, fsym, show=true, filepath="/tmp/testDP_2pp_3vpvp_init.pdf");


export Clock, RT_SEC, RT_MSEC, RT_USEC, RT_NSEC, TICK, SLEEP_TIME
export nsleep, usleep, msleep, ssleep, tick, job!
export clk

mutable struct Clock <: System
  started::Base.Event
  stopped::Bool
  periodic_jobs::Vector{Tuple{float,Function,String}}
  freq::AbstractFloat
end

Clock() = Clock(Base.Event(), true, [], 0.01667) # default frequency of approximately 60 Hz

const clk = Clock()

# RT == "real time"
# Δ carries the "actual" number of given time units elapsed
struct RT_SCALE{scale, signature}
  Δ::AbstractFloat
end
const SIG_TIME_COUNT = 0
const SIG_TIME_TICK = 1
RT_SEC = RT_SCALE{1.0, SIG_TIME_COUNT}
RT_MSEC = RT_SCALE{1e-3, SIG_TIME_COUNT}
RT_USEC = RT_SCALE{1e-6, SIG_TIME_COUNT}
RT_NSEC = RT_SCALE{1e-9, SIG_TIME_COUNT}
TICK = RT_SCALE{1.0, SIG_TIME_TICK}

struct SLEEP_TIME
  Δ::UInt # time in nanoseconds to sleep for
end

function Base.sleep(s::SLEEP_TIME)
  t1 = time_ns()
  while true
    if time_ns() - t1 >= s.Δ break end
    yield()
  end
  return time_ns() - t1
end

function sleep_with_message(sleep_time,timescale, message, debug)
  δ = sleep(SLEEP_TIME(sleep_time*timescale))
  sendMessage(message(δ/timescale))
  @debug debug
end

nsleep(Δ) = sleep_with_message(Δ,1.0, RT_NSEC, "nanosecond")
usleep(Δ) = sleep_with_message(Δ,1e3, RT_USEC, "microsecond")
msleep(Δ) = sleep_with_message(Δ,1e6, RT_MSEC, "millisecond")
ssleep(Δ) = sleep_with_message(Δ,1e9, RT_SEC, "second")
tick(Δ) = sleep_with_message(Δ,1e9, TICK, "tick")

function job!(c::Clock, f, arg=1)
  function job()
    Base.wait(c.started)
    while !c.stopped
      f(arg)
    end
  end
  schedule(Task(job))
end

function awake!(c::Clock)
  for i in c.periodic_jobs
    job!(c, Δ-> sleep_with_message(Δ,i[1], i[2], i[3]))
  end

  job!(c, tick, c.freq)

  c.stopped = false

  Base.notify(c.started)

  return true
end

function shutdown!(c::Clock)
  c.stopped = true
  c.started = Base.Event() # old one remains signaled no matter what, replace
  return false
end


ENV["PLOTS_TEST"] = "true"
ENV["GKSwstype"] = "100"
using TrackRoots
import TrackRoots:Mark
using Base.Test

using DataDeps

ENV["DATADEPS_ALWAY_ACCEPT"]=true
register(DataDep("test",
                "These are two folders with their `nd` files and multiple dark and light timelapse 16 bit TIF images (1.9 GB total).",
                "https://s3.eu-central-1.amazonaws.com/yakirgagnon/roots/test.zip",
                "d99b8f2edce5e72104ba1cfed02798a6683bc22f4b76a5cb823c80257bc4bf48",
                post_fetch_method=unpack))

# # incase there are old test files, remove them just in case they are old/bad
rm(joinpath(datadep"test", "1", "d2"), recursive=true, force=true)

@testset "all" begin
    startpoints = [[Mark[[774.3011654713115, 911.7642161885246]]], [Mark[[761.334080430328, 413.93003970286884]], Mark[[811.9541095671107, 510.3906089907787]]]]
    endpoints = [[Mark[[905.2760077185163, 918.9115390162102]]], [Mark[[1016.9281490727263, 209.57046484226038]], Mark[[1017.350592982095, 315.11227808371984]]]]
    for (i, dataset) in enumerate(["1","5"])
        files = readdir(joinpath(datadep"test", dataset))
        ind = findfirst(x -> last(splitext(x)) == ".nd", files)
        ndfile = joinpath(datadep"test", dataset, files[ind])
        stages = TrackRoots.nd2stages(ndfile)
        Δx = TrackRoots.pixel_width(stages[1])
        calibstages = TrackRoots.stages2calib(stages, Δx)
        tracks = TrackRoots.trackroot.(calibstages, startpoints[i])
        endpoint = map(tracks) do t
            map(t) do r
                r.coordinates[end]
            end
        end
        @test endpoints[i] == endpoint
    end
end

@testset "utils" begin
    a = TrackRoots.disk(1)
    @test all(CartesianIndex(i) ∈ a for i in [(-1,0), (0,-1), (0,0), (0,1), (1,0)])
    @test TrackRoots.isnd("name.nd")
    @test !TrackRoots.isnd("name.ndd")
    ndfiles = TrackRoots.findall_nd(datadep"test")
    @test length(ndfiles) == 2
end

@testset "startpoint" begin

    @test TrackRoots.get_point(GtkReactive.XY{GtkReactive.UserUnit}(.2,.4)) == Mark(.4,.2)

    ndfile = joinpath(datadep"test", "1", "d2.nd")
    stages = TrackRoots.nd2stages(ndfile)
    file = stages[1].timelapse[1].dark
    img, imgtmp, ms = TrackRoots.fetch_image(file)
    @test img == imgtmp
    @test ms == [0.0038452735179674985, 0.005874723430228122]

    M = 0.5
    message = "test"
    s, b = TrackRoots.build_slider(M, message)
    @test GtkReactive.value(s) == M
    @test b isa Gtk.GtkBoxLeaf

end

@testset "plotting" begin

    startpoint = [Mark[[774.3011654713115, 911.7642161885246]]]
    ndfile = joinpath(datadep"test", "1", "d2.nd")
    main(ndfile, startpoint, Base.DevNullStream())

    @test isfile(joinpath(datadep"test", "1", "d2", "stage 1", "roots.png"))
    @test isfile(joinpath(datadep"test", "1", "d2", "stage 1", "root 1", "coordinates.csv"))
    @test isfile(joinpath(datadep"test", "1", "d2", "stage 1", "root 1", "intensities.csv"))
    @test isfile(joinpath(datadep"test", "1", "d2", "stage 1", "root 1", "summary.mp4"))

    #clean up
    #=rm(joinpath(first(DataDeps.default_loadpath), "test", "1", "d2"), recursive=true, force=true)=#
end


using Decimals
using Compat.Test
using Compat: @info

@testset "Deprecated Functions" begin
    @info("The following deprecation warnings are expected")

    @test inv(d) == map(inv, d)

    @test 1 + d == 1 .+ d
    @test d + 3.0 == d .+ 3.0
    @test 8.0 - d == 8.0 .- d
    @test d - 10 == d .- 10
    @test [1] + d == [1] .+ d
    @test d + [3.0] == d .+ [3.0]
    @test [8.0] - d == [8.0] .- d
    @test d - [10] == d .- [10]

    @test number(d) == number.(d)
    @test all(isint.([0, -102, 0x99]))
    @test !any(isint.(float.(d)))
    @test !any(isint.(string.(d)))

    @test decimal(float(d)) == d
end


struct Field
    name::Symbol
    interpolation::Interpolation
    dim::Int
end

abstract type AbstractDofHandler end

"""
    DofHandler(grid::Grid)

Construct a `DofHandler` based on the grid `grid`.
"""
struct DofHandler{dim,C,T} <: AbstractDofHandler
    field_names::Vector{Symbol}
    field_dims::Vector{Int}
    # TODO: field_interpolations can probably be better typed: We should at least require
    #       all the interpolations to have the same dimension and reference shape
    field_interpolations::Vector{Interpolation}
    bc_values::Vector{BCValues{T}} # TODO: BcValues is created/handeld by the constrainthandler, so this can be removed
    cell_dofs::Vector{Int}
    cell_dofs_offset::Vector{Int}
    closed::ScalarWrapper{Bool}
    grid::Grid{dim,C,T}
    ndofs::ScalarWrapper{Int}
end

function DofHandler(grid::Grid)
    isconcretetype(getcelltype(grid)) || error("Grid includes different celltypes. Use MixedDofHandler instead of DofHandler")
    DofHandler(Symbol[], Int[], Interpolation[], BCValues{Float64}[], Int[], Int[], ScalarWrapper(false), grid, Ferrite.ScalarWrapper(-1))
end

function Base.show(io::IO, ::MIME"text/plain", dh::DofHandler)
    println(io, "DofHandler")
    println(io, "  Fields:")
    for i in 1:nfields(dh)
        println(io, "    ", repr(dh.field_names[i]), ", interpolation: ", dh.field_interpolations[i],", dim: ", dh.field_dims[i])
    end
    if !isclosed(dh)
        print(io, "  Not closed!")
    else
        println(io, "  Dofs per cell: ", ndofs_per_cell(dh))
        print(io, "  Total dofs: ", ndofs(dh))
    end
end

ndofs(dh::AbstractDofHandler) = dh.ndofs[]
ndofs_per_cell(dh::AbstractDofHandler, cell::Int=1) = dh.cell_dofs_offset[cell+1] - dh.cell_dofs_offset[cell]
isclosed(dh::AbstractDofHandler) = dh.closed[]
nfields(dh::AbstractDofHandler) = length(dh.field_names)
getfieldnames(dh::AbstractDofHandler) = dh.field_names
ndim(dh::AbstractDofHandler, field_name::Symbol) = dh.field_dims[find_field(dh, field_name)]
function find_field(dh::DofHandler, field_name::Symbol)
    j = findfirst(i->i == field_name, dh.field_names)
    j == 0 && error("did not find field $field_name")
    return j
end

# Calculate the offset to the first local dof of a field
function field_offset(dh::DofHandler, field_name::Symbol)
    offset = 0
    for i in 1:find_field(dh, field_name)-1
        offset += getnbasefunctions(dh.field_interpolations[i])::Int * dh.field_dims[i]
    end
    return offset
end

getfieldinterpolation(dh::DofHandler, field_idx::Int) = dh.field_interpolations[field_idx]
getfielddim(dh::DofHandler, field_idx::Int) = dh.field_dims[field_idx]
getbcvalue(dh::DofHandler, field_idx::Int) = dh.bc_values[field_idx]

function getfielddim(dh::DofHandler, name::Symbol)
    field_pos = findfirst(i->i == name, getfieldnames(dh))
    field_pos === nothing && error("did not find field $name")
    return dh.field_dims[field_pos]
end

"""
    dof_range(dh:DofHandler, field_name)

Return the local dof range for `field_name`. Example:

```jldoctest
julia> grid = generate_grid(Triangle, (3, 3));

julia> dh = DofHandler(grid); push!(dh, :u, 3); push!(dh, :p, 1); close!(dh);

julia> dof_range(dh, :u)
1:9

julia> dof_range(dh, :p)
10:12
```
"""
function dof_range(dh::DofHandler, field_name::Symbol)
    f = find_field(dh, field_name)
    offset = field_offset(dh, field_name)
    n_field_dofs = getnbasefunctions(dh.field_interpolations[f])::Int * dh.field_dims[f]
    return (offset+1):(offset+n_field_dofs)
end

function Base.push!(dh::DofHandler, name::Symbol, dim::Int, ip::Interpolation=default_interpolation(getcelltype(dh.grid)))
    @assert !isclosed(dh)
    @assert !in(name, dh.field_names)
    push!(dh.field_names, name)
    push!(dh.field_dims, dim)
    push!(dh.field_interpolations, ip)
    push!(dh.bc_values, BCValues(ip, default_interpolation(getcelltype(dh.grid))))
    return dh
end

# sort and return true (was already sorted) or false (if we had to sort)
function sortedge(edge::Tuple{Int,Int})
    a, b = edge
    a < b ? (return (edge, true)) : (return ((b, a), false))
end

sortface(face::Tuple{Int,Int}) = minmax(face[1], face[2])
function sortface(face::Tuple{Int,Int,Int})
    a, b, c = face
    b, c = minmax(b, c)
    a, c = minmax(a, c)
    a, b = minmax(a, b)
    return (a, b, c)
end

function sortface(face::Tuple{Int,Int,Int,Int})
    a, b, c, d = face
    c, d = minmax(c, d)
    b, d = minmax(b, d)
    a, d = minmax(a, d)
    b, c = minmax(b, c)
    a, c = minmax(a, c)
    a, b = minmax(a, b)
    return (a, b, c)
end

function close!(dh::DofHandler)
    dh, _, _, _ = __close!(dh)
    return dh
end

# close the DofHandler and distribute all the dofs
function __close!(dh::DofHandler{dim}) where {dim}
    @assert !isclosed(dh)

    # `vertexdict` keeps track of the visited vertices. We store the global vertex
    # number and the first dof we added to that vertex.
    vertexdicts = [Dict{Int,Int}() for _ in 1:nfields(dh)]

    # `edgedict` keeps track of the visited edges, this will only be used for a 3D problem
    # An edge is determined from two vertices, but we also need to store the direction
    # of the first edge we encounter and add dofs too. When we encounter the same edge
    # the next time we check if the direction is the same, otherwise we reuse the dofs
    # in the reverse order
    edgedicts = [Dict{Tuple{Int,Int},Tuple{Int,Bool}}() for _ in 1:nfields(dh)]

    # `facedict` keeps track of the visited faces. We only need to store the first dof we
    # added to the face; if we encounter the same face again we *always* reverse the order
    # In 2D a face (i.e. a line) is uniquely determined by 2 vertices, and in 3D a
    # face (i.e. a surface) is uniquely determined by 3 vertices.
    facedicts = [Dict{NTuple{dim,Int},Int}() for _ in 1:nfields(dh)]

    # celldofs are never shared between different cells so there is no need
    # for a `celldict` to keep track of which cells we have added dofs too.

    # We create the `InterpolationInfo` structs with precomputed information for each
    # interpolation since that allows having the cell loop as the outermost loop,
    # and the interpolation loop inside without using a function barrier
    interpolation_infos = InterpolationInfo[]
    for interpolation in dh.field_interpolations
        # push!(dh.interpolation_info, InterpolationInfo(interpolation))
        push!(interpolation_infos, InterpolationInfo(interpolation))
    end

    # not implemented yet: more than one facedof per face in 3D
    dim == 3 && @assert(!any(x->x.nfacedofs > 1, interpolation_infos))

    nextdof = 1 # next free dof to distribute
    push!(dh.cell_dofs_offset, 1) # dofs for the first cell start at 1

    # loop over all the cells, and distribute dofs for all the fields
    for (ci, cell) in enumerate(getcells(dh.grid))
        @debug println("cell #$ci")
        for fi in 1:nfields(dh)
            interpolation_info = interpolation_infos[fi]
            @debug println("  field: $(dh.field_names[fi])")
            if interpolation_info.nvertexdofs > 0
                for vertex in vertices(cell)
                    @debug println("    vertex#$vertex")
                    token = Base.ht_keyindex2!(vertexdicts[fi], vertex)
                    if token > 0 # haskey(vertexdicts[fi], vertex) # reuse dofs
                        reuse_dof = vertexdicts[fi].vals[token] # vertexdicts[fi][vertex]
                        for d in 1:dh.field_dims[fi]
                            @debug println("      reusing dof #$(reuse_dof + (d-1))")
                            push!(dh.cell_dofs, reuse_dof + (d-1))
                        end
                    else # token <= 0, distribute new dofs
                        for vertexdof in 1:interpolation_info.nvertexdofs
                            Base._setindex!(vertexdicts[fi], nextdof, vertex, -token) # vertexdicts[fi][vertex] = nextdof
                            for d in 1:dh.field_dims[fi]
                                @debug println("      adding dof#$nextdof")
                                push!(dh.cell_dofs, nextdof)
                                nextdof += 1
                            end
                        end
                    end
                end # vertex loop
            end
            if dim == 3 # edges only in 3D
                if interpolation_info.nedgedofs > 0
                    for edge in edges(cell)
                        sedge, dir = sortedge(edge)
                        @debug println("    edge#$sedge dir: $(dir)")
                        token = Base.ht_keyindex2!(edgedicts[fi], sedge)
                        if token > 0 # haskey(edgedicts[fi], sedge), reuse dofs
                            startdof, olddir = edgedicts[fi].vals[token] # edgedicts[fi][sedge] # first dof for this edge (if dir == true)
                            for edgedof in (dir == olddir ? (1:interpolation_info.nedgedofs) : (interpolation_info.nedgedofs:-1:1))
                                for d in 1:dh.field_dims[fi]
                                    reuse_dof = startdof + (d-1) + (edgedof-1)*dh.field_dims[fi]
                                    @debug println("      reusing dof#$(reuse_dof)")
                                    push!(dh.cell_dofs, reuse_dof)
                                end
                            end
                        else # token <= 0, distribute new dofs
                            Base._setindex!(edgedicts[fi], (nextdof, dir), sedge, -token) # edgedicts[fi][sedge] = (nextdof, dir),  store only the first dof for the edge
                            for edgedof in 1:interpolation_info.nedgedofs
                                for d in 1:dh.field_dims[fi]
                                    @debug println("      adding dof#$nextdof")
                                    push!(dh.cell_dofs, nextdof)
                                    nextdof += 1
                                end
                            end
                        end
                    end # edge loop
                end
            end
            if interpolation_info.nfacedofs > 0 && (interpolation_info.dim == dim)
                for face in faces(cell)
                    sface = sortface(face) # TODO: faces(cell) may as well just return the sorted list
                    @debug println("    face#$sface")
                    token = Base.ht_keyindex2!(facedicts[fi], sface)
                    if token > 0 # haskey(facedicts[fi], sface), reuse dofs
                        startdof = facedicts[fi].vals[token] # facedicts[fi][sface]
                        for facedof in interpolation_info.nfacedofs:-1:1 # always reverse (YOLO)
                            for d in 1:dh.field_dims[fi]
                                reuse_dof = startdof + (d-1) + (facedof-1)*dh.field_dims[fi]
                                @debug println("      reusing dof#$(reuse_dof)")
                                push!(dh.cell_dofs, reuse_dof)
                            end
                        end
                    else # distribute new dofs
                        Base._setindex!(facedicts[fi], nextdof, sface, -token)# facedicts[fi][sface] = nextdof,  store the first dof for this face
                        for facedof in 1:interpolation_info.nfacedofs
                            for d in 1:dh.field_dims[fi]
                                @debug println("      adding dof#$nextdof")
                                push!(dh.cell_dofs, nextdof)
                                nextdof += 1
                            end
                        end
                    end
                end # face loop
            end
            if interpolation_info.ncelldofs > 0 # always distribute new dofs for cell
                @debug println("    cell#$ci")
                for celldof in 1:interpolation_info.ncelldofs
                    for d in 1:dh.field_dims[fi]
                        @debug println("      adding dof#$nextdof")
                        push!(dh.cell_dofs, nextdof)
                        nextdof += 1
                    end
                end # cell loop
            end
        end # field loop
        # push! the first index of the next cell to the offset vector
        push!(dh.cell_dofs_offset, length(dh.cell_dofs)+1)
    end # cell loop
    dh.ndofs[] = maximum(dh.cell_dofs)
    dh.closed[] = true

    return dh, vertexdicts, edgedicts, facedicts

end

function celldofs!(global_dofs::Vector{Int}, dh::DofHandler, i::Int)
    @assert isclosed(dh)
    @assert length(global_dofs) == ndofs_per_cell(dh, i)
    unsafe_copyto!(global_dofs, 1, dh.cell_dofs, dh.cell_dofs_offset[i], length(global_dofs))
    return global_dofs
end

function cellnodes!(global_nodes::Vector{Int}, grid::Grid{dim,C}, i::Int) where {dim,C}
    @assert length(global_nodes) == nnodes(C)
    for j in 1:nnodes(C) # Currently assuming that DofHandler only has one celltype
        global_nodes[j] = grid.cells[i].nodes[j]
    end
    return global_nodes
end

function cellcoords!(global_coords::Vector{Vec{dim,T}}, grid::Grid{dim,C}, i::Int) where {dim,C,T}
    @assert length(global_coords) == nnodes(C)
    for j in 1:nnodes(C) # Currently assuming that DofHandler only has one celltype
        nodeid = grid.cells[i].nodes[j]
        global_coords[j] = grid.nodes[nodeid].x
    end
    return global_coords
end
cellcoords!(global_coords::Vector{<:Vec}, dh::DofHandler, i::Int) = cellcoords!(global_coords, dh.grid, i)

function celldofs(dh::DofHandler, i::Int)
    @assert isclosed(dh)
    n = ndofs_per_cell(dh, i)
    global_dofs = zeros(Int, n)
    unsafe_copyto!(global_dofs, 1, dh.cell_dofs, dh.cell_dofs_offset[i], n)
    return global_dofs
end

# Creates a sparsity pattern from the dofs in a DofHandler.
# Returns a sparse matrix with the correct storage pattern.
"""
    create_sparsity_pattern(dh::DofHandler)

Create the sparsity pattern corresponding to the degree of freedom
numbering in the [`DofHandler`](@ref). Return a `SparseMatrixCSC`
with stored values in the correct places.

See the [Sparsity Pattern](@ref) section of the manual.
"""
@inline create_sparsity_pattern(dh::DofHandler) = _create_sparsity_pattern(dh, false)

"""
    create_symmetric_sparsity_pattern(dh::DofHandler)

Create the symmetric sparsity pattern corresponding to the degree of freedom
numbering in the [`DofHandler`](@ref) by only considering the upper
triangle of the matrix. Return a `Symmetric{SparseMatrixCSC}`.

See the [Sparsity Pattern](@ref) section of the manual.
"""
@inline create_symmetric_sparsity_pattern(dh::DofHandler) = Symmetric(_create_sparsity_pattern(dh, true), :U)

function _create_sparsity_pattern(dh::DofHandler, sym::Bool)
    ncells = getncells(dh.grid)
    n = ndofs_per_cell(dh)
    N = sym ? div(n*(n+1), 2) * ncells : n^2 * ncells
    N += ndofs(dh) # always add the diagonal elements
    I = Int[]; resize!(I, N)
    J = Int[]; resize!(J, N)
    global_dofs = zeros(Int, n)
    cnt = 0
    for element_id in 1:ncells
        celldofs!(global_dofs, dh, element_id)
        @inbounds for j in 1:n, i in 1:n
            dofi = global_dofs[i]
            dofj = global_dofs[j]
            sym && (dofi > dofj && continue)
            cnt += 1
            if cnt > length(J)
                resize!(I, trunc(Int, length(I) * 1.5))
                resize!(J, trunc(Int, length(J) * 1.5))
            end
            I[cnt] = dofi
            J[cnt] = dofj

        end
    end
    @inbounds for d in 1:ndofs(dh)
        cnt += 1
        if cnt > length(J)
            resize!(I, trunc(Int, length(I) + ndofs(dh)))
            resize!(J, trunc(Int, length(J) + ndofs(dh)))
        end
        I[cnt] = d
        J[cnt] = d
    end
    resize!(I, cnt)
    resize!(J, cnt)
    V = zeros(length(I))
    K = sparse(I, J, V)
    return K
end

# dof renumbering
"""
    renumber!(dh::DofHandler, perm)

Renumber the degrees of freedom in the DofHandler according to the
permuation `perm`.

!!! warning
    Remember to do renumbering *before* adding boundary conditions,
    otherwise the mapping for the dofs will be wrong.
"""
function renumber!(dh::AbstractDofHandler, perm::AbstractVector{<:Integer})
    @assert isperm(perm) && length(perm) == ndofs(dh)
    cell_dofs = dh.cell_dofs
    for i in eachindex(cell_dofs)
        cell_dofs[i] = perm[cell_dofs[i]]
    end
    return dh
end

function WriteVTK.vtk_grid(filename::AbstractString, dh::AbstractDofHandler; compress::Bool=true)
    vtk_grid(filename, dh.grid; compress=compress)
end

"""
    reshape_to_nodes(dh::AbstractDofHandler, u::Vector{T}, fieldname::Symbol) where T

Reshape the entries of the dof-vector `u` which correspond to the field `fieldname` in nodal order.
Return a matrix with a column for every node and a row for every dimension of the field.
For superparametric fields only the entries corresponding to nodes of the grid will be returned. Do not use this function for subparametric approximations.
"""
function reshape_to_nodes(dh::DofHandler, u::Vector{T}, fieldname::Symbol) where T
    # make sure the field exists
    fieldname ∈ Ferrite.getfieldnames(dh) || error("Field $fieldname not found.")

    field_idx = findfirst(i->i==fieldname, getfieldnames(dh))
    offset = field_offset(dh, fieldname)
    field_dim = getfielddim(dh, field_idx)

    space_dim = field_dim == 2 ? 3 : field_dim
    data = fill(zero(T), space_dim, getnnodes(dh.grid))

    reshape_field_data!(data, dh, u, offset, field_dim)

    return data
end

function reshape_field_data!(data::Matrix{T}, dh::AbstractDofHandler, u::Vector{T}, field_offset::Int, field_dim::Int, cellset=Set{Int}(1:getncells(dh.grid))) where T

    _celldofs = Vector{Int}(undef, ndofs_per_cell(dh, first(cellset)))
    for cell in CellIterator(dh, collect(cellset))
        celldofs!( _celldofs, cell)
        counter = 1
        for node in getnodes(cell)
            for d in 1:field_dim
                data[d, node] = u[_celldofs[counter + field_offset]]
                @debug println("  exporting $(u[_celldofs[counter + field_offset]]) for dof#$(_celldofs[counter + field_offset])")
                counter += 1
            end
            if field_dim == 2
                # paraview requires 3D-data so pad with zero
                data[3, node] = 0
            end
        end
    end
    return data
end


using Blink

using MeshCat
using CoordinateTransformations
using Rotations
using GeometryBasics: HyperRectangle, Vec, Point, Mesh, Point3f0
using Colors: RGBA, RGB

if (false)
    vis = MeshCat.Visualizer()
    win = Blink.Window()
    open(vis, win)
    Blink.AtomShell.opentools(win)
end

# box = HyperRectangle(Vec(0., 0, 0), Vec(1., 1, 1))
# setobject!(vis, box)
# settransform!(vis, Translation(0., 1, 0))

# green_box_vis = setobject!(vis["group1"]["greenbox"], box, green_material)
# settransform!(green_box_vis, Translation(0, 0, 1))
# group1 = vis["group1"]
# settransform!(group1, Translation(0, 0, -1))

delete!(vis)

v = vis[:lines]
settransform!(v, Translation(-1, -1, 0))
points = [
    Point3f0(1, 1, 1),
    Point3f0(2, 2, 2),
    Point3f0(1, 1, 3),
    Point3f0(2, 2, 3),
]
colors = [
    RGBA(1, 0, 0, 1),
    RGBA(1, 0, 0, 1),
    RGBA(0, 1, 0, 1),
    RGBA(0, 1, 0, 1),
]
mat = LineBasicMaterial(
    color=RGBA(1.0, 1.0, 0.0, 1.0),
    linewidth = 11,
    # vertexColors = 1,
)
setobject!(v[:line_segments], LineSegments(points, colors))

# v = vis[:lines]
# settransform!(v, Translation(-1, -1, 0))
# θ = range(0, stop=2π, length=10)
# mat = LineBasicMaterial(
#     color=RGBA(1.0, 1.0, 0.0, 1.0),
#     linewidth = 11,
#     # vertexColors = 1,
# )
# setobject!(v[:line_segments], LineSegments(Point.(0.5 .* sin.(θ), 0, 0.5 .* cos.(θ)), mat))

# box = HyperRectangle(Vec(0., 0, 0), Vec(1., 1, 1))
# setobject!(vis["box"], box)
# settransform!(vis, Translation(0., 2, 0))

# vbox = vis["box"]

# va1 = vbox["arrow1"]
# arrow1 = ArrowVisualizer(va1)
# setobject!(arrow1)
# settransform!(arrow1, Point(5.0, 5.0, 5.0), Vec(-4.0, -4.0, -4.0), shaft_radius=0.1)

# va2 = vbox["arrow2"]
# arrow2 = ArrowVisualizer(va2)
# setobject!(arrow2)
# settransform!(arrow2, Point(-1.0, 2.0, 2.0), Vec(1.0, -1.0, -1.0))

# va3 = vbox["arrow3"]
# arrow3 = ArrowVisualizer(va3)
# setobject!(arrow3)
# settransform!(arrow3, Point(-1.0, -1.0, 2.0), Vec(1.0, 1.0, -1.0))

# va4 = vbox["arrow4"]
# arrow4 = ArrowVisualizer(va4)
# setobject!(arrow4)
# settransform!(arrow4, Point(2.0, -1.0, 2.0), Vec(-1.0, 1.0, -1.0))


using GitTest
using Test

@testset "testfunctionderivatives.jl" begin
    # Derivatives
    @test dfdx(1.23,-93.2) == 2
end


# Array info
for (m, f) in ((:Base, :size), (:Base, :axes), (:Base, :firstindex), (:Base, :lastindex))
    @eval begin
        @inline $m.$f(A::AbstractDimArray, dims::AllDims) = $m.$f(A, dimnum(A, dims))
    end
end

# Reducing methods

# With a function arg version
for (m, f) in ((:Base, :sum), (:Base, :prod), (:Base, :maximum), (:Base, :minimum),
                     (:Base, :extrema), (:Statistics, :mean))
    _f = Symbol('_', f)
    @eval begin
        # Base methods
        $m.$f(A::AbstractDimArray; dims=:, kw...) = $_f(A::AbstractDimArray, dims; kw...)
        $m.$f(f, A::AbstractDimArray; dims=:, kw...) = $_f(f, A::AbstractDimArray, dims; kw...)
        # Local dispatch methods
        # - Return a reduced DimArray
        $_f(A::AbstractDimArray, dims; kw...) =
            rebuild(A, $m.$f(parent(A); dims=dimnum(A, _astuple(dims)), kw...), reducedims(A, dims))
        $_f(f, A::AbstractDimArray, dims; kw...) =
            rebuild(A, $m.$f(f, parent(A); dims=dimnum(A, _astuple(dims)), kw...), reducedims(A, dims))
        # - Return a scalar
        $_f(A::AbstractDimArray, dims::Colon; kw...) = $m.$f(parent(A); dims, kw...)
        $_f(f, A::AbstractDimArray, dims::Colon; kw...) = $m.$f(f, parent(A); dims, kw...)
    end
end
# With no function arg version
for (m, f) in ((:Statistics, :std), (:Statistics, :var))
    _f = Symbol('_', f)
    @eval begin
        # Base methods
        $m.$f(A::AbstractDimArray; corrected::Bool=true, mean=nothing, dims=:) =
            $_f(A, corrected, mean, dims)
        # Local dispatch methods - Returns a reduced array
        $_f(A::AbstractDimArray, corrected, mean, dims) =
            rebuild(A, $m.$f(parent(A); corrected=corrected, mean=mean, dims=dimnum(A, _astuple(dims))), reducedims(A, dims))
        # - Returns a scalar
        $_f(A::AbstractDimArray, corrected, mean, dims::Colon) =
            $m.$f(parent(A); corrected=corrected, mean=mean, dims=:)
    end
end
for (m, f) in ((:Statistics, :median), (:Base, :any), (:Base, :all))
    _f = Symbol('_', f)
    @eval begin
        # Base methods
        $m.$f(A::AbstractDimArray; dims=:) = $_f(A, dims)
        # Local dispatch methods - Returns a reduced array
        $_f(A::AbstractDimArray, dims) =
            rebuild(A, $m.$f(parent(A); dims=dimnum(A, _astuple(dims))), reducedims(A, dims))
        # - Returns a scalar
        $_f(A::AbstractDimArray, dims::Colon) = $m.$f(parent(A); dims=:)
    end
end

# These are not exported but it makes a lot of things easier using them
function Base._mapreduce_dim(f, op, nt::NamedTuple{(),<:Tuple}, A::AbstractDimArray, dims)
    rebuild(A, Base._mapreduce_dim(f, op, nt, parent(A), dimnum(A, _astuple(dims))), reducedims(A, dims))
end
function Base._mapreduce_dim(f, op, nt::NamedTuple{(),<:Tuple}, A::AbstractDimArray, dims::Colon)
    Base._mapreduce_dim(f, op, nt, parent(A), dims)
end
function Base._mapreduce_dim(f, op, nt, A::AbstractDimArray, dims)
    rebuild(A, Base._mapreduce_dim(f, op, nt, parent(A), dimnum(A, dims)), reducedims(A, dims))
end
@static if VERSION >= v"1.6"
    function Base._mapreduce_dim(f, op, nt::Base._InitialValue, A::AbstractDimArray, dims)
        rebuild(A, Base._mapreduce_dim(f, op, nt, parent(A), dimnum(A, dims)), reducedims(A, dims))
    end
    function Base._mapreduce_dim(f, op, nt::Base._InitialValue, A::AbstractDimArray, dims::Colon)
        Base._mapreduce_dim(f, op, nt, parent(A), dims)
    end
end

# TODO: Unfortunately Base/accumulate.jl kw methods all force dims to be Integer.
# accumulate wont work unless that is relaxed, or we copy half of the file here.
# Base._accumulate!(op, B, A, dims::AllDims, init::Union{Nothing, Some}) =
    # Base._accumulate!(op, B, A, dimnum(A, dims), init)

# Dimension dropping

function Base.dropdims(A::AbstractDimArray; dims)
    dims = DD.dims(A, dims)
    data = Base.dropdims(parent(A); dims=dimnum(A, dims))
    rebuildsliced(A, data, _dropinds(A, dims))
end

@inline _dropinds(A, dims::DimTuple) = dims2indices(A, map(d -> basetypeof(d)(1), dims))
@inline _dropinds(A, dim::Dimension) = dims2indices(A, basetypeof(dim)(1))

# Function application

function Base.map(f, As::AbstractDimArray...)
    comparedims(As...)
    newdata = map(f, map(parent, As)...)
    rebuild(first(As); data=newdata)
end

function Base.mapslices(f, A::AbstractDimArray; dims=1, kw...)
    dimnums = dimnum(A, _astuple(dims))
    data = mapslices(f, parent(A); dims=dimnums, kw...)
    rebuild(A, data)
end

# This is copied from base as we can't efficiently wrap this function
# through the kw with a rebuild in the generator. Doing it this way
# also makes it faster to use a dim than an integer.
function Base.eachslice(A::AbstractDimArray; dims=1, kw...)
    if dims isa Tuple && length(dims) != 1
        throw(ArgumentError("only single dimensions are supported"))
    end
    dim = first(dimnum(A, dims))
    dim <= ndims(A) || throw(DimensionMismatch("A doesn't have $dim dimensions"))
    idx1, idx2 = ntuple(d->(:), dim-1), ntuple(d->(:), ndims(A)-dim)
    return (view(A, idx1..., i, idx2...) for i in axes(A, dim))
end

# Duplicated dims

for fname in (:cor, :cov)
    @eval function Statistics.$fname(A::AbstractDimArray{<:Any,2}; dims=1, kw...)
        newdata = Statistics.$fname(parent(A); dims=dimnum(A, dims), kw...)
        removed_idx = dimnum(A, dims)
        newrefdims = $dims(A)[removed_idx]
        newdim = $dims(A)[3 - removed_idx]
        rebuild(A, newdata, (newdim, newdim), (newrefdims,))
    end
end

# Rotations

struct _Rot90 end
struct _Rot180 end
struct _Rot270 end
struct _Rot360 end

Base.rotl90(A::AbstractDimMatrix) = rebuild(A, rotl90(parent(A)), _rot(_Rot90(), dims(A)))
Base.rotl90(A::AbstractDimMatrix, k::Integer) =
    rebuild(A, rotl90(parent(A), k), _rot(_rottype(k), dims(A)))

Base.rotr90(A::AbstractDimMatrix) = rebuild(A, rotr90(parent(A)), _rot(_Rot270(), dims(A)))
Base.rotr90(A::AbstractDimMatrix, k::Integer) =
    rebuild(A, rotr90(parent(A), k), _rot(_rottype(-k), dims(A)))

Base.rot180(A::AbstractDimMatrix) = rebuild(A, rot180(parent(A)), _rot(_Rot180(), dims(A)))

# Not type stable - but we have to lose type stability somewhere when
# dims are being swapped, by an Int value, so it may as well be here
function _rottype(k)
    k = mod(k, 4)
    if k == 1
        _Rot90()
    elseif k == 2
        _Rot180()
    elseif k == 3
        _Rot270()
    else
        _Rot360()
    end
end

_rot(::_Rot90, (dima, dimb)) = (flip(Relation, dimb), dima)
_rot(::_Rot180, dims) = map(d -> flip(Relation, d), dims)
_rot(::_Rot270, (dima, dimb)) = (dimb, flip(Relation, dima))
_rot(::_Rot360, dims) = dims

# Dimension reordering

for (pkg, fname) in [(:Base, :permutedims), (:Base, :adjoint),
                     (:Base, :transpose), (:LinearAlgebra, :Transpose)]
    @eval begin
        @inline $pkg.$fname(A::AbstractDimArray{<:Any,2}) =
            rebuild(A, $pkg.$fname(parent(A)), reverse(dims(A)))
        @inline $pkg.$fname(A::AbstractDimArray{<:Any,1}) =
            rebuild(A, $pkg.$fname(parent(A)), (AnonDim(Base.OneTo(1)), dims(A)...))
    end
end
for fname in [:permutedims, :PermutedDimsArray]
    @eval begin
        @inline function Base.$fname(A::AbstractDimArray, perm)
            rebuild(A, $fname(parent(A), dimnum(A, Tuple(perm))), sortdims(dims(A), Tuple(perm)))
        end
    end
end

# Concatenation
function Base._cat(catdims::Union{Int,Base.Dims}, Xin::AbstractDimArray...)
    Base._cat(dims(first(Xin), catdims), Xin...)
end
function Base._cat(catdims::Tuple, Xin::AbstractDimArray...)
    A1 = first(Xin)
    comparedims(map(x -> otherdims(x, catdims), Xin)...)
    newcatdims = map(catdims) do catdim
        if all(x -> hasdim(x, catdim), Xin)
            # We concatenate an existing dimension
            newcatdim = if mode(A1, catdim) isa NoIndex
                # Colon will be converted to array axis in `formatdims`
                rebuild(catdim; val=:)
            else
                # vcat the index for the catdim in each of Xin
                reduce(vcat, map(x -> dims(x, catdim), Xin))
            end
        else
            # Concatenate new dims
            if all(map(x -> hasdim(refdims(x), catdim), Xin))
                # vcat the refdims
                reduce(vcat, map(x -> refdims(x, catdim), Xin))
            else
                # Use the catdim as the new dimension
                catdim
            end
        end
    end
    inserted_dims = dims(newcatdims, dims(A1))
    appended_dims = otherdims(newcatdims, inserted_dims)
    updated_dims = setdims(dims(A1), inserted_dims)
    newdims = (updated_dims..., appended_dims...)

    inserted_dnums = dimnum(A1, inserted_dims)
    appended_dnums = ntuple(i -> i + length(dims(A1)), length(appended_dims))
    cat_dnums = (inserted_dnums..., appended_dnums...)

    newrefdims = otherdims(refdims(A1), newcatdims)
    newA = Base._cat(cat_dnums, map(data, Xin)...)
    rebuild(A1, newA, formatdims(newA, newdims), newrefdims)
end
function Base._cat(catdim::DimType, Xin::AbstractDimArray...)
    Base._cat(catdim(; mode=NoIndex()), Xin...)
end
function Base._cat(catdim::DimOrDimType, Xin::AbstractDimArray...)
    Base._cat((catdim,), Xin...)
end
# function Base._cat(catdim::DimOrDimType, Xin::AbstractDimArray...)
#     A1 = first(Xin)
#     comparedims(map(x -> otherdims(x, catdim), Xin)...)
#     newrefdims = refdims(A1)
#     if all(A -> hasdim(A, catdim), Xin)
#         # We concatenate an existing dimension
#         if mode(A1, catdim) isa NoIndex
#             # Make new dims for NoIndex
#             dnum = dimnum(A1, catdim)
#             newdims = setdims(dims(A1), catdim)
#         else
#             # Use existing dims
#             dnum = dimnum(A1, catdim)
#             # cat the catdim, ignore others
#             newcatdim = reduce(vcat, map(x -> dims(x, catdim), Xin))
#             newdims = setdims(dims(A1), newcatdim)
#         end
#     else
#         # Concatenate a new dimension
#         if all(map(x -> hasdim(refdims(x), catdim), Xin))
#             # vcat the refdims
#             newcatdim = reduce(vcat, map(x -> refdims(x, catdim), Xin))
#             newdims = (dims(A1)..., newcatdim)
#             dnum = ndims(A1) + 1
#             newrefdims = otherdims(refdims(A1), catdim)
#         else
#             newdims = (dims(A1)..., catdim)
#             dnum = ndims(A1) + 1
#         end
#     end
#     newA = Base._cat(dnum, map(parent, Xin)...)
#     rebuild(A1; data=newA, dims=formatdims(newA, newdims), refdims=newrefdims)
# end

function Base.vcat(dims::Dimension...)
    newmode = _vcat_modes(mode(dims)...)
    newindex = _vcat_index(newmode, map(val, dims)...)
    rebuild(dims[1], newindex, newmode)
end

# IndexModes may need adjustment for `cat`
_vcat_modes(modes::IndexMode...) = first(modes)
function _vcat_modes(modes::AbstractSampled...)
    _vcat_modes(sampling(first(modes)), span(first(modes)), modes...)
end
function _vcat_modes(::Any, ::Regular, modes...)
    _step = step(first(modes))
    map(modes) do mode
        step(span(mode)) == _step || error("Step sizes $(step(span(mode))) and $_step do not match ")
    end
    first(modes)
end
function _vcat_modes(::Intervals, ::Irregular, modes...)
    allbounds = map(bounds ∘ span, modes)
    newbounds = minimum(map(first, allbounds)), maximum(map(last, allbounds))
    rebuild(modes[1]; span=Irregular(newbounds))
end
_vcat_modes(::Points, ::Irregular, modes...) =
    rebuild(first(modes); span=Irregular(nothing, nothing))

# Index vcat depends on mode: NoIndex is always Colon()
_vcat_index(mode::NoIndex, A...) = OneTo(sum(map(length, A)))
# TODO: handle vcat OffsetArrays?
# Otherwise just vcat. TODO: handle order breaking vcat?
_vcat_index(mode::IndexMode, A...) = vcat(A...)

Base.inv(A::AbstractDimArray{T,2}) where T =
    rebuild(A, inv(parent(A)), reverse(map(d -> flip(IndexOrder, d), dims(A))))

# Index breaking

# TODO: change the index and traits of the reduced dimension and return a DimArray.
Base.unique(A::AbstractDimArray; dims::Union{DimOrDimType,Colon}=:) = _unique(A, dims)
Base.unique(A::AbstractDimArray{<:Any,1}) = unique(parent(A))

_unique(A::AbstractDimArray, dims::DimOrDimType) =
    unique(parent(A); dims=dimnum(A, dims))
_unique(A::AbstractDimArray, dims::Colon) = unique(parent(A); dims=:)

Base.diff(A::AbstractDimVector; dims=1) = _diff(A, dimnum(A, dims))
Base.diff(A::AbstractDimArray; dims) = _diff(A, dimnum(A, dims))

@inline function _diff(A::AbstractDimArray{<:Any,N}, dims::Integer) where {N}
    r = axes(A)
    # Copied from Base.diff
    r0 = ntuple(i -> i == dims ? UnitRange(1, last(r[i]) - 1) : UnitRange(r[i]), N)
    rebuildsliced(A, diff(parent(A); dims=dimnum(A, dims)), r0)
end

# Forward `replace` to parent objects
function Base._replace!(new::Base.Callable, res::AbstractDimArray, A::AbstractDimArray, count::Int)
    Base._replace!(new, parent(res), parent(A), count)
    return res
end
function Base._replace!(new::Base.Callable, res::AbstractArray, A::AbstractDimArray, count::Int)
    Base._replace!(new, res, parent(A), count)
    return res
end
function Base._replace!(new::Base.Callable, res::AbstractDimArray, A::AbstractArray, count::Int)
    Base._replace!(new, parent(res), A, count)
    return res
end


#
# This file is part of the DiscreteEvents.jl Julia package, MIT license
#
# Paul Bayer, 2019
#
# This is a Julia package for discrete event simulation
#

"Supertype for clocks."
abstract type AbstractClock end

"Supertype for events."
abstract type AbstractEvent end

# abstract types for clock state machines
abstract type ClockState end
abstract type ClockEvent end

"""
    Timing

Enumeration type for scheduling events and timed conditions:

- `at`: schedule an event at a given time,
- `after`: schedule an event a given time after current time,
- `every`: schedule an event every given time from now on,
- `before`: a timed condition is true before a given time,
- `until`: delay until t.
"""
@enum Timing at after every before until

"""
    Action

An action is either  a `Function` or an `Expr` or a `Tuple` of them.
It can be scheduled in an event for later execution.
"""
const Action = Union{Function, Expr, Tuple{Vararg{Union{Function, Expr}}}}

"""
````
DiscreteEvent{T<:Action, X} <: AbstractEvent
```
A discrete event is an [`Action`](@ref) to be executed at an event time.

# Arguments, fields
- `ex::T`: a function or an expression or a tuple of them,
- `t::Float64`: event time,
- `Δt::X`: repeat interval (Float64, Distribution or Nothing),
- `n::Int`: number of repeats.
"""
struct DiscreteEvent{T<:Action, X} <: AbstractEvent
    ex::T
    t::Float64
    Δt::X
    n::Int
end

"""
    DiscreteCond{S<:Action, T<:Action} <: AbstractEvent

A condition to be evaluated repeatedly with expressions or functions
to be executed if conditions are met.

# Arguments, fields
- `cond::S`: a conditional function or an expression or a tuple of them
    (conditions must evaluate to `Bool`),
- `ex::T`: a function or an expression or a tuple of them to be executed
    if conditions are met,
"""
struct DiscreteCond{S<:Action, T<:Action} <: AbstractEvent
    cond::S
    ex::T
end

"""
    Sample{T<:Action} <: AbstractEvent

Sampling actions are executed at sampling time.

# Arguments, fields
- `ex<:Action`: an [`Action`](@ref) to be executed at sample time.
"""
struct Sample{T<:Action} <: AbstractEvent
    ex::T
end

"""
    PrcException(event, value)

An exception to be thrown at processes.

# Arguments, fields
- `event`: deliver an event to the interrupted task
- `value`: deliver some other value
"""
struct PrcException{U,V} <: Exception
  event::U
  value::V
end

"""
```
Prc(id, f, arg...; kw...)
Prc(f, arg...; kw...)
```

Prepare a function to run as a process and get registered to a clock.

# Arguments, Fields
- `id`: some unique identification for registration,
- `f::Function`: a function `f(clk, arg...; kw...)`, must take `clk`
    (a [`Clock`](@ref)) as its first argument,
- `arg...`: further arguments to `f`
- `kw...`: keyword arguments to `f`

# Fields identified during registration
- `id`: if it is not provided, some integer will be calculated
    for it during registration,
- `task::Union{Task,Nothing}`: a task structure used for diagnosis,
- `clk::Union{AbstractClock,Nothing}`: clock where the process is
    registered,

!!! note "The clock is identified dynamically!"

    The clock `clk` where a process runs and gets registered is
    identified during process startup and then passed as 1st
    argument to `f`.
"""
mutable struct Prc
    id::Any
    task::Union{Task,Nothing}
    clk::Union{AbstractClock,Nothing}
    f::Function
    arg::Tuple
    kw::Base.Iterators.Pairs

    Prc( id, f::Function, arg...; kw...) = new(id, nothing, nothing, f, arg, kw)
    Prc(f::Function, arg...; kw...) = new(1, nothing, nothing, f, arg, kw)
end

"""
    Schedule()

A schedule contains events, conditional events and sampling functions
to be executed or evaluated on the clock's time line.

# Fields
- `events::PriorityQueue{DiscreteEvent,Float64}`: scheduled events,
- `cevents::Array{DiscreteCond,1}`: conditional events to evaluate at each tick,
- `samples::Array{Sample,1}`: sampling expressions to evaluate at each tick,
"""
mutable struct Schedule
    events::PriorityQueue{DiscreteEvent,Float64}
    cevents::Array{DiscreteCond,1}
    samples::Array{Sample,1}

    Schedule() = new(PriorityQueue{DiscreteEvent,Float64}(), DiscreteCond[], Sample[])
end

"""
    ClockChannel{T <: ClockEvent}

Provide a message channel to an active clock or a real time clock.

# Fields
- `ref::Ref{Task}`: a reference to an active clock task, useful for diagnosis,
- `forth::Channel{T}`: a communication channel to an active clock,
- `back::Channel{T}`: response channel from the active clock,
- `thread::Int`: the thread id of the active clock,
- `done::Bool`: flag indicating if the active clock has completed its cycle,
- `load::Int`: internal flag.
"""
mutable struct ClockChannel{T <: ClockEvent}
    ref::Ref{Task}
    forth::Channel{T}
    back::Channel{T}
    thread::Int
    done::Bool
    load::Int
end

"""
    Clock{AC} <: AbstractClock

A virtual clock structure, used for global and thread local clocks.

# Fields
- `id::Int`: clock ident number 1: master clock, > 1: parallel clock,
- `ac::AC`: if id == 1: a [`Vector{ClockChannel}`](@ref ClockChannel) else: [`Ref{ActiveClock}`](@ref ActiveClock),
- `state::ClockState`: clock state,
- `time::Float64`: clock time,
- `unit::FreeUnits`: time unit,
- `end_time::Float64`: end time for simulation,
- `Δt::Float64`: sampling time, timestep between ticks,
- `sc::Schedule`: the clock [`Schedule`](@ref) (events, cond events and sampling),
- `processes::Dict{Any, Prc}`: registered `Prc`es,
- `channels::Vector{Channel}`: registered (Actor) channels,
- `tn::Float64`: next timestep,
- `tev::Float64`: next event time,
- `evcount::Int`: event counter,
- `scount::Int`: sample counter
"""
mutable struct Clock{AC} <: AbstractClock
    id::Int
    ac::AC
    state::ClockState
    time::Float64
    unit::FreeUnits
    Δt::Float64
    sc::Schedule
    processes::Dict{Any, Prc}
    channels::Vector{Channel}
    tn::Float64
    tev::Float64
    end_time::Float64
    evcount::Int
    scount::Int
end

"Global clock with vector of active clocks."
const GlobalClock = Clock{Vector{ClockChannel}}

"""
```
Clock(Δt::T=0.01; t0::U=0, unit::FreeUnits=NoUnits) where {T<:Number,U<:Number}
```
Create a new virtual clock.

# Arguments
- `Δt::T=0.01`: time increment for sampling. Δt can be set later with `sample_time!`.
- `t0::U=0`: start time for simulation
- `unit::FreeUnits=NoUnits`: clock time unit. Units can be set explicitely by
    setting e.g. `unit=minute` or implicitly by giving Δt as a time or else setting
    t0 to a time, e.g. `t0=60s`.

The created clock has id==1 (master).
"""
function Clock(Δt::T=0.01;
    t0::U=0, unit::FreeUnits=NoUnits) where {T<:Number,U<:Number}
    if 1unit isa Time
        Δt = isa(Δt, Time) ? uconvert(unit, Δt).val : Δt
        t0 = isa(t0, Time) ? uconvert(unit, t0).val : t0
    elseif Δt isa Time
        unit = Unitful.unit(Δt)
        t0 = isa(t0, Time) ? uconvert(unit, t0).val : t0
        Δt = Δt.val
    elseif t0 isa Time
        unit = Unitful.unit(t0)
        t0 = t0.val
    else
        nothing
    end
    GlobalClock(threadid(), ClockChannel[], Idle(),
        t0, unit, Δt, Schedule(), Dict{Any, Prc}(), Channel[],
        t0 + Δt, t0, t0, 0, 0)
end

"""
    ActiveClock{E <: ClockEvent} <: AbstractClock

An active clock is a wrapper around a local [`Clock`](@ref) on a
parallel thread. It is operated by a thread local task. The master
clock on thread 1 communicates with it through messages over its
channels.

# Fields
- `clock::Clock`: the thread specific local clock,
- `master::Ref{Clock}`: a pointer to the master clock (on thread 1),
- `forth::Channel{E}`: the command channel from master,
- `back::Channel{E}`: the response channel to master,
- `id::Int`: the clocks id/thread number,
- `task::Task`: the active clock`s task.

!!! note "Don't setup an active clock explicitly!"

    It is done implicitly with [`PClock`](@ref) or by [`fork!`](@ref)ing
    a [`Clock`](@ref) to other available threads.

On a parallel thread tasks can access their local clock with
[`pclock(clk)`](@ref pclock). Then they can schedule 'thread local'
events, `delay!` or `wait!` on it.

!!! note "Don't share active clocks between threads!"

    In multithreading we communicate over channels and don't want
    to share variables between threads. A user can still access
    active clocks for diagnostic purposes.
"""
mutable struct ActiveClock{E <: ClockEvent} <: AbstractClock
    clock::Clock
    master::Ref{Clock{Vector{ClockChannel}}}
    forth::Channel{E}
    back ::Channel{E}
    id::Int
    task::Task
end

"Local clock with reference to a wrapping active clock."
const LocalClock = Clock{Ref{ActiveClock{ClockEvent}}}

"""
    localClock(c::Clock)

Create a thread local clock with an undefined reference to an
active clock which inherits parameters of the master clock `c`.
"""
function localClock(c::Clock)
    LocalClock(threadid(),
        Ref{ActiveClock{ClockEvent}}(), Idle(),
        c.time, c.unit, c.Δt, Schedule(), Dict{Any, Prc}(), Channel[],
        c.time + c.Δt, c.time, c.time, 0, 0)
end

"""
    RTClock{E <: ClockEvent} <: AbstractClock

A real time clock checks every given period for scheduled events and executes them. It has a
time in seconds since its start or last reset and uses system time for updating.

Real time clocks are controlled over channels. Multiple real time clocks can be setup with
arbitrary periods (≥ 1 ms). Real time clocks should not be created directly but rather with
`CreateRTClock`.

# Fields
- `Timer::Timer`:     clock period in seconds, minimum is 0.001 (1 ms)
- `clock::Clock`:     clock work
- `cmd::Channel{T}`:  command channel to asynchronous clock
- `back::Channel{T}`: back channel from async clock
- `id::Int`:          arbitrary id number
- `thread::Int`:      thread the async clock is living in
- `time::Float64`:    clock time since start in seconds
- `t0::Float64`:      system time at clock start in seconds
- `T::Float64`:       clock period in seconds
"""
mutable struct RTClock{E <: ClockEvent} <: AbstractClock
    Timer::Timer
    clock::Clock
    cmd::Channel{E}
    back::Channel{E}
    id::Int
    thread::Int
    time::Float64
    t0::Float64
    T::Float64
    task::Task
end


struct Screen
    window
    renderer
    height::Int
    width::Int
    background::Union{ARGB, Ptr{SDL_Surface}}

    function Screen(name, w, h, background)
        win, renderer = makeWinRenderer(name, w, h)
        new(win, renderer, h, w, to_ARGB(background))
    end
end

#non ARGB colorant is converted to ARGB
#ARGB colorant is rerturned as is
# non colorant is returned as is (required since background is stored as an Union )
to_ARGB(c) = c
to_ARGB(c::ARGB) = c
to_ARGB(c::Colorant) = ARGB(c)

abstract type Geom end

"""
`Rect(x::Int,y::Int,w::Int,h::Int)`
`Rect(x::Tuple, y::Tuple)`

Creates an actor representing a rectangle.
"""
mutable struct Rect <: Geom
    x::Int
    y::Int
    w::Int
    h::Int
end
Rect(x::Tuple, y::Tuple) = Rect(x[1], x[2], y[1], y[2])

import Base:+
+(r::Rect, t::Tuple{T,T}) where T <: Number = Rect(Int(r.x+t[1]), Int(r.y+t[2]), r.h, r.w)

"""
`Line(x1::Int, y1::Int, x2::Int, y2::Int)`
`Line(x::Tuple, y::Tuple)`

Creates an actor representing a line.
"""
mutable struct Line <: Geom
    x1::Int
    y1::Int
    x2::Int
    y2::Int
end

Line(x::Tuple, y::Tuple) = Line(x[1], x[2], y[1], y[2])

mutable struct Triangle <: Geom
  p1::Vector{Int}
  p2::Vector{Int}
  p3::Vector{Int}

  function Triangle(p1::Vector{Int}, p2::Vector{Int}, p3::Vector{Int})
    if !(length(p1) == length(p2) == length(p3) == length(p3) == 2)
      error("Given vectors are of incompatible size.")
    end
    return new(p1, p2, p3)
  end

  function Triangle(x1::Int, y1::Int, x2::Int, y2::Int, x3::Int, y3::Int)
    new([x1; y1], [x2; y2], [x3; y3])
  end

  function Triangle(p1::Tuple, p2::Tuple, p3::Tuple)
    if !(length(p1) == length(p2) == length(p3) == length(p3) == 2)
      error("Given tuples are of incompatible size.")
    end
    return new([p1[1]; p1[2]], [p2[1]; p2[2]], [p3[1]; p3[2]])
  end
end

mutable struct Circle <: Geom
    x::Int
    y::Int
    r::Int
end

Base.convert(T::Type{SDL_Rect}, r::Rect) = SDL_Rect(Cint.((r.x, r.y, r.w, r.h))...)

function Base.setproperty!(s::Geom, p::Symbol, x)
    if hasfield(typeof(s), p)
        setfield!(s, p, Int(round(x)))
    else
        v = getPos(Val(p), s, x)
        setfield!(s, :x, Int(round(v[1])))
        setfield!(s, :y, Int(round(v[2])))
    end
end

function Base.getproperty(s::Geom, p::Symbol)
    if hasfield(typeof(s), p)
        getfield(s, p)
    else
        v = getPos(Val(p), s)
        return v
    end
end

# A rect is stored as x/y/width/height. The following functions
# then calculate other metrics dynamically. Some metrics refer to
# two dimensions (topright); they return tuples. Some other refer to
# a single dimension (top); they return scalars
# The functions are called from `getproperty` for Rect
getPos(::Val{:left}, s::Rect) = s.x
getPos(::Val{:right}, s::Rect) = s.x+s.w
getPos(::Val{:top}, s::Rect) = s.y
getPos(::Val{:bottom}, s::Rect) = s.y+s.h
getPos(::Val{:pos}, s::Rect) = getPos(Val(:topleft), s)
getPos(::Val{:topleft}, s::Rect) = (s.x, s.y)
getPos(::Val{:topright}, s::Rect) = (s.x+s.w, s.y)
getPos(::Val{:bottomleft}, s::Rect) = (s.x, s.y+s.h)
getPos(::Val{:bottomright}, s::Rect) = (s.x+s.w, s.y+s.h)
getPos(::Val{:center}, s::Rect) = (s.x+s.w/2, s.y+s.h/2)
getPos(::Val{:centerx}, s::Rect) = s.x+s.w/2
getPos(::Val{:centery}, s::Rect) =  s.y+s.h/2
getPos(::Val{:centerleft}, s::Rect) = (s.x, s.y+s.h/2)
getPos(::Val{:centerright}, s::Rect) = (s.x+s.w, s.y+s.h/2)
getPos(::Val{:bottomcenter}, s::Rect) = (s.x+s.w/2, s.y+s.h)
getPos(::Val{:topcenter}, s::Rect) = (s.x+s.w/2, s.y)

# The following functions are used to postion a rectangle using various metrics.
# They essentially return the x and y position, given other metrics (eg. bottomleft)
# These functions are called from `setproperty` for Rect
getPos(X::Val, s::Geom, v...) = nothing
getPos(X::Val, s::Geom, v::Tuple) = getPos(X, s, v[1], v[2])

getPos(::Val{:left}, s::Rect, v) = (v, s.y)
getPos(::Val{:right}, s::Rect, v) = (v-s.w, s.y)
getPos(::Val{:top}, s::Rect, v) = (s.x, v)
getPos(::Val{:bottom}, s::Rect, v) = (s.x, v-s.h)
getPos(::Val{:pos}, s::Rect, u, v) = getPos(Val(:topleft), s, u, v)
getPos(::Val{:topleft}, s::Rect, u, v) = (u, v)
getPos(::Val{:topright}, s::Rect, u, v) = (u-s.w, v)
getPos(::Val{:bottomleft}, s::Rect, u, v) = (u, v-s.h)
getPos(::Val{:bottomright}, s::Rect, u, v) = (u-s.w, v-s.h)
getPos(::Val{:center}, s::Rect, u, v) = (u-s.w/2, v-s.h/2)
getPos(::Val{:centerx}, s::Rect, u) = (u-s.w/2, s.y)
getPos(::Val{:centery}, s::Rect, v) = (s.x, v-s.h/2)
getPos(::Val{:centerleft}, s::Rect, u, v) = (u, v-s.h/2)
getPos(::Val{:centerright}, s::Rect, u, v) = (u-s.w, v-s.h/2)
getPos(::Val{:bottomcenter}, s::Rect, u, v) = (u-s.w/2, v-s.h)
getPos(::Val{:topcenter}, s::Rect, u, v) = (u-s.w/2, v)

getPos(::Val{:center}, s::Triangle) = (s.p1 + s.p2 + s.p3) / 3
getPos(::Val{:left}, s::Triangle) = min(s.p1[1], s.p2[1], s.p3[1])
getPos(::Val{:right}, s::Triangle) = max(s.p1[1], s.p2[1], s.p3[1])
getPos(::Val{:top}, s::Triangle) = max(s.p1[2], s.p2[2], s.p3[2])
getPos(::Val{:bottom}, s::Triangle) = min(s.p1[2], s.p2[2], s.p3[2])
getPos(::Val{:centerx}, s::Triangle) = (s.p1[1] + s.p2[1] + s.p3[1]) / 3
getPos(::Val{:centery}, s::Triangle) = (s.p1[2] + s.p2[2] + s.p3[2]) / 3

getPos(::Val{:center}, s::Circle, u, v) = (u, v)
getPos(::Val{:top}, s::Circle, v) = (s.x, v-s.r)
getPos(::Val{:bottom}, s::Circle, v) = (s.x, v+s.r)
getPos(::Val{:left}, s::Circle, u) = (u-s.r, s.y)
getPos(::Val{:right}, s::Circle, u) = (u+s.r, s.y)
getPos(::Val{:centerx}, s::Circle, u) = (u, s.y)
getPos(::Val{:centery}, s::Circle, v) = (s.x, v)

getPos(::Val{:center}, s::Circle) = (s.x, s.y)
getPos(::Val{:top}, s::Circle) = s.y-s.r
getPos(::Val{:bottom}, s::Circle) = s.y+s.r
getPos(::Val{:left}, s::Circle) = s.x-s.r
getPos(::Val{:right}, s::Circle) = s.x+s.r
getPos(::Val{:centerx}, s::Circle) = s.x
getPos(::Val{:centery}, s::Circle) = s.y

function clear(s::Screen)
    fill(s, s.background)
end

clear() = clear(game[].screen)

function Base.fill(s::Screen, c::Colorant)
    SDL_SetRenderDrawColor(
        s.renderer,
        sdl_colors(c)...,
    )
    SDL_RenderClear(s.renderer)
end

function Base.fill(s::Screen, sf::Ptr{SDL_Surface})
    texture = SDL_CreateTextureFromSurface(s.renderer, sf)
    SDL_RenderCopy(s.renderer, texture, C_NULL, C_NULL)
    SDL_DestroyTexture(texture)
end

draw(l::T, args...; kv...) where T <: Geom = draw(game[].screen, l, args...; kv...)

function draw(s::Screen, l::Line, c::Colorant=colorant"black")
    SDL_SetRenderDrawColor(
        s.renderer,
        sdl_colors(c)...,
    )
    SDL_RenderDrawLine(s.renderer, Cint.((l.x1, l.y1, l.x2, l.y2))...)
end

function draw(s::Screen, r::Rect, c::Colorant=colorant"black"; fill=false)
    SDL_SetRenderDrawColor(
        s.renderer,
        sdl_colors(c)...,
    )
    sr = convert(SDL_Rect, r)
    if !fill
        SDL_RenderDrawRect(s.renderer, Ref(sr))
    else
        SDL_RenderFillRect(s.renderer, Ref(sr))
    end
end

sdl_colors(c::Colorant) = sdl_colors(convert(ARGB{Colors.FixedPointNumbers.Normed{UInt8,8}}, c))
sdl_colors(c::ARGB) = Int.(reinterpret.((red(c), green(c), blue(c), alpha(c))))

function draw(s::Screen, tr::Triangle, c::Colorant=colorant"black"; fill=false)
  p1, p2, p3 = Cint.(tr.p1), Cint.(tr.p2), Cint.(tr.p3)
  SDL_SetRenderDrawColor(s.renderer, sdl_colors(c)...)
  SDL_RenderDrawLines(s.renderer, [p1; p2; p3; p1], Cint(4))

  ymax = max(p1[2], p2[2], p3[2])
  ymin = min(p1[2], p2[2], p3[2])
  if fill && ymin != ymax
    # Set q1, q2 and q3 in descending order of y-value
    q1 = (p1[2] != ymax != p2[2]) * p3 +
        (p2[2] != ymax != p3[2]) * p1 +
        (p3[2] != ymax != p1[2]) * p2 +
        (p1[2] == p2[2] == ymax) * p2 +
        (p2[2] == p3[2] == ymax) * p3 +
        (p3[2] == p1[2] == ymax) * p1
    q3 = (p1[2] != ymin != p2[2]) * p3 +
        (p2[2] != ymin != p3[2]) * p1 +
        (p3[2] != ymin != p1[2]) * p2 +
        (p1[2] == p2[2] == ymin) * p2 +
        (p2[2] == p3[2] == ymin) * p3 +
        (p3[2] == p1[2] == ymin) * p1
    q2 = ((q1 == p1 && q3 == p3) || (q1 == p3 && q3 == p1)) * p2 +
        ((q1 == p1 && q3 == p2) || (q1 == p2 && q3 == p1)) * p3 +
        ((q1 == p2 && q3 == p3) || (q1 == p3 && q3 == p2)) * p1

    n = q1[2] - q2[2]
    x0 = q1[1] + (q2[2] - q1[2]) / (q3[2] - q1[2]) * (q3[1] - q1[1])
    for j = Cint(0):n-Cint(1)
      r1 = [round(Cint, q2[1] + j / n * (q1[1] - q2[1])); q2[2] + j]
      r2 = [round(Cint, x0 + j / n * (q1[1] - x0)); q2[2] + j]
      SDL_RenderDrawLines(s.renderer, [r1; r2], Cint(2))
    end
    n = q2[2] - q3[2]
    for j = Cint(1):n-Cint(1)
      r1 = [round(Cint, q2[1] + j / n * (q3[1] - q2[1])); q2[2] - j]
      r2 = [round(Cint, x0 + j / n * (q3[1] - x0)); q2[2] - j]
      SDL_RenderDrawLines(s.renderer, [r1; r2], Cint(2))
    end
  end
end

# improved circle drawing algorithm. slower but fills completely. needs optimization
function draw(s::Screen, circle::Circle, c::Colorant=colorant"black"; fill=false)
    # define the center and needed sides of circle
    centerX = Cint(circle.x)
    centerY = Cint(circle.y)
    int_rad = Cint(circle.r)
    left = centerX - int_rad
    top = centerY - int_rad

    SDL_SetRenderDrawColor(
        s.renderer,
        sdl_colors(c)...,
    )

    # we consider a grid with sides equal to the circle's diameter
    for x in left:centerX
        for y in top:centerY

            # for each pixel in the top left quadrant of the grid we measure the distance from the center.
            dist = sqrt( (centerX - x)^2 + (centerY - y)^2 )

            # if it is close to the circle's radius it and all associated points in the other quadrants are colored in.
            if (dist <= circle.r + 0.5 && dist >= circle.r - 0.5)
                rel_x = centerX - x
                rel_y = centerY - y

                quad1 = (x              , y              )
                quad2 = (centerX + rel_x, y              )
                quad3 = (x              , centerY + rel_y)
                quad4 = (quad2[1]       , quad3[2]       )

                SDL_RenderDrawPoint(s.renderer, quad1[1], quad1[2])
                SDL_RenderDrawPoint(s.renderer, quad2[1], quad2[2])
                SDL_RenderDrawPoint(s.renderer, quad3[1], quad3[2])
                SDL_RenderDrawPoint(s.renderer, quad4[1], quad4[2])

                # if we are told to fill in the circle we draw lines between all of the quadrants to completely fill the circle
                if (fill == true)
                    SDL_RenderDrawLine(s.renderer, quad1[1], quad1[2], quad2[1], quad2[2])
                    SDL_RenderDrawLine(s.renderer, quad2[1], quad2[2], quad4[1], quad4[2])
                    SDL_RenderDrawLine(s.renderer, quad4[1], quad4[2], quad3[1], quad3[2])
                    SDL_RenderDrawLine(s.renderer, quad3[1], quad3[2], quad1[1], quad1[2])
                end
            end

        end
    end

end

rect(x::Rect) = x
rect(x::Circle) = Rect(x.left, x.top, 2*x.r, 2*x.r)


##################################################
#   Override default handling (default = inline) #
##################################################

get_latex_function(r::DataFrames.DataFrame) = mdtable
get_md_function(args::DataFrames.DataFrame) = mdtable

###############################################
#         Overload environment functions      #
###############################################

function mdtable(d::DataFrames.DataFrame; kwargs...)
    body = Matrix(d)
    head = propertynames(d)
    mdtable(body; head=head, kwargs...)
end

function _latextabular(d::DataFrames.DataFrame; kwargs...)
    body = Matrix(d)
    head = propertynames(d)
    _latextabular(body; head=head, kwargs...)
end

function _latexarray(d::DataFrames.DataFrame; kwargs...)
    body = Matrix(d)
    head = permutedims(propertynames(d))
    result = vcat(head, body)
    _latexarray(result; kwargs...)
end


function matchregret_dbr(I::Int64, r::Vector{Vector{Float64}}, gs::GameSet,
                            pdbr::Vector{Float64}, σfix::Vector{Vector{Float64}})
    ni_stage, na_stage = gs.ni_stage, gs.na_stage
    na = numactions(I, ni_stage, na_stage)
    σ = Vector{Float64}(undef, na)
    if I <= ni_stage[3] || I > sum(ni_stage[1:5]) # attacker infoset
        for a in 1:na
            denom = sum(max(r[I][b], 0.0) for b in 1:na)
            if denom > 0.0
                σ[a] = pdbr[I] * σfix[I][a] + (1 - pdbr[I]) * (max(r[I][a], 0.0) / denom)
            else
                σ[a] = pdbr[I] * σfix[I][a] + (1 - pdbr[I]) * (1.0 / na)
            end
        end
    else # defender infoset
        for a in 1:na
            denom = sum(max(r[I][b], 0.0) for b in 1:na)
            if denom > 0.0
                σ[a] = max(r[I][a], 0.0) / denom
            else
                σ[a] = 1.0 / na
            end
        end
    end
    return σ
end

function updateutility_dbr!(player::Int64, I::Int64, h::SVector, σ::Vector{Float64},
        u::Float64, πi::Float64, πo::Float64,
        r::Vector{Vector{Float64}}, s::Vector{Vector{Float64}},
        g::AirDefenseGame, gs::GameSet, depth::Int64, pdbr::Vector{Float64}, σfix::Vector{Vector{Float64}})
    A, ni_stage, na_stage = gs.A, gs.ni_stage, gs.na_stage
    na = numactions(I, ni_stage, na_stage)
    m = Vector{Float64}(undef, na)
    nextactions = actions(depth, A)
    # @show na[I]
    for a in 1:na
        ha = setindex(h, nextactions[a], depth)
        if getplayer(depth) == player
            πip = σ[a] * πi
            # up = updatetree_dbr!(ha, player, πip, πo, r, s, g, gs)
            up = updatetree_dbr!(ha, player, πip, πo, r, s, g, gs, depth + 1, pdbr, σfix)
            m[a] = up
            u = u + σ[a] * up
        else
            πop = σ[a] * πo
            # up = updatetree_dbr!(ha, player, πi, πop, r, s, g, gs)
            up = updatetree_dbr!(ha, player, πi, πop, r, s, g, gs, depth + 1, pdbr, σfix)
            u = u + up
        end
    end
    return m, u
end

function updateregret_dbr!(player::Int64, I::Int64, h::SVector, σ::Vector{Float64},
        u::Float64, m::Vector{Float64}, πi::Float64,
        r::Vector{Vector{Float64}}, s::Vector{Vector{Float64}}, gs::GameSet, depth ::Int64)
    ni_stage, na_stage = gs.ni_stage, gs.na_stage
    if getplayer(depth) == player
        for a in 1:numactions(I, ni_stage, na_stage)
            r[I][a] = r[I][a] + m[a] - u
            s[I][a] = s[I][a] + πi * σ[a]
        end
    end
end

function updatetree_dbr!(h::SVector, player::Int64, πi::Float64, πo::Float64,
    r::Vector{Vector{Float64}}, s::Vector{Vector{Float64}}, g::AirDefenseGame,
    gs::GameSet, depth::Int64, pdbr::Vector{Float64}, σfix::Vector{Vector{Float64}})
    # πo is short for \vec{π}_{-i}
    A = gs.A
    (terminal(depth)) && (return leafutility(h, player, πi, πo, g, gs))
    if getplayer(depth) == 3
        as = actions(depth, A)
        chance_weights = pweights([chance(depth, a, g, gs) for a in as])
        a = as[sample(chance_weights)]
        ha = setindex(h, a, depth)
        # return updatetree_dbr!(ha, player, πi, πo, r, s, g, gs)
        return updatetree_dbr!(ha, player, πi, πo, r, s, g, gs, depth + 1, pdbr, σfix)
    end
    I = infoset(h, depth, g, gs)
    u = 0.0
    σ = matchregret_dbr(I, r, gs, pdbr, σfix)
    m, u = updateutility_dbr!(player, I, h, σ, u, πi, πo, r, s, g, gs, depth, pdbr, σfix)
    updateregret_dbr!(player, I, h, σ, u, m, πi, r, s, gs, depth)
    return u
end

function cfr_dbr(g::AirDefenseGame, gs::GameSet, pdbr::Vector{Float64}, σfix::Vector{Vector{Float64}};
        iterlimit::Int = 100_000, timelimit::Number = 600, tol::Float64 = 5e-5)
    ni, ni_stage, na_stage, players = gs.ni, gs.ni_stage, gs.na_stage, gs.players
    r = [zeros(numactions(i, ni_stage, na_stage)) for i in 1:ni] # regret
    s = [zeros(numactions(i, ni_stage, na_stage)) for i in 1:ni] # cumulative strategies
    u = 0.0
    u1 = Float64[]
    u1deriv = 0
    u1prev = 0
    converged = false
    iter = 1
    tstart = now()
    while !converged && tominute(now() - tstart) < timelimit && iter < iterlimit
        for player in players
            h0 = SVector(0, 0, 0, 0, 0, 0)
            u = updatetree_dbr!(h0, player, 1.0, 1.0, r, s, g, gs, 1, pdbr, σfix)
            (player == 1) && push!(u1, u)
        end
        u1deriv = u1deriv * (1 - 1 / iter) + (u1[iter] - u1prev) * (1 / iter)
        converged = abs(u1deriv) < tol
        u1prev = u1[iter]
        iter % 10 == 0 && print("\rIter: $iter")
        iter += 1
    end
    denoms = [sum(i) for i in s]
    σ = Vector{Vector{Float64}}(undef, gs.ni)
    for i in eachindex(s)
        if denoms[i] == 0
            σ[i] = fill(1 / length(s[i][:]), length(s[i][:]))
        else
            σ[i] = s[i][:] ./ denoms[i]
        end
    end
    return u1, r, s, σ, converged
end

function strat_cfr_dbr(s::Vector{Vector{Float64}})
    denoms = [sum(i) for i in s]
    σ = Vector{Vector{Float64}}(undef, length(s))
    for i in eachindex(s)
        if denoms[i] == 0
            σ[i] = fill(1 / length(s[i][:]), length(s[i][:]))
        else
            σ[i] = s[i][:] ./ denoms[i]
        end
    end
    return σ
end


@testset "Day5.jl" begin
    # Write your tests here.
    @test AdventOfCode2020.row("FFFFFFF") == 0
    @test AdventOfCode2020.row("BBBBBBB") == 127
    @test AdventOfCode2020.row("FFFFFFB") == 1
    @test AdventOfCode2020.row("FBFBBFF") == 44

    @test AdventOfCode2020.col("LLL") == 0
    @test AdventOfCode2020.col("RRR") == 7
    @test AdventOfCode2020.col("LLR") == 1
    @test AdventOfCode2020.col("RLR") == 5

    @test AdventOfCode2020.toseat("FBFBBFFRLR") == AdventOfCode2020.SeatLoc(44,5)
    @test AdventOfCode2020.toseat("FBFBBFFRLR").id == 357
    @test AdventOfCode2020.toseat("BFFFBBFRRR").id == 567
    @test AdventOfCode2020.toseat("FFFBBBFRRR").id == 119
    @test AdventOfCode2020.toseat("BBFFBBFRLL").id == 820
end


"""
$(SIGNATURES)

Default postprocess perfoms duality-based bound tightening on the `y`.
"""
function postprocess!(t::ExtensionType, m::GlobalOptimizer)
    if m._parameters.dbbt_depth > m._iteration_count
        variable_dbbt!(m._current_node, m._lower_lvd, m._lower_uvd,
                       m._lower_objective_value, m._global_upper_bound,
                       m._branch_variable_count)
    end
    return
end
postprocess!(m::GlobalOptimizer{R,S,Q}) where {R,S,Q<:ExtensionType} = postprocess!(_ext_typ(m), m)

"""
$(SIGNATURES)

Checks to see if current node should be reprocessed.
"""
repeat_check(t::ExtensionType, m::GlobalOptimizer) = false
repeat_check(m::GlobalOptimizer{R,S,Q}) where {R,S,Q<:ExtensionType} = repeat_check(_ext_typ(m), m)


using Test
using MIPVerify: get_example_network_params, read_datasets, frac_correct
@isdefined(TestHelpers) || include("../TestHelpers.jl")

TestHelpers.@timed_testset "import_example_nets.jl" begin
    TestHelpers.@timed_testset "get_example_network_params" begin
        TestHelpers.@timed_testset "MNIST.n1" begin
            nn = get_example_network_params("MNIST.n1")
            mnist = read_datasets("mnist")
            @test frac_correct(nn, mnist.test, 10000) == 0.9695
        end
        TestHelpers.@timed_testset "MNIST.WK17a_linf0.1_authors" begin
            nn = get_example_network_params("MNIST.WK17a_linf0.1_authors")
            mnist = read_datasets("mnist")
            @test frac_correct(nn, mnist.test, 10000) == 0.9811
        end
        TestHelpers.@timed_testset "MNIST.RSL18a_linf0.1_authors" begin
            nn = get_example_network_params("MNIST.RSL18a_linf0.1_authors")
            mnist = read_datasets("mnist")
            @test frac_correct(nn, mnist.test, 10000) == 0.9582
        end
    end

    @testset "unrecognized example network" begin
        @test_throws DomainError get_example_network_params("the social network")
    end
end


using BenchmarkTools
using StatsBase
# function lrIndices(trnidx::Vector{Int},f::DecisionTrees.PooledArraysDTM.PooledArray{String,T,1},subset::Array) where T<:Unsigned
include("c:\\temp\\2.jl") # PDA module
using .PooledArraysDTM

"""
function lrIndices!(trnidx::Vector{Int},f,subset::Array)
modifies trnidx to 'become' the left child index, returns r
l and r are defined by subset. l corresponds to the observations 'in' subset
"""
function lrIndices!(trnidx::Vector{Int}, f, subset::Array)
    if eltype(f.pool) <: Number
    # NOTE: subset may not necessarily start at one here (because after a few splits, certain 'middle' values of the pool may not exist in the current node)
    # still, the definition of the split should be of the form x<subset[end] -> go left (otherwise go right)
    # therefore we can consider this function for numerical variables
        return lrIndicesForContiguousSubsetStartingAtONE!(trnidx, f, subset)
    else
        if isContiguous(subset)
            if (length(subset) > 0) && (isone(subset[1]))
                return lrIndicesForContiguousSubsetStartingAtONE!(trnidx, f, subset)
            else
                return lrIndicesForContiguousSubsetNOTStartingAtONE!(trnidx, f, subset)
            end
        else
            return lrIndicesDefault!(trnidx, f, subset)
        end
    end
end

"""
function lrIndices!(trnidx::Vector{Int},f,subset::Array)
modifies trnidx to 'become' the left child index, returns r
l and r are defined by subset. l corresponds to the observations 'in' subset
"""
function lrIndicesDefault!(trnidx::Vector{Int}, f, subset::Array)
	r = Vector{Int}(undef, 0)
	sizehint!(r, length(trnidx))
	indexSize = length(trnidx)
    for jj = indexSize:-1:1
        @inbounds i = trnidx[jj]
		@inbounds thisref = f.refs[i]
		if !in(thisref, subset)
			pushfirst!(r, i)
			deleteat!(trnidx, jj)
		end
	end
	return r
end

"""
function lrIndicesForContiguousSubsetStartingAtONE!(trnidx::Vector{Int},f,subset::Array)
this is a similar function as lrIndices! (->see the docs of lrIndices!)
Here subset needs to be of the form collect(1:n)
"""
function lrIndicesForContiguousSubsetStartingAtONE!(trnidx::Vector{Int}, f, subset::Array)
	r = Vector{Int}(undef, 0)
	sizehint!(r, length(trnidx))
    subsetEnd = subset[end]
    indexSize = length(trnidx)
    for jj = indexSize:-1:1
        @inbounds i = trnidx[jj]
		@inbounds thisref = f.refs[i]
		if !(thisref <= subsetEnd)
			pushfirst!(r, i)
			deleteat!(trnidx, jj)
		end
	end
	return r
end

"""
this is a similar function as lrIndices! (->see the docs of lrIndices!)
Here subset needs to be of the form collect(m:n)
"""
function lrIndicesForContiguousSubsetNOTStartingAtONE!(trnidx::Vector{Int}, f, subset::Array)
	r = Vector{Int}(undef, 0)
	sizehint!(r, length(trnidx))
    subsetEnd = subset[end]
    indexSize = length(trnidx)
	subsetEnd = subset[end]
	subsetStart = subset[1]
    for jj = indexSize:-1:1
        @inbounds i = trnidx[jj]
		@inbounds thisref = f.refs[i]
		# if !((thisref<=subsetEnd)&&(thisref>=subsetStart))
        if (thisref > subsetEnd) || (thisref < subsetStart)
			pushfirst!(r, i)
			deleteat!(trnidx, jj)
		end
	end
	return r
end

"""
isContiguous(v) returns true if the vector v is of the form collect(n:m)
"""
function isContiguous(subset::Vector)
    mi = subset[1]
    ma = subset[end]
    return (length(subset) == (ma - mi + 1)) && issorted(subset)
end

import Random

ntest = 0
whereStartsAtONE = 0
nn = 15
trni = sample(1:nn, floor(Int, .4 * nn), replace=false)
sort!(trni)
strArr = [randstring(1) for i = 1:nn]
floatArr = float.(abs.(rand(Int, nn) .% 9))
pdStr = PooledArray(strArr)
pdFloat = PooledArray(floatArr)

for klU = 1:400000
    # @show klU
    pd = rand() < .5 ? pdStr : pdFloat

    subsetStrOrFloat = sample(pd.pool, 4, replace=false)
    subset = convert(Vector{eltype(pd.refs)}, findall((in)(subsetStrOrFloat), pd.pool))
    sort!(subset)

    # if isContiguous(subset)

    whereStartsAtONE += isone(subset[1])
    ntest += 1

    trniWillBeModified = deepcopy(trni)
    rrr = lrIndices!(trniWillBeModified, pd, subset)
    if eltype(pd.pool) == Float64
        ll2, rr2 = DecisionTrees.lrIndicesForContiguousSubsetStartingAtONE(trni, pd, subset)
    else
        ll2, rr2 = DecisionTrees.lrIndices(trni, pd, subset)
    end
        #=
        @show ll2,rr2
        @show subset
        @show pd
        @show pd.pool
        @show sort!(unique(pd.refs))
        @show ll2
        @show rrr
        @show trni
        @show trniWillBeModified =#
    @assert ll2 == trniWillBeModified
    @assert rr2 == rrr

        # @btime lrIndices($trni,$pd,$subset)
        # @btime lrIndices2($trni,$pd,$subset)
    # end
end


module Tools

export kw_qreg, kw_creg, kw_gate, qasm_id, qasm_int, qasm_f64, qasm_str, cmp_ast, cmp_exp

using ..Types
using MLStyle
using RBNF: Token

const kw_qreg = Token{:reserved}("qreg")
const kw_creg = Token{:reserved}("creg")
const kw_gate = Token{:reserved}("gate")

qasm_id(s) = qasm_id(string(s))
qasm_id(s::String) = Token{:id}(s)
qasm_int(n::Int) = Token{:int}(string(n))
qasm_f64(x) = Token{:float64}(string(Float64(x)))
qasm_str(x) = Token{:str}(string("\"", x, "\""))

Base.isapprox(lhs::ASTNode, rhs::ASTNode) = cmp_ast(lhs, rhs)
Base.isapprox(lhs::Token, rhs::Token) = cmp_ast(lhs, rhs)

function cmp_ast(lhs::Token{A}, rhs::Token{B}) where {A, B}
    A === B || return false
    return lhs.str == rhs.str
end

function cmp_ast(x::Vector, y::Vector)
    length(x) == length(y) || return false
    for (x_stmt, y_stmt) in zip(x, y)
        cmp_ast(x_stmt, y_stmt) || return false
    end
    return true
end

cmp_ast(lhs::ASTNode, rhs::ASTNode) = false
function cmp_ast(lhs::T, rhs::T) where {T <: ASTNode}
    for name in fieldnames(T)
        a = getfield(lhs, name)
        b = getfield(rhs, name)
        cmp_ast(a, b) || return false
    end
    return true
end

function cmp_ast(lhs::Instruction, rhs::Instruction)
    lhs.name == rhs.name || return false
    all(map(cmp_exp, lhs.cargs, rhs.cargs)) || return false
    return cmp_ast(lhs.qargs, rhs.qargs)
end

function cmp_ast(lhs::UGate, rhs::UGate)
    cmp_exp(lhs.y, rhs.y) || return false
    cmp_exp(lhs.z1, rhs.z1) || return false
    cmp_exp(lhs.z2, rhs.z2) || return false
    return true
end

cmp_ast(lhs::Neg, rhs::Neg) = cmp_exp(lhs.val, rhs.val)

function cmp_exp(lhs::Tuple, rhs::Tuple)
    length(lhs) == length(rhs) || return false
    return all(map(cmp_ast, lhs, rhs))
end

function cmp_exp(lhs, rhs)
    @switch (lhs, rhs) begin
        @case (Neg(a::Token{:float64}), b::Token{:float64}) || (b::Token{:float64}, Neg(a::Token{:float64}))
            startswith(b.str, '-') && a.str == b.str[2:end]
        @case _
            cmp_ast(lhs, rhs)
    end
end

cmp_ast(lhs, rhs) = lhs == rhs

@deprecate issimilar(x, y) cmp_ast(x, y)

end


using Test
using DFTK
import DFTK: AdaptiveDamping, ensure_damping_within_range

@testset "Damping adjustment in adaptive damping" begin
    damping = AdaptiveDamping(;α_min=0.05,
                               α_max=1.0,
                               α_trial_init=0.8,
                               α_trial_min=0.2,
                               α_trial_enhancement=1.1,
                               modeltol=0.1)

    # If within range we accept
    @test ensure_damping_within_range(damping,  0.2, 0.1) == 0.1
    @test ensure_damping_within_range(damping, -0.2, 0.1) == 0.1

    # If above maximum, than limit
    @test ensure_damping_within_range(damping,  1.5, 1.5) == 1.0
    @test ensure_damping_within_range(damping, -1.5, 1.5) == 1.0

    # Ensure shrinkage
    @test ensure_damping_within_range(damping,  0.2, 0.2) == 0.19
    @test ensure_damping_within_range(damping,  0.2, 0.5) == 0.19
    @test ensure_damping_within_range(damping, -0.2, 0.2) == 0.19
    @test ensure_damping_within_range(damping, -0.2, 0.5) == 0.19

    # ... but not below α_min
    @test ensure_damping_within_range(damping,  0.2, 0.01) == 0.05
    @test ensure_damping_within_range(damping, -0.2, 0.01) == 0.05

    # Ensure sign is kept
    @test ensure_damping_within_range(damping, -0.2, -0.07) == -0.07
    @test ensure_damping_within_range(damping,  0.2, -0.07) == -0.07
    @test ensure_damping_within_range(damping, -0.2, -0.2)  == -0.1
    @test ensure_damping_within_range(damping,  0.2, -0.2)  == -0.1
    @test ensure_damping_within_range(damping, -0.2, -0.5)  == -0.1
    @test ensure_damping_within_range(damping,  0.2, -0.5)  == -0.1

    # ... unless too small
    @test ensure_damping_within_range(damping, -0.2, -1e-3) == 0.05
    @test ensure_damping_within_range(damping,  0.2, -1e-3) == 0.05
end