module FLuxSymbolEquationsV2
# excitvar à automatiser OK
# affectation des valeurs pour les composants
# afectation des variables pour plot
# variables d'intégration automatique OK
# just for test
export SolveSymbolic,NumericSolve,PlotSol

using Plots
using DifferentialEquations
using SymPy
mutable struct Schematics
	Template::Array{String}
end

mutable struct Component

	name::String
	loopNb::Int64
	formula::String
	position::String
	current::String
	special::Bool
	ctype::String
	value::Float64
	rect::Bool
	commons::Int64
	voltage::String
	Component()=new()
end

mutable struct LoopC
	CompInLoop::Array{Component}
	CommonComp::Array{Component}
	LoopNumber::Int64
	LoopC()=new()
end

mutable struct CommonComp
	Common::Component
	Loops::Array{Int64}
end

myInputCircuit=["!VUd 110.,Rp 1e-4 ,Lr 32.4e-6,T1P 1.82e-3,MT1 0.000823308666297155,Cr 39e-9","MT1 ,T1S 380e-6 ,R2 1e-4 ,!rect ,Co 10e-6","Co ,Rl 2.4"]

#export greet, myStrgToSolve, myGraph

function formul(typ,nam,cur)
	formul=""
	if typ=="R"
		formul="V"*nam*"="*nam*"*"*cur
	elseif typ=="L"
		formul="V"*nam*"="*nam*"*d"*cur
	elseif typ=="C"
		formul=cur*"="*nam*"*dV"*nam
	elseif typ=="T"
		formul="V"*nam*"="*nam*"*d"*cur
	elseif typ=="M"
		formul="V"*nam*"=-"*nam*"*d"*cur
	elseif typ=="m"
		formul="V"*nam*"=-"*"M"*nam[2:end]*"*d"*cur
	end
	return formul
end
# flatten formulas
function flattenEqs(LLCExample,CommonCmpt,myLoop1)
	flattenEq=""
	# process voltage sum in loop equal zero
	sumvolt=String[]
	for i in 1:length(LLCExample.Template ) push!(sumvolt,"") end
	for i in 1:length(myLoop1)
		sumvolt[myLoop1[i].loopNb]*=myLoop1[i].voltage*"+"
	end
	for j in 1:length(LLCExample.Template)
		sumvolt[j]=sumvolt[j][1:end-1]
	end
	for j in 1:4:length(CommonCmpt)
		if CommonCmpt[j][1:1]!="M"
			sumvolt[CommonCmpt[j+3]]=replace(sumvolt[CommonCmpt[j+3]],"V"*CommonCmpt[j]=>"(-V"*CommonCmpt[j]*")")
		end
	end
	for i in 1:length(sumvolt)
		sumvolt[i]=replace(sumvolt[i],"++"=>"+")
	end
	# delete extra formula
	for i in length(myLoop1):-1:1
		for j in 1:4:length(CommonCmpt)
			if (myLoop1[i].name ==CommonCmpt[j]) && myLoop1[i].current=="I"*string(CommonCmpt[j+3])  && myLoop1[i].ctype!="M"
				splice!(myLoop1, i)
			end
		end
	end

	# build list of equation
	for i in 1:length(myLoop1)
		flattenEq*=replace(myLoop1[i].formula,"="=>"-(")*"),"
	end
	flattenEq=flattenEq[3:end]
	for i in 1:length(sumvolt)
		sumvolt[i]=replace(sumvolt[i],"++"=>"+")
		flattenEq*=sumvolt[i]*","
	end

	flattenEq=replace(flattenEq,",),"=>",")

	return flattenEq[1:end-1],sumvolt
end

function fillCmpt(LLCExample)
	rectOn=false
	myLoop=[]
	CommonCmpt=[]
	CommonLoop=[]
	test=[]
    excitVar0=[]
	for j in 1:length(LLCExample.Template)  # numero de boucle
		parts=split(LLCExample.Template[j],",")
		for i in 1:length(parts)
			tCmpt=Component()
			temp0=split(parts[i]," ")
			if temp0[1][1:1]=="!"
				tCmpt.special=true
				tCmpt.name=temp0[1][2:end]
				if tCmpt.name=="rect"
					rectOn=true
					tCmpt.voltage=""
				else
					tCmpt.voltage=tCmpt.name
					push!(excitVar0,tCmpt.name)
				end

			else
				tCmpt.special=false
				tCmpt.name=temp0[1][1:end]

				if temp0[2]==""  !!!!!!!!!!!!!!!
					tCmpt.commons=j
					push!(CommonCmpt,tCmpt.name)
					push!(CommonLoop,j)
					#tCmpt.value=1e-139 # dummy value
				end

				tCmpt.voltage="V"*tCmpt.name

			end
			tCmpt.ctype=tCmpt.name[1:1]
			tCmpt.loopNb=j
			tCmpt.current="I"*string(tCmpt.loopNb)
			tCmpt.formula=formul(tCmpt.ctype,tCmpt.name,tCmpt.current)
			tCmpt.rect=rectOn
			#if tCmpt.value!=false
			try
				tCmpt.value=parse(Float64, temp0[2])
			catch
				for k in 1:length(myLoop)
					if myLoop[k].name==tCmpt.name
						tCmpt.value=myLoop[k].value
						break
					end
				end
			end
			#end
			push!(myLoop,tCmpt)
		end
	end
	for i in 1:length(myLoop)
		if myLoop[i].name in CommonCmpt
			push!(test,myLoop[i].name)
			push!(test,myLoop[i].loopNb)
		end
	end
	return myLoop,test,excitVar0
end
function proct(myLoop,CommonCmpt)
	cur=[]
	for i in 1:length(myLoop)
		if myLoop[i].ctype=="M"
			push!(cur,myLoop[i].current)
		end
	end
	fstok=0
	for i in 1:length(myLoop)
		if myLoop[i].ctype=="M"	&& fstok==0
			myLoop[i].current=pop!(cur)
			#myLoop[i].formula=myLoop[i].formula[1:end-2]*myLoop[i].current
			myLoop[i].formula=myLoop[i].formula[1:4]*"P"*myLoop[i].formula[5:end-2]*myLoop[i].current

			myLoop[i].voltage=myLoop[i].voltage*"P"
			fstok=1
		elseif myLoop[i].ctype=="M"	&& fstok==1
			myLoop[i].current=pop!(cur)
			#myLoop[i].formula=myLoop[i].formula[1:end-2]*myLoop[i].current
			myLoop[i].formula=myLoop[i].formula[1:4]*"S"*myLoop[i].formula[5:end-2]*myLoop[i].current
			myLoop[i].voltage=myLoop[i].voltage*"S"
			fstok=0
		end
	end

	for i in 1:2:length(CommonCmpt)   # courants impliqués  cur= [2, 3]
		if CommonCmpt[i][1:1]!="M"
			push!(cur,CommonCmpt[i+1])
		end
	end
	for i in 1:4:length(CommonCmpt)
		if CommonCmpt[i][1:1]!="M"
			for j in 1:length(myLoop)    # process for common componzents
				if myLoop[j].name==CommonCmpt[i]   #replace I2  with (I2-I3)
					myLoop[j].formula=replace(myLoop[j].formula,"I"*string(cur[1])=>"(I"*string(cur[1])*"-I"*string(cur[2])*")")
				end
			end
		end
	end
	return  myLoop
end
function getmyVars(myLoop1,LLCExample)
	mySymbvars=[]  # all symbolic var
	myvars=[]   # var to be integrated by ODE
	myvars1=""
	for i in 1:length(myLoop1)
		if myLoop1[i].ctype in ["L","M","T"]
			push!(myvars,"d"*myLoop1[i].current)
			push!(mySymbvars,myLoop1[i].current)
		elseif myLoop1[i].ctype=="C"
			push!(myvars,"d"*myLoop1[i].voltage)
			push!(mySymbvars,myLoop1[i].voltage)

		end
	end
	for i in 1:length(myLoop1)
		if myLoop1[i].name !="" && myLoop1[i].name !="rect"
		#push!(myvars, myLoop1[i].current)
			if myLoop1[i].special==false && ("d"*myLoop1[i].voltage in myvars) == false
				push!(myvars,myLoop1[i].voltage)
			else
				push!(mySymbvars,myLoop1[i].voltage)
			end
		push!(mySymbvars,myLoop1[i].name)
		end
	end
	myvars=unique(myvars)
    mySymbvars=unique(mySymbvars)
	#il faut rajouter I3 car pas dI3
	for i in 1:length(LLCExample.Template)
		if ("dI"*string(i) in myvars) == false
			push!(myvars,"I"*string(i))
		end
	end
	return myvars,mySymbvars
end
function flatVars(myVar)
	myvars1=""
   for i in 1:length(myVar[1])
		myvars1*=myVar[1][i]*","
	end
	return myvars1[1:end-1]
end
# create rectComp="I2,Co,Rl"
function rectdata(myLoop1)
	rectComp=""
	for i in 1:length(myLoop1)
		if myLoop1[i].name=="rect"
			rectComp*=myLoop1[i].current*","
		elseif myLoop1[i].rect==true
			rectComp*=myLoop1[i].name*","
		end
	end
		return rectComp[1:end-1]
end

function rect(rectComp,mydifEq)
	compsRect=split(rectComp,",")
	cour=""
	if compsRect!=[] cour=compsRect[1] end
	my=[]
	for i in 1:length(compsRect)
		if compsRect[i][1:1]=="C"
			for j in 1:length(mydifEq)
				if mydifEq[j][1:2+length(compsRect[i])]=="dV"*compsRect[i]
					push!(my,replace(mydifEq[j],cour=>"abs("*cour*")"))
				else #Uco*(-sign(Is))
					push!(my,replace(mydifEq[j],"V"*compsRect[i]=>"V"*compsRect[i]*"*(sign("*cour*"))"))
				end
			end
		end
	end

	return my,cour
end

function collectVar(rt)
	myst=""
	myVrArray=[]
	for i in 1:length(rt)
	myst=myst*rt[i][2:findfirst(isequal('='), rt[i])-1]*","
	push!(myVrArray,rt[i][2:findfirst(isequal('='), rt[i])-1])
	end
	return myst[1:end-1],myVrArray
end

function flatEqs(sympyEq,myVar)
	eqstoSolv=""
	myflatVar=""
	for ii in 1:length(sympyEq)-1
		eqstoSolv=eqstoSolv*sympyEq[ii]*","
	end
	eqstoSolv=eqstoSolv*sympyEq[length(sympyEq)]
	for ii in 1:length(myVar)-1
		myflatVar=myflatVar*myVar[1][ii]*","
	end
	myflatVar=myflatVar*myVar[1][length(myVar)]
	return eqstoSolv,myflatVar
end

# function giving equation from circuit
function myEquationsSet(myInputCircuit)
	mys=""
	excitVar=""  #
	LLCExample=Schematics(myInputCircuit)
	myLoop,CommonCmpt,excitVar=fillCmpt(LLCExample)
	myLoop1=proct(myLoop,CommonCmpt)
	flattenEqs1,sumvolt=flattenEqs(LLCExample,CommonCmpt,myLoop1)
	myVar=getmyVars(myLoop1,LLCExample)
	symbdat=""
	#Création des noms symboliques)
	for j in 1:length(myVar)
		for i in 1:length(myVar[j])
			try
				symbdat=replace(replace(replace(string(myVar[j][i]),"\"" =>""),"Any["=>""),"]"=>"")
				eval(Meta.parse(symbdat*"=symbols(\""*symbdat*"\")"))
			catch
			end
		end
	end

	Az=linsolve(eval(Meta.parse(flattenEqs1)),eval(Meta.parse(flatVars(myVar))))
	mysol=split(replace(string.(Az),"FiniteSet(("=>"")[1:end-2],",")
	VartoSolve=myVar[1]
	mydVoudIindx=findall.("d",VartoSolve)
	difEqIndex=findall(mydVoudIindx -> mydVoudIindx!=[],mydVoudIindx)
    mydifEq=VartoSolve[difEqIndex].*"=".*mysol[difEqIndex]
	rectComp=rectdata(myLoop1)
	compsRect=split(rectComp,",")
	cour=rect(rectComp,mydifEq)[2]
	if compsRect!=[] cour=compsRect[1] end
    myst,myVrArray=collectVar(rect(rectComp,mydifEq)[1])
	myStrgToSolve=""
	myStrgToSolve1=""
	rect1=[]
	for i in 1:length(rect(rectComp,mydifEq)[1])
		push!(rect1,"du["*string(i)*"] = "*rect(rectComp,mydifEq)[1][i])
	end
	excitVar=fillCmpt(LLCExample)[3]
	#replace.(rect1,excitVar=>excitVar*"*p[1]")
	myStrgToSolve=flatEqs(replace.(rect1,excitVar[1]=>excitVar[1]*"*p[1]"),myVar)[1]*"; end"
	myStrgToSolve1="function eqDiffToSolve!(du,u,p,t) "* myst *" = u; " *replace(myStrgToSolve,","=>"; ") #!!!!!!
	eval(Meta.parse(myStrgToSolve1))
	freq=1e5  #OK
	u₀=[0,0,0,0]
	p=[-1.0,1.0]
	tspan = (0.0,600.e-6)
	nbPeriod=60
	dosetimes = collect(1:nbPeriod)/freq/2
	#eval(Meta.parse(myStrgToSolve1))
	for i in 1:length(myLoop)
		#if typeof(myLoop[i].value)!=1e-139
			try eval(Meta.parse(myLoop[i].name*"="*string(myLoop[i].value)))
			catch
			end
		#end
	end
	prob = ODEProblem(eqDiffToSolve! ,u₀,tspan,p)
	condition(u,t,integrator) = t ∈ dosetimes
	affect!(integrator) = integrator.p[1] = -integrator.p[1]
	cb = DiscreteCallback(condition,affect!)
	sol= DifferentialEquations.solve(prob ,Tsit5(),callback=cb,tstops=dosetimes)
    myGraph=plot(sol,linewidth=2,xaxis="t",label=permutedims(myVrArray),layout=(4,1))
	return myGraph
    #return myStrgToSolve1
	#return excitVar,myStrgToSolve
	#return rect1
    #return myst,myVrArray
	#return cour
	#return VartoSolve.*"=".*mysol
	#return VartoSolve[difEqIndex].*"=".*mysol[difEqIndex]
end

function SolveSymbolic(myInputCircuit)
	mys=""
	excitVar=""  #
	LLCExample=Schematics(myInputCircuit)
	myLoop,CommonCmpt,excitVar=fillCmpt(LLCExample)
	myLoop1=proct(myLoop,CommonCmpt)
	flattenEqs1,sumvolt=flattenEqs(LLCExample,CommonCmpt,myLoop1)
	myVar=getmyVars(myLoop1,LLCExample)
	symbdat=""
	#Création des noms symboliques)
	for j in 1:length(myVar)
		for i in 1:length(myVar[j])
			try
				symbdat=replace(replace(replace(string(myVar[j][i]),"\"" =>""),"Any["=>""),"]"=>"")
				eval(Meta.parse(symbdat*"=symbols(\""*symbdat*"\")"))
			catch
			end
		end
	end

	Az=linsolve(eval(Meta.parse(flattenEqs1)),eval(Meta.parse(flatVars(myVar))))
	mysol=split(replace(string.(Az),"FiniteSet(("=>"")[1:end-2],",")
	VartoSolve=myVar[1]
	mydVoudIindx=findall.("d",VartoSolve)
	difEqIndex=findall(mydVoudIindx -> mydVoudIindx!=[],mydVoudIindx)
    mydifEq=VartoSolve[difEqIndex].*"=".*mysol[difEqIndex]
	rectComp=rectdata(myLoop1)
	compsRect=split(rectComp,",")
	cour=rect(rectComp,mydifEq)[2]
	if compsRect!=[] cour=compsRect[1] end
    myst,myVrArray=collectVar(rect(rectComp,mydifEq)[1])
	myStrgToSolve=""
	myStrgToSolve1=""
	rect1=[]
	for i in 1:length(rect(rectComp,mydifEq)[1])
		push!(rect1,"du["*string(i)*"] = "*rect(rectComp,mydifEq)[1][i])
	end
	excitVar=fillCmpt(LLCExample)[3]
	#replace.(rect1,excitVar=>excitVar*"*p[1]")
	myStrgToSolve=flatEqs(replace.(rect1,excitVar[1]=>excitVar[1]*"*p[1]"),myVar)[1]*"; end"
	myStrgToSolve1="function eqDiffToSolve!(du,u,p,t) "* myst *" = u; " *replace(myStrgToSolve,","=>"; ") #!!!!!!
	eval(Meta.parse(myStrgToSolve1))
	return myStrgToSolve1,myLoop,myVrArray
end

function NumericSolve(myStrgToSolve1,myLoop)
	freq=1e5  #OK
	u₀=[0,0,0,0]
	p=[-1.0,1.0]
	tspan = (0.0,600.e-6)
	nbPeriod=60
	dosetimes = collect(1:nbPeriod)/freq/2
	#eval(Meta.parse(myStrgToSolve1))
	for i in 1:length(myLoop)
		#if typeof(myLoop[i].value)!=1e-139
			try eval(Meta.parse(myLoop[i].name*"="*string(myLoop[i].value)))
			catch
			end
		#end
	end
	prob = ODEProblem(eqDiffToSolve! ,u₀,tspan,p)
	condition(u,t,integrator) = t ∈ dosetimes
	affect!(integrator) = integrator.p[1] = -integrator.p[1]
	cb = DiscreteCallback(condition,affect!)
	sol= DifferentialEquations.solve(prob ,Tsit5(),callback=cb,tstops=dosetimes)
    return sol
end

function PlotSol(sol,myVrArray)
	myGraph=plot(sol,linewidth=2,xaxis="t",label=permutedims(myVrArray),layout=(4,1))
	return myGraph
end

# example d'utilisation du module
#myStrgToSolve1,myLoop,myVrArray=SolveSymbolic(myInputCircuit)
#sol=NumericSolve(myStrgToSolve1,myLoop)
#PlotSol(sol,myVrArray)

end


isinteractive() && include("common.jl")

#####
##### test diagnostics
#####

@testset "summarize tree statistics" begin
    N = 1000
    directions = Directions(UInt32(0))
    function rand_invalidtree()
        if rand() < 0.1
            REACHED_MAX_DEPTH
        else
            left = rand(-5:5)
            right = left + rand(0:5)
            InvalidTree(left, right)
        end
    end
    tree_statistics = [TreeStatisticsNUTS(randn(), rand(0:5), rand_invalidtree(), rand(),
                                          rand(1:30), directions) for _ in 1:N]
    stats = summarize_tree_statistics(tree_statistics)
    # acceptance rates
    @test stats.N == N
    @test stats.a_mean ≈ mean(x -> x.acceptance_rate, tree_statistics)
    @test stats.a_quantiles ==
        quantile((x -> x.acceptance_rate).(tree_statistics), ACCEPTANCE_QUANTILES)
    # termination counts
    @test stats.termination_counts.divergence ==
        count(x -> is_divergent(x.termination), tree_statistics)
    @test stats.termination_counts.max_depth ==
        count(x -> x.termination == REACHED_MAX_DEPTH, tree_statistics)
    @test stats.termination_counts.turning ==
        (N - stats.termination_counts.max_depth - stats.termination_counts.divergence)
    # depth counts
    for (i, c) in enumerate(stats.depth_counts)
        @test count(x -> x.depth == i - 1, tree_statistics) == c
    end
    @test sum(stats.depth_counts) == N
    # misc
    @test 1.8 ≤ EBFMI(tree_statistics) ≤ 2.2 # nonsensical value, just checking calculation
    @test repr(stats) isa AbstractString # just test that it prints w/o error
end

@testset "log acceptance ratios" begin
    ℓ = multivariate_normal(ones(5))
    log2ϵs = -5:5
    N = 13
    logA = explore_log_acceptance_ratios(ℓ, zeros(5), log2ϵs; N = N)
    @test all(isfinite.(logA))
    @test size(logA) == (length(log2ϵs), N)
end

@testset "leapfrog trajectory" begin
    # problem setup
    K = 2
    ℓ = multivariate_normal(ones(K))
    κ = GaussianKineticEnergy(K)
    q = zeros(K)
    Q = evaluate_ℓ(ℓ, q)
    p = ones(K) .* 0.98
    H = Hamiltonian(κ, ℓ)
    ϵ = 0.1
    ixs = 1:15
    ix0 = 5

    # calculate trajectory manually
    zs1 = let z = PhasePoint(Q, p)
        [(z1 = z; z = leapfrog(H, z, ϵ); z1) for _ in ixs]
    end
    πs1 = logdensity.(Ref(H), zs1)
    Δs1 = πs1 .- πs1[ix0]

    # calculate using function
    traj = leapfrog_trajectory(ℓ, zs1[ix0].Q.q, ϵ, ixs .- ix0; κ = κ, p = zs1[ix0].p)
    @test all(isapprox.(map(t -> t.Δ, traj), Δs1; atol = 1e-5))
    @test all(map((t, y) -> t.z.Q.q ≈ y.Q.q, traj, zs1))
    @test all(map((t, y) -> t.z.p ≈ y.p, traj, zs1))
end


__precompile__(false)

module TimeManager

export programStartTime
export currentTime
export programTime
export programTimeStr
export OnTime
export GetTimer
export SetTimer
export UpdateTimers
export GetTimePassed

using Dates
using Printf

TIMERS = Dict{Any,Base.RefValue{Float64}}()
time_start = time()

currentTime(startTime::Real) = (time() - startTime)
programStartTime() = time_start
programTime() = currentTime(time_start)
programTimeStr() = @sprintf("%.3f", programTime())

"""
TODO
"""
GetTimer(key) = Base.getindex(TIMERS[key])

"""
TODO
"""
SetTimer(key, time::Number) = (TIMERS[key] = Ref{Float64}(time))

"""
TODO
"""
function UpdateTimers()
  global TIMERS
  time = GetTimer("FRAME_TIMER")

  LOCAL_TIMERS = filter(tuple -> (f=first(tuple); isa(f,AbstractFloat) || isa(f,Integer)), collect(TIMERS))
  for (milisec,prevTime) in LOCAL_TIMERS
    r=(time - Base.getindex(prevTime)) >= milisec
    if r Base.setindex!(prevTime, time) end
  end
  SetTimer("FRAME_TIMER", Dates.time())
end

timePassed = 0
GetTimePassed() = timePassed
SetTimePassed(t) = global timePassed=t

"""
TODO
"""
function OnTime(milisec::Number)
  global TIMERS

  if !haskey(TIMERS,milisec)
    prevTime = 0.0
    SetTimer(milisec, 0.0)
  else
    prevTime = GetTimer(milisec)
  end

  SetTimePassed(GetTimer("FRAME_TIMER") - prevTime) >= milisec
end

"""
TODO
"""
function OnTime(milisec::Number, prevTime::Ref{Float64}; time=time()) #RefValue
  r = (time - Base.getindex(prevTime)) >= milisec
  if r Base.setindex!(prevTime,time) end
  r
end

SetTimer("FRAME_TIMER", time())

end #TimeManager


#= Load these if running these tests in terminal
import Pkg; Pkg.activate("../")  # CytofRepFAM
using CytofRepFAM, Random, BSON, Test, Distributions
=#

printstyled("Test update times for r ...\n", color=:yellow)
@testset "time update of r" begin
  Random.seed!(0)

  config = init_state_const_data(N=[3, 2, 1] * 100)

  cfs = CytofRepFAM.Model.ConstantsFS(config[:c])
  dfs = CytofRepFAM.Model.DataFS(config[:d], config[:X])
  sfs = CytofRepFAM.Model.StateFS{Float64}(config[:s], dfs)
  tfs = CytofRepFAM.Model.TunersFS(config[:t], config[:s], config[:X])

  println("N: $(dfs.data.N)")

  println("updating r ...")

  # Warmup
  CytofRepFAM.Model.update_r!(sfs, cfs, dfs)

  # time these:
  for i in 1:5
    @time CytofRepFAM.Model.update_r!(sfs, cfs, dfs)
  end
  println("checksum: $(sum(sfs.theta.lam[2]))")

  # NOTE:
  # Using two threads cuts time in half. Any more threads than that, and
  # runtime get longer.
end


using StochasticSearch, FactCheck

facts("[Configuration]") do
    context("constructors") do
        c = Configuration("test")
        p = [StringParameter("value",  "test1"),
             IntegerParameter(1, 5, 4, "test2")]
        c = Configuration(p, "test")
        @fact (c["test1"] == p[1])                   --> true
        @fact (c["test2"] == p[2])                   --> true
        p = Dict{String, Parameter}()
        p["test1"] = StringParameter("value",  "test")
        p["test2"] = IntegerParameter(1, 5, 4, "test")
        c = Configuration(p, "test")
        @fact (c["test1"] == p["test1"])             --> true
        @fact (c["test2"] == p["test2"])             --> true
        p = [StringParameter("valuea",  "test1"),
             StringParameter("valueb", "test2"),
             IntegerParameter(1, 44, 3, "test3")]
        c = Configuration(p, "test")
        @fact (c["test1"] == p[1])                   --> true
        @fact (c["test2"] == p[2])                   --> true
    end
    context("perturb!") do
        p = [StringParameter("valuea",  "test1"),
             StringParameter("valueb", "test2"),
             IntegerParameter(1, 44, 3, "test3")]
        t = Configuration(p, "test1")
        l = [IntegerParameter(1, 38, 3, "i0"),
             IntegerParameter(4, 66, 55, "i1"),
             StringParameter("value", "i2"),
             FloatParameter(2.33, 85.33, 22.2, "i3"),
             t]
        c = Configuration(l, "test")
        r = perturb!(c)
        r = perturb!(c)
        r = perturb!(c)
        r = perturb!(c)
        @fact (typeof(r["test1"]) <: Configuration)              --> true
        @fact (r["test1"]["test1"].value == "valuea")            --> true
        @fact (r["test1"]["test2"].value == "valueb")            --> true
        @fact (r["i2"].value             == "value")             --> true
        @fact (typeof(r["test1"]["test3"]) <: NumberParameter)   --> true
        @fact (typeof(r["i0"])             <: NumberParameter)   --> true
        @fact (typeof(r["i1"])             <: NumberParameter)   --> true
        @fact (typeof(r["i3"])             <: NumberParameter)   --> true
        d = Dict{String, Any}()
        d["i0"]             = 3
        d["i1"]             = 4
        d["i3"]             = 2.3
        d["test1"]          = Dict{String, Any}()
        d["test1"]["test3"] = 2
        @fact (typeof(perturb!(c, d)) <: Configuration)          --> true
        r = perturb!(c, d)
        r = perturb!(c, d)
        r = perturb!(c, d)
        r = perturb!(c, d)
        @fact (r["i0"].value == c["i0"].value)                   --> true
        @fact (r["i1"].value == c["i1"].value)                   --> true
        @fact (r["i3"].value == c["i3"].value)                   --> true
        @fact (r["test1"]["test3"] == c["test1"]["test3"])       --> true
        d["test1"]["test1"] = 23
        @fact_throws MethodError perturb!(c, d)
    end
    context("neighbor!") do
        l = [IntegerParameter(1, 38, 3, "i0"),
             IntegerParameter(4, 66, 55, "i1"),
             StringParameter("value", "i2"),
             FloatParameter(2.33, 85.33, 22.2, "i3")]
        d = Dict{String, Any}()
        c = Configuration(l, "test")
        println(c)
        d["i0"] = 5
        v = c["i0"].value
        neighbor!(c, d)
        @fact v                                              --> not(exactly(c["i0"].value))
        v = c["i0"].value
        neighbor!(c, d, distance = 9)
        d = Dict{String, Any}()
        d["i2"] = 5
        @fact_throws MethodError neighbor!(c, d)
        @fact_throws MethodError neighbor!(c, d, 2)
        l = [IntegerParameter(1, 38, 3, "i0")]
        c = Configuration(l, "test2")
        v = c["i0"].value
        neighbor!(c)
        @fact v                                              --> not(exactly(c["i0"].value))
    end
    context("update! and convert") do
        c     = Configuration("test")
        c["test"] = IntegerParameter(1, 2323, 2, "i0")
        a     = convert(Array{Parameter}, c)
        @fact (a[1] == c["test"])                            --> true
        push!(a, StringParameter("name", "i2"))
        update!(c, a)
        @fact (c["i2"] != a[1])                              --> true
        @fact (c["i2"].value == a[2].value)                  --> true
    end
end


using Unitful, ImagineInterface, Imagine
using Base.Test

#modified from ImagineInterface test
srate = 10000*Unitful.s^-1
pmin = 0.0*Unitful.μm
pmax = 200.0*Unitful.μm
stack_time = 3.0*Unitful.s
reset_time = 2.0*Unitful.s
exp_time = 0.011*Unitful.s
flash_frac = 0.1 #fraction of time to keep laser on during exposure
z_spacing = 3.1*Unitful.μm
z_pad = 5.0*Unitful.μm
nstacks = 10
d = gen_unidirectional_stack(pmin, pmax, z_spacing, stack_time, stack_time, exp_time, srate, flash_frac; z_pad = z_pad)

ocpi2 = rigtemplate("ocpi-2"; sample_rate = srate)
pos = getpositioners(ocpi2)[1]
pos_mon = getname(ocpi2, monitor_name(pos))
append!(pos, "uni_stack_pos", d["positioner"])
replicate!(pos, nstacks-1)
#write_commands("test.json", ocpi2, nstacks, nframes, exp_time; isbidi = false)

aos = [pos;]
#ais = [pos_mon;]
#ais = ImagineSignal[]

#prepare_ao(aos, bufsz::Int, trigger_terminal::String)
ImagineWorker._set_device("Dev2/")
#tsk = ImagineWorker.prepare_ao(aos, 100000, "PFI1")
#print("starting...\n")
#start(tsk)

ready_chan = RemoteChannel(()->Channel{Int}(1))
ImagineWorker._output_analog_signals(aos, 50000, "PFI1", ready_chan)

#and execute this from another julia instance:
#using NIDAQ, Imagine; Imagine.ttl_pulse(; line_name = "Port2/Line0")


function add(obj::AbstractRealMatrix, arg0::RealMatrix)
    return jcall(obj, "add", RealMatrix, (RealMatrix,), arg0)
end

function add_to_entry(obj::AbstractRealMatrix, arg0::jint, arg1::jint, arg2::jdouble)
    return jcall(obj, "addToEntry", void, (jint, jint, jdouble), arg0, arg1, arg2)
end

function copy(obj::AbstractRealMatrix)
    return jcall(obj, "copy", RealMatrix, ())
end

function copy_sub_matrix(obj::AbstractRealMatrix, arg0::Vector{jint}, arg1::Vector{jint}, arg2::Vector{Vector{jdouble}})
    return jcall(obj, "copySubMatrix", void, (Vector{jint}, Vector{jint}, Vector{Vector{jdouble}}), arg0, arg1, arg2)
end

function copy_sub_matrix(obj::AbstractRealMatrix, arg0::jint, arg1::jint, arg2::jint, arg3::jint, arg4::Vector{Vector{jdouble}})
    return jcall(obj, "copySubMatrix", void, (jint, jint, jint, jint, Vector{Vector{jdouble}}), arg0, arg1, arg2, arg3, arg4)
end

function create_matrix(obj::AbstractRealMatrix, arg0::jint, arg1::jint)
    return jcall(obj, "createMatrix", RealMatrix, (jint, jint), arg0, arg1)
end

function equals(obj::AbstractRealMatrix, arg0::Object)
    return jcall(obj, "equals", jboolean, (Object,), arg0)
end

function get_column(obj::AbstractRealMatrix, arg0::jint)
    return jcall(obj, "getColumn", Vector{jdouble}, (jint,), arg0)
end

function get_column_dimension(obj::AbstractRealMatrix)
    return jcall(obj, "getColumnDimension", jint, ())
end

function get_column_matrix(obj::AbstractRealMatrix, arg0::jint)
    return jcall(obj, "getColumnMatrix", RealMatrix, (jint,), arg0)
end

function get_column_vector(obj::AbstractRealMatrix, arg0::jint)
    return jcall(obj, "getColumnVector", RealVector, (jint,), arg0)
end

function get_data(obj::AbstractRealMatrix)
    return jcall(obj, "getData", Vector{Vector{jdouble}}, ())
end

function get_entry(obj::AbstractRealMatrix, arg0::jint, arg1::jint)
    return jcall(obj, "getEntry", jdouble, (jint, jint), arg0, arg1)
end

function get_frobenius_norm(obj::AbstractRealMatrix)
    return jcall(obj, "getFrobeniusNorm", jdouble, ())
end

function get_norm1(obj::RealMatrix)
    return jcall(obj, "getNorm1", jdouble, ())
end

function get_norm_infty(obj::RealMatrix)
    return jcall(obj, "getNormInfty", jdouble, ())
end

function get_row(obj::AbstractRealMatrix, arg0::jint)
    return jcall(obj, "getRow", Vector{jdouble}, (jint,), arg0)
end

function get_row_dimension(obj::AbstractRealMatrix)
    return jcall(obj, "getRowDimension", jint, ())
end

function get_row_matrix(obj::AbstractRealMatrix, arg0::jint)
    return jcall(obj, "getRowMatrix", RealMatrix, (jint,), arg0)
end

function get_row_vector(obj::AbstractRealMatrix, arg0::jint)
    return jcall(obj, "getRowVector", RealVector, (jint,), arg0)
end

function get_sub_matrix(obj::AbstractRealMatrix, arg0::Vector{jint}, arg1::Vector{jint})
    return jcall(obj, "getSubMatrix", RealMatrix, (Vector{jint}, Vector{jint}), arg0, arg1)
end

function get_sub_matrix(obj::AbstractRealMatrix, arg0::jint, arg1::jint, arg2::jint, arg3::jint)
    return jcall(obj, "getSubMatrix", RealMatrix, (jint, jint, jint, jint), arg0, arg1, arg2, arg3)
end

function get_trace(obj::AbstractRealMatrix)
    return jcall(obj, "getTrace", jdouble, ())
end

function hash_code(obj::AbstractRealMatrix)
    return jcall(obj, "hashCode", jint, ())
end

function is_square(obj::AbstractRealMatrix)
    return jcall(obj, "isSquare", jboolean, ())
end

function is_transposable(obj::RealLinearOperator)
    return jcall(obj, "isTransposable", jboolean, ())
end

function map(obj::RealMatrix, arg0::UnivariateFunction)
    return jcall(obj, "map", RealMatrix, (UnivariateFunction,), arg0)
end

function map_to_self(obj::RealMatrix, arg0::UnivariateFunction)
    return jcall(obj, "mapToSelf", RealMatrix, (UnivariateFunction,), arg0)
end

function multiply(obj::AbstractRealMatrix, arg0::RealMatrix)
    return jcall(obj, "multiply", RealMatrix, (RealMatrix,), arg0)
end

function multiply_entry(obj::AbstractRealMatrix, arg0::jint, arg1::jint, arg2::jdouble)
    return jcall(obj, "multiplyEntry", void, (jint, jint, jdouble), arg0, arg1, arg2)
end

function multiply_transposed(obj::RealMatrix, arg0::RealMatrix)
    return jcall(obj, "multiplyTransposed", RealMatrix, (RealMatrix,), arg0)
end

function operate(obj::AbstractRealMatrix, arg0::RealVector)
    return jcall(obj, "operate", RealVector, (RealVector,), arg0)
end

function operate(obj::AbstractRealMatrix, arg0::Vector{jdouble})
    return jcall(obj, "operate", Vector{jdouble}, (Vector{jdouble},), arg0)
end

function operate_transpose(obj::RealLinearOperator, arg0::RealVector)
    return jcall(obj, "operateTranspose", RealVector, (RealVector,), arg0)
end

function power(obj::AbstractRealMatrix, arg0::jint)
    return jcall(obj, "power", RealMatrix, (jint,), arg0)
end

function pre_multiply(obj::AbstractRealMatrix, arg0::RealMatrix)
    return jcall(obj, "preMultiply", RealMatrix, (RealMatrix,), arg0)
end

function pre_multiply(obj::AbstractRealMatrix, arg0::RealVector)
    return jcall(obj, "preMultiply", RealVector, (RealVector,), arg0)
end

function pre_multiply(obj::AbstractRealMatrix, arg0::Vector{jdouble})
    return jcall(obj, "preMultiply", Vector{jdouble}, (Vector{jdouble},), arg0)
end

function scalar_add(obj::AbstractRealMatrix, arg0::jdouble)
    return jcall(obj, "scalarAdd", RealMatrix, (jdouble,), arg0)
end

function scalar_multiply(obj::AbstractRealMatrix, arg0::jdouble)
    return jcall(obj, "scalarMultiply", RealMatrix, (jdouble,), arg0)
end

function set_column(obj::AbstractRealMatrix, arg0::jint, arg1::Vector{jdouble})
    return jcall(obj, "setColumn", void, (jint, Vector{jdouble}), arg0, arg1)
end

function set_column_matrix(obj::AbstractRealMatrix, arg0::jint, arg1::RealMatrix)
    return jcall(obj, "setColumnMatrix", void, (jint, RealMatrix), arg0, arg1)
end

function set_column_vector(obj::AbstractRealMatrix, arg0::jint, arg1::RealVector)
    return jcall(obj, "setColumnVector", void, (jint, RealVector), arg0, arg1)
end

function set_entry(obj::AbstractRealMatrix, arg0::jint, arg1::jint, arg2::jdouble)
    return jcall(obj, "setEntry", void, (jint, jint, jdouble), arg0, arg1, arg2)
end

function set_row(obj::AbstractRealMatrix, arg0::jint, arg1::Vector{jdouble})
    return jcall(obj, "setRow", void, (jint, Vector{jdouble}), arg0, arg1)
end

function set_row_matrix(obj::AbstractRealMatrix, arg0::jint, arg1::RealMatrix)
    return jcall(obj, "setRowMatrix", void, (jint, RealMatrix), arg0, arg1)
end

function set_row_vector(obj::AbstractRealMatrix, arg0::jint, arg1::RealVector)
    return jcall(obj, "setRowVector", void, (jint, RealVector), arg0, arg1)
end

function set_sub_matrix(obj::AbstractRealMatrix, arg0::Vector{Vector{jdouble}}, arg1::jint, arg2::jint)
    return jcall(obj, "setSubMatrix", void, (Vector{Vector{jdouble}}, jint, jint), arg0, arg1, arg2)
end

function subtract(obj::AbstractRealMatrix, arg0::RealMatrix)
    return jcall(obj, "subtract", RealMatrix, (RealMatrix,), arg0)
end

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

function transpose(obj::AbstractRealMatrix)
    return jcall(obj, "transpose", RealMatrix, ())
end

function transpose_multiply(obj::RealMatrix, arg0::RealMatrix)
    return jcall(obj, "transposeMultiply", RealMatrix, (RealMatrix,), arg0)
end

function walk_in_column_order(obj::AbstractRealMatrix, arg0::RealMatrixChangingVisitor)
    return jcall(obj, "walkInColumnOrder", jdouble, (RealMatrixChangingVisitor,), arg0)
end

function walk_in_column_order(obj::AbstractRealMatrix, arg0::RealMatrixChangingVisitor, arg1::jint, arg2::jint, arg3::jint, arg4::jint)
    return jcall(obj, "walkInColumnOrder", jdouble, (RealMatrixChangingVisitor, jint, jint, jint, jint), arg0, arg1, arg2, arg3, arg4)
end

function walk_in_column_order(obj::AbstractRealMatrix, arg0::RealMatrixPreservingVisitor)
    return jcall(obj, "walkInColumnOrder", jdouble, (RealMatrixPreservingVisitor,), arg0)
end

function walk_in_column_order(obj::AbstractRealMatrix, arg0::RealMatrixPreservingVisitor, arg1::jint, arg2::jint, arg3::jint, arg4::jint)
    return jcall(obj, "walkInColumnOrder", jdouble, (RealMatrixPreservingVisitor, jint, jint, jint, jint), arg0, arg1, arg2, arg3, arg4)
end

function walk_in_optimized_order(obj::AbstractRealMatrix, arg0::RealMatrixChangingVisitor)
    return jcall(obj, "walkInOptimizedOrder", jdouble, (RealMatrixChangingVisitor,), arg0)
end

function walk_in_optimized_order(obj::AbstractRealMatrix, arg0::RealMatrixChangingVisitor, arg1::jint, arg2::jint, arg3::jint, arg4::jint)
    return jcall(obj, "walkInOptimizedOrder", jdouble, (RealMatrixChangingVisitor, jint, jint, jint, jint), arg0, arg1, arg2, arg3, arg4)
end

function walk_in_optimized_order(obj::AbstractRealMatrix, arg0::RealMatrixPreservingVisitor)
    return jcall(obj, "walkInOptimizedOrder", jdouble, (RealMatrixPreservingVisitor,), arg0)
end

function walk_in_optimized_order(obj::AbstractRealMatrix, arg0::RealMatrixPreservingVisitor, arg1::jint, arg2::jint, arg3::jint, arg4::jint)
    return jcall(obj, "walkInOptimizedOrder", jdouble, (RealMatrixPreservingVisitor, jint, jint, jint, jint), arg0, arg1, arg2, arg3, arg4)
end

function walk_in_row_order(obj::AbstractRealMatrix, arg0::RealMatrixChangingVisitor)
    return jcall(obj, "walkInRowOrder", jdouble, (RealMatrixChangingVisitor,), arg0)
end

function walk_in_row_order(obj::AbstractRealMatrix, arg0::RealMatrixChangingVisitor, arg1::jint, arg2::jint, arg3::jint, arg4::jint)
    return jcall(obj, "walkInRowOrder", jdouble, (RealMatrixChangingVisitor, jint, jint, jint, jint), arg0, arg1, arg2, arg3, arg4)
end

function walk_in_row_order(obj::AbstractRealMatrix, arg0::RealMatrixPreservingVisitor)
    return jcall(obj, "walkInRowOrder", jdouble, (RealMatrixPreservingVisitor,), arg0)
end

function walk_in_row_order(obj::AbstractRealMatrix, arg0::RealMatrixPreservingVisitor, arg1::jint, arg2::jint, arg3::jint, arg4::jint)
    return jcall(obj, "walkInRowOrder", jdouble, (RealMatrixPreservingVisitor, jint, jint, jint, jint), arg0, arg1, arg2, arg3, arg4)
end


# interfacing with other packages

using LinearAlgebra
using LinearAlgebra: BlasComplex, BlasFloat, BlasReal

function mv_wrapper(transa::SparseChar, alpha::Number, A::CuSparseMatrix{T}, X::DenseCuVector{T},
                    beta::Number, Y::CuVector{T}) where {T}
    mv!(transa, alpha, A, X, beta, Y, 'O')
end

function mm_wrapper(transa::SparseChar, transb::SparseChar, alpha::Number,
                    A::CuSparseMatrix{T}, B::CuMatrix{T}, beta::Number, C::CuMatrix{T}) where {T}
    if version() <= v"10.3.1"
        # Generic mm! doesn't support transposed B on CUDA10
        return mm2!(transa, transb, alpha, A, B, beta, C, 'O')
    end
    mm!(transa, transb, alpha, A, B, beta, C, 'O')
end

tag_wrappers = ((identity, identity),
                (T -> :(HermOrSym{T, <:$T}), A -> :(parent($A))))
op_wrappers = (
    (identity, 'N', identity),
    (T -> :(Transpose{<:T, <:$T}), 'T', A -> :(parent($A))),
    (T -> :(Adjoint{<:T, <:$T}), 'C', A -> :(parent($A)))
)
for (taga, untaga) in tag_wrappers, (wrapa, transa, unwrapa) in op_wrappers
    TypeA = wrapa(taga(:(CuSparseMatrix{T})))

    @eval begin
        function LinearAlgebra.mul!(C::CuVector{T}, A::$TypeA, B::DenseCuVector{T},
                                    alpha::Number, beta::Number) where {T <: BlasFloat}
            mv_wrapper($transa, alpha, $(untaga(unwrapa(:A))), B, beta, C)
        end
    end

    for (tagb, untagb) in tag_wrappers, (wrapb, transb, unwrapb) in op_wrappers
        TypeB = wrapb(tagb(:(DenseCuMatrix{T})))

        isadjoint(expr) = expr.head == :curly && expr.args[1] == :Adjoint
        if isadjoint(TypeA) || isadjoint(TypeB)
            # CUSPARSE defines adjoints only for complex inputs. For real inputs we run
            # adjoints as tranposes so that we can still support the whole API surface of
            # LinearAlgebra.
            @eval begin
                function LinearAlgebra.mul!(C::CuMatrix{T}, A::$TypeA, B::$TypeB,
                                            alpha::Number, beta::Number) where {T <: BlasComplex}
                    mm_wrapper($transa, $transb, alpha, $(untaga(unwrapa(:A))),
                               $(untagb(unwrapb(:B))), beta, C)
                end
            end

            transa_real = transa == 'C' ? 'T' : transa
            transb_real = transb == 'C' ? 'T' : transb
            @eval begin
                function LinearAlgebra.mul!(C::CuMatrix{T}, A::$TypeA, B::$TypeB,
                                            alpha::Number, beta::Number) where {T <: BlasReal}
                    mm_wrapper($transa_real, $transb_real, alpha, $(untaga(unwrapa(:A))),
                               $(untagb(unwrapb(:B))), beta, C)
                end
            end
        else
            @eval begin
                function LinearAlgebra.mul!(C::CuMatrix{T}, A::$TypeA, B::$TypeB,
                                            alpha::Number, beta::Number) where {T <: BlasFloat}
                    mm_wrapper($transa, $transb, alpha, $(untaga(unwrapa(:A))),
                               $(untagb(unwrapb(:B))), beta, C)
                end
            end
        end
    end
end

Base.:(+)(A::Union{CuSparseMatrixCSR,CuSparseMatrixCSC},
          B::Union{CuSparseMatrixCSR,CuSparseMatrixCSC}) = geam(A,B,'O','O','O')
Base.:(-)(A::Union{CuSparseMatrixCSR,CuSparseMatrixCSC},
          B::Union{CuSparseMatrixCSR,CuSparseMatrixCSC}) = geam(A,-one(eltype(A)),B,'O','O','O')

# triangular

## direct
for (t, uploc, isunitc) in ((:LowerTriangular, 'L', 'N'),
                            (:UnitLowerTriangular, 'L', 'U'),
                            (:UpperTriangular, 'U', 'N'),
                            (:UnitUpperTriangular, 'U', 'U'))
    @eval begin
        # Left division
        LinearAlgebra.ldiv!(A::$t{T,<:AbstractCuSparseMatrix},
                            B::DenseCuVector{T}) where {T<:BlasFloat} =
            sv2!('N', $uploc, $isunitc, one(T), parent(A), B, 'O')

        LinearAlgebra.ldiv!(A::$t{T,<:AbstractCuSparseMatrix},
                            B::DenseCuMatrix{T}) where {T<:BlasFloat} =
            sm2!('N', 'N', $uploc, $isunitc, one(T), parent(A), B, 'O')
    end
end

## adjoint/transpose ('uploc' reversed)
for (t, uploc, isunitc) in ((:LowerTriangular, 'U', 'N'),
                            (:UnitLowerTriangular, 'U', 'U'),
                            (:UpperTriangular, 'L', 'N'),
                            (:UnitUpperTriangular, 'L', 'U'))
    @eval begin
        # Left division with vectors
        LinearAlgebra.ldiv!(A::$t{<:Any,<:Transpose{T,<:AbstractCuSparseMatrix}},
                            B::DenseCuVector{T}) where {T<:BlasFloat} =
            sv2!('T', $uploc, $isunitc, one(T), parent(parent(A)), B, 'O')

        LinearAlgebra.ldiv!(A::$t{<:Any,<:Adjoint{T,<:AbstractCuSparseMatrix}},
                            B::DenseCuVector{T}) where {T<:BlasReal} =
            sv2!('T', $uploc, $isunitc, one(T), parent(parent(A)), B, 'O')

        LinearAlgebra.ldiv!(A::$t{<:Any,<:Adjoint{T,<:AbstractCuSparseMatrix}},
                            B::DenseCuVector{T}) where {T<:BlasComplex} =
            sv2!('C', $uploc, $isunitc, one(T), parent(parent(A)), B, 'O')

        # Left division with matrices
        LinearAlgebra.ldiv!(A::$t{<:Any,<:Transpose{T,<:AbstractCuSparseMatrix}},
                            B::DenseCuMatrix{T}) where {T<:BlasFloat} =
            sm2!('T', 'N', $uploc, $isunitc, one(T), parent(parent(A)), B, 'O')

        LinearAlgebra.ldiv!(A::$t{<:Any,<:Adjoint{T,<:AbstractCuSparseMatrix}},
                            B::DenseCuMatrix{T}) where {T<:BlasReal} =
            sm2!('T', 'N', $uploc, $isunitc, one(T), parent(parent(A)), B, 'O')

        LinearAlgebra.ldiv!(A::$t{<:Any,<:Adjoint{T,<:AbstractCuSparseMatrix}},
                            B::DenseCuMatrix{T}) where {T<:BlasComplex} =
            sm2!('C', 'N', $uploc, $isunitc, one(T), parent(parent(A)), B, 'O')
    end
end


############################################################################################
"""
$(TYPEDEF)
Contains maximum memory for latent and observed data. Relevant for number of
propagation steps from initial particle distribution and for first time particles
are weighted with data point.

# Fields
$(TYPEDFIELDS)
"""
struct ParticleFilterMemory
    "Latent variable memory. Number of times that initial particle distribution is used."
    latent::Int64
    "Observed data memory. First particle weighting taken at this point."
    data::Int64
    "Number of times when sampled from initial distribution. Per default equal to latent."
    initial::Int64
    function ParticleFilterMemory(latent::Int64, data::Int64, initial::Int64)
        ArgCheck.@argcheck latent >= 0 "Latent memory cannot be negative."
        ArgCheck.@argcheck data >= 0 "Data memory cannot be negative."
        ArgCheck.@argcheck initial >= 0 "initial number of sampling steps cannot be negative."
        return new(latent, data, initial)
    end
end

function max(memory::ParticleFilterMemory)
    return max(memory.latent, memory.data)
end

############################################################################################
"""
$(SIGNATURES)
Propagate forward a single trajectory given ParticleKernel.

# Examples
```julia
```

"""
function propagate(
    _rng, kernel::ParticleKernel, memory::ParticleFilterMemory, reference::AbstractArray{P}
) where {P}
    trajectory = Vector{P}(undef, size(reference, 1))
    Ninitial = memory.initial #max(1, memory.latent)
    ## Initialize particle
    for iter in 1:Ninitial
        trajectory[iter] = initial(_rng, kernel)
    end
    ## Propoagate particle forward
    for iter in (Ninitial + 1):size(trajectory, 1)
        trajectory[iter] = transition(_rng, kernel, trajectory, iter)
    end
    return trajectory
end

############################################################################################
"""
$(SIGNATURES)
Check if function is exectuable.

# Examples
```julia
```

"""
function _try(f::Function, input...)
    try
        f(input...)
    catch err
        return err
    end
end

"""
$(SIGNATURES)
Return first iteration at which function is executable.

# Examples
```julia
```

"""
function _checkmemory(f::Function; maxiter=1000)
    for iter in Base.OneTo(maxiter)
        val = _try(f, iter)
        if !isa(val, Exception)
            return iter
        end
    end
    return "Memory for Objective exceeds 1000 - check your dynamics(objective) implementation."
end

"""
$(SIGNATURES)
Guess memory of ParticleKernel.

# Examples
```julia
```

"""
function _guessmemory(
    _rng::Random.AbstractRNG, kernel::ParticleKernel, reference::AbstractArray{T}
) where {T}
    ## Initialize state and evidence transition distribution as a function of the current iteration
    guess_state(iter) = transition(_rng, kernel, reference, iter)
    #!NOTE: We dont need to use more general ℓevidence here as evidence distribution independent of Particle type
    guess_evidence(iter) = kernel.evidence(reference, iter)
    ## Compute first iteration at which kernel works
    Mparticle = _checkmemory(guess_state)
    Mevidence = _checkmemory(guess_evidence)
    ## Check if dependency structure could be determined
    ArgCheck.@argcheck isa(Mparticle, Integer) "Could not determine particle dependency - check kernel.transition in dynamics, or consider writing an issue in BaytesFilters."
    ArgCheck.@argcheck isa(Mevidence, Integer) "Could not determine data dependency - check kernel.observation in dynamics, or consider writing an issue in BaytesFilters."
    ## Return ParticleFilterMemory - subtract 1 from memory as iterations start with 1, which would mean memoryless transition.
    #=
    println(
    "Latent memory set to: ", Mparticle - 1,
    ". Data memory set to: ", Mevidence - 1,
    ". Initial distribution sampling steps: ", max(1, Mparticle - 1),
    ". If either of those is not as intended, you can manually define the memory in the 'ParticleFilterDefault' container as
    ParticleFilterDefault(; memory = ParticleFilterMemory(latent, data, initial))")
    =#
    return ParticleFilterMemory(Mparticle - 1, Mevidence - 1, max(1, Mparticle - 1))
end

################################################################################
#export
export ParticleFilterMemory


############################################################################################
"""
$(TYPEDEF)

Default arguments for Particle Filter constructor.

# Fields
$(TYPEDFIELDS)
"""
struct ParticleFilterDefault{
    M<:Union{Nothing, ParticleFilterMemory},
    A<:BaytesCore.ParameterWeighting,
    B<:BaytesCore.ResamplingMethod,
    C<:ParticleReferencing,
    T<:Integer,
    U<:BaytesCore.UpdateBool
}
    "Memory for particles and data."
    memory::M
    "Weighting Methods for particles."
    weighting::A
    "Resampling methods for particle trajectories."
    resampling::B
    "Referencing type for last particle at each iteration - either Conditional, Ancestral or Marginal Implementation."
    referencing::C
    "Coverage of Particles/datapoints."
    coverage::Float64
    "ESS threshold for resampling particle trajectories."
    threshold::Float64
    "Type for ancestors indices"
    ancestortype::Type{T}
    "Boolean if initial parameter are fixed or resampled."
    TunedModel::Bool
    "Boolean if generate(_rng, objective) for corresponding model is stored in PF Diagnostics."
    generated::U
    function ParticleFilterDefault(;
        memory::M=nothing,
        weighting::A=Bootstrap(),
        resampling::B=Systematic(),
        referencing::C=Marginal(),
        coverage=0.50,
        threshold=0.75,
        ancestortype::Type{T}=Int32,
        TunedModel=true,
        generated=BaytesCore.UpdateFalse()
    ) where {
        M<:Union{Nothing, ParticleFilterMemory},
        A<:BaytesCore.ParameterWeighting,
        B<:BaytesCore.ResamplingMethod,
        C<:ParticleReferencing,
        T<:Integer
    }
        ArgCheck.@argcheck 0.0 < coverage
        ArgCheck.@argcheck 0.0 <= threshold <= 1.0
        return new{M,A,B,C,T,typeof(generated)}(
            memory,
            weighting,
            resampling,
            referencing,
            coverage,
            threshold,
            ancestortype,
            TunedModel,
            generated
        )
    end
end

############################################################################################
"""
$(TYPEDEF)

Contains particles, transition kernel and all other relevant tuning information for particle propagation.

# Fields
$(TYPEDFIELDS)
"""
struct ParticleFilter{A<:Particles,B<:ParticleFilterTune} <: AbstractAlgorithm
    "Particle values and kernel."
    particles::A
    "Tuning configuration for particles."
    tune::B
    function ParticleFilter(
        particles::A, tune::B
    ) where {A<:Particles,B<:ParticleFilterTune}
        return new{A,B}(particles, tune)
    end
end

function ParticleFilter(
    _rng::Random.AbstractRNG,
    objective::Objective,
    default::ParticleFilterDefault=ParticleFilterDefault(),
    info::BaytesCore.SampleDefault = BaytesCore.SampleDefault()
)
    ## Checks before algorithm is initiated
    ArgCheck.@argcheck hasmethod(dynamics, Tuple{typeof(objective)}) "No Filter dynamics given your objective exists - assign dynamics(objective::Objective{MyModel})"
    @unpack memory, weighting, resampling, referencing, coverage, threshold, ancestortype, TunedModel, generated = default
    @unpack model, data, tagged = objective
    ## Assign model dynamics
    kernel = ModelWrappers.dynamics(objective)
    ## Initiate a valid reference given model.data and tagged.parameter
    reference = get_reference(referencing, objective)
    ## Guess particle and data memory
    memory = memory isa ParticleFilterMemory ? memory : _guessmemory(_rng, kernel, reference)
    ArgCheck.@argcheck memory isa ParticleFilterMemory
    ## Forward sample new reference if TunedModel == false
    if !TunedModel
        reference = propagate(_rng, kernel, memory, reference)
    end
    ## Assign tuning struct
    Ndata = maximum(size(data))
    Nparticles = Int64(floor(coverage * Ndata))
    tune = ParticleFilterTune(
        objective,
        weighting,
        resampling,
        referencing,
        ParticleFilterConfig(data, reference),
        BaytesCore.ChainsTune(coverage, threshold, Nparticles, Ndata),
        memory,
        generated,
        Iterator(1),
    )
    ## Prediction buffer
    Tprediction = infer(_rng, tune, kernel, model, data)
    prediction_buffer = Vector{Tprediction}(undef, 1)
    ## Create Particles container
    particles = Particles(reference, prediction_buffer, kernel, ancestortype, Nparticles, Ndata)
    ## Return Particle filter
    return ParticleFilter(particles, tune)
end

############################################################################################
"""
$(SIGNATURES)
Run particle filter and do not change reference trajectory.

# Examples
```julia
```

"""
function propose(
    _rng::Random.AbstractRNG,
    pf::ParticleFilter,
    objective::Objective,
    reference::AbstractArray{P}=get_reference(pf.tune.referencing, objective),
) where {P}
    ## Set iterations and log likelihood to initial state and resize proposal
    init!(pf.particles.ℓobjective)
    init!(pf.tune.iter, 1)
    ## Initialize particles
    initial!(_rng, pf.particles, pf.tune, reference, objective)
    ## Propagate particles forward
    propagate!(_rng, pf.particles, pf.tune, reference, objective)
    ## Sort all particles back into correct order
    BaytesCore.shuffle_backward!(pf.particles, pf.tune)
    ## Draw proposal path, update proposal with corresponding path and predict future state
    path = BaytesCore.draw!(_rng, pf.particles.weights)
    prediction = predict(_rng, pf.particles, pf.tune, reference, path)
    pf.particles.buffer.prediction[1] = prediction
    ## Update model parameter with reference trajectory
    ModelWrappers.fill!(
        objective.model,
        objective.tagged,
        #!NOTE: Create new Array with current iteration length to keep same type as in Model
        BaytesCore.to_NamedTuple(
            keys(objective.tagged.parameter),
            pf.particles.val[path, 1:(pf.tune.iter.current - 1)],
        ),
    )
    ## Create Diagnostics and return output
    diagnostics = ParticleFilterDiagnostics(
        BaytesCore.BaseDiagnostics(
            pf.particles.ℓobjective.cumulative,
            objective.temperature,
            prediction,
            pf.tune.iter.current-1
        ),
        pf.particles.ℓobjective.current,
        pf.tune.chains.Nchains,
        mean(pf.particles.buffer.resampled),
        ModelWrappers.generate(_rng, objective, pf.tune.generated)
    )
    ## Return new model parameter and diagnostics
    return objective.model.val, diagnostics
end

############################################################################################
"""
$(SIGNATURES)
Run particle filter and change reference trajectory with proposal trajectory.

# Examples
```julia
```

"""
function propose!(
    _rng::Random.AbstractRNG,
    pf::ParticleFilter,
    model::ModelWrapper,
    data::D,
    temperature::T = model.info.reconstruct.default.output(1.0),
    update::U=BaytesCore.UpdateTrue(),
) where {D, T<:AbstractFloat, U<:BaytesCore.UpdateBool}
    ## Update PF parameter
    objective = Objective(model, data, pf.tune.tagged, temperature)
    ## Collect reference
    reference = get_reference(pf.tune.referencing, objective)
    ## Check if Ndata and Nparticles have to be adjusted
    if update isa BaytesCore.UpdateTrue
        ## Check if number of particles need to be updated
        update_Nparticles, update_Ndata, Ndata = update!(pf.tune, data, reference)
        ## Resize particles if needed
        update!(
            pf.particles,
            update_Nparticles,
            update_Ndata,
            dynamics(objective),
            reference,
            pf.tune.chains.Nchains,
            Ndata,
        )
    end
    ## Propagate particles forward
    val, diagnostics = propose(_rng, pf, objective, reference)
    ## Update ModelWrapper parameter with reference trajectory
    model.val = val
    ## Return diagnostics
    return val, diagnostics
end

############################################################################################
"""
$(SIGNATURES)
Propagate particle filter forward with new data.

# Examples
```julia
```

"""
function propagate!(
    _rng::Random.AbstractRNG,
    pf::ParticleFilter,
    model::ModelWrapper,
    data::D,
    temperature::T = model.info.reconstruct.default.output(1.0),
) where {P, D, T<:AbstractFloat}
    ## Check if pf and data fulfill requirements for particle propagation
    ArgCheck.@argcheck isa(pf.tune.referencing, Marginal) "PF propagation only allowed for marginal particle filter"
    ## Assign new dynamics in case particles dependent on data
    objective = Objective(model, data, pf.tune.tagged, temperature)
    pf.particles.kernel = ModelWrappers.dynamics(objective)
    ## Collect reference trajectory
    reference = get_reference(pf.tune.referencing, objective)
    # Check if reference no larger than data dimension
    ArgCheck.@argcheck size(reference, 1) <= maximum(size(objective.data)) "Reference trajectory is longer than provided data input."
    ## Update maximal number of iterations and resize particles ~ Need to manually resize as want to keep weights and ancestor indices
    pf.tune.chains.Ndata = maximum(size(objective.data))
    resize!(pf.particles.val, pf.tune.chains.Nchains, pf.tune.chains.Ndata)
    resize!(pf.particles.ancestor, pf.tune.chains.Nchains, pf.tune.chains.Ndata)
    update!(pf.particles.buffer, pf.tune.chains.Nchains, pf.tune.chains.Ndata)
    ## Propagate particles forward
    propagate!(_rng, pf.particles, pf.tune, reference, objective)
    ## Sort all particles back into correct order
    BaytesCore.shuffle_backward!(pf.particles, pf.tune)
    ## Draw proposal path, update proposal with corresponding path and predict future state
    path = BaytesCore.draw!(_rng, pf.particles.weights)
    prediction = predict(_rng, pf.particles, pf.tune, reference, path)
    pf.particles.buffer.prediction[1] = prediction
    ## Update model parameter with reference trajectory
    ModelWrappers.fill!(
        model,
        pf.tune.tagged,
        #!NOTE: Create new Array with current iteration length to keep same type as in Model
        BaytesCore.to_NamedTuple(
            keys(pf.tune.tagged.parameter),
            pf.particles.val[path, 1:(pf.tune.iter.current - 1)],
        ),
    )
    ## Create Diagnostics and return output
    diagnostics = ParticleFilterDiagnostics(
        BaytesCore.BaseDiagnostics(
            pf.particles.ℓobjective.cumulative,
            temperature,
            prediction,
            pf.tune.iter.current-1
        ),
        pf.particles.ℓobjective.current,
        pf.tune.chains.Nchains,
        mean(pf.particles.buffer.resampled),
        ModelWrappers.generate(_rng, objective, pf.tune.generated)
    )
    return model.val, diagnostics
end

############################################################################################
#export
export ParticleFilter, ParticleFilterDefault, propose, propose!, propagate!


using RealNeuralNetworks
using Test
using HDF5
using BigArrays
using BigArrays.GSDicts
using RealNeuralNetworks.NodeNets
using RealNeuralNetworks.SWCs

using LinearAlgebra

const CELL_ID = UInt32(76880)
const EXPANSION= (UInt32(80), UInt32(80), UInt32(40))
const MIP = 4

function get_seg_from_h5()
    # read seg data
    f = h5open("/usr/people/jingpeng/seungmount/research/Ashwin/Scripts/NG_scripts/77605.h5")
    seg = f["main"][1,:,:,101:500]
    close(f)
    seg = reshape(seg, size(seg)[2:4])
    @assert ndims(seg) == 3
    for i in eachindex(seg)
        if seg[i] == 77605
            seg[i] = convert(UInt32,1)
        else
            seg[i] = convert(UInt32, 0)
        end
    end
    return seg
end

function get_seg_from_gs()
    ba = BigArray(GSDict( SEG_PATH ))
    seg = ba[2457:2968, 1777:2288, 16401:16912]
end

function create_fake_seg()
    seg = zeros(UInt32,(100,100,100))
    seg[50,50,:] .= one(UInt32)
    seg[49:52, 49:52, 48:52] .= one(UInt32)
    seg[47:54, 47:54, 71:78] .= one(UInt32)
    return seg
end

@testset "test NodeNets module" begin
    seg = create_fake_seg()
    # @time seg = get_seg_from_h5()
    # @time seg = get_seg_from_gs()
    println("building nodeNet ...")
    #@time nodeNet = NodeNet( seg; obj_id = CELL_ID )
    @time nodeNet = NodeNet( seg; obj_id = one(UInt32) )
    NodeNets.add_offset!(nodeNet, (-one(Float32),-one(Float32),-one(Float32)))
    bin = NodeNets.get_neuroglancer_precomputed(nodeNet)
    # open("/tmp/fake.bin", "w") do f write(f, bin)  end
    @time swc = SWCs.SWC( nodeNet )
    SWCs.stretch_coordinates!(swc, MIP)
    SWCs.save(swc, "/tmp/test.swc")
    @test length(swc) > 1
end


"""Entropy MCTS solver with IPFT

Derived from MCTS-DPW (JuliaPOMDP/MCTS.jl/src/dpw_types.jl)

Fields:

    depth::Int64:
        Maximum rollout horizon and tree depth.
        default: 10

    exploration_constant::Float64:
        Specified how much the solver should explore.
        In the UCB equation, Q + c*sqrt(log(t/N)), c is the exploration constant.
        default: 1.0

    n_iterations::Int64
        Number of iterations during each action() call.
        default: 100

    max_time::Float64
        Maximum amount of CPU time spent iterating through simulations.
        default: Inf

    k_action::Float64
    alpha_action::Float64
    k_observation::Float64
    alpha_observation::Float64
        These constants control the double progressive widening. A new state
        or action will be added if the number of children is less than or equal to kN^alpha.
        defaults: k:10, alpha:0.5

    lambda::Float64
        Hyper parameter balancing reward and information reward

    keep_tree::Bool
        If true, store the tree in the planner for reuse at the next timestep (and every time it is used in the future). There is a computational cost for maintaining the state dictionary necessary for this.
        default: false

    reuse_states::Bool
        If true, reuses first sample in a tree node instead of sampling new particles in every visit

    enable_action_pw::Bool
        If true, enable progressive widening on the action space; if false just use the whole action space.
        default: true

    check_repeat_state::Bool
    check_repeat_action::Bool
        When constructing the tree, check whether a state or action has been seen before (there is a computational cost to maintaining the dictionaries necessary for this)
        default: true

    tree_in_info::Bool:
        If true, return the tree in the info dict when action_info is called. False by default because it can use a lot of memory if histories are being saved.
        default: false

    rng::AbstractRNG:
        Random number generator

    estimate_value::Any (rollout policy)
        Function, object, or number used to estimate the value at the leaf nodes.
        If this is a function `f`, `f(mdp, s, depth)` will be called to estimate the value.
        If this is an object `o`, `estimate_value(o, mdp, s, depth)` will be called.
        If this is a number, the value will be set to that number.
        default: RolloutEstimator(RandomSolver(rng))

    init_Q::Any
        Function, object, or number used to set the initial Q(s,a) value at a new node.
        If this is a function `f`, `f(mdp, s, a)` will be called to set the value.
        If this is an object `o`, `init_Q(o, mdp, s, a)` will be called.
        If this is a number, Q will always be set to that number.
        default: 0.0

    init_N::Any
        Function, object, or number used to set the initial N(s,a) value at a new node.
        If this is a function `f`, `f(mdp, s, a)` will be called to set the value.
        If this is an object `o`, `init_N(o, mdp, s, a)` will be called.
        If this is a number, N will always be set to that number.
        default: 0

    next_action::Any
        Function or object used to choose the next action to be considered for progressive widening.
        The next action is determined based on the MDP, the state, `s`, and the current `IPFTStateNode`, `snode`.
        If this is a function `f`, `f(mdp, s, snode)` will be called to set the value.
        If this is an object `o`, `next_action(o, mdp, s, snode)` will be called.
        default: RandomActionGenerator(rng)

    default_action::Any
        Function, action, or Policy used to determine the action if POMCP fails with exception `ex`.
        If this is a Function `f`, `f(pomdp, belief, ex)` will be called.
        If this is a Policy `p`, `action(p, belief)` will be called.
        If it is an object `a`, `default_action(a, pomdp, belief, ex)` will be called, and if this method is not implemented, `a` will be returned directly.
        default: `ExceptionRethrow()`
"""
mutable struct IPFTSolver <: AbstractMCTSSolver
    depth::Int
    criterion::AbstractMultiObjectiveCriterion
    final_criterion::AbstractMultiObjectiveCriterion
    # exploration_constant::Float64
    n_iterations::Int
    max_time::Float64
    k_action::Float64
    alpha_action::Float64
    k_observation::Float64
    alpha_observation::Float64
    keep_tree::Bool
    reuse_states::Bool
    enable_action_pw::Bool
    check_repeat_state::Bool
    check_repeat_action::Bool
    tree_in_info::Bool
    rng::AbstractRNG
    estimate_value::Any
    init_Q::Any
    init_N::Any
    next_action::Any
    default_action::Any
end

"""
    IPFTSolver()

Use keyword arguments to specify values for the fields
"""
function IPFTSolver(;depth::Int=10,
                    criterion::AbstractMultiObjectiveCriterion=MultiObjectiveUCB([1.0,0.0],1.0),
                    final_criterion::AbstractMultiObjectiveCriterion=MultiObjective(criterion),
                    # exploration_constant::Float64=1.0,
                    n_iterations::Int=100,
                    max_time::Float64=Inf,
                    k_action::Float64=10.0,
                    alpha_action::Float64=0.5,
                    k_observation::Float64=10.0,
                    alpha_observation::Float64=0.5,
                    keep_tree::Bool=false,
                    reuse_states::Bool=false,
                    enable_action_pw::Bool=true,
                    check_repeat_state::Bool=true,
                    check_repeat_action::Bool=true,
                    tree_in_info::Bool=false,
                    rng::AbstractRNG=Random.GLOBAL_RNG,
                    estimate_value::Any = RolloutEstimator(RandomSolver(rng)),
                    # init_Q::Any = 0.0,
                    init_Q::Any = [0.0, 0.0],
                    init_N::Any = 0,
                    next_action::Any = RandomActionGenerator(rng),
                    default_action::Any = ExceptionRethrow()
                   )
    IPFTSolver(depth, criterion, final_criterion, n_iterations, max_time, k_action, alpha_action, k_observation, alpha_observation, keep_tree, reuse_states, enable_action_pw, check_repeat_state, check_repeat_action, tree_in_info, rng, estimate_value, init_Q, init_N, next_action, default_action)
end

mutable struct IPFTree{S,A}
    # for each state node
    total_n::Vector{Int}
    children::Vector{Vector{Int}}
    s_labels::Vector{S}
    s_lookup::Dict{S, Int}

    # for each state-action node
    n::Vector{Int}
    q::Vector{Vector{Float64}}
    transitions::Vector{Vector{Tuple{Int,S,Array{Float64,1}}}}
    a_labels::Vector{A}
    a_lookup::Dict{Tuple{Int,A}, Int}

    # for tracking transitions
    n_a_children::Vector{Int}
    unique_transitions::Set{Tuple{Int,Int}}

    function IPFTree{S,A}(sz::Int=1000) where {S,A}
        sz = min(sz, 100_000)
        return new(sizehint!(Int[], sz),
                   sizehint!(Vector{Int}[], sz),
                   sizehint!(S[], sz),
                   Dict{S, Int}(),

                   sizehint!(Int[], sz),
                   sizehint!(Float64[], sz),
                   sizehint!(Vector{Tuple{Int,Float64}}[], sz),
                   sizehint!(A[], sz),
                   Dict{Tuple{Int,A}, Int}(),

                   sizehint!(Int[], sz),
                   Set{Tuple{Int,Int}}()
                  )
    end
end

function insert_state_node!(tree::IPFTree{S,A}, s::S, maintain_s_lookup=true) where {S,A}
    push!(tree.total_n, 0)
    push!(tree.children, Int[])
    push!(tree.s_labels, s)
    snode = length(tree.total_n)
    if maintain_s_lookup
        tree.s_lookup[s] = snode
    end
    return snode
end

function insert_action_node!(tree::IPFTree{S,A}, snode::Int, a::A, n0::Int, q0::Vector{Float64}, maintain_a_lookup=true) where {S,A}
    push!(tree.n, n0)
    push!(tree.q, q0)
    push!(tree.a_labels, a)
    push!(tree.transitions, Vector{Tuple{Int,Float64}}[])
    sanode = length(tree.n)
    push!(tree.children[snode], sanode)
    push!(tree.n_a_children, 0)
    if maintain_a_lookup
        tree.a_lookup[(snode, a)] = sanode
    end
    return sanode
end

Base.isempty(tree::IPFTree) = isempty(tree.n) && isempty(tree.q)

struct IPFTStateNode{S,A} <: AbstractStateNode
    tree::IPFTree{S,A}
    index::Int
end

total_n(n::IPFTStateNode) = n.tree.total_n[n.index]
children(n::IPFTStateNode) = n.tree.children[n.index]
n_children(n::IPFTStateNode) = length(children(n))
isroot(n::IPFTStateNode) = n.index == 1

mutable struct IPFTPlanner{P<:Union{MDP,POMDP}, S, A, SE, NA, RNG} <: InformationMCTSPlanner{P}
    solver::IPFTSolver
    mdp::P
    tree::Union{Nothing, IPFTree{S,A}}
    solved_estimate::SE
    next_action::NA
    rng::RNG
end

function IPFTPlanner(solver::IPFTSolver, mdp::P) where P<:Union{MDP,POMDP}
    se = convert_estimator(solver.estimate_value, solver, mdp)
    return IPFTPlanner{P,
                      statetype(P),
                      actiontype(P),
                      typeof(se),
                      typeof(solver.next_action),
                      typeof(solver.rng)}(solver,
                                            mdp,
                                            nothing,
                                            se,
                                            solver.next_action,
                                            solver.rng
    )
end

Random.seed!(p::IPFTPlanner, seed) = Random.seed!(p.rng, seed)


###
### Sampler states
###

struct HMCState{
    TV<:AbstractVarInfo,
    TTraj<:AHMC.AbstractTrajectory,
    THam<:AHMC.Hamiltonian,
    PhType<:AHMC.PhasePoint,
    TAdapt<:AHMC.Adaptation.AbstractAdaptor,
}
    vi::TV
    i::Int
    traj::TTraj
    hamiltonian::THam
    z::PhType
    adaptor::TAdapt
end

##########################
# Hamiltonian Transition #
##########################

struct HMCTransition{T,NT<:NamedTuple,F<:AbstractFloat}
    θ::T
    lp::F
    stat::NT
end

function HMCTransition(vi::AbstractVarInfo, t::AHMC.Transition)
    theta = tonamedtuple(vi)
    lp = getlogp(vi)
    return HMCTransition(theta, lp, t.stat)
end

function metadata(t::HMCTransition)
    return merge((lp = t.lp,), t.stat)
end

DynamicPPL.getlogp(t::HMCTransition) = t.lp

###
### Hamiltonian Monte Carlo samplers.
###

"""
    HMC(ϵ::Float64, n_leapfrog::Int)

Hamiltonian Monte Carlo sampler with static trajectory.

Arguments:

- `ϵ::Float64` : The leapfrog step size to use.
- `n_leapfrog::Int` : The number of leapfrog steps to use.

Usage:

```julia
HMC(0.05, 10)
```

Tips:

- If you are receiving gradient errors when using `HMC`, try reducing the leapfrog step size `ϵ`, e.g.

```julia
# Original step size
sample(gdemo([1.5, 2]), HMC(0.1, 10), 1000)

# Reduced step size
sample(gdemo([1.5, 2]), HMC(0.01, 10), 1000)
```
"""
struct HMC{AD, space, metricT <: AHMC.AbstractMetric} <: StaticHamiltonian{AD}
    ϵ::Float64 # leapfrog step size
    n_leapfrog::Int # leapfrog step number
end

HMC(args...; kwargs...) = HMC{ADBackend()}(args...; kwargs...)
function HMC{AD}(ϵ::Float64, n_leapfrog::Int, ::Type{metricT}, space::Tuple) where {AD, metricT <: AHMC.AbstractMetric}
    return HMC{AD, space, metricT}(ϵ, n_leapfrog)
end
function HMC{AD}(
    ϵ::Float64,
    n_leapfrog::Int,
    ::Tuple{};
    kwargs...
) where AD
    return HMC{AD}(ϵ, n_leapfrog; kwargs...)
end
function HMC{AD}(
    ϵ::Float64,
    n_leapfrog::Int,
    space::Symbol...;
    metricT=AHMC.UnitEuclideanMetric
) where AD
    return HMC{AD}(ϵ, n_leapfrog, metricT, space)
end

DynamicPPL.initialsampler(::Sampler{<:Hamiltonian}) = SampleFromUniform()

# Handle setting `nadapts` and `discard_initial`
function AbstractMCMC.sample(
    rng::AbstractRNG,
    model::AbstractModel,
    sampler::Sampler{<:AdaptiveHamiltonian},
    N::Integer;
    chain_type=MCMCChains.Chains,
    resume_from=nothing,
    progress=PROGRESS[],
    nadapts=sampler.alg.n_adapts,
    discard_adapt=true,
    discard_initial=-1,
    kwargs...
)
    if resume_from === nothing
        # If `nadapts` is `-1`, then the user called a convenience
        # constructor like `NUTS()` or `NUTS(0.65)`,
        # and we should set a default for them.
        if nadapts == -1
            _nadapts = min(1000, N ÷ 2)
        else
            _nadapts = nadapts
        end

        # If `discard_initial` is `-1`, then users did not specify the keyword argument.
        if discard_initial == -1
            _discard_initial = discard_adapt ? _nadapts : 0
        else
            _discard_initial = discard_initial
        end

        return AbstractMCMC.mcmcsample(rng, model, sampler, N;
                                       chain_type=chain_type, progress=progress,
                                       nadapts=_nadapts, discard_initial=_discard_initial,
                                       kwargs...)
    else
        return resume(resume_from, N; chain_type=chain_type, progress=progress,
                      nadapts=0, discard_adapt=false, discard_initial=0, kwargs...)
    end
end

function DynamicPPL.initialstep(
    rng::AbstractRNG,
    model::AbstractModel,
    spl::Sampler{<:Hamiltonian},
    vi::AbstractVarInfo;
    init_params=nothing,
    nadapts=0,
    kwargs...
)
    # Transform the samples to unconstrained space and compute the joint log probability.
    link!(vi, spl)
    model(rng, vi, spl)

    # Extract parameters.
    theta = vi[spl]

    # Create a Hamiltonian.
    metricT = getmetricT(spl.alg)
    metric = metricT(length(theta))
    ∂logπ∂θ = gen_∂logπ∂θ(vi, spl, model)
    logπ = gen_logπ(vi, spl, model)
    hamiltonian = AHMC.Hamiltonian(metric, logπ, ∂logπ∂θ)

    # Compute phase point z.
    z = AHMC.phasepoint(rng, theta, hamiltonian)

    # If no initial parameters are provided, resample until the log probability
    # and its gradient are finite.
    if init_params === nothing
        while !isfinite(z)
            model(rng, vi, SampleFromUniform())
            link!(vi, spl)
            theta = vi[spl]

            hamiltonian = AHMC.Hamiltonian(metric, logπ, ∂logπ∂θ)
            z = AHMC.phasepoint(rng, theta, hamiltonian)
        end
    end

    # Cache current log density.
    log_density_old = getlogp(vi)

    # Find good eps if not provided one
    if iszero(spl.alg.ϵ)
        ϵ = AHMC.find_good_stepsize(hamiltonian, theta)
        @info "Found initial step size" ϵ
    else
        ϵ = spl.alg.ϵ
    end

    # Generate a trajectory.
    traj = gen_traj(spl.alg, ϵ)

    # Create initial transition and state.
    # Already perform one step since otherwise we don't get any statistics.
    t = AHMC.step(rng, hamiltonian, traj, z)

    # Adaptation
    adaptor = AHMCAdaptor(spl.alg, hamiltonian.metric; ϵ=ϵ)
    if spl.alg isa AdaptiveHamiltonian
        hamiltonian, traj, _ =
            AHMC.adapt!(hamiltonian, traj, adaptor,
                        1, nadapts, t.z.θ, t.stat.acceptance_rate)
    end

    # Update `vi` based on acceptance
    if t.stat.is_accept
        vi[spl] = t.z.θ
        setlogp!(vi, t.stat.log_density)
    else
        vi[spl] = theta
        setlogp!(vi, log_density_old)
    end

    transition = HMCTransition(vi, t)
    state = HMCState(vi, 1, traj, hamiltonian, t.z, adaptor)

    return transition, state
end

function AbstractMCMC.step(
    rng::Random.AbstractRNG,
    model::Model,
    spl::Sampler{<:Hamiltonian},
    state::HMCState;
    nadapts=0,
    kwargs...
)
    # Get step size
    @debug "current ϵ" getstepsize(spl, state)

    # Compute transition.
    hamiltonian = state.hamiltonian
    z = state.z
    t = AHMC.step(rng, hamiltonian, state.traj, z)

    # Adaptation
    i = state.i + 1
    if spl.alg isa AdaptiveHamiltonian
        hamiltonian, traj, _ =
            AHMC.adapt!(hamiltonian, state.traj, state.adaptor,
                        i, nadapts, t.z.θ, t.stat.acceptance_rate)
    else
        traj = state.traj
    end

    # Update variables
    vi = state.vi
    if t.stat.is_accept
        vi[spl] = t.z.θ
        setlogp!(vi, t.stat.log_density)
    end

    # Compute next transition and state.
    transition = HMCTransition(vi, t)
    newstate = HMCState(vi, i, traj, hamiltonian, t.z, state.adaptor)

    return transition, newstate
end

function get_hamiltonian(model, spl, vi, state, n)
    metric = gen_metric(n, spl, state)
    ℓπ = gen_logπ(vi, spl, model)
    ∂ℓπ∂θ = gen_∂logπ∂θ(vi, spl, model)
    return AHMC.Hamiltonian(metric, ℓπ, ∂ℓπ∂θ)
end

"""
    HMCDA(n_adapts::Int, δ::Float64, λ::Float64; ϵ::Float64=0.0)

Hamiltonian Monte Carlo sampler with Dual Averaging algorithm.

Usage:

```julia
HMCDA(200, 0.65, 0.3)
```

Arguments:

- `n_adapts::Int` : Numbers of samples to use for adaptation.
- `δ::Float64` : Target acceptance rate. 65% is often recommended.
- `λ::Float64` : Target leapfrog length.
- `ϵ::Float64=0.0` : Inital step size; 0 means automatically search by Turing.

For more information, please view the following paper ([arXiv link](https://arxiv.org/abs/1111.4246)):

- Hoffman, Matthew D., and Andrew Gelman. "The No-U-turn sampler: adaptively
  setting path lengths in Hamiltonian Monte Carlo." Journal of Machine Learning
  Research 15, no. 1 (2014): 1593-1623.
"""
struct HMCDA{AD, space, metricT <: AHMC.AbstractMetric} <: AdaptiveHamiltonian{AD}
    n_adapts    ::  Int         # number of samples with adaption for ϵ
    δ           ::  Float64     # target accept rate
    λ           ::  Float64     # target leapfrog length
    ϵ           ::  Float64     # (initial) step size
end
HMCDA(args...; kwargs...) = HMCDA{ADBackend()}(args...; kwargs...)
function HMCDA{AD}(n_adapts::Int, δ::Float64, λ::Float64, ϵ::Float64, ::Type{metricT}, space::Tuple) where {AD, metricT <: AHMC.AbstractMetric}
    return HMCDA{AD, space, metricT}(n_adapts, δ, λ, ϵ)
end

function HMCDA{AD}(
    δ::Float64,
    λ::Float64;
    init_ϵ::Float64=0.0,
    metricT=AHMC.UnitEuclideanMetric
) where AD
    return HMCDA{AD}(-1, δ, λ, init_ϵ, metricT, ())
end

function HMCDA{AD}(
    n_adapts::Int,
    δ::Float64,
    λ::Float64,
    ::Tuple{};
    kwargs...
) where AD
    return HMCDA{AD}(n_adapts, δ, λ; kwargs...)
end

function HMCDA{AD}(
    n_adapts::Int,
    δ::Float64,
    λ::Float64,
    space::Symbol...;
    init_ϵ::Float64=0.0,
    metricT=AHMC.UnitEuclideanMetric
) where AD
    return HMCDA{AD}(n_adapts, δ, λ, init_ϵ, metricT, space)
end

"""
    NUTS(n_adapts::Int, δ::Float64; max_depth::Int=5, Δ_max::Float64=1000.0, ϵ::Float64=0.0)

No-U-Turn Sampler (NUTS) sampler.

Usage:

```julia
NUTS()            # Use default NUTS configuration.
NUTS(1000, 0.65)  # Use 1000 adaption steps, and target accept ratio 0.65.
```

Arguments:

- `n_adapts::Int` : The number of samples to use with adaptation.
- `δ::Float64` : Target acceptance rate for dual averaging.
- `max_depth::Int` : Maximum doubling tree depth.
- `Δ_max::Float64` : Maximum divergence during doubling tree.
- `init_ϵ::Float64` : Inital step size; 0 means automatically searching using a heuristic procedure.

"""
struct NUTS{AD,space,metricT<:AHMC.AbstractMetric} <: AdaptiveHamiltonian{AD}
    n_adapts::Int         # number of samples with adaption for ϵ
    δ::Float64        # target accept rate
    max_depth::Int         # maximum tree depth
    Δ_max::Float64
    ϵ::Float64     # (initial) step size
end

NUTS(args...; kwargs...) = NUTS{ADBackend()}(args...; kwargs...)

function NUTS{AD}(
    n_adapts::Int,
    δ::Float64,
    max_depth::Int,
    Δ_max::Float64,
    ϵ::Float64,
    ::Type{metricT},
    space::Tuple
) where {AD, metricT}
    return NUTS{AD, space, metricT}(n_adapts, δ, max_depth, Δ_max, ϵ)
end

function NUTS{AD}(
    n_adapts::Int,
    δ::Float64,
    ::Tuple{};
    kwargs...
) where AD
    NUTS{AD}(n_adapts, δ; kwargs...)
end

function NUTS{AD}(
    n_adapts::Int,
    δ::Float64,
    space::Symbol...;
    max_depth::Int=10,
    Δ_max::Float64=1000.0,
    init_ϵ::Float64=0.0,
    metricT=AHMC.DiagEuclideanMetric
) where AD
    NUTS{AD}(n_adapts, δ, max_depth, Δ_max, init_ϵ, metricT, space)
end

function NUTS{AD}(
    δ::Float64;
    max_depth::Int=10,
    Δ_max::Float64=1000.0,
    init_ϵ::Float64=0.0,
    metricT=AHMC.DiagEuclideanMetric
) where AD
    NUTS{AD}(-1, δ, max_depth, Δ_max, init_ϵ, metricT, ())
end

function NUTS{AD}(kwargs...) where AD
    NUTS{AD}(-1, 0.65; kwargs...)
end

for alg in (:HMC, :HMCDA, :NUTS)
    @eval getmetricT(::$alg{<:Any, <:Any, metricT}) where {metricT} = metricT
end

#####
##### HMC core functions
#####

getstepsize(sampler::Sampler{<:Hamiltonian}, state) = sampler.alg.ϵ
getstepsize(sampler::Sampler{<:AdaptiveHamiltonian}, state) = AHMC.getϵ(state.adaptor)

"""
    gen_∂logπ∂θ(vi, spl::Sampler, model)

Generate a function that takes a vector of reals `θ` and compute the logpdf and
gradient at `θ` for the model specified by `(vi, spl, model)`.
"""
function gen_∂logπ∂θ(vi, spl::Sampler, model)
    function ∂logπ∂θ(x)
        return gradient_logp(x, vi, model, spl)
    end
    return ∂logπ∂θ
end

"""
    gen_logπ(vi, spl::Sampler, model)

Generate a function that takes `θ` and returns logpdf at `θ` for the model specified by
`(vi, spl, model)`.
"""
function gen_logπ(vi, spl::AbstractSampler, model)
    function logπ(x)::Float64
        x_old, lj_old = vi[spl], getlogp(vi)
        vi[spl] = x
        model(vi, spl)
        lj = getlogp(vi)
        vi[spl] = x_old
        setlogp!(vi, lj_old)
        return lj
    end
    return logπ
end

gen_metric(dim::Int, spl::Sampler{<:Hamiltonian}, state) = AHMC.UnitEuclideanMetric(dim)
function gen_metric(dim::Int, spl::Sampler{<:AdaptiveHamiltonian}, state)
    return AHMC.renew(state.hamiltonian.metric, AHMC.getM⁻¹(state.adaptor.pc))
end

gen_traj(alg::HMC, ϵ) = AHMC.StaticTrajectory(AHMC.Leapfrog(ϵ), alg.n_leapfrog)
gen_traj(alg::HMCDA, ϵ) = AHMC.HMCDA(AHMC.Leapfrog(ϵ), alg.λ)
gen_traj(alg::NUTS, ϵ) = AHMC.NUTS(AHMC.Leapfrog(ϵ), alg.max_depth, alg.Δ_max)

####
#### Compiler interface, i.e. tilde operators.
####
function DynamicPPL.assume(
    rng,
    spl::Sampler{<:Hamiltonian},
    dist::Distribution,
    vn::VarName,
    vi,
)
    DynamicPPL.updategid!(vi, vn, spl)
    r = vi[vn]
    # acclogp!(vi, logpdf_with_trans(dist, r, istrans(vi, vn)))
    # r
    return r, logpdf_with_trans(dist, r, istrans(vi, vn))
end

function DynamicPPL.dot_assume(
    rng,
    spl::Sampler{<:Hamiltonian},
    dist::MultivariateDistribution,
    vns::AbstractArray{<:VarName},
    var::AbstractMatrix,
    vi,
)
    @assert length(dist) == size(var, 1)
    DynamicPPL.updategid!.(Ref(vi), vns, Ref(spl))
    r = vi[vns]
    var .= r
    return var, sum(logpdf_with_trans(dist, r, istrans(vi, vns[1])))
end
function DynamicPPL.dot_assume(
    rng,
    spl::Sampler{<:Hamiltonian},
    dists::Union{Distribution, AbstractArray{<:Distribution}},
    vns::AbstractArray{<:VarName},
    var::AbstractArray,
    vi,
)
    DynamicPPL.updategid!.(Ref(vi), vns, Ref(spl))
    r = reshape(vi[vec(vns)], size(var))
    var .= r
    return var, sum(logpdf_with_trans.(dists, r, istrans(vi, vns[1])))
end

function DynamicPPL.observe(
    spl::Sampler{<:Hamiltonian},
    d::Distribution,
    value,
    vi,
)
    return DynamicPPL.observe(SampleFromPrior(), d, value, vi)
end

function DynamicPPL.dot_observe(
    spl::Sampler{<:Hamiltonian},
    ds::Union{Distribution, AbstractArray{<:Distribution}},
    value::AbstractArray,
    vi,
)
    return DynamicPPL.dot_observe(SampleFromPrior(), ds, value, vi)
end

####
#### Default HMC stepsize and mass matrix adaptor
####

function AHMCAdaptor(alg::AdaptiveHamiltonian, metric::AHMC.AbstractMetric; ϵ=alg.ϵ)
    pc = AHMC.MassMatrixAdaptor(metric)
    da = AHMC.StepSizeAdaptor(alg.δ, ϵ)

    if iszero(alg.n_adapts)
        adaptor = AHMC.Adaptation.NoAdaptation()
    else
        if metric == AHMC.UnitEuclideanMetric
            adaptor = AHMC.NaiveHMCAdaptor(pc, da)  # there is actually no adaptation for mass matrix
        else
            adaptor = AHMC.StanHMCAdaptor(pc, da)
            AHMC.initialize!(adaptor, alg.n_adapts)
        end
    end

    return adaptor
end

AHMCAdaptor(::Hamiltonian, ::AHMC.AbstractMetric; kwargs...) = AHMC.Adaptation.NoAdaptation()

##########################
# HMC State Constructors #
##########################

function HMCState(
    rng::AbstractRNG,
    model::Model,
    spl::Sampler{<:Hamiltonian},
    vi::AbstractVarInfo;
    kwargs...
)
    # Link everything if needed.
    !islinked(vi, spl) && link!(vi, spl)

    # Get the initial log pdf and gradient functions.
    ∂logπ∂θ = gen_∂logπ∂θ(vi, spl, model)
    logπ = gen_logπ(vi, spl, model)

    # Get the metric type.
    metricT = getmetricT(spl.alg)

    # Create a Hamiltonian.
    θ_init = Vector{Float64}(spl.state.vi[spl])
    metric = metricT(length(θ_init))
    h = AHMC.Hamiltonian(metric, logπ, ∂logπ∂θ)

    # Find good eps if not provided one
    if iszero(spl.alg.ϵ)
        ϵ = AHMC.find_good_stepsize(h, θ_init)
        @info "Found initial step size" ϵ
    else
        ϵ = spl.alg.ϵ
    end

    # Generate a trajectory.
    traj = gen_traj(spl.alg, ϵ)

    # Generate a phasepoint. Replaced during sample_init!
    h, t = AHMC.sample_init(rng, h, θ_init) # this also ensure AHMC has the same dim as θ.

    # Unlink everything.
    invlink!(vi, spl)

    return HMCState(vi, 0, 0, traj, h, AHMCAdaptor(spl.alg, metric; ϵ=ϵ), t.z)
end


# This file is a part of Julia, but is derived from
# https://github.com/floitsch/double-conversion which has the following license
#
# Copyright 2006-2014, the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#     * Neither the name of Google Inc. nor the names of its
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

const kMinExp = -60
const kMaxExp = -32

function roundweed(buffer,len,rest,tk,unit,kappa,too_high::UInt64,unsafe_interval::UInt64)
    small = too_high - unit
    big = too_high + unit
    while rest < small &&
            unsafe_interval - rest >= tk &&
            (rest + tk < small ||
            small - rest >= rest + tk - small)
        buffer[len-1] -= 1
        rest += tk
    end
    if rest < big &&
        unsafe_interval - rest >= tk &&
        (rest + tk < big ||
        big - rest > rest + tk - big)
        return false, kappa
    end
    return (2 * unit <= rest) && (rest <= unsafe_interval - 4 * unit), kappa
end

const SmallPowersOfTen = [
        0, 1, 10, 100, 1000, 10000, 100000,
        1000000, 10000000, 100000000, 1000000000]

function bigpowten(n,n_bits)
    guess = (n_bits + 1) * 1233 >> 12
    guess += 1
    i = SmallPowersOfTen[guess+1]
    return n < i ? (SmallPowersOfTen[guess], guess-1) : (i,guess)
end

function digitgen(low,w,high,buffer)
    unit::UInt64 = 1
    one = Float(unit << -w.e, w.e)
    too_high = Float(high.s+unit,high.e)
    unsafe_interval = too_high - Float(low.s-unit,low.e)
    integrals = too_high.s >> -one.e
    fractionals = too_high.s & (one.s-1)
    divisor, kappa = bigpowten(integrals, 64 + one.e)
    len = 1
    rest = UInt64(0)
    while kappa > 0
        digit = div(integrals,divisor)
        buffer[len] = 0x30 + digit
        len += 1
        integrals %= divisor
        kappa -= 1
        rest = (UInt64(integrals) << -one.e) + fractionals
        if rest < unsafe_interval.s
            r, kappa = roundweed(buffer, len, rest, UInt64(divisor) << -one.e,
                        unit,kappa,(too_high - w).s,unsafe_interval.s)
            return r, kappa, len
        end
        divisor = div(divisor,10)
    end
    while true
        fractionals *= 10
        unit *= 10
        unsafe_interval = Float(unsafe_interval.s*10,unsafe_interval.e)
        digit = fractionals >> -one.e
        buffer[len] = 0x30 + digit
        len += 1
        fractionals &= one.s - 1
        kappa -= 1
        if fractionals < unsafe_interval.s
            r, kappa = roundweed(buffer,len,fractionals,one.s,
                        unit,kappa,(too_high - w).s*unit,unsafe_interval.s)
            return r, kappa, len
        end
    end
end

function fastshortest(v, buffer = Array{UInt8}(17))
    f = normalize(Float64(v))
    bound_minus, bound_plus = normalizedbound(v)
    ten_mk_min_exp = kMinExp - (f.e + FloatSignificandSize)
    ten_mk_max_exp = kMaxExp - (f.e + FloatSignificandSize)
    cp = binexp_cache(ten_mk_min_exp,ten_mk_max_exp)
    scaled_w = f * cp
    scaled_bound_minus = bound_minus * cp
    scaled_bound_plus = bound_plus * cp
    r, kappa, len = digitgen(scaled_bound_minus,scaled_w,
                             scaled_bound_plus,buffer)
    decimal_exponent = -cp.de + kappa
    return r, len, decimal_exponent+len-1
end


"""
$(TYPEDEF)

Grid component key type for storing node in parent array
"""
abstract type NodeInParent <: AbstractGridIntegerArray1D end

"""
$(TYPEDEF)

Grid component key type for storing parent grid
"""
abstract type ParentGrid <: AbstractGridComponent end

"""
$(TYPEDSIGNATURES)

Default transform for subgrid creation
"""
function _copytransform!(a::AbstractArray,b::AbstractArray)
    for i=1:length(a)
        a[i]=b[i]
    end
end

"""
$(TYPEDSIGNATURES)

Create subgrid from list of regions.

- `parent`: parent grid
- `subregions`:  Array of subregions
- `transform` (kw parameter): transformation function between
   grid and subgrid coordinates acting on one point.
   Default: `copytransform`
- `boundary`: if true, create codimension 1 subgrid from boundary region.

A subgrid is of type `ExtendableGrid` and stores two additional components:
[`ParentGrid`](@ref) and [`NodeInParent`](@ref)

"""
function subgrid(parent,
                 subregions::AbstractArray;
                 transform::Function=_copytransform!,
                 boundary=false,
                 project=true)

    Tc=coord_type(parent)
    Ti=index_type(parent)

    @inline function insubregions(xreg)
        for i in eachindex(subregions)
            if subregions[i]==xreg
                return true
            end
        end
        return false
    end

    if boundary
        xregions=parent[BFaceRegions]
        xnodes=parent[BFaceNodes]
        sub_gdim=dim_grid(parent)-1
        xct=parent[BFaceGeometries]
        sub_gdim=dim_grid(parent)-1
    else
        xregions=parent[CellRegions]
        xnodes=parent[CellNodes]
        xct=parent[CellGeometries]
        sub_gdim=dim_grid(parent)
    end

    nodemark=zeros(Ti,num_nodes(parent))

    nsubcells=0
    nsubnodes=0
    for icell in eachindex(xregions)
        if insubregions(xregions[icell])
            nsubcells+=1
            for inode=1:num_targets(xnodes,icell)
                ipnode=xnodes[inode,icell]
                if nodemark[ipnode]==0
                    nsubnodes+=1
                    nodemark[ipnode]=nsubnodes
                end
            end
        end
    end

    sub_xnodes=VariableTargetAdjacency(Ti)
    sub_nip=zeros(Ti,nsubnodes)
    sub_ct=Vector{DataType}(undef,0)
    sub_cr=Vector{Ti}(undef,0)
    for inode in eachindex(nodemark)
        if nodemark[inode]>0
            sub_nip[nodemark[inode]]=inode
        end
    end

    isubcell=0
    for icell in eachindex(xregions)
        if insubregions(xregions[icell])
            ncn=num_targets(xnodes,icell)
            col=zeros(Ti,0)
            for inode=1:ncn
                push!(col,nodemark[xnodes[inode,icell]])
            end
            append!(sub_xnodes,col)
            push!(sub_ct,xct[icell])
            push!(sub_cr,xregions[icell])
        end
    end

    if project
        sub_coord=zeros(Tc,sub_gdim,nsubnodes)
    else
        sub_coord=zeros(Tc,dim_space(parent),nsubnodes)
    end
    coord=parent[Coordinates]
    @views for inode=1:nsubnodes
        transform(sub_coord[:,inode],coord[:,sub_nip[inode]])
    end
    subgrid=ExtendableGrid{Tc,Ti}()
    subgrid[Coordinates]=sub_coord
    subgrid[CellRegions]=sub_cr
    subgrid[CellGeometries]=sub_ct
    subgrid[CellNodes]=tryfix(sub_xnodes)
    subgrid[ParentGrid]=parent
    subgrid[NodeInParent]=sub_nip

    if boundary
        subgrid[NumBFaceRegions]=0
    else
        bfacenodes=parent[BFaceNodes]
        bfaceregions=parent[BFaceRegions]
        bfacetypes=parent[BFaceGeometries]

        sub_bfacenodes=VariableTargetAdjacency(Ti)
        sub_bfaceregions=Vector{Ti}(undef,0)
        sub_bfacetypes=Vector{DataType}(undef,0)

        for ibface in eachindex(bfaceregions)
            nbn=num_targets(bfacenodes,ibface)
            insubgrid=true
            for inode=1:nbn
                if nodemark[bfacenodes[inode,ibface]]==0
                    insubgrid=false
                    continue
                end
            end
            if insubgrid
                col=zeros(Ti,0)
                for inode=1:nbn
                    push!(col,nodemark[bfacenodes[inode,ibface]])
                end
                append!(sub_bfacenodes,col)
                push!(sub_bfacetypes,bfacetypes[ibface])
                push!(sub_bfaceregions,bfaceregions[ibface])
            end
        end

        subgrid[BFaceRegions]=sub_bfaceregions
        subgrid[BFaceGeometries]=sub_bfacetypes
        subgrid[BFaceNodes]=tryfix(sub_bfacenodes)
        subgrid[NumBFaceRegions]=maximum(sub_bfaceregions)
    end

    subgrid
end

"""
$(TYPEDEF)
Vector view on subgrid

$(TYPEDFIELDS)
"""
struct SubgridVectorView{Tv,Ti} <: AbstractVector{Tv}
    sysarray::AbstractVector{Tv}
    node_in_parent::Vector{Ti}
end

##################################################################
"""
$(TYPEDSIGNATURES)

Create a view of the vector on a subgrid.
"""
Base.view(a::AbstractVector,subgrid::ExtendableGrid)  = SubgridVectorView(a,subgrid[NodeInParent])

##############################################################################
"""
$(TYPEDSIGNATURES)

Accessor method for subgrid vector view.
"""
Base.getindex(aview::SubgridVectorView,inode::Integer) = aview.sysarray[aview.node_in_parent[inode]]

##############################################################################
"""
$(TYPEDSIGNATURES)

Accessor method for subgrid vector view.
"""
@inline function Base.setindex!(aview::SubgridVectorView,v,inode::Integer)
    aview.sysarray[aview.node_in_parent[inode]]=v
    return aview
end

##################################################################
"""
$(TYPEDSIGNATURES)

Return size of vector view.
"""
Base.size(a::SubgridVectorView)=(size(a.node_in_parent,1),)